天天看点

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

文章首发于同名微信公众号:

DigCore

欢迎关注同名微信公众号:

DigCore

,及时获取最新技术博文。

原文链接:https://mp.weixin.qq.com/s/xy6Se81XN9gfSPAZ9bmvJQ

(说明:此处的文章从微信公众号拷贝而来,图片或者排版上可能存在一定的瑕疵,欢迎点击原文链接阅读)

经过上一篇《嵌入式硬件通信接口-使用RingBuffer处理数据(一)》简单了解循环缓冲区,读代码后,接着开始设计自己的循环缓冲区功能模块。

设计思路

这里设计的难点在于,如何把控tail这个写地址,每增加一个数据时tail自增,在写的过程中,如果使用缓冲区的所有空间,那么head等于tail的时候,这个情况可能是空或者满,需要在程序设计时候,多加留意。

在实际项目使用中,仍需要考虑的两个问题:

  1. 读多个字节时,缓冲区内可读数量比用户想读的个数少,这时候是逐字节把可读的都读出来,还是直接报错?
  2. 写多个字节时,缓冲区内可写数量比用户想写的个数少,这时候是逐字节把可写的都写满,还是直接报错?

鉴于这样的问题,在读多字节和写多字节这两个接口上增加一个变量mode,用于设定接口在读写多字节时遇到长度超出范围,是逐字节继续处理还是直接报错。

还有,相比于上一篇文章中参考的源码,这里的设计思路采用的是

地址指针的方式,而不是偏移量

并且为了区别于缓冲区空或者满,将牺牲掉一个字节的空间:当缓冲区空的时候head等于tail,当缓冲区满的时候head在tail相邻的后一个位置。

本次的设计,使用head指向缓冲区中可读数据的首地址,使用tail指向缓冲区中可读数据的末地址(同时这也是可写数据的首地址):

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

数据流是55 04 18 02 07

B7这样的一连串16进制数,接收端先收到的是0x55,后收到的是0x04,以这样的顺序逐字节通过物理层接口接收。这也是一个数据包的常规帧结构,也符合人从左到右的阅读顺序。

所以在这里以head指向的是数据包的头,tail指向数据包的尾,head到tail之间就是一个可读数据的范围,同时,tail指向的是可写区域的首地址,当有新的数据进来时,新的数据会被从tail地址继续写到内存,而后tail在环内递增。

知道了循环缓冲区的这几个属性:缓冲区大小、缓冲区在内存中的地址范围、存取数据时的读写地址。可根据这些属性,构造一个循环缓冲区的结构体:

typedef struct{

uint32_t size;

uint8_t *head;

uint8_t *tail;

uint8_t *buff;

}rb_t;

ringbuffer简写rb,其中

size表示用户申请成功的或定义的buffer空间大小;

head是一个地址指针,指向缓冲区读的首地址;

tail也是个地址指针,指向缓冲区读的末地址(也就是写的首地址);

buff还是一个指针,一直指向用户定义或者程序动态申请的内存buffer首地址。

当我们需要使用环形缓冲区前,需要定义数组变量或者申请内存,作为数据实际存储的地方,同时定义一个rb_t的结构体,用于关联数组变量或者内存空间。

功能模块将完成以下接口:

  1. 初始化
  2. 可读数量
  3. 可写数量
  4. 读一个字节
  5. 读多个字节
  6. 写一个字节
  7. 写多个字节
  8. 查看指定偏移的数据
  9. 查看指定数据是否在缓冲区并取其在内存的地址
  10. 复位清空
使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

关于命名,暂且做个说明:dcclib是DigCore_Library的简写,这是功能模块库层的代码;rb是ringbuffer模块简写。

初始化

初始化的目的是把用户定义的将要使用的数组,与结构体变量关联起来,后续的操作脱离数组,直接操作结构体变量,正如此,结构体变量的初始化后,将对size、head、tail、buff各个成员赋值。

(截图略.)

可读数量

head指向的是可读数据的首地址,该地址内是有数据可以读的;

hail指向的是可读数据的末地址,该地址内没有数据可读,同时tail指向的是一个空的字节,作为写的首地址。

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

可写数量

这个需要留意的是总有一个字节空间不可写,

因此size大小的空间内,除了可读,剩下就是一个空字节和可写数量

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

