找回密码
 立即注册
首页 业界区 业界 环形缓冲区在嵌入式系统中的应用:串口中断VS主循环 ...

环形缓冲区在嵌入式系统中的应用:串口中断VS主循环

讣丢 3 小时前
为什么要用环形缓冲区

假设有这样的场景:串口中断正在快速读取数据,主循环中较慢地解析数据。如果保存串口当前发送的数据后立即做处理,可能会有丢帧的风险。如果我们使用先进先出的数据结构——环形缓冲区,把串口存取的数据存进去,主循环可随时读取,既可以规避掉丢帧的风险,也确保了数据次序正确。
环形缓冲区的实现

需要一个缓存数组,它是固定大小的,来作为环形缓冲区。为了正确的读取数据,我们还需要写指针和读指针。指针的移动,通过_next()函数中指针+1取模实现,确保数组不会越界。
  1. // 1. 缓存数组:固定大小的存储空间(比如256字节)
  2. static volatile uint8_t  s_rxbuf[IMU_UART_RX_BUF_SIZE];
  3. // 2. 写指针:下一个要写入的位置
  4. static volatile uint16_t s_wr = 0;
  5. // 3. 读指针:下一个要读取的位置
  6. static volatile uint16_t s_rd = 0;
  7. // 4. 计算下一个位置(核心:取模实现“环形”)
  8. static inline uint16_t _next(uint16_t idx)
  9. {
  10.    return (uint16_t)((idx + 1u) % IMU_UART_RX_BUF_SIZE);
  11. }
复制代码
环形缓冲区的使用

回到我们一开始的场景,也就是我们决定使用环形缓冲区的起点,我们遭遇的是串口中断和主循环之间的矛盾。
我们应该在中断中使用写指针写数据。如下_push()函数:计算下一个写入位置,把字节写入写指针指向的环形缓冲区位置。当下一个写位置与读位置相等时,说明缓冲区满了,将读位置后移一位,即丢弃最旧的一个字节。最后把当前写指针后移。

  • 写入数据 _push() :中断里调用
  1. static inline void _push(uint8_t b)
  2. {
  3.     // 计算下一个写入位置
  4.     uint16_t next = _next(s_wr);
  5.     // 如果【下一个写位置 == 读位置】= 缓冲区满了
  6.     if (next == s_rd) {
  7.         s_rd = _next(s_rd);   // 丢弃最旧的一个字节
  8.     }
  9.     s_rxbuf[s_wr] = b;  // 把字节写入缓存
  10.     s_wr = next;        // 写指针后移
  11. }
复制代码
同样地,在主循环解析数据时需要用到读指针读取数据。如下_pop()函数:当写指针和读指针指向同一个位置时,说明没有待读数据,返回-1;如果有待读数据,则读出数据,读指针后移。
2. 读取数据 _pop() : 主循环解析调用
  1. static inline int _pop(uint8_t *out)
  2. {
  3.     if (s_wr == s_rd) return -1;  // 空的,没数据可读
  4.     *out = s_rxbuf[s_rd];         // 读出数据
  5.     s_rd = _next(s_rd);           // 读指针后移
  6.     return 0;
  7. }
复制代码

  • 状态判断总结
    缓冲区空(没数据)-》s_wr == s_rd
    缓冲区满(存不下)-》        _next(s_wr) == s_rd

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册
发帖

0

粉丝关注

28

主题发布

板块介绍填写区域,请于后台编辑