使用空闲中断IDLE+DMA+列队方式。
串口数据因为是按字节连续发送的,如果数据包发送的间隔很小,或者处理不及时,很容易存在丢包、粘包的问题。最近项目开发中发现一个比较通用的方法来防止丢包,就是使用队列的方式。
原理:就是把接收的数据按顺序压入队列(队列一般是先进先出),然后处理的时候,从队列里取最早压入的数据,在应用层里一个字节一个字节的取出来,取出一个,队列就空出来一个,找到完整的数据包,然后进行处理,这样来了数据就压入队列,两不耽误。
接收方式可以按字节,也可以用DMA中断或空闲中断、或DMA+空闲中断,随意。
主要就是两个函数:向队列压入数据、从队列取出完整的数包(需要根据帧头、帧尾来找)。
/* 压入数据 */
void queue_push(uint8_t _data, p_QUEUE _que)
{
uint16_t pos = (_que->_head + 1) % QUEUE_MAX_SIZE;
if(pos!=_que->_tail)
{
_que->_data[_que->_head] = _data;
_que->_head = pos;
}
}
/* 取出数据 */
static void queue_pop(uint8_t * _data, p_QUEUE _que)
{
if(_que->_tail!=_que->_head)
{
*_data = _que->_data[_que->_tail];
_que->_tail = (_que->_tail + 1) % QUEUE_MAX_SIZE;
}
}
下面的代码就是从队列里一个字节一个字节的找到帧头、帧尾,组成一帧完整的数据包。
uint16_t queue_find_cmd_rs485(uint8_t *buffer, uint16_t buf_len, p_QUEUE _que, uint8_t cmdHead, uint32_t cmdEnd)
{
uint16_t cmd_size = 0;
uint8_t _data = 0;
static uint32_t cmd_state = 0; //队列帧尾检测状态
static qsize cmd_pos = 0; //当前指令指针位置
while(queue_size(_que)>0)
{
//取一个数据
queue_pop(&_data, _que);
if(cmd_pos==0&&_data!=cmdHead) //指令第一个字节必须是帧头,否则跳过
{
continue;
}
if(cmd_pos<buf_len) //防止缓冲区溢出
buffer[cmd_pos++] = _data;
cmd_state = ((cmd_state<<8)|_data); //拼接最后4个字节,组成一个32位整数
//最后4个字节与帧尾匹配,得到完整帧
if(cmd_state==cmdEnd)
{
cmd_size = cmd_pos; //指令字节长度
cmd_state = 0; //重新检测帧尾巴
cmd_pos = 0; //复位指令指针
return cmd_size;
}
}
return 0; //没有形成完整的一帧
}
遇到的问题:
因为队列开辟了一块缓存,所以导致程序运行出现点问题,最后通过调整.s文件的堆栈大小解决了。