串口环形队列的应用
串口环形队列的应用
--Written by SimonLien 2020.05.25(转载请注明出处)
在项目的开发过程中,由于有些串口不具备FIFO或者FIFO的buffer很小,我们经常会自己设计一个队列(Queue)来避免串口收发过程中由于数据处理的速度比数据收发慢而导致的数据丢包。
我这边简单介绍一下队列的概念,队列,简单说是一种先入先出的线性表,只允许在一端插入(入队),而在另一端删除(出队)。队列的特点就是FIFO(First In First Out)先进先出,所以队列都有队首和队尾。
我们常见的队列有两种队列:顺序队列和环形队列。
顺序队列:见如下:这种队列设计比较简单,但是存在的缺点也大,所以一般建议不适用这种。
缺点:我们的数据都是存储在内存单元里,上面的每一个框,你可以看做是一个内存单元格子,可以把它理解为我们常见的数组,存放我们需要的数据。这种数据结构,当有大量的数据来临时,我们不能存储所有的数据,计算机总是处理先来的数据,处理完就释放掉,由于顺序队列数据的增加都是往后添加,这样就导致已经使用过的数据的内存浪费,如果要将剩余的数据往前挪,也不大现实,会导致执行效率变慢。
这时候,环形队列的设计就出现了,可以避免顺序队列存在的缺点。
环形队列:见如下,实际是一个环,设计是以顺时针的顺序进行数据排序。
环形队列可以有效地避免了顺序队列存在的缺点,这是因为设计成环形队列可以使得队列首尾相连,实际在计算机内存里,是没有环形的内存的,只不过我们将顺序的内存进行处理以达到我们想要的结果。
我们可以定义两个指针,一个指向队列头,一个指向队列尾。通过移动这两个指针(Head) &(Tail)即可对缓冲区的数据进行读写操作了,直到缓冲区已满(头尾相接),将数据处理完,可以释放掉数据,又可以进行存储新的数据了。
队列头 (Head) :允许进行删除的一端称为队首。
队列尾 (Tail) :允许进行插入的一端称为队尾。
了解了队列的基本概念和缺点后,我们开始正题。这边举我最近开发一个EC项目为例,来进行代码的实现:
1.如下,我们需要定义一个队列的结构体,结构成员如**释:
2.队列初始化,定义了三个参数如下:
/***********************************************************************************************
** 函数: QueueInit, 串口队列初始化
**-----------------------------------------------------------------------------
** 参数: *buf数据缓存、Size 缓存空间、*Queue 队列指针
** 返回: 无
***********************************************************************************************/
在初始化时,需要对入队指针pQueue->unFront和出队指针pQueue->unRear都清零,数据个数pQueue->unResidue和数据长度pQueue->unSize清零,以及初始化*psBuf数据缓存的指针。
3.入队操作,定义两个参数如下:
/***********************************************************************************************
** 函数: EnQueue, 队列写函数
**-----------------------------------------------------------------------------
** 参数: cData数据、 *Queue 队列指针
** 返回: FALSE队列满 TRUE读成功
***********************************************************************************************/
设计思想:在进行入队操作的时候,我们需要判断队列是否为满,如果为满,则return回去,不做入队操作。若队列未满,则入队cData,入队OK后,则需要将队列尾指针pQueue->unRear进行后移操作,以便进入下一个数据的插入。每入队一个数据都需要让pQueue->unResidue数据个数加1。
4.出队操作,定义两个参数如下:
/***********************************************************************************************
** 函数: DeQueue, 队列读函数
**-----------------------------------------------------------------------------
** 参数: *pcData 数据指针、 *pQueue 队列指针
** 返回: FALSE队列空 TRUE读成功
***********************************************************************************************/
设计思想:在进行出队操作的时候,我们需要判断是否为空,如果是空队列则return回去,不做出队操作。如果队列非空,则进行出队,将数据缓存的第一个数据psBuff[ptQueue->unFront]赋值给pcData,然后出队指针后移,以便下一个数据的出队,
每出队一个数据,则需要将pQueue->unResidue数据个数减1。
对于读写操作需要注意的地方有两个:
1:判断队列是否为空或者满,如果空的话,是不允许读取数据的,返回FLASE。如果是满的话,也是不允许写入数据的,避免将已有数据覆盖掉。那么如果处理的速度赶不上接收的速度,可以适当增大缓冲区的大小,用空间换取时间。
2:防止指针越界非法访问,程序有说明,需要使用者对整个缓冲区的大小进行把握。
以上为队列的设计,那如何应用?这边那EC code来说明:
- 采用定址宣告开辟内存空间,这边简单介绍变量的作用:
g_tUsart1SendQueue发送队列
g_tUsart1SendFrameLenQueue发送帧长度队列
g_USART1_SendFrameLenBuf发送帧长度缓存
s_USART1_SendFrameLen发送帧长度
g_tUsart1RecvQueue接收队列
g_tUsart1RecvFrameLenQueue接收帧长度队列
s_USART1_RecFrameLen接收帧长度
g_USART1_RecvFrameLenBuf接收帧长度缓存
s_sUsart1_ProcessBuf串口处理缓存
g_USART1_RxBuf串口接收缓存
2.队列初始化
3.进行入队操作,从串口接收到的数据直接放入环形队列中,以及可以入队长度队列,后面进入数据buffer处理时,可以根据长度队列在进入出队操作。
4.收包处理,如下可以将数据出队拿去解析再用。