1. 首页
  2. 编程语言

Linux平台RS232/485串口编程实例

在Linux下,串口的读写跟文件的读写无异,我们只需对相应的设备文件操作,即可实现对串口的通讯,这里给出的是一个实例,具体概念的东西可能不会详细解释,可自行百度,简单来说串口通讯就是双方按照一定的数据格式发送接收数据,一般是主从模式,即主机发请求数据,从机收到后返回对应的数据。

串口通讯的应用场景非常广泛,常见的温湿度采集、自动门的控制等等。因为需要对这些简单的装置信息采集或控制,从而构建出一个综合的系统,这里串口通讯必不可少,方便、廉价。

下面就以温湿度采集作为实例写一篇博文。

我手上的这款温湿度是上海拓福电气SZ-WS系列温湿度变送器,如下图:(大家不用细究报文格式含义,弄懂通讯原理即可举一反三)
Linux平台RS232/485串口编程实例

其中说明书主要是说了通讯规约,即报文的格式:如下

/************************************************************************************************

 * 传感器->主站RS485帧结构

 *  _________________________________________________________________________

 * | DestAddr 1Byte | MSG_TYPE 1Byte| DataLen 1Byte  | Data <= 255Byte | CRC 2Byte |

 * |________________|________________|________________|______________|______________

 *

 * 主站->传感器 RS485帧结构

 * __________________________________________________________________________

 * | DestAddr 1Byte | MSG_TYPE 1Byte|star add 2Byte |Register Num 2Byte | CRC 2Byte

 * |_______________|_______________|_______________|____________________|___________|

 *

 *****************************************************************************/
程序的流程大概如下,没有消息发送和无数据接收时都是睡眠状态,释放CPU。

Linux平台RS232/485串口编程实例

部分代码解析如下
main函数主要是创建温湿度类,然后0.5秒获取一次值,将其打印出来。
其中串口的参数要根据具体的设备来,tty设备就是对应的串口文件,具体怎么找出使用的串口是哪个tty这里就不详解了,可自行百度。

