C:从环形缓冲区提取帧数据

it2025-07-12  4

嵌入式平台如何从数据流中提取一帧数据?

通过实例来说明问题:

A作为主机(上位机软件),B作为从机(STM32)硬件和电机连接,A和B之间通过串口通讯,使A能间接的控制电机正反转,速度等。

协议初定:

问题一:怎么判断帧数据开始信息?

通过定义一个固定的帧头,作为数据开始信号。记为0x01。

问题二:如何判断帧的长度?

帧的长度可以选择固定长度或可变长度。固定长度逻辑简单,检测到帧头后,往后读取固定的长度即可。可变长度灵活度高,一帧数据可以携带更多的信息量。那么可以在帧头后增加一个字节作为长度,记为0xXX,以便从机判断帧长。

问题三:数据格式?

数据格式按需编写,这里用16bit功能+16bit数据的方式。一组数据共4个字节。

问题四:如何校验帧完整性?

可以用两种方式: 1、增加帧尾作为数据结束标志,验证帧尾就知道数据是否完整。但是存在数据=帧尾、通讯异常的情况,这时候可能提取到了错误的数据。 2、采用通用校验方式,工业控制一般使用CRC16-MODBUS。这里增加两个字节作为检验码。这种方式可以保证数据不会出错。

至此:我们可以将通讯协议制定下来:

帧头帧长功能1数据1功能2数据2功能…数据…CRC16高位CRC16低位0x01len0xXX0xXX0xXX0xXX0xXX0xXXCRC_HCRC_L

在理想状态下,从机在串口中断里面判断帧头,然后循环往后读取len长度的字节到数组里面去,最后验证一下CRC是否一致即可。 但实际使用往往面对更复杂的情形:

问题五:如果一帧数据没有提取完成,下一帧数据又过来了怎么处理?

这个问题其实就是要解决两件事情:什么时候接收数据?什么时候处理数据? 假设提取数据的函数为:UserExtractFrameData();该函数执行时间为1us。 那么由简入繁看看几种方式:

1、中断接收中断处理。(弊端太明显不再赘述) 2、中断保存到buff,中断外处理buff,处理完后buff清空。

uint8_t RecvBuff[50]; uint8_t RecvIndex=0; void USART1_IRQHandler(void) //串口1中断服务函数 { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { RecvIndex ++; RecvBuff[RecvIndex] = (uint8_t)USART_ReceiveData(USART1); USART_ClearITPendingBit(USART1,USART_IT_RXNE); } } void UserExtractFrameData() { if(RecvIndex > FRAME_LEN) { ... RecvIndex = 0; } }

当传输速率>UserExtractFrameData()处理速度时,必然会导致丢数据。因为RecvIndex被清0了,这篇文章重点不是这个,这里不再赘述。

3、使用环形缓冲区。 环形缓冲区原理网上能找到很多,具体实现方式可以看这里 C环形缓冲区实现

使用环形缓冲区就可以很好的解决这个问题,将接收和处理彻底解耦,接收数据自动填入缓冲区,处理数据自动从缓冲区读取数据。当瞬间传输速率很大时,数据被保存到缓冲区不会丢失,等到传输空闲时就有足够的时间去处理。

void USART1_IRQHandler(void) //串口1中断服务程序 { u8 temp; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { temp =USART_ReceiveData(USART1); RingBuffWriteByte(temp); USART_ClearITPendingBit(USART1,USART_IT_RXNE); } } void UserExtractFrameData() { uint8_t len = RingBuffGetLength() if(len > FRAME_LEN) { RingBuffReadData(len,buff); .... } }

至此通过CRC和缓冲区,我们已经可以保证数据的完整性和正确性了,只需要从缓冲区拿数据出来比对协议就可以了。

那么接下来再分析一下更加复杂的情形:

问题六:假如主机采用的是一对多的方式,那么必然有大量的无效数据和类似数据,如何处理?

处理方式有很多,这里推荐一种字节流的处理方式。 处理思路:从缓冲区复制一个字节,如果是帧头,继续复制一个字节判断长度n,然后继续往后复制一个字节,判断是否是正确的功能码,再然后继续往后复制n个字节,直到判断CRC是否通过。只要遇到错误就读取帧头不处理即可。 无论主机发送多少数据,内存消耗只是环形缓冲区大小+有效数据大小。未检测到帧头前,处理速度>传输速率,所以环形缓冲区的大小可以不用设很大。但是有一种极端情况,就是主机发送的数据含有大量的帧头,且帧头后的功能码检测也是通过的,那就没救了,只能增大缓冲区大小。 经过验证可以高效率提取有效数据。3KB字节中提取最长32byte的有效数据,时间消耗100ms~220ms左右,主要是传输过程占用的时间。内存消耗仅100+32byte。 因为是给别人做的项目,这部分代码就不贴了~

问题七:假设数据长度较长,传输时间>>1us,传输数据到一半时,进入了处理数据的函数,处理函数由于没有接收到len长度的数据而判断为数据不完整错误,如何处理?

这个问题很好解决,我们可以再增加一个缓冲区pbuff[],先将数据搬到pbuff,直到接收到了len长度的数据,再去处理pbuff即可。 但这不是最好的解决方法,假如数据大小是1KB,那么环形缓冲区大小需要>1KB,再来一个pbuff也需要1KB内存,增加了一倍内存消耗。 因此在C环形缓冲区实现这里,我增加两个接口 uint8_t RingBuffCopyByte(uint8_t *pdata); uint8_t RingBuffCopyData(uint8_t len, uint8_t *pdata); 处理函数将环形缓冲区数据复制到内部进行处理,处理结束后才会读掉n个字节,缺点是程序执行时间有所增大。

最新回复(0)