0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

单片机中的几种环形缓冲区的分析和实现

掌芯元器 2024-08-14 08:39 次阅读


单片机中的几种环形缓冲区的分析和实现

一、简介

环形缓冲区(Ring Buffer)是一种高效的使用内存的方法,它将一段固定长度的内存看成一个环形结构,用于存储数据,能够避免使用动态申请内存导致的内存碎片问题,而且其能够更高效的使用内存。

在单片机中,由于内存有限,而且需要尽可能避免使用动态内存,所以环形缓冲区在单片机中应用非常广泛。

二、原理

通常我们需要使用一个数组或者在程序开头申请一段不被释放的内存,将其作为缓冲区。然后使用两个指令分别指向读写位置。

使用读指针管理读位置,使用写指针管理写位置。当读指针追上写指针时,表示缓冲区为空,当写指针追上读指针时,表示缓冲区已满。

环形缓冲区的读写操作都是循环进行的,当读指针或写指针到达缓冲区的末尾时,会自动回到缓冲区的开头。

环形缓冲区的读写操作都是原子操作,即一次只能进行一个读写操作,避免了多线程或多任务同时读写缓冲区导致的数据混乱。

三、实现

缓冲区满了以后可以选择覆盖写或者阻塞等待,需要注意的是,如果选择覆盖写,那么读索引也应该向前移动,此时最前面的数据就会丢失;如果选择阻塞等待的话,尽可能不要在中断中使用,否则中断嵌套会有无法预料的执行流程。

注:我们可以使用数组索引代替读写指针位置,毕竟对整数的加减还是比较容易理解的。下面我将读指针用读索引代替,写指针用写索引代替。

1、数据结构设计

我们需要支持多个环形缓冲区,使用同一套代码逻辑,那么就不能将缓冲区数组的大小进行硬编码,而是需要在初始化环形缓冲区的时候通过参数传递进来,我们使用一个结构体来表示缓冲区数组。

我们的缓冲区不能只是支持字节数组,而是支持任意类型的数据,所以我们需要一个变量来保存缓冲区数组中每个元素的大小,这样我们就可以根据这个大小然后结合读写索引来获取和写入数据。

