嵌入式串口通讯处理机制(附FIFO源码),大家可以下载作为参考
在嵌入式串口通讯中,串口通讯应用几乎是必须的,非常常用,做好串口的数据处理是非常关键的一步,这里再分享一下如何在裸机中对串口数据的有效处理,
比如一包数据0xaa,0x55,0xXX,0xXX,0x0a,0x0d,简单介绍串口处理的方法
一.直接在接受中断中判断数据
先定义一个uint8_t Buff和一个uint8_t Table[10];
在接收中断函数里用HAL_UART_Receive_IT(&UART1_Handler, &Buff, 1)这个函数,每次在中断里面都判断Buff是不是0xaa,如果是则将数据存入到Table[0]中且继续接收下面的数据,都存入到Table的数组中,如果不是则继续进行判断。然后对Table进行判断,首先判断Table[0]和Table[1]分别为0xaa,0x55后,在进行判断Table[4]和Table[5]分别为0x0a,0x0d。在进行判断校验是否正确,正确后取出Table[2]的数据进行处理。这种方法在数据传输慢的情况下,比较简单方便,还可以,但是在数据快的时候,非常容易造成数据的丢失,还有就是要是第一次数据接收错误,回不到初始化状态,必须复位操作
二.FIFO方式 超时接受
接收中断函数里用HAL_UART_Receive_IT(&UART1_Handler, &Buff, 1)这个函数接收数据的时候不要做数据处理,而是忠实地接收原始字节流,只管接受入列,就是接受完数据0xaa,0x55,0xXX,0xXX,0x0a,0x0d后,超时时间RxTimeOut3到以后再对数据处理
/* 数据入队 */chfifo_in(&RxFifo, &Buff);
/* 清零超时 */RxTimeOut3 = 0;
三.DMA+空闲中断
就是接收到一帧数据0xaa,0x55,0xXX,0xXX,0x0a,0x0d后才触发中断接受,处理数据非常高效。
下边具体介绍串口环形队列 FIFO方式
环形缓冲区(FIFO)就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。如下图所示。
当然,环形缓冲区的“头指针”和“尾指针”可以用“头变量”和“尾变量”来代替,因为切换数组的元素空间,除了可以用“指针偏移法”之外,还可以用“素组下标偏移法”。当串口接收到新的数组,则将数组保存到环形缓冲区中,同时将“尾变量”加一,以保存下一个数据;应用程序在读取数据时,“头变量”加一,以读取下一个数据。
“环形缓冲区”数据接收处理机制的好处在于:利用了队列的特点,一头进,一头出,互不影响,在数据进去(往里存)的时候,另一边也可以把数据读出来,而读出来的数据,留下的空位,又可以增加多的存储空间,从而避免一边接收数据且一边处理数据会在数据量密集的时候而导致的丢掉数据或者数据产生冲突的问题。
如果仅有一个线程读取环形缓冲区的数据,只有一个串口往环形缓冲区写入数据,则不需要添加互斥保护机制就可以保证数据的正确性。
需要注意的是,如果串口每接收x个字节的数据才处理一次,则环形缓冲区的缓冲数组的大小必须是x的N倍,具体N为多少,需要结合具体的数据接收速率以及处理速率,适当调节。这就好比喻,水壶永远大于水杯,这样子水壶才能存放很多杯水。
如果觉得前文隐晦难懂,那么下面我们来一起讨论一下环形队列的具体状态以及实现。下文构建的环形队列采用的是“头变量”“尾变量”来控制队列的存储和读取。
首先,我们会构造一个结构体,并定义一个结构变量。
#define MAX_SIZE 12 //缓冲区大小
typedef struct
{
unsigned char head; //缓冲区头部位置
unsigned char tail; //缓冲区尾部位置
unsigned char ringBuf[MAX_SIZE]; //缓冲区数组
} ringBuffer_t;
ringBuffer_t buffer; //定义一个结构体
定义一个结构头体则表示新的消息队列已经创建完成。新建创建的队列,头指针head和尾指针tail都是指向数组的元素0。如下图所示,此时的消息队列是“空队列”。
当如果l加入队列,则缓冲队列处于满载状态,如下图所示:如果此时,接收到新的数据并需要保存,则tail需要归零,将接收到的数据存到数组的第一个元素空间,如果尚未读取缓冲数组的一个元素空间的数据,则此数据会被新接收的数据覆盖。同时head需要增加1,修改头节点偏移位置丢弃早期数据。
当消息队列中的所有数据都读取出来后,此时环形队列是空的,状态如下图所示。从图可以总结得知,如果tail和head相等,则表示缓冲队列是空的。
1.3.1 ringBuffer.c
1. 构造环形缓冲区
/**********************************************************************************************
描述 : 环形缓冲读写
作者 : Jahol Fan
版本 : V1.0
修改 :
完成日期:
Notice :本程序只供学习使用,未经作者许可,不得用于其它任何用途。版权所有,盗版必究
***********************************************************************************************/
#include "ringbuffer.h"
#define BUFFER_MAX 36 //缓冲区大小
typedef struct
{
unsigned char headPosition; //缓冲区头部位置
unsigned char tailPositon; //缓冲区尾部位置
unsigned char ringBuf[BUFFER_MAX]; //缓冲区数组
} ringBuffer_t;
ringBuffer_t buffer; //定义一个结构体
首先,需要构建一个结构体ringBuffer_t,如果定义一个结构体变量buffer,则意味着创建一个环形缓冲区。
2. 往环形缓冲区存数据
/**
* @brief 写一个字节到环形缓冲区
* @param data:待写入的数据
* @return none
*/
void RingBuf_Write(unsigned char data)
{
buffer.ringBuf[buffer.tailPositon]=data; //从尾部追加
if(++buffer.tailPositon>=BUFFER_MAX) //尾节点偏移
buffer.tailPositon=0; //大于数组最大长度 归零 形成环形队列
if(buffer.tailPositon == buffer.headPosition)//如果尾部节点追到头部节点,则修改头节点偏移位置丢弃早期数据
if(++buffer.headPosition>=BUFFER_MAX)
buffer.headPosition=0;
}
8行:将数据存放到tailPosition所指向的元素空间。
9行:tailPosition变量自增1,并且判断,如果大于最大缓冲,则将tailPosition归零。
11行:如果tailPositon与headPosition相等,则表示,数据存入速度大于数据取出速度,从到导致“追尾”。此时headPosition需要自增1,以丢弃早期数据,这也就是数据“覆盖现象”,这种现象是不允许存在的,解决这种现象的办法是将缓冲队列的空间再开大点。
13行:如果headPosition也大于最大数组,则需要将headPosition清零。
3. 读取环形缓冲区的数据
/**
* @brief 读取环形缓冲区的一个字节的数据
* @param *pData:指针,用于保存读取到的数据
* @return 1表示缓冲区是空的,0表示读取数据成功
*/
u8 RingBuf_Read(unsigned char* pData)
{
if(buffer.headPosition == buffer.tailPositon) //如果头尾接触表示缓冲区为空
{
return 1; //返回1,环形缓冲区是空的
}
else
{
*pData=buffer.ringBuf[buffer.headPosition]; //如果缓冲区非空则取头节点值并偏移头节点
if(++buffer.headPosition>=BUFFER_MAX)
buffer.headPosition=0;
return 0; //返回0,表示读取数据成功
}
}
8行:首先判断headPosition是否等于tailPositon,如果相等,则表明,此时缓冲区是空的。
10行:缓冲区为空,则直接返回,不执行后面的程序
12行:如果缓冲区不为空,则条件成立并执行
14行:读取headPosition所指向的环形缓冲队列的元素空间的数据。
15行:headPosition自增1以读取下一个数据。如果headPosition大于最大值BUFFER_MAX,则将headPosition归零。
17行:返回0,表示读取数据成功。
看串口中断函数 ,数据接收成功,则将数据存入环形缓冲队列
/**
* @brief 串口1中断函数
* @param none
* @return none
*/
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断接收标志位是否为1
{
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清楚标志位
RingBuf_Write(USART_ReceiveData(USART1));
//阻塞等待直到传输完成
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
}
主函数就是解析数据
if(0 == RingBuf_Read(&data))//从环形缓冲区中读取数据
{
user code //读取接收到的数据并回发数据
}