本专栏由Mculover666创建,主要内容为寻找嵌入式领域内的优质开源项目,一是帮助开发者使用开源项目实现更多的功能,二是通过这些开源项目,学习大佬的代码及背后的实现思想,提升自己的代码水平,和其它专栏相比,本专栏的优势在于:
不会单纯的介绍分享项目,还会包含作者亲自实践的过程分享,甚至还会有对它背后的设计思想解读。
目前本专栏包含的开源项目有:
如果您自己编写或者发现的开源项目不错,欢迎留言或者私信投稿到本专栏,分享获得双倍的快乐!
本期给大家带来的开源项目是 ringbuff ,一款通用FIFO环形缓冲区实现的开源库,作者MaJerle,目前收获 79 个 star,遵循 MIT 开源许可协议。
目前 ringbuff 的特点有:
项目地址:https://github.com/MaJerle/ringbuff
在移植过程中主要参考两个资料:项目的readme文档和demo工程。
对于这些开源项目,其实移植起来也就两步:
本文中我使用的是小熊派IoT开发套件,主控芯片为STM32L431RCT6:
移植之前需要准备一份裸机工程,我使用STM32CubeMX生成,需要初始化以下配置:
ringbuff中默认volatile关键词没有定义,需要手动配置一下,在ringbuff.h
中:
至此,ringbuff移植修改完成,可以愉快的使用ringbuff啦~
缓冲区一般用于解决设备接收数据的速度和设备处理速度不匹配的情况下,防止丢包,通俗的来说就是:收到数据先存进缓冲区,等到CPU来处理的时候一次性取出处理。
缓冲区有两种形式,一种是数组,一种就是本文所介绍的环形缓冲区ringbuff。
相较于数组,环形缓冲区对整段内存的利用达到最大,并且使用非常方便,如下:
本文设计的一个简单的不定长串口协议如下:
接下来演示如何用环形缓冲区做到不丢包解析。
假定数据每200ms处理一次,而数据10ms接收一次,每次接收的数据包长度为7个字节。
要想做到不丢包,就需要将200ms内接收到的所有数据包都存进缓冲区,所以缓冲区大小至少为:200/10*7 = 140 个字节。
保险起见,可以将缓冲区适当的扩大一下,设置为150个字节。
使用时包含头文件:
#include "ringbuff/ringbuff.h"
接着初始化缓冲区:
uint8_t ringbuff_init(RINGBUFF_VOLATILE ringbuff_t* buff, void* buffdata, size_t size);
该 API 用来初始化一个ringbuff句柄(指向ringbuff结构体的指针),其中传入的参数分别为:
buff
:ringbuff句柄;buffdata
:缓冲区地址;size
:缓冲区大小;首先创建一个缓冲区句柄,开辟一块缓冲区:
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
//用于串口接收
uint8_t recv_data = 0;
//用于存储从缓冲区读取出的数据
uint8_t read_data = 0;
//用于串口1的ringbuff句柄
ringbuff_t usart1_ringbuff;
//开辟一块内存用于缓冲区
#define USART1_BUFFDATA_SIZE 150
uint8_t usart1_buffdata[USART1_BUFFDATA_SIZE];
/* USER CODE END 0 */
然后在main函数中初始化ringbuff:
/* USER CODE BEGIN 2 */
printf("ringbuff Port By Mculover666\r\n");
//初始化ringbuff句柄
if(1 != ringbuff_init(&usart1_ringbuff, (uint8_t*)usart1_buffdata, USART1_BUFFDATA_SIZE))
{
printf("usart1 ringbuff init fail.\r\n");
}
//使能串口中断接收
HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_data, 1);
/* USER CODE END 2 */
接收到一个字节数据后,话不多说,直接往缓冲区扔:
/* USER CODE BEGIN 4 */
/* 中断回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* 判断是哪个串口触发的中断 */
if(huart ->Instance == USART1)
{
/* 将接收到的数据写入缓冲区 */
ringbuff_write(&usart1_ringbuff, &recv_data, 1);
//重新使能串口接收中断
HAL_UART_Receive_IT(huart, (uint8_t*)&recv_data, 1);
}
}
/* USER CODE END 4 */
数据处理在while(1)中进行,每隔200ms将缓冲区数据全部读出进行处理:
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
while((len = ringbuff_read(&usart1_ringbuff, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
/* 捕获起始标志 */
if(read_data == 0x3F)
{
//读取数据字节数,最大支持0xFF
if((len = ringbuff_read(&usart1_ringbuff, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
data_len = read_data;
printf("your data has %d byte(s):\r\n\t", data_len);
}
//提取data_len个数据
for(i = 0; i < data_len; i++)
{
if((len = ringbuff_read(&usart1_ringbuff, (uint8_t*)&read_data, sizeof(read_data))) > 0)
{
printf("[0x%02x] ", read_data);
}
}
printf("over\r\n");
}
}
HAL_Delay(200);
}
/* USER CODE END 3 */
经过3.2节的测试,不丢包的最小缓冲区大小是140个字节,接下里我们将缓冲区大小修改为100个字节,测试一下是否产生丢包:
//开辟一块内存用于缓冲区
#define USART1_BUFFDATA_SIZE 100 //会发生丢包
//#define USART1_BUFFDATA_SIZE 150 //10ms接收7byte的协议包时不丢包
uint8_t usart1_buffdata[USART1_BUFFDATA_SIZE];
关于环形缓冲区背后的设计实现,请阅读这篇文章,写的非常棒:
目前我将ringbuff源码、我移植到小熊派STM32L431RCT6开发板的工程源码上传到了QQ群里(包含好几份HAL库,QQ相对速度快点),可以在QQ群里下载,有问题也可以在群里交流,当然也欢迎大家分享出来自己移植的工程到QQ群里:
放上QQ群二维码:
接收更多精彩文章及资源推送,欢迎订阅我的微信公众号:『mculover666』。