USART串口-环形缓冲区笔记
USART串口-环形缓冲区笔记1.1 实验简介1.2 软件设计1. 构造环形缓冲区2. 往环形缓冲区存数据3. 读取环形缓冲区的数据1.1 实验简介最简单的串口数据处理机制是数据接收并原样回发的机制是成功接收到一个数触发进入中断在中断函数中将数据读取出来然后立即。这一种数据处理机制是“非缓冲中断方式”虽然这种数据处理方式不消耗时间但是这种数据处理方式严重的缺点是数据无缓冲区如果先前接收的的数据如果尚未发送完成处理完成然后串口又接收到新的数据新接收的数据就会把尚未处理的数据覆盖从而导致“数据丢包”。对于“数据丢包”最简单的办法就是使用一个数组来接收数据每接收一个数据数组下标偏移。虽然这样的做法能起到一定的“缓冲效果”但是数组的空间得不到很好的利用已处理的数据仍然会占据原有的数据空间直到该数组“满载”数组的每一个元素都保存了有效的数据将整个数组的数据处理完成后重新清零数组才能开启新一轮的数据接收。那么有什么好的数据接收处理机制既能起到“缓冲”效果又能有效地利用“数组空间”答案是有的那就是“环形缓冲区”。环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下应用程序读取环形缓冲区的数据仅仅会影响“头指针”而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组则将数组保存到环形缓冲区中同时将“尾指针”加1以保存下一个数据应用程序在读取数据时“头指针”加1以读取下一个数据。当“尾指针”超过数组大小则“尾指针”重新指向数组的首元素从而形成“环形缓冲区”有效数据区域在“头指针”和“尾指针”之间。如下图所示。当然环形缓冲区的“头指针”和“尾指针”可以用“头变量”和“尾变量”来代替因为切换数组的元素空间除了可以用“指针偏移法”之外还可以用“素组下标偏移法”。当串口接收到新的数组则将数组保存到环形缓冲区中同时将“尾变量”加一以保存下一个数据应用程序在读取数据时“头变量”加一以读取下一个数据。“环形缓冲区”数据接收处理机制的好处在于利用了队列的特点一头进一头出互不影响在数据进去往里存的时候另一边也可以把数据读出来而读出来的数据留下的空位又可以增加多的存储空间从而避免一边接收数据且一边处理数据会在数据量密集的时候而导致的丢掉数据或者数据产生冲突的问题。如果仅有一个线程读取环形缓冲区的数据只有一个串口往环形缓冲区写入数据则不需要添加互斥保护机制就可以保证数据的正确性。需要注意的是如果串口每接收x个字节的数据才处理一次则环形缓冲区的缓冲数组的大小必须是x的N倍具体N为多少需要结合具体的数据接收速率以及处理速率适当调节。这就好比喻水壶永远大于水杯这样子水壶才能存放很多杯水。如果觉得前文隐晦难懂那么下面我们来一起讨论一下环形队列的具体状态以及实现。下文构建的环形队列采用的是“头变量”“尾变量”来控制队列的存储和读取。首先我们会构造一个结构体并定义一个结构变量。#defineMAX_SIZE12//缓冲区大小typedefstruct{unsignedcharhead;//缓冲区头部位置unsignedchartail;//缓冲区尾部位置unsignedcharringBuf[MAX_SIZE];//缓冲区数组}ringBuffer_t;ringBuffer_tbuffer;//定义一个结构体定义一个结构头体则表示新的消息队列已经创建完成。新建创建的队列头指针head和尾指针tail都是指向数组的元素0。如下图所示此时的消息队列是“空队列”。当如果有11个数据abcdefghijk存入缓冲队列则如下图所示当如果l加入队列则缓冲队列处于满载状态如下图所示如果此时接收到新的数据并需要保存则tail需要归零将接收到的数据存到数组的第一个元素空间如果尚未读取缓冲数组的一个元素空间的数据则此数据会被新接收的数据覆盖。同时head需要增加1修改头节点偏移位置丢弃早期数据。读取缓冲队列中的11个数据后状态如下当消息队列中的所有数据都读取出来后此时环形队列是空的状态如下图所示。从图可以总结得知如果tail和head相等则表示缓冲队列是空的。1.2 软件设计1.3.1 ringBuffer.c1. 构造环形缓冲区/********************************************************************************************** 描述 : 环形缓冲读写 作者 : Jahol Fan 版本 : V1.0 修改 : 完成日期 Notice :本程序只供学习使用未经作者许可不得用于其它任何用途。版权所有盗版必究 ***********************************************************************************************/#includeringbuffer.h#defineBUFFER_MAX36//缓冲区大小typedefstruct{unsignedcharheadPosition;//缓冲区头部位置unsignedchartailPositon;//缓冲区尾部位置unsignedcharringBuf[BUFFER_MAX];//缓冲区数组}ringBuffer_t;ringBuffer_tbuffer;//定义一个结构体首先需要构建一个结构体ringBuffer_t如果定义一个结构体变量buffer则意味着创建一个环形缓冲区。2. 往环形缓冲区存数据/** * brief 写一个字节到环形缓冲区 * param data待写入的数据 * return none */voidRingBuf_Write(unsignedchardata){buffer.ringBuf[buffer.tailPositon]data;//从尾部追加if(buffer.tailPositonBUFFER_MAX)//尾节点偏移buffer.tailPositon0;//大于数组最大长度 归零 形成环形队列if(buffer.tailPositonbuffer.headPosition)//如果尾部节点追到头部节点则修改头节点偏移位置丢弃早期数据if(buffer.headPositionBUFFER_MAX)buffer.headPosition0;}8行将数据存放到tailPosition所指向的元素空间。9行tailPosition变量自增1并且判断如果大于最大缓冲则将tailPosition归零。11行如果tailPositon与headPosition相等则表示数据存入速度大于数据取出速度从到导致“追尾”。此时headPosition需要自增1以丢弃早期数据这也就是数据“覆盖现象”这种现象是不允许存在的解决这种现象的办法是将缓冲队列的空间再开大点。13行如果headPosition也大于最大数组则需要将headPosition清零。3. 读取环形缓冲区的数据/** * brief 读取环形缓冲区的一个字节的数据 * param *pData:指针用于保存读取到的数据 * return 1表示缓冲区是空的0表示读取数据成功 */u8RingBuf_Read(unsignedchar*pData){if(buffer.headPositionbuffer.tailPositon)//如果头尾接触表示缓冲区为空{return1;//返回1环形缓冲区是空的}else{*pDatabuffer.ringBuf[buffer.headPosition];//如果缓冲区非空则取头节点值并偏移头节点if(buffer.headPositionBUFFER_MAX)buffer.headPosition0;return0;//返回0表示读取数据成功}}8行首先判断headPosition是否等于tailPositon如果相等则表明此时缓冲区是空的。10行缓冲区为空则直接返回不执行后面的程序12行如果缓冲区不为空则条件成立并执行14行读取headPosition所指向的环形缓冲队列的元素空间的数据。15行headPosition自增1以读取下一个数据。如果headPosition大于最大值BUFFER_MAX则将headPosition归零。17行返回0表示读取数据成功。*brief 串口1中断函数*param none*returnnone*/voidUSART1_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);}}11行数据接收成功则将数据存入环形缓冲队列。/***************************************************************file main.c*brief MCU entry file*author JaholFan*date2017-11-20*version V010100*note***********************************************************/#includeHal_Led/Hal_Led.h#includeHal_delay/delay.h#includeHal_Key/Hal_Key.h#includeHal_Relay/Hal_Relay.h#includeHal_Usart/hal_uart.h#includeringbuffer.h/** * brief 程序入口 * param none * return none */intmain(void){u8 data0;SystemInit();//系统时钟初始化delayInit(72);//滴答定时器初始化uartxInit();while(1){if(0RingBuf_Read(data))//从环形缓冲区中读取数据{USART_SendData(USART1,data);//读取接收到的数据并回发数据}delayMs(1);//延时1ms:使得处理数据的速度小于接收数据的速度用于验证接收缓冲区的“缓冲”特性}}30行读取环形缓冲的数据如果环形缓冲队列有数据则条件成立32行将数据原样回发34行延时1ms的目的是使得处理数据的速度小于接收数据的速度用于验证接收缓冲区的“缓冲”特性。实际上在项目工程中项目代码的执行是消耗一定的CPU时间的本例程序用延时1毫秒来等效替代项目代码执行小号的CPU时间。综合实际项目所用的详细粘贴出代码加以说明