1.发现问题
在嵌入式Linux设备中,用C/C++语言开发串口通信软件,发现嵌入式设备通过串口接收外部设备传入的报文时,经常接收不全,具体表现为:
这种串口接收“断帧”问题对于通信功能是严重问题。
2.分析问题
根据问题现象,分析可能的原因有以下两项:
- 串口软件驱动原因,能接收全,但需要自行缓冲及组帧。
根据可能的原因,制定排查方案如下:
- 首先通过大量测试,检查是否能接收完整,以便排除硬件原因。
- 如果能接收完整,再试验如何能在一帧内完整接收长报文。
3.解决问题
经排查,硬件没问题,断断续续能收到长报文的所有内容。所以考虑自行缓冲及组帧。
根据以上分析结论,制定解决方案,在串口普通接收的基础上,增加缓冲层,再增加完整帧判断机制,原理如下:
- 用while循环+select机制,持续监听串口接收,将接收到的报文拼接并缓存。
核心源代码如下:
/* * FD:串口设备文件描述符 * data:输出缓冲区 * datalength:最多读取多少字节数 * gap_time_ms:指定的帧间隔,以ms为单位 * 返回值:返回实际读取的字节数 */intReadComPort_longdata(int FD,void *data, int datalength, int gap_time_ms){ int loopi = 0; int nread1 = 0; unsigned char u8_buf[1500+1]; //一次最多接收1500字节,对于普通应用足够了 int cur_pos = 0; fd_set inputs, testfds; struct timeval timeout; int result, nread; if(data == NULL) { return 0; } if(datalength > 1500) //一次最多接收1500字节,对于普通应用足够了 { datalength = 1500; //一次最多接收1500字节,对于普通应用足够了 } FD_ZERO(&inputs); FD_SET(FD, &inputs); cur_pos = 0; loopi=0; do { testfds = inputs; timeout.tv_sec = 0; timeout.tv_usec = gap_time_ms * 1000;//ms级 断帧足够用了 result = select(FD + 1, &testfds, (fd_set *)NULL, (fd_set *)NULL, &timeout); switch (result) { case 0: loopi++;//超时计数 break; case -1: loopi++; break; default: if(FD_ISSET(FD, &testfds)) { ioctl(FD, FIONREAD, &nread);//查看有多少字节可以读取 if(nread > 0)//如果可读字节数大于0 { if((cur_pos + nread) > datalength) { nread1 = datalength - cur_pos; } else { nread1 = nread; } nread = read(FD, &u8_buf[cur_pos], nread1); cur_pos = cur_pos + nread; } } break; } if(cur_pos >= datalength) { break; } }while(loopi < 1); if(cur_pos > 0) { memcpy(data, u8_buf, cur_pos); } return cur_pos; }
通过这种方式,即可完美接收串口报文。
完整的源代码、使用说明、自测报告等参见附件“最好的Linux串口接收驱动.zip”,请私信联系我获取。
4.问题总结
本文对Linux中C/C++编写串口通信软件时,接收报文不完整的原因进行了详细分析。然后提出while循环+select监听机制的处理措施,解决了该问题。该方案通用性强,灵活性高,值得参考和使用。