在软件开发过程中,串口读写功能应该是必修课,读写内容会包含字符串、16进制数组等,而且会涉及到通信的数据格式,针对串口的通信功能来说,个人认为串口的读还是较为有点难度,因为涉及到数据的解析,串口有时候读取的数据并不是一条完整的数据,例如,一条完整的数据内容为:0xaa 0xbb 0xcc 0xdd 0x99 0x88 0x77 0x66 0x00 0x78 0x16 0x80,但往往在串口的读数据时,会出现一包无法读取完毕的现象,很可能上述的数据会分两次读取完毕,比如第一包读取内容为:0xaa 0xbb 0xcc 0xdd 0x99 0x88,第二包读取内容为:0x77 0x66 0x00 0x78 0x16 0x80,此种情况我们必须要对读取到的数据进行组包。字符串的组包方式可以根据字符串的格式来进行,本文只介绍16进制方式的组包。
状态机方式
适用于有限的条件【数据内容的长度一定要知道】,状态机的方式比较通用,可以根据数据格式来确认数据解包时的状态,最终实现解析一个完整的数据包;实例参照如下:
数据格式定义
数据通信协议 | |||
类型 | 内容 | 字节数 | 说明 |
消息头 | Head | 2 | 消息包头(0xFF 0xEE) |
Len | 2 | 整包数据总长度(11+N) | |
ID | 2 | 识别设备类型(型号) | |
CMD | 2 | 指令类型 | |
reserve | 2 | 保留位(0x00 0x00) | |
数据体 | Data | N |
|
校验 | CHK | 1 | CRC8校验 |
如上为数据格式的定义,只有数据头,没有数据尾,而且在数据内容上也没有考虑与消息头相同的情况,后续还需要优化。下面对代码进行展开描述:
代码:
/*
* 接收到串口数据后的处理函数
*
*/
void handle_recv_data(const unsigned char * buf, int len)
{
int i = 0;
RLOGD("==== %s ==== len:%d\n", __func__, len);
util_hexdump('&', dump_hexstr, (unsigned char*)buf, len);
util_hexdump('#', dump_hexstr, (unsigned char*)serial_data.recvBuf, serial_data.pos);
while(len > 0)
{
//RLOGD("%s serial_data.stat:%d,len:%d i:%d, pos:%d\n", __func__, serial_data.stat, len, i, serial_data.pos);
//util_hexdump('Q', dump_hexstr, serial_data.recvBuf, serial_data.pos);
//判断当前的状态机状态
switch(serial_data.stat)
{
case TYPE_HEADER://消息头
if(buf[i] == 0xEB && buf[i+1] == 0x90)
{
serial_data.stat = TYPE_LEN;
memcpy(serial_data.recvBuf, buf + i, 2);
i += 2;
serial_data.pos += 2;
len -= 2;
}
else
{
len--;
i++;
}
continue;
case TYPE_LEN://消息总长度
if(len < 2)
{
serial_data.recvBuf[i] = buf[i];
serial_data.pos++;
i++;
}
else
{
//printf("%s, serial_data.pos:%d\n", __func__, serial_data.pos);
if(serial_data.pos == 2)//recvBuf:0xFF, 0xEE
{
serial_data.stat = TYPE_DATA;
memcpy(serial_data.recvBuf + 2, buf + i, 2);
serial_data.pos += 2;
//printf("%s, buf[%d]:0x%x, buf[%d]:0x%x\n", __func__, i, buf[i], i+1, buf[i+1]);
serial_data.dataLen = charArray2Short((unsigned char *)buf, i, i+1);
len -= 2;
i += 2;
}
else//recvBuf:0xFF, 0xEE, 0x00
{
serial_data.stat = TYPE_DATA;//更新状态机
serial_data.recvBuf[i] = buf[i];//缓存当前buf到缓存recvBuf中,此处recvBuf更新后0xff 0xee 0x00 0xXX,前四个字节已经填充完毕
//更新结构体下标
serial_data.pos++;
//更新当前串口数据的长度;
serial_data.dataLen = charArray2Short((unsigned char *)buf, i, i+1);
len--;
i++;
}
RLOGD("%s, serial_data.dataLen:%d\n", __func__, serial_data.dataLen);
}
continue;
case TYPE_DATA://状态机为data
//printf("%s, serial_data.pos:%d, serial_data.dataLen:%d\n", __func__, serial_data.pos, serial_data.dataLen);
if(serial_data.pos > (serial_data.dataLen - 1) && len > 1)
{
util_hexdump('*', dump_hexstr, (unsigned char*)serial_data.recvBuf, serial_data.pos);
//已经组成完整的数据包:0xff 0xee 数据长度 .... CRC
handle_recv_master_cmd((const unsigned char*)serial_data.recvBuf, serial_data.dataLen);
// next loop
serial_data.stat = TYPE_HEADER;//更新状态机,初始化消息头状态,继续下个循环
serial_data.dataLen = 0;
serial_data.pos = 0;
memset(serial_data.recvBuf, 0, sizeof(serial_data.recvBuf));
}
else
{//数据内容的拷贝
serial_data.recvBuf[serial_data.pos] = buf[i];
i++;
serial_data.pos++;
len--;
}
continue;
default:
RLOGD("%s, error serial data!\n", __func__);
break;
}
}
//最后一个字节刚好结束循环,因此在结束循环后再次判断数据内容的完整度
if(serial_data.pos > 0 && (serial_data.pos > (serial_data.dataLen - 1)))
{
util_hexdump('G', dump_hexstr, (unsigned char*)serial_data.recvBuf, serial_data.pos);
handle_recv_master_cmd((const unsigned char*)serial_data.recvBuf, serial_data.dataLen);
// next loop
serial_data.stat = TYPE_HEADER;
serial_data.dataLen = 0;
serial_data.pos = 0;
memset(serial_data.recvBuf, 0, sizeof(serial_data.recvBuf));
}
}
//结构体的定义与初始化
enum
{
TYPE_HEADER,
TYPE_LEN,
TYPE_DATA,
};
struct _SERIAL_DATA
{
int stat;
unsigned char recvBuf[1024];
int pos;
int dataLen;
};
struct _SERIAL_DATA serial_data;
int mcu_uart_com_init()
{
//初始化结构体
memset(&serial_data, 0, sizeof(struct _SERIAL_DATA));
serial_data.stat = TYPE_HEADER;
serial_data.dataLen = 0;
serial_data.pos = 0;
memset(serial_data.recvBuf, 0, sizeof(serial_data.recvBuf));
}