Chinaunix首页 | 论坛 | 博客
  • 博客访问: 237499
  • 博文数量: 5
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 75
  • 用 户 组: 普通用户
  • 注册时间: 2019-03-06 14:11
个人简介

我,并不比别人差。

文章分类
文章存档

2020年(2)

2019年(3)

我的朋友

分类: C/C++

2020-03-08 16:16:07

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

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

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

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

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

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

 * 传感器->主站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



部分代码解析如下
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("\033[0;31m [%s][%d] humidity=%d, temperature=%f\033[0;39m \n", __func__, __LINE__, m_pCHumiture->humidity(), m_pCHumiture->temperature());
  12.         
  13.         mSleep(500);
  14.     }
  15.     return 0;
  16. }
温湿度管理模块的构造函数
主要功能是根据传进来的参数初始化串口、创建读写和数据发送线程。

点击(此处)折叠或打开

  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.     
  10.     CreateNormalThread(SendMsgThread, this, NULL);
  11.     CreateNormalThread(ReceMsgThread, this, NULL);
  12.     CreateNormalThread(cycleGetDeviceParam, this, NULL);
  13. }
串口初始化大体流程

点击(此处)折叠或打开

  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.     
  58.     m_bIsInit = true;
  59.     return WD_SUCCESS;
  60. }
构建一帧数据函数体并加入链表
构建好后添加入链表,并唤醒发送线程。实际应用中我们会再增加一个对外的接口,如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.     
  14.     WD_U8 *pNode = NULL;
  15.     WD_U8 aFrameHead[8] = {0};
  16.     WD_S32 ret = 0;
  17.     WD_U16 u16Temp = enAddr;
  18.     aFrameHead[SFI_DEST_ADDR] = m_u8SensorAdd; // 总线上的设备地址
  19.     aFrameHead[SFI_MSG_TYPE] = bWrite ? MT_WRITE : MT_READ;
  20.     ShortToChar(u16Temp, &aFrameHead[SFI_REG_ADDR], true);
  21.     
  22.     if(bWrite){
  23.         ShortToChar(u16SetData, &aFrameHead[SFI_REG_PARM], true);
  24.     }
  25.     else {
  26.         ShortToChar(u16ReadNum, &aFrameHead[SFI_REG_PARM], true);
  27.     }
  28.     // 填充CRC
  29.     ShortToChar(createCrcCode(aFrameHead, 6), &aFrameHead[SFI_CRC]);
  30.     pNode = (WD_U8 *)malloc(sizeof(aFrameHead)); /* 发送线程会free掉它 */
  31.     memcpy(pNode, aFrameHead, sizeof(aFrameHead));
  32.     
  33.     /* 添加入发送的列表 */
  34.     m_SendBufLock.Lock();
  35.     if (m_pSendBufList.empty()){
  36.         m_SendBufLock.Signal();
  37.     }
  38.     m_pSendBufList.push_back(pNode);
  39.     m_SendBufLock.UnLock();
  40.     return ret;
  41. }
发送数据线程
主要功能是从链表中取出一帧数据发送,无数据可写时处于休眠状态。

点击(此处)折叠或打开

  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.         
  16.         m_pCUart->writeData(pu8SenBufNode, 8);
  17.         delete pu8SenBufNode;
  18.     }
  19. }
接收数据线程体

点击(此处)折叠或打开

  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.     
  9.     while(1)
  10.     {
  11.         if(!m_pCUart->dataAvailable(100)){ // 是否有数据可读
  12.             continue;
  13.         }
  14.         memset(m_pReadBuf, 0, sizeof(MaxBufLen));
  15.         readCount = 0;
  16.         do{
  17.             if(m_pCUart->dataAvailable(100))
  18.             {
  19.                 readLen = m_pCUart->readData(m_pReadBuf + readCount, MaxBufLen);
  20.                 if (readLen < 0){
  21.                     break;
  22.                 }
  23.                 readCount += readLen;
  24.             }
  25.         }while(readCount < m_pReadBuf[RFI_DATA_LEN] + FRAME_EXTRA_LEN);
  26.         // 处理读到的数据-------------
  27.         handleMsg(m_pReadBuf, readCount);
  28.     }
  29.     delete [] m_pReadBuf;
  30. }
数据处理函数
该函数在实际应用中也可以使用回调函数,主要功能是处理拿到的数据。

点击(此处)折叠或打开

  1. WD_VOID CHumitureManager::handleMsg(WD_U8 *pMsgData, WD_U32 )
  2. {
  3.     if(pMsgData[RFI_DEST_ADDR] != 0x1){// 判断有效性
  4.         return ;
  5.     }
  6.     
  7.     if(pMsgData[RFI_MSG_TYPE] == MT_READ)
  8.     {
  9.         /* Baud Rate */
  10.         m_enBaudRate = (BAUD_RATE_E)pMsgData[RFI_DATA + 1];
  11.         
  12.         WD_U8 u8Backup = pMsgData[RFI_DATA + 2];
  13.         /* 温度 */
  14.          /* bit 15为温度正负值,0为正, 1为负 */
  15.         pMsgData[RFI_DATA + 2] &= 0x7F;
  16.         m_fRtTemper = (u8Backup & 0x80 ? -CharToShort(&pMsgData[RFI_DATA + 2], true)
  17.                                         : CharToShort(&pMsgData[RFI_DATA + 2], true)) / 10;
  18.         /* 湿度 */
  19.         m_u32RtHumidity = CharToShort(&pMsgData[RFI_DATA + 4], true);
  20.     }
  21.     else if(pMsgData[RFI_MSG_TYPE] == MT_READ_ERR)
  22.     {
  23.         m_fRtTemper = 0;
  24.         m_u32RtHumidity = 0;
  25.         //DBG_HUMI_PRINT(LEVEL_ERROR, "Get Invalid read msg\n");
  26.     }
  27.     //printf("\033[0;31m [%s][%d]m_enBaudRate=%d Temper=%02f°C Humidity=%d\033[0;39m \n", __func__, __LINE__,m_enBaudRate, m_fRtTemper, m_u32RtHumidity);
  28. }
大体的流程就在上面了。具体的数据分析是根据具体的设备来的,只需做下简单的修改即可移植到工程中来,主要需要配置两点,一是串口通讯参数和tty设备,二是帧结构。流程和方法都是一样的,以上例程供大家参考和学习,有疑问欢迎一起留言交流

源码下载地址:
GitHub:https://github.com/lianglican/RS232-485-For-Linux
百度云:https://pan.baidu.com/s/1xUo00hQUFzhr8GlzeRbomw,提取码uflk

源码下载下来直接make即可。需要交叉编译的修改一下编译选项,Makefile是通用模板,修改很方便。








阅读(4192) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册