嵌入式开源项目精选专栏
本专栏由Mculover666创建,主要内容为寻找嵌入式领域内的优质开源项目,一是帮助开发者使用开源项目实现更多的功能,二是通过这些开源项目,学习大佬的代码及背后的实现思想,提升自己的代码水平,和其它专栏相比,本专栏的优势在于:
不会单纯的介绍分享项目,还会包含作者亲自实践的过程分享,甚至还会有对它背后的设计思想解读。
目前本专栏包含的开源项目有:
- cJSON | 一个轻量级C语言JSON解析器
- paho | 支持10种语言编写mqtt客户端,总有一款适合你!
- MultiButton | 一个小巧简单易用的事件驱动型按键驱动模块
- letter-shell | 一个功能强大的嵌入式shell
- EasyLogger | 一款轻量级且高性能的日志库
- SFUD | 一款串行 Flash 通用驱动库
- EasyFlash | 让 Flash 成为小型 KV 数据库
- MultiTimer | 一款可无限扩展的软件定时器
- cmd-parser | 一个基于哈希匹配的超快命令解析器
- jsmn | 一个资源占用极少的json解析器
- CmBacktrace | 一款 ARM Cortex-M 系列 MCU 错误追踪库
如果您自己编写或者发现的开源项目不错,欢迎留言或者私信投稿到本专栏,分享获得双倍的快乐!
1. ringbuff
本期给大家带来的开源项目是 ringbuff ,一款通用FIFO环形缓冲区实现的开源库,作者MaJerle,目前收获 79 个 star,遵循 MIT 开源许可协议。
目前 ringbuff 的特点有:
- 使用C99语法编写,并且没有平台相关代码;
- 没有动态内存分配;
- 使用更优的内存复制而不是循环从内存读取数据/向内存写入数据;
项目地址:https://github.com/MaJerle/ringbuff
2. 移植ringbuff
2.1. 移植思路
在移植过程中主要参考两个资料:项目的readme文档和demo工程。
对于这些开源项目,其实移植起来也就两步:
- ① 添加源码到裸机工程中;
- ② 实现需要的接口即可;
2.2. 准备裸机工程
本文中我使用的是小熊派IoT开发套件,主控芯片为STM32L431RCT6:
移植之前需要准备一份裸机工程,我使用STM32CubeMX生成,需要初始化以下配置:
- 配置一个串口,中断方式接收数据,查询方式发送数据;
- printf重定向;
2.3. 添加ringbuff 到工程中
① 复制 ringbuff 源码到工程中:
② 在keil中添加 ringbuff 组件的源码文件:
③ 添加 ringbuff 的头文件路径:
2.4. 配置ringbuff
ringbuff中默认volatile关键词没有定义,需要手动配置一下,在ringbuff.h
中:
至此,ringbuff移植修改完成,可以愉快的使用ringbuff啦~
3. 使用ringbuff
3.1. 为什么使用ringbuff
缓冲区一般用于解决设备接收数据的速度和设备处理速度不匹配的情况下,防止丢包,通俗的来说就是:收到数据先存进缓冲区,等到CPU来处理的时候一次性取出处理。
缓冲区有两种形式,一种是数组,一种就是本文所介绍的环形缓冲区ringbuff。
相较于数组,环形缓冲区对整段内存的利用达到最大,并且使用非常方便,如下:
- ① 写入的时候不用手动维护下标,直接写入即可(由缓冲区的实现维护);
- ② 读取的时候不用判断从哪里读,直接读取即可(有缓冲区的实现维护)
本文设计的一个简单的不定长串口协议如下:
- 数据类型:比如0x3F表示这是通道1的数据,0x4E表示通道2的数据;
- 数据长度:表示后面跟着有效数据的长度;
- 有效数据:有效字节数;
- 校验数据:省略;
接下来演示如何用环形缓冲区做到不丢包解析。
3.2. 计算缓冲区大小
假定数据每200ms处理一次,而数据10ms接收一次,每次接收的数据包长度为7个字节。
要想做到不丢包,就需要将200ms内接收到的所有数据包都存进缓冲区,所以缓冲区大小至少为:200/10*7 = 140 个字节。
保险起见,可以将缓冲区适当的扩大一下,设置为150个字节。
3.3. 初始化缓冲区
使用时包含头文件:
代码语言:javascript复制#include "ringbuff/ringbuff.h"
接着初始化缓冲区:
代码语言:javascript复制uint8_t ringbuff_init(RINGBUFF_VOLATILE ringbuff_t* buff, void* buffdata, size_t size);
该 API 用来初始化一个ringbuff句柄(指向ringbuff结构体的指针),其中传入的参数分别为:
buff
:ringbuff句柄;buffdata
:缓冲区地址;size
:缓冲区大小;
首先创建一个缓冲区句柄,开辟一块缓冲区:
代码语言:javascript复制/* 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:
代码语言:javascript复制/* USER CODE BEGIN 2 */
printf("ringbuff Port By Mculover666rn");
//初始化ringbuff句柄
if(1 != ringbuff_init(&usart1_ringbuff, (uint8_t*)usart1_buffdata, USART1_BUFFDATA_SIZE))
{
printf("usart1 ringbuff init fail.rn");
}
//使能串口中断接收
HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_data, 1);
/* USER CODE END 2 */
3.4. 数据接收
接收到一个字节数据后,话不多说,直接往缓冲区扔:
代码语言:javascript复制/* 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 */
3.5. 数据处理
数据处理在while(1)中进行,每隔200ms将缓冲区数据全部读出进行处理:
代码语言:javascript复制 /* 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):rnt", 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("[0xx] ", read_data);
}
}
printf("overrn");
}
}
HAL_Delay(200);
}
/* USER CODE END 3 */
编译下载测试,实验结果如下,可以做到不丢包解析:
3.6. 丢包测试
经过3.2节的计算,不丢包的最小缓冲区大小是140个字节,接下里我们将缓冲区大小修改为100个字节,测试一下是否产生丢包:
代码语言:javascript复制//开辟一块内存用于缓冲区
#define USART1_BUFFDATA_SIZE 100 //会发生丢包
//#define USART1_BUFFDATA_SIZE 150 //10ms接收7byte的协议包时不丢包
uint8_t usart1_buffdata[USART1_BUFFDATA_SIZE];
再次编译下载,查看串口输出:
4. 设计思想解读
关于环形缓冲区背后的设计实现,请阅读这篇文章,写的非常棒:
- STM32进阶之串口环形缓冲区实现