点击(此处)折叠或打开

  1. int main()
  2. {
  3.     SERIAL_S stSerialParam;
  4.     stSerialParam.u8BaudRate = BR_9600;
  5.     stSerialParam.u8DataBit = 0; // 8bit
  6.     stSerialParam.u8StopBit = 0; // 1bit
  7.     stSerialParam.u8Check = 0; // None
  8.     CHumitureManager *m_pCHumiture = new CHumitureManager(“/dev/ttyS2”, &stSerialParam, 1);
  9.     while(1)
  10.     {
  11.         printf(“33[0;31m [%s][%d] humidity=%d, temperature=%f33[0;39m n”, __func__, __LINE__, m_pCHumiture->humidity(), m_pCHumiture->temperature());
  12.         mSleep(500);
  13.     }
  14.     return 0;
  15. }

温湿度管理模块的构造函数
主要功能是根据传进来的参数初始化串口、创建读写和数据发送线程。

点击(此处)折叠或打开

  1. CHumitureManager::CHumitureManager(const WD_C8 *pTtyDevPath, const SERIAL_S *pstSerialPara, WD_U8 SensorAdd) :
  2.     m_fRtTemper(0), m_u32RtHumidity(0), m_enBaudRate(BR_9600)
  3. {
  4.     assert(pTtyDevPath != NULL && pstSerialPara != NULL);
  5.     m_u8SensorAdd = SensorAdd;
  6.     /* 初始化串口并连接 */
  7.     m_pCUart = new CUartOperator();
  8.     m_pCUart->init(pTtyDevPath, pstSerialPara);
  9.     CreateNormalThread(SendMsgThread, this, NULL);
  10.     CreateNormalThread(ReceMsgThread, this, NULL);
  11.     CreateNormalThread(cycleGetDeviceParam, this, NULL);
  12. }

串口初始化大体流程

点击(此处)折叠或打开

  1. /*******************************************
  2. * Function Name : init
  3. * Parameter : pTtyDevPath,串口所使用的tty设备绝对路径,如/dev/tty02
  4. * Description : 配置串口参数
  5. * Return Value : On success, 0 is returned.
  6.                   On error, -1 is returned
  7. * Author : LiangLiCan
  8. * Created : 2019/06/26
  9. ********************************************/
  10. WD_S32 CUartOperator::init(const WD_C8 *pTtyDevPath, const SERIAL_S *pstSerialPara)
  11. {
  12.     if (NULL == pTtyDevPath || NULL == pstSerialPara)
  13.     {
  14.         printf(“Get Null pointer, Check!!!n”);
  15.         return WD_FAILURE;
  16.     }
  17.     /* O_NOCTTY : 表示当前进程不期望与终端关联 */
  18.     m_s32DevFd = open(pTtyDevPath, O_RDWR | O_NOCTTY);
  19.     if (m_s32DevFd < 0)
  20.     {
  21.         printf(“Open dev %s fail! n”, pTtyDevPath);
  22.         return WD_FAILURE;
  23.     }
  24.     printf(“Open dev %s success! n”, pTtyDevPath);
  25.     /* 先清空参数 */
  26.     struct termios stOldParm;
  27.     bzero(&stOldParm, sizeof(stOldParm));
  28.     tcsetattr(m_s32DevFd, TCSANOW, &stOldParm);
  29.     //设置波特率
  30.     if (SetBaudRate(m_s32DevFd, (BAUD_RATE_E)pstSerialPara->u8BaudRate) != WD_SUCCESS)
  31.     {
  32.         printf(“SetBaudRate(%d) fail! n”, pstSerialPara->u8BaudRate);
  33.         close(m_s32DevFd);
  34.         return WD_FAILURE;
  35.     }
  36.     //设置数据位
  37.     if (SetDataBit(m_s32DevFd, pstSerialPara->u8DataBit) != WD_SUCCESS)
  38.     {
  39.         printf(“SetDataBit fail! n”);
  40.         close(m_s32DevFd);
  41.         return WD_FAILURE;
  42.     }
  43.     // 设置校验位
  44.     if (SetCheck(m_s32DevFd, pstSerialPara->u8Check) != WD_SUCCESS)
  45.     {
  46.         printf(“SetCheck fail! n”);
  47.         close(m_s32DevFd);
  48.         return WD_FAILURE;
  49.     }
  50.     //停止位
  51.     if (SetStopBit(m_s32DevFd, pstSerialPara->u8StopBit) != WD_SUCCESS)
  52.     {
  53.         printf(“SetStopBit fail! n”);
  54.         close(m_s32DevFd);
  55.         return WD_FAILURE;
  56.     }
  57.     m_bIsInit = true;
  58.     return WD_SUCCESS;
  59. }

构建一帧数据函数体并加入链表
构建好后添加入链表,并唤醒发送线程。实际应用中我们会再增加一个对外的接口,如sendMsg()。用于二次封建AddSendFrameToList函数,在需要的时候再发送消息,该例子是直接用了一个线程定时循环去获取温湿度。

点击(此处)折叠或打开

  1. /*******************************************
  2. * Function Name : AddSendFrameToList
  3. * Parameter : enAddr是寄存器地址, bWrites是否是写寄存器
  4. * Description : 构建一帧完整的485数据,包括地址、功能码、CRC的赋值,并加入到发送链表中去
  5. * Return Value : On success, 0 is returned.
  6.                   錯誤返回非0.
  7. * Author : LiangLiCan
  8. * Created : 2019/11/26
  9. ********************************************/
  10. WD_S32 CHumitureManager::AddSendFrameToList(REGISTER_ADD_E enAddr, WD_U16 u16ReadNum/* = 0 */,bool bWrite/* = false */, WD_U16 u16SetData/* = 0 */)
  11. {
  12.     CObjectLock ObjLock(&m_MuteSendLock);
  13.     WD_U8 *pNode = NULL;
  14.     WD_U8 aFrameHead[8] = {0};
  15.     WD_S32 ret = 0;
  16.     WD_U16 u16Temp = enAddr;
  17.     aFrameHead[SFI_DEST_ADDR] = m_u8SensorAdd; // 总线上的设备地址
  18.     aFrameHead[SFI_MSG_TYPE] = bWrite ? MT_WRITE : MT_READ;
  19.     ShortToChar(u16Temp, &aFrameHead[SFI_REG_ADDR], true);
  20.     if(bWrite){
  21.         ShortToChar(u16SetData, &aFrameHead[SFI_REG_PARM], true);
  22.     }
  23.     else {
  24.         ShortToChar(u16ReadNum, &aFrameHead[SFI_REG_PARM], true);
  25.     }
  26.     // 填充CRC
  27.     ShortToChar(createCrcCode(aFrameHead, 6), &aFrameHead[SFI_CRC]);
  28.     pNode = (WD_U8 *)malloc(sizeof(aFrameHead)); /* 发送线程会free掉它 */
  29.     memcpy(pNode, aFrameHead, sizeof(aFrameHead));
  30.     /* 添加入发送的列表 */
  31.     m_SendBufLock.Lock();
  32.     if (m_pSendBufList.empty()){
  33.         m_SendBufLock.Signal();
  34.     }
  35.     m_pSendBufList.push_back(pNode);
  36.     m_SendBufLock.UnLock();
  37.     return ret;
  38. }

发送数据线程
主要功能是从链表中取出一帧数据发送,无数据可写时处于休眠状态。

点击(此处)折叠或打开

  1. WD_VOID CHumitureManager::SendMsgThreadBody()
  2. {
  3.     prctl(PR_SET_NAME, (WD_U32 *)”HumitSend”);
  4.     WD_U8 *pu8SenBufNode = NULL;
  5.     while(1)
  6.     {
  7.         /* 从消息队列中取出一条发送的消息 */
  8.         m_SendBufLock.Lock();
  9.         if (m_pSendBufList.empty()){
  10.             m_SendBufLock.Wait();
  11.         }
  12.         pu8SenBufNode = m_pSendBufList.front();
  13.         m_pSendBufList.pop_front();
  14.         m_SendBufLock.UnLock();
  15.         m_pCUart->writeData(pu8SenBufNode, 8);
  16.         delete pu8SenBufNode;
  17.     }
  18. }

接收数据线程体

点击(此处)折叠或打开

  1. WD_VOID CHumitureManager::ReceMsgThreadBody()
  2. {
  3.     prctl(PR_SET_NAME, (WD_U32 *)”HumiRece”);
  4.     WD_S32 readLen = 0;
  5.     WD_S32 readCount = 0; /* 已读数据长度 */
  6.     WD_S32 MaxBufLen = sizeof(WD_U8) * MAX_FRAME_LEN;
  7.     m_pReadBuf = new WD_U8[MaxBufLen];
  8.     while(1)
  9.     {
  10.         if(!m_pCUart->dataAvailable(100)){ // 是否有数据可读
  11.             continue;
  12.         }
  13.         memset(m_pReadBuf, 0, sizeof(MaxBufLen));
  14.         readCount = 0;
  15.         do{
  16.             if(m_pCUart->dataAvailable(100))
  17.             {
  18.                 readLen = m_pCUart->readData(m_pReadBuf + readCount, MaxBufLen);
  19.                 if (readLen < 0){
  20.                     break;
  21.                 }
  22.                 readCount += readLen;
  23.             }
  24.         }while(readCount < m_pReadBuf[RFI_DATA_LEN] + FRAME_EXTRA_LEN);
  25.         // 处理读到的数据————-
  26.         handleMsg(m_pReadBuf, readCount);
  27.     }
  28.     delete [] m_pReadBuf;
  29. }

数据处理函数
该函数在实际应用中也可以使用回调函数,主要功能是处理拿到的数据。

点击(此处)折叠或打开

  1. WD_VOID CHumitureManager::handleMsg(WD_U8 *pMsgData, WD_U32 )
  2. {
  3.     if(pMsgData[RFI_DEST_ADDR] != 0x1){// 判断有效性
  4.         return ;
  5.     }
  6.     if(pMsgData[RFI_MSG_TYPE] == MT_READ)
  7.     {
  8.         /* Baud Rate */
  9.         m_enBaudRate = (BAUD_RATE_E)pMsgData[RFI_DATA + 1];
  10.         WD_U8 u8Backup = pMsgData[RFI_DATA + 2];
  11.         /* 温度 */
  12.          /* bit 15为温度正负值,0为正, 1为负 */
  13.         pMsgData[RFI_DATA + 2] &= 0x7F;
  14.         m_fRtTemper = (u8Backup & 0x80 ? -CharToShort(&pMsgData[RFI_DATA + 2], true)
  15.                                         : CharToShort(&pMsgData[RFI_DATA + 2], true)) / 10;
  16.         /* 湿度 */
  17.         m_u32RtHumidity = CharToShort(&pMsgData[RFI_DATA + 4], true);
  18.     }
  19.     else if(pMsgData[RFI_MSG_TYPE] == MT_READ_ERR)
  20.     {
  21.         m_fRtTemper = 0;
  22.         m_u32RtHumidity = 0;
  23.         //DBG_HUMI_PRINT(LEVEL_ERROR, “Get Invalid read msgn”);
  24.     }
  25.     //printf(“33[0;31m [%s][%d]m_enBaudRate=%d Temper=%02f°C Humidity=%d33[0;39m n”, __func__, __LINE__,m_enBaudRate, m_fRtTemper, m_u32RtHumidity);
  26. }

大体的流程就在上面了。具体的数据分析是根据具体的设备来的,只需做下简单的修改即可移植到工程中来,主要需要配置两点,一是串口通讯参数和tty设备,二是帧结构。流程和方法都是一样的,以上例程供大家参考和学习,有疑问欢迎一起留言交流

本文来自投稿,不代表程序员编程网立场,如若转载,请注明出处:http://www.cxybcw.com/187345.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code