高质量代码是怎么写出来的?串口环形队列
扫描二维码
随时随地手机看文章
干货福利,第一时间送达!
摘要:串口是通信中最常用的通信方式,可能写串口的驱动,能写几十种方法, 查询方式,中断方式,DMA方式,定时器方式。可能也其中几种方式的组合形式,经典的用法是:发送用查询方式,接收用中断方式,或者DMA+空闲中断。本篇不讲串口是啥,现在还在讲串口是啥,估计会被喷。今天来聊一聊串口常用的几种方式,最简单的方法就不说了。一、经典方法
- 查询方式 可靠性很高,要考虑下个数据包覆盖上一个数据包的问题,小数据量,在10个字节以内,可以这样考虑, 很简单,很方便,很可靠。但是在数据量大的时候,程序阻塞的时间特别长,影响其他比较重要的外设的处理。
- 中断方式 中断方式 , 不占用系统资源,但是如果数据量大,会频繁中断cpu, 会其他高优先的数据处理造成影响。但是没有DMA不占用资源的好处, 如果没有串口队列的实现,必须通过标志位判断上一个包数据是否发送完成,在把新的数据覆盖到串口的缓冲区。
- DMA方式 优点: 不占用系统资源,减少CPU对中断的响应。如何不建立数据包的队列,还是会出现,需要等待阻塞的问题。
二、环形队列
队列这个词在数据局结构中出现的比较多,与之对应的就是堆栈,但是两者的读取方式又完全不同。FIFO 是First-In First-Out的缩写,它是一个具有先入先出特点的缓冲区。串口设计FIFO的目的是为了提高串口的通讯性能。如果没有FIFO或者说缓冲区的长度只有1字节,那么使用接收中断,就意味着每次收到一个字节的数据就要进一次中断,这样频繁进中断会占用CPU资源。另外如果没有及时读走数据,那么下一个字节数据就会覆盖之前的数据,导致数据丢失,这在通讯速率高的场合很有可能出现。使用FIFO,可以在连续接收若干个数据后才产生一次中断,然后一起进行处理。这样可以提高接收效率,避免频繁进中断,适用于大数据传输。你可能会想到如果FIFO中的数据没有达到指定长度而无法产生中断怎么办,通常MCU会有接收超时中断,即在一定的时间内没有接收到数据会进入中断,可以利用这个中断把不足FIFO长度的数据最后都读取完。FIFO类似售票排队窗口,先到的人看到能先买到票,然后先走,后来的人只能后买到票。在计算机中,每个信息都是存储在存储单元中的,当有大量数据的时候,我们不能存储所有的数据,那么计算机处理数据的时候,只能先处理先来的,那么处理完后呢,就会把数据释放掉,再处理下一个。那么,已经处理的数据的内存就会被浪费掉。因为后来的数据只能往后排队,如过要将剩余的数据都往前移动一次,那么效率就会低下了,肯定不现实,所以,环形队列就出现了。点击下方视频动态演示出队入队1、环形队列的实现
在计算机中,是没有环形的内存的,只不过是我们将顺序的内存处理过,让某一段内存形成环形,使他们首尾相连,简单来说,这其实就是一个数组,只不过有两个指针,一个指向列队头,一个指向列队尾。指向列队头的指针是缓冲区可读的数据,指向列队尾的指针是缓冲区可写的数据,通过移动这两个指针即可对缓冲区的数据进行读写操作了,直到缓冲区已满(头尾相接),将数据处理完,可以释放掉数据,又可以进行存储新的数据了。实现的原理:视频来自正在一名考研的UP主:秃头少女王某人。计算机专业考研这个是必考点,视频讲的很棒,祝她一战成硕,金榜题名!串口环形缓冲区收发:在初学单片机的时候我们知道的串口收发都是:接收一个数据,触发中断,然后把数据发回来。这种处理方式是没有缓冲的,当数量太大的时候,亦或者当数据接收太快的时候,我们来不及处理已经收到的数据,那么,当再次收到数据的时候,就会将之前还未处理的数据覆盖掉。那么就会出现丢包的现象了,对我们的程序是一个致命的创伤。那么如何避免这种情况的发生呢,很显然,上面说的一些队列的特性很容易帮我们实现我们需要的情况。将接受的数据缓存一下,让处理的速度有些许缓冲,使得处理的速度赶得上接收的速度,上面又已经分析了普通队列与环形队列的优劣了,那么我们肯定是用环形队列来进行实现了。下面就是代码的实现:
2、定义一个结构体
typedef struct
{
uint16_t usWrite;
uint16_t usRead;
uint16_t usLenght;
/* FIFO 结构 */
uint8_t ucRing_Buff[RINGBUFF_LEN];
}RingBuff_T;
extern RingBuff_T g_ringBuff;
3、初始化队列
初始化结构体相关信息:使得我们的环形缓冲区是头尾相连的,并且里面没有数据,也就是空的队列,所有元素清0。void RingBuff_Init(void)
{
g_ringBuff.usWrite = 0;
g_ringBuff.usRead = 0;
g_ringBuff.usLenght = 0;
}
4、数据压入队列
/**
* @brief Write_RingBuff
* @param uint8_t _ucWriteData
* @return 0:环形缓冲区已满,写入失败;1:写入成功
* @note 往环形缓冲区写入uint8_t类型的数据
*/
uint8_t Write_RingBuff(uint8_t _ucWriteData)
{
if(g_ringBuff.usLenght >= RINGBUFF_LEN) /*判断缓冲区是否已满*/
{
return 0;
}
g_ringBuff.ucRing_Buff[g_ringBuff.usRead] = _ucWriteData;
g_ringBuff.usRead = (g_ringBuff.usRead 1) % RINGBUFF_LEN; /*防止越界非法访问*/
g_ringBuff.usLenght ;
return 1;
}
5、从队列中读出数据
/**
* @brief Read_RingBuff
* @param u8 *rData,用于保存读取的数据
* @return 0:环形缓冲区没有数据,读取失败; 1:读取成功
* @note 从环形缓冲区读取一个uint8_t类型的数据
*/
uint8_t Read_RingBuff(uint8_t *_usReadData)
{
if(g_ringBuff.usLenght == 0)/*判断非空*/
{
return 0;
}
*_usReadData = g_ringBuff.ucRing_Buff[g_ringBuff.usWrite];/*先进先出FIFO,从缓冲区头出*/
g_ringBuff.usWrite = (g_ringBuff.usWrite 1) % RINGBUFF_LEN; /*防止越界非法访问*/
g_ringBuff.usLenght--;
return 1;
}
对于读写操作需要注意的地方有两个:“1:判断队列是否为空或者满,如果空的话,是不允许读取数据的,返回0。如果是满的话,也是不允许写入数据的,避免将已有数据覆盖掉。那么如果处理的速度赶不上接收的速度,可以适当增大缓冲区的大小,用空间换取时间。2:防止指针越界非法访问,程序有说明,需要使用者对整个缓冲区的大小进行把握。”
四、环形缓冲器
环形缓冲器(ringr buffer),也称作圆形队列(circular queue),循环缓冲区(cyclic buffer),圆形缓冲区(circula buffer),是一种用于表示一个固定尺寸、头尾相连的缓冲区的数据结构,适合缓存数据流。圆形缓冲区的一个有用特性是:当一个数据元素被用掉后,其余数据元素不需要移动其存储位置。相反,一个非圆形缓冲区(例如一个普通的队列)在用掉一个数据元素后,其余数据元素需要向前搬移。换句话说,圆形缓冲区适合实现先进先出缓冲区,而非圆形缓冲区适合后进先出缓冲区。那么如何将环形缓冲器ringr buffer应用到串口上面呢?这里我们使用RT-Thread
的源码。1、定义一个结构体
/* ring buffer */
struct rt_ringbuffer
{
uint8_t *buffer_ptr;
uint16_t read_mirror : 1;
uint16_t read_index : 15;
uint16_t write_mirror : 1;
uint16_t write_index : 15;
uint16_t buffer_size;
};
2、初始化ringbuffer
void rt_ringbuffer_init(struct rt_ringbuffer *rb,
uint8_t *pool,
uint16_t size)
{
RT_ASSERT(rb != NULL);
RT_ASSERT(size > 0);
/* initialize read and write index */
rb->read_mirror = rb->read_index = 0;
rb->write_mirror = rb->write_index = 0;
/* set buffer pool and size */
rb->buffer_ptr = pool;
rb->buffer_size = RT_ALIGN_DOWN(size, RT_ALIGN_SIZE);
}
3、将数据压入ringbuffer
unsigned long rt_ringbuffer_put(struct rt_ringbuffer *rb,
const uint8_t *ptr,
uint16_t length)
{
uint16_t size;
RT_ASSERT(rb != NULL);
/* whether has enough space */
size = rt_ringbuffer_space_len(rb);
/* no space */
if (size == 0)
return 0;
/* drop some data */
if (size < length)
length = size;
if (rb->buffer_size - rb->write_index > length)
{
/* read_index - write_index = empty space */
memcpy(