在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这里就不详解了,可自行百度。
-
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);
-
}
大体的流程就在上面了。具体的数据分析是根据具体的设备来的,只需做下简单的修改即可移植到工程中来,主要需要配置两点,一是串口
通讯参数和tty设备,二是
帧结构。流程和方法都是一样的,以上例程供大家参考和学习,有疑问欢迎一起留言交流
源码下载地址:
GitHub:
百度云:,提取码uflk
源码下载下来直接make即可。需要交叉编译的修改一下编译选项,Makefile是通用模板,修改很方便。
阅读(387265) | 评论(0) | 转发(1) |