读一个字节

读一个字节时,基于指针地址的操作模式,需要留意的是读指针自增后超过缓冲区的范围时,将读指针折返回到缓冲区的起始位置。

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

读多个字节

读多个字节,必然是基于读一个字节的基础上实现的。这里相比于前一篇文章提到的参考项目源码,不同的是增加了读模式的选择,当要读的数据个数大于可读数量时,可以利用mode来选择是否仍然逐字节地把能读的都读出来。

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...
如果在调用接口时mode设为FORCE的强制读取,要读的数据个数大于可读数量时,将以RET_RB_RDWRN的错误返回码,旨在说明读取的数据不足,但是也已经把可读的都读出了

写一个字节

写一个字节时,基于指针地址的操作模式,需要留意的是写指针自增后超过缓冲区的范围时,将写指针折返回到缓冲区的起始位置。

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

写多个字节

写多个字节,必然是基于写一个字节的基础上实现的。这里相比于前一篇文章提到的参考项目源码,不同的是增加了写模式的选择,当要写的数据个数大于可写数量时,可以利用mode来选择是否仍然逐字节地把能写的都写进去。

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...
如果在调用接口时mode设为FORCE的强制读写入,要写的数据个数大于可写数量时,将以RET_RB_WRWRN的错误返回码,旨在说明可写的空间不足,但是也已经把剩余空间的都写满了

查看指定偏移的数据

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

查看指定数据在缓冲区内存的位置

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

为了在按地址寻数据时不越界超出Buffer范围,这里分情况处理,分别head在tail之前或之后的两种情况处理。

复位清空

其实复位情况,也只是对读写指针操作,当读写指针在同一个位置时,就表示空。实现办法可以是将head赋值给tail,或者将缓冲区的起始地址都重新赋值给head和tail。

其实这两种办法都可以,因为对于环形缓冲区而言,它只关心head和tail的指针地址,而不关心对应内存块的首末

同样复位的时候也可以将内存中的数据清零,memset函数实现。

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

到此,环形缓冲区模块的代码基本搞定了。

还需要执行调试验证,为了验证各个接口的执行效果,编写了测试代码,并且将Debug结果打印输出,有条件可以在线仿真,并且可以具体查看内存中的真实数据!

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

附上Debug过程中对变量的观察的截图:

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

同样附上Debug过程中对Buffer在内存中呈现的截图:

使用outputstream写到指定位置_嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程...

参考资料:

《Circular_buffer》@维基百科

https://en.wikipedia.org/wiki/Circular_buffer

《环形缓冲器》@百度百科

https://baike.baidu.com/item/%E7%8E%AF%E5%BD%A2%E7%BC%93%E5%86%B2%E5%99%A8

《Ring-Buffer》@Github

https://github.com/AndersKaloer/Ring-Buffer

★★★★★推荐文章

《【嵌入式编程】函数返回类型设计》

《【嵌入式编程】平台大小端存储差异解决办法》

《嵌入式硬件通信接口-使用RingBuffer处理数据(二)详细设计过程》

《嵌入式硬件通信接口-使用RingBuffer处理数据(一)》

《快速开发MQTT(一)电子工程师眼中的MQTT》

《快速开发MQTT(二)初识MQTT》

《MQTT客户端搭建-最清晰的MQTT协议架构》

《MQTT服务端搭建-最快方式验证自己开发的客户端》

★★★★★相似文章

《嵌入式硬件通信接口协议-UART(五)数据包设计与解析》

《嵌入式硬件通信接口协议-UART(四)设计起止式的应用层协议》

《嵌入式硬件通信接口协议-UART(三)快速使用串口及应用》

《嵌入式硬件通信接口协议-UART(二)不同电气规范下的标准》

《嵌入式硬件通信接口协议-UART(一)协议基础》

《嵌入式硬件通信接口协议-SPI(三)模拟接口应用》

《嵌入式硬件通信接口协议-SPI(二)分层架构设计模拟接口》

《嵌入式硬件通信接口协议-SPI(一)协议基础》

《嵌入式硬件通信接口协议-IIC(一)协议基础》

★★★★★扩展阅读

《【硬件电路】AltiumDesigner18规则检查含义》

《【硬件电路】N沟道、P沟道MOS管基本原理与应用案例》