在Linux下,串口的读写跟文件的读写无异,我们只需对相应的设备文件操作,即可实现对串口的通讯,这里给出的是一个实例,具体概念的东西可能不会详细解释,可自行百度,简单来说串口通讯就是双方按照一定的数据格式发送接收数据,一般是主从模式,即主机发请求数据,从机收到后返回对应的数据。
串口通讯的应用场景非常广泛,常见的温湿度采集、自动门的控制等等。因为需要对这些简单的装置信息采集或控制,从而构建出一个综合的系统,这里串口通讯必不可少,方便、廉价。
下面就以温湿度采集作为实例写一篇博文。
我手上的这款温湿度是上海拓福电气SZ-WS系列温湿度变送器,如下图:(大家不用细究报文格式含义,弄懂通讯原理即可举一反三)
其中说明书主要是说了通讯规约,即报文的格式:如下
/************************************************************************************************
* 传感器->主站RS485帧结构
* _________________________________________________________________________
* |DestAddr 1Byte | MSG_TYPE 1Byte| DataLen 1Byte | Data
*|________________|________________|________________|______________|______________
*
* 主站->传感器 RS485帧结构
* __________________________________________________________________________
* |DestAddr 1Byte | MSG_TYPE 1Byte|star add 2Byte |Register Num 2Byte | CRC 2Byte
*|_______________|_______________|_______________|____________________|___________|
*
*****************************************************************************/
程序的流程大概如下,没有消息发送和无数据接收时都是睡眠状态,释放CPU。
部分代码解析如下:
main函数主要是创建温湿度类,然后0.5秒获取一次值,将其打印出来。
其中串口的参数要根据具体的设备来,tty设备就是对应的串口文件,具体怎么找出使用的串口是哪个tty这里就不详解了,可自行百度。
点击(此处)折叠或打开
- int main()
- {
- SERIAL_S stSerialParam;
- stSerialParam.u8BaudRate = BR_9600;
- stSerialParam.u8DataBit = 0; // 8bit
- stSerialParam.u8StopBit = 0; // 1bit
- stSerialParam.u8Check = 0; // None
- CHumitureManager *m_pCHumiture = new CHumitureManager("/dev/ttyS2", &stSerialParam, 1);
- while(1)
- {
- printf("\033[0;31m [%s][%d] humidity=%d, temperature=%f\033[0;39m \n", __func__, __LINE__, m_pCHumiture->humidity(), m_pCHumiture->temperature());
-
- mSleep(500);
- }
- return 0;
- }
主要功能是根据传进来的参数初始化串口、创建读写和数据发送线程。
点击(此处)折叠或打开
- CHumitureManager::CHumitureManager(const WD_C8 *pTtyDevPath, const SERIAL_S *pstSerialPara, WD_U8 SensorAdd) :
- m_fRtTemper(0), m_u32RtHumidity(0), m_enBaudRate(BR_9600)
- {
- assert(pTtyDevPath != NULL && pstSerialPara != NULL);
- m_u8SensorAdd = SensorAdd;
- /* 初始化串口并连接 */
- m_pCUart = new CUartOperator();
- m_pCUart->init(pTtyDevPath, pstSerialPara);
-
- CreateNormalThread(SendMsgThread, this, NULL);
- CreateNormalThread(ReceMsgThread, this, NULL);
- CreateNormalThread(cycleGetDeviceParam, this, NULL);
- }
点击(此处)折叠或打开
- /*******************************************
- * Function Name : init
- * Parameter : pTtyDevPath,串口所使用的tty设备绝对路径,如/dev/tty02
- * Description : 配置串口参数
- * Return Value : On success, 0 is returned.
- On error, -1 is returned
- * Author : LiangLiCan
- * Created : 2019/06/26
- ********************************************/
- WD_S32 CUartOperator::init(const WD_C8 *pTtyDevPath, const SERIAL_S *pstSerialPara)
- {
- if (NULL == pTtyDevPath || NULL == pstSerialPara)
- {
- printf("Get Null pointer, Check!!!\n");
- return WD_FAILURE;
- }
- /* O_NOCTTY : 表示当前进程不期望与终端关联 */
- m_s32DevFd = open(pTtyDevPath, O_RDWR | O_NOCTTY);
- if (m_s32DevFd < 0)
- {
- printf("Open dev %s fail! \n", pTtyDevPath);
- return WD_FAILURE;
- }
- printf("Open dev %s success! \n", pTtyDevPath);
- /* 先清空参数 */
- struct termios stOldParm;
- bzero(&stOldParm, sizeof(stOldParm));
- tcsetattr(m_s32DevFd, TCSANOW, &stOldParm);
- //设置波特率
- if (SetBaudRate(m_s32DevFd, (BAUD_RATE_E)pstSerialPara->u8BaudRate) != WD_SUCCESS)
- {
- printf("SetBaudRate(%d) fail! \n", pstSerialPara->u8BaudRate);
- close(m_s32DevFd);
- return WD_FAILURE;
- }
- //设置数据位
- if (SetDataBit(m_s32DevFd, pstSerialPara->u8DataBit) != WD_SUCCESS)
- {
- printf("SetDataBit fail! \n");
- close(m_s32DevFd);
- return WD_FAILURE;
- }
- // 设置校验位
- if (SetCheck(m_s32DevFd, pstSerialPara->u8Check) != WD_SUCCESS)
- {
- printf("SetCheck fail! \n");
- close(m_s32DevFd);
- return WD_FAILURE;
- }
- //停止位
- if (SetStopBit(m_s32DevFd, pstSerialPara->u8StopBit) != WD_SUCCESS)
- {
- printf("SetStopBit fail! \n");
- close(m_s32DevFd);
- return WD_FAILURE;
- }
-
- m_bIsInit = true;
- return WD_SUCCESS;
- }
构建好后添加入链表,并唤醒发送线程。实际应用中我们会再增加一个对外的接口,如sendMsg()。用于二次封建AddSendFrameToList函数,在需要的时候再发送消息,该例子是直接用了一个线程定时循环去获取温湿度。
点击(此处)折叠或打开
- /*******************************************
- * Function Name : AddSendFrameToList
- * Parameter : enAddr是寄存器地址, bWrites是否是写寄存器
- * Description : 构建一帧完整的485数据,包括地址、功能码、CRC的赋值,并加入到发送链表中去
- * Return Value : On success, 0 is returned.
- 錯誤返回非0.
- * Author : LiangLiCan
- * Created : 2019/11/26
- ********************************************/
- WD_S32 CHumitureManager::AddSendFrameToList(REGISTER_ADD_E enAddr, WD_U16 u16ReadNum/* = 0 */, bool bWrite/* = false */, WD_U16 u16SetData/* = 0 */)
- {
- CObjectLock ObjLock(&m_MuteSendLock);
-
- WD_U8 *pNode = NULL;
- WD_U8 aFrameHead[8] = {0};
- WD_S32 ret = 0;
- WD_U16 u16Temp = enAddr;
- aFrameHead[SFI_DEST_ADDR] = m_u8SensorAdd; // 总线上的设备地址
- aFrameHead[SFI_MSG_TYPE] = bWrite ? MT_WRITE : MT_READ;
- ShortToChar(u16Temp, &aFrameHead[SFI_REG_ADDR], true);
-
- if(bWrite){
- ShortToChar(u16SetData, &aFrameHead[SFI_REG_PARM], true);
- }
- else {
- ShortToChar(u16ReadNum, &aFrameHead[SFI_REG_PARM], true);
- }
- // 填充CRC
- ShortToChar(createCrcCode(aFrameHead, 6), &aFrameHead[SFI_CRC]);
- pNode = (WD_U8 *)malloc(sizeof(aFrameHead)); /* 发送线程会free掉它 */
- memcpy(pNode, aFrameHead, sizeof(aFrameHead));
-
- /* 添加入发送的列表 */
- m_SendBufLock.Lock();
- if (m_pSendBufList.empty()){
- m_SendBufLock.Signal();
- }
- m_pSendBufList.push_back(pNode);
- m_SendBufLock.UnLock();
- return ret;
- }
主要功能是从链表中取出一帧数据发送,无数据可写时处于休眠状态。
点击(此处)折叠或打开
- WD_VOID CHumitureManager::SendMsgThreadBody()
- {
- prctl(PR_SET_NAME, (WD_U32 *)"HumitSend");
- WD_U8 *pu8SenBufNode = NULL;
- while(1)
- {
- /* 从消息队列中取出一条发送的消息 */
- m_SendBufLock.Lock();
- if (m_pSendBufList.empty()){
- m_SendBufLock.Wait();
- }
- pu8SenBufNode = m_pSendBufList.front();
- m_pSendBufList.pop_front();
- m_SendBufLock.UnLock();
-
- m_pCUart->writeData(pu8SenBufNode, 8);
- delete pu8SenBufNode;
- }
- }
点击(此处)折叠或打开
- WD_VOID CHumitureManager::ReceMsgThreadBody()
- {
- prctl(PR_SET_NAME, (WD_U32 *)"HumiRece");
- WD_S32 readLen = 0;
- WD_S32 readCount = 0; /* 已读数据长度 */
- WD_S32 MaxBufLen = sizeof(WD_U8) * MAX_FRAME_LEN;
- m_pReadBuf = new WD_U8[MaxBufLen];
-
- while(1)
- {
- if(!m_pCUart->dataAvailable(100)){ // 是否有数据可读
- continue;
- }
- memset(m_pReadBuf, 0, sizeof(MaxBufLen));
- readCount = 0;
- do{
- if(m_pCUart->dataAvailable(100))
- {
- readLen = m_pCUart->readData(m_pReadBuf + readCount, MaxBufLen);
- if (readLen < 0){
- break;
- }
- readCount += readLen;
- }
- }while(readCount < m_pReadBuf[RFI_DATA_LEN] + FRAME_EXTRA_LEN);
- // 处理读到的数据-------------
- handleMsg(m_pReadBuf, readCount);
- }
- delete [] m_pReadBuf;
- }
该函数在实际应用中也可以使用回调函数,主要功能是处理拿到的数据。
点击(此处)折叠或打开
- WD_VOID CHumitureManager::handleMsg(WD_U8 *pMsgData, WD_U32 )
- {
- if(pMsgData[RFI_DEST_ADDR] != 0x1){// 判断有效性
- return ;
- }
-
- if(pMsgData[RFI_MSG_TYPE] == MT_READ)
- {
- /* Baud Rate */
- m_enBaudRate = (BAUD_RATE_E)pMsgData[RFI_DATA + 1];
-
- WD_U8 u8Backup = pMsgData[RFI_DATA + 2];
- /* 温度 */
- /* bit 15为温度正负值,0为正, 1为负 */
- pMsgData[RFI_DATA + 2] &= 0x7F;
- m_fRtTemper = (u8Backup & 0x80 ? -CharToShort(&pMsgData[RFI_DATA + 2], true)
- : CharToShort(&pMsgData[RFI_DATA + 2], true)) / 10;
- /* 湿度 */
- m_u32RtHumidity = CharToShort(&pMsgData[RFI_DATA + 4], true);
- }
- else if(pMsgData[RFI_MSG_TYPE] == MT_READ_ERR)
- {
- m_fRtTemper = 0;
- m_u32RtHumidity = 0;
- //DBG_HUMI_PRINT(LEVEL_ERROR, "Get Invalid read msg\n");
- }
- //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);
- }
源码下载地址:
GitHub:https://github.com/lianglican/RS232-485-For-Linux
百度云:https://pan.baidu.com/s/1xUo00hQUFzhr8GlzeRbomw,提取码uflk
源码下载下来直接make即可。需要交叉编译的修改一下编译选项,Makefile是通用模板,修改很方便。