typedef struct{ void *ptr; // 缓冲区数组指针 uint32_t elem_num; // 缓冲区数组元素个数 uint32_t elem_size; // 缓冲区数组中每个元素的大小} Array;

我们通过一个环形缓冲区的结构体来管理缓冲区,读写索引,同时使用一个变量来记录当前缓冲区中有效的元素个数,以便判断环形缓冲区是否为空或者是否已满。

如果在RTOS中使用环形缓冲区,那么读写索引需要使用原子操作,防止多线程或多任务同时读写缓冲区导致的数据混乱。为保证可以实现原子操作,需要传入RTOS提供的原子操作方法(进入临界区、互斥量等)

typedef struct{ Array *buffer; // 缓冲区 uint32_t read_index; // 读索引 uint32_t write_index; // 写索引 uint32_t count; // 环形缓冲区中元素个数 void (*lock)(void); // 进入原子操作 void (*unlock)(void); // 退出原子操作} RingBuffer;

为了及时了解我们的函数执行结果,我们在函数执行结束后需要返回一个错误码用于判断执行情况。

typedef enum{ ARRAY_OK, // 成功 ARRAY_PARAMS_NULL, // 参数为空 ARRAY_INDEX_OUT_OF_RANGE, // 索引越界} ArrayError;typedef enum{ RB_OK, // 操作成功 RB_READ_NOT_ENOUGH, // 缓冲区中元素个数不足,这只是一个警告,程序可以进行进行 RB_PARAM_ERROR, // 参数错误 RB_FULL, // 缓冲区已满 RB_EMPTY, // 缓冲区为空} RingBufferStatus;

2、方法实现

我们的缓冲区其实就是一个数组,但是我们这里为了支持存储不同类型的元素,使用了Array来对这个数组进行管理(类似C++中的Vector)。

在读写某个元素的时候,需要判断输入的索引是否在范围内。

// 初始化数组// pthis: 数组结构体指针// ptr: 数组指针// size: 数组元素个数// elem_size: 数组中每个元素的大小ArrayError array_init(Array *pthis, void *ptr, const uint32_t elem_num, const uint32_t elem_size){ if (pthis == NULL || ptr == NULL) { return ARRAY_PARAMS_NULL; } pthis->ptr = ptr; pthis->elem_num = elem_num; pthis->elem_size = elem_size; return ARRAY_OK;}// 获取数组元素个数// pthis: 数组结构体指针uint32_t array_get_elem_num(Array *pthis){ return pthis->elem_num;}// 获取数组中每个元素的大小// pthis: 数组结构体指针uint32_t array_get_elem_size(Array *pthis){ return pthis->elem_size;}// 向数组写入一个元素// pthis: 数组结构体指针// index: 要写入的元素索引// elem: 要写入的元素ArrayError array_write_elem(Array *pthis, const uint32_t index, const void *elem){ if (pthis == NULL || elem == NULL) { return ARRAY_PARAMS_NULL; } if (index >= pthis->elem_num) { return ARRAY_INDEX_OUT_OF_RANGE; } memcpy((char *)pthis->ptr + index * pthis->elem_size, elem, pthis->elem_size); return ARRAY_OK;}// 从数组读取一个元素// pthis: 数组结构体指针// index: 要读取的元素索引// elem: 读取的元素ArrayError array_read_elem(Array *pthis, const uint32_t index, void *elem){ if (pthis == NULL || elem == NULL) { return ARRAY_PARAMS_NULL; } if (index >= pthis->elem_num) { return ARRAY_INDEX_OUT_OF_RANGE; } memcpy(elem, (char *)pthis->ptr + index * pthis->elem_size, pthis->elem_size); return ARRAY_OK;}

为了方便判断环形缓冲区是否满或者空了,我使用一个变量来记录有效的元素数量,当有效元素数量为0时表示环形缓冲区空了,当有效元素数量为缓存数组的长度时表示环形缓冲区满了。

读写多个元素时,在内部都是一个一个进行的读写,只有在读写某个元素前才后判断环形缓冲区是否满或者空。那么读写多个元素的操作就不一定都能成功,在这里如果全部读写成功
或者只是因为环形缓冲区满、空导致失败,只需要返回成功读写的数据,如果是其他原因导致的读写失败,那么就需要根据RingBufferStatus的枚举类型返回相应的负数。

#define RB_LOCK() \ if (rb->lock) \ rb->lock()#define RB_UNLOCK() \ if (rb->unlock) \ rb->unlock()// 初始化环形缓冲区// @rb: 环形缓冲区// @array: 环形缓冲区使用的数组// @lock: 锁函数,进入原子操作时调用// @unlock: 解锁函数,退出原子操作时调用// 返回值: RB_OK, RB_PARAM_ERRORRingBufferStatus ring_buffer_init(RingBuffer *rb, Array *array, void (*lock)(void), void (*unlock)(void)){ if (rb == NULL || array == NULL) { return RB_PARAM_ERROR; } rb->buffer = array; rb->lock = lock; rb->unlock = unlock; rb->write_index = 0; rb->read_index = 0; rb->count = 0; return RB_OK;}// 向环形缓冲区写入一个元素// @rb: 环形缓冲区// @data: 要写入的数据// 返回值: RB_OK, RB_FULL, RB_PARAM_ERRORstatic RingBufferStatus ring_buffer_write_one(RingBuffer *rb, const void *data){ ArrayError err = ARRAY_OK; if (rb == NULL || data == NULL) { return RB_PARAM_ERROR; } if (rb->count == array_get_elem_num(rb->buffer)) { return RB_FULL; } err = array_write_elem(rb->buffer, rb->write_index, data); if (err != ARRAY_OK) { return RB_PARAM_ERROR; } rb->write_index = (rb->write_index + 1) % array_get_elem_num(rb->buffer); rb->count++; return RB_OK;}// 从环形缓冲区读取一个元素// @rb: 环形缓冲区// @data: 读取的数据// 返回值: RB_OK, RB_EMPTY, RB_PARAM_ERRORstatic RingBufferStatus ring_buffer_read_one(RingBuffer *rb, void *data){ ArrayError err = ARRAY_OK; if (rb == NULL || data == NULL) { return RB_PARAM_ERROR; } if (rb->count == 0) { return RB_EMPTY; } err = array_read_elem(rb->buffer, rb->read_index, data); if (err != ARRAY_OK) { return RB_PARAM_ERROR; } rb->read_index = (rb->read_index + 1) % array_get_elem_num(rb->buffer); rb->count--; return RB_OK;}// 向环形缓冲区写入数据// @rb: 环形缓冲区// @data: 要写入的数据// @elem_num: 要写入的元素个数// 返回值: 当至少能写入一个元素时,返回实际写入的元素个数;其他时候返回 -RB_PARAM_ERROR, -RB_FULLint32_t ring_buffer_write(RingBuffer *rb, const void *data, const uint32_t elem_num){ RingBufferStatus ret = RB_OK; int32_t write_num = 0; if (rb == NULL || data == NULL || elem_num == 0) { return -RB_PARAM_ERROR; } RB_LOCK(); for (int32_t i = 0; i < elem_num; i++) { ret = ring_buffer_write_one(rb, (char *)data + i * array_get_elem_size(rb->buffer)); write_num++; if (ret != RB_OK) { break; } } RB_UNLOCK(); if (ret == RB_OK || ret == RB_FULL) { return write_num; // 返回实际写入的元素个数 } else { return -ret; // 返回负数表示写入失败 }}// 从环形缓冲区读取数据// @rb: 环形缓冲区// @data: 读取的数据// @elem_num: 要读取的元素个数// 返回值: 当至少能读取一个元素时,返回实际读取的元素个数;其他时候返回 -RB_PARAM_ERROR, -RB_EMPTYint32_t ring_buffer_read(RingBuffer *rb, void *data, const uint32_t elem_num){ RingBufferStatus ret = RB_OK; int32_t read_num = 0; if (rb == NULL || data == NULL || elem_num == 0) { return -RB_PARAM_ERROR; } RB_LOCK(); for (int32_t i = 0; i < elem_num; i++) { ret = ring_buffer_read_one(rb, (char *)data + i * array_get_elem_size(rb->buffer)); read_num++; if (ret != RB_OK) { break; } } RB_UNLOCK(); if (ret == RB_OK || ret == RB_READ_NOT_ENOUGH) { return read_num; // 返回实际读取的元素个数 } else { return -ret; // 返回负数表示读取失败 }}

总结

本章主要介绍了一种在单片机中常用的环形缓冲区,分析了设计思路和代码实现。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6032

    文章

    44513

    浏览量

    632691
  • 内存
    +关注

    关注

    8

    文章

    2996

    浏览量

    73867
  • 存储数据
    +关注

    关注

    0

    文章

    85

    浏览量

    14090
收藏 人收藏

    评论

    相关推荐

    基于C语言实现环形缓冲区/循环队列

    这里分享一个自己用纯C实现环形缓冲区
    的头像 发表于 04-11 10:39 3130次阅读
    基于C语言<b class='flag-5'>实现</b><b class='flag-5'>环形</b><b class='flag-5'>缓冲区</b>/循环队列

    单片机应用简单技巧 - 环形缓冲

    了解了串口的相关操作,知道了环形缓冲在嵌入式系统的重要作用,本次介绍下如何在单片机等小型嵌入式系统引入
    发表于 09-18 11:01

    STM32进阶之串口环形缓冲区实现

    非法访问19ringBuff.Lenght--;20return TRUE;21} 当然,我们完全可以用空闲中断与DMA传输,效率更高,但是某些单片机没有空闲中断与DMA,那么这种环形缓冲区的作用就很大了,并且移植简便。 嵌入式
    发表于 06-08 14:03

    MCU进阶之串口环形缓冲区实现

    );8u8 Write_RingBuff(u8 data);9u8 Read_RingBuff(u8 *rData);10#endif当然,我们完全可以用空闲中断与DMA传输,效率更高,但是某些单片机没有空闲中断与DMA,那么这种环形
    发表于 08-17 13:11

    STM32串口环形缓冲区实现

    );u8 Read_RingBuff(u8 *rData);#endif 当然,我们完全可以用空闲中断与DMA传输,效率更高,但是某些单片机没有空闲中断与DMA,那么这种环形缓冲区的作用就很大了,并且移植简便。
    发表于 10-16 11:40

    环形缓冲区简介

    STM32串口数据接收 --环形缓冲区环形缓冲区简介  在单片机串口通信是我们使用最频繁的,使
    发表于 08-17 06:56

    怎么实现串口环形缓冲区

    怎么实现串口环形缓冲区
    发表于 12-06 06:01

    环形缓冲区读写操作的分析实现

    环形缓冲区是嵌入式系统中一种重要的常用数据结构。在多任务环境下实现时,如果有多个读写任务,一般需要用信号量来保护多个任务共享的环形缓冲区。但
    发表于 04-15 11:35 40次下载

    环形缓冲区实现原理

    在通信程序,经常使用环形缓冲区作为数据结构来存放通信中发送和接收的数据。环形缓冲区是一个先进先出的循环
    的头像 发表于 03-22 10:03 7492次阅读
    <b class='flag-5'>环形</b><b class='flag-5'>缓冲区</b>的<b class='flag-5'>实现</b>原理

    缓冲区是啥意思 STM32串口数据接收之环形缓冲区

    缓冲区顾名思义是缓冲数据用的。实现缓冲区最简单的办法时,定义多个数组,接收一包数据到数组A,就把接收数据的地址换成数组B,每个数据有个标记字节用于表示这个数组是否收到数据,收到数据是否
    的头像 发表于 07-22 15:33 1.1w次阅读

    STM32串口数据接收 --环形缓冲区

    STM32串口数据接收 --环形缓冲区环形缓冲区简介  在单片机串口通信是我们使用最频繁的,使
    发表于 12-28 19:24 30次下载
    STM32串口数据接收 --<b class='flag-5'>环形</b><b class='flag-5'>缓冲区</b>

    环形缓冲区简介 STM32环形缓冲区示例

    单片机串口通信是我们使用最频繁的,使用串口通信就会用到串口的数据接收与发送,环形缓冲区方式接收数据可以更好的保证数据丢帧率第。
    的头像 发表于 05-31 11:27 5990次阅读
    <b class='flag-5'>环形</b><b class='flag-5'>缓冲区</b>简介 STM32<b class='flag-5'>环形</b><b class='flag-5'>缓冲区</b>示例

    环形缓冲区实现思路

    单片机程序开发一般都会用到UART串口通信,通过通信来实现上位单片机程序的数据交互。通信中为了实现正常的收发,一般都会有对应的发送和接收
    的头像 发表于 01-17 15:07 1580次阅读

    STM32进阶之串口环形缓冲区实现

    STM32进阶之串口环形缓冲区实现
    的头像 发表于 09-19 09:20 2134次阅读
    STM32进阶之串口<b class='flag-5'>环形</b><b class='flag-5'>缓冲区</b><b class='flag-5'>实现</b>

    C++环形缓冲区设计与实现

    的存储空间。环形缓冲区的特点是其终点和起点是相连的,形成一个环状结构。这种数据结构在处理流数据和实现数据缓存等场景具有广泛的应用。 环形
    的头像 发表于 11-09 11:21 1847次阅读
    C++<b class='flag-5'>环形</b><b class='flag-5'>缓冲区</b>设计与<b class='flag-5'>实现</b>