在使用串口或其他通信模块接收协议数据时,是以流的方式接收的,有可能1个协议包分段接收到,也有可能多个数据包一起接收到,这时需要使用通信协议预处理机制,解决分包粘包的问题。
基本流程:
- 从通信模块获取字节流,逐个字节输入到预解码模块
- 在初始状态,寻找协议头部标志字节,如果找到,则转入协议内容接收字节,如果协议前部包含长度字段,则先转入到长度字段解码状态
- 在长度字段解码状态,解码长度,获取剩余需要接收的字节数,然后转入协议内容接收状态
- 在协议内容接收状态,如果剩余需要接收字节数为0,或找到协议尾部标志,则预解码完成
- 所有经过预解码的字节,都保存到预先定义的接收缓冲区(字节数组),预解码完成后,该缓冲区保存的是一个协议数据包
下面代码以协议格式 AT=xxx\r\n 为例
uint8_t com_app_precode_state; //预解码状态
#define COM_APP_RECEIVE_BUF_SIZE 50
uint8_t com_app_receive_buf[COM_APP_RECEIVE_BUF_SIZE];
uint16_t com_app_receive_buf_len = 0;
//预解码状态重置
void com_app_precode_reset(void)
{
com_app_precode_state = 0;
com_app_receive_buf_len = 0;
com_app_receive_remain_len = 0;
app_timer_disable(com_app_timer_hdl);//停止定时
}
//预解码处理
int16_t com_app_predecode(uint8_t c)
{
switch(com_app_precode_state)
{
case 0:
if(c == 'A') //寻找协议头
{
com_app_receive_buf[0] = c;
com_app_receive_buf_len = 1;
com_app_precode_state = 1;
}else
{
return -1;//不是协议头
}
break;
case 1:
com_app_receive_buf[com_app_receive_buf_len++] = c;
if(c == 'T')
{
com_app_precode_state = 2;
}else
{
return -2;//协议格式错误
}
break;
case 2:
if(com_app_receive_buf_len >= COM_APP_RECEIVE_BUF_SIZE)
{
return -3;//超出缓冲区长度
}
com_app_receive_buf[com_app_receive_buf_len++] = c;
if(c == '\n')
{
return com_app_receive_buf_len;
}
break;
}
return 0;//无错误或无结果,继续接收后续字节
}
//串口数据接收&处理流程
void com_app_process(void)
{
uint8_t c;
while(!uart_app_rx_fifo_is_empty())
{
uart_app_read_char_from_rx_fifo(&c);
int16_t result = com_app_predecode(c);
if(result > 0)//接收到完整的包
{
xxx_handle_data(com_app_receive_buf, com_app_receive_buf_len);//其它模块处理
com_app_precode_reset();
}
else if(result == 0)//等待后续数据
{
app_timer_enable(com_app_timer_hdl, 200);//启动定时器200ms
}
else if(result == -1) //没找到协议头,忽略
{
}else //其它错误
{
com_app_precode_reset();
}
}
}