STM32HAL 快速入门(十五):环形缓冲区实战 —— 解决按键数据丢失问题
摘要:本文通过STM32实战验证环形缓冲区在异步数据处理中的有效性。以按键状态存储为例,初始化容量为100的环形缓冲区,在定时器回调中写入按键状态(0x11/0x81),主程序读取并显示于OLED。通过10秒延时模拟耗时操作,实验证明缓冲区能完整保存期间所有按键数据(如连续3次触发),解决了中断快写与主程序慢读的速度矛盾。关键点包括结构体封装缓冲区参数、指针循环移动实现高效存取,展示了C语言在嵌入
前言
大家好,这里是 Hello_Embed。上一篇我们介绍了环形缓冲区的原理与实现,本篇将通过实战验证其效果 —— 将按键状态(按下 / 松开)写入环形缓冲区,再在主程序中读取并显示,即使主程序执行耗时操作,也能确保按键数据不丢失。这一方案完美解决了 “中断写入快、主程序读取慢” 的矛盾,下一篇我们将学习 UART 协议,进一步拓展数据通信的场景。
一、实战目标与思路
目标:通过环形缓冲区存储按键状态(按下为0x11,松开为0x81),在主程序中读取并显示,验证即使主程序延时 10 秒,按键数据也不会丢失。
思路:
- 中断 / 定时器消抖后,将按键状态写入环形缓冲区;
- 主程序从缓冲区读取数据,通过 OLED 显示按键计数和状态;
- 在主程序中加入 10 秒延时,模拟耗时操作,验证缓冲区的存储能力。
二、代码实现步骤
1. 初始化环形缓冲区
首先定义缓冲区变量和存储数组,在main函数中完成初始化:
// 定义存储数组(容量100,可存储100个按键状态)
static uint8_t g_data_buf[100];
// 定义环形缓冲区结构体变量
static circle_buf g_key_bufs;
int main(void)
{
// 其他初始化(GPIO、OLED、定时器等)
HAL_Init();
MX_GPIO_Init();
MX_I2C1_Init();
OLED_Init();
OLED_Clear();
// 初始化环形缓冲区:绑定数组、设置长度100
circle_buf_init(&g_key_bufs, 100, g_data_buf);
// ... 后续代码
}
2. 按键状态写入缓冲区
在定时器消抖的超时回调函数中,读取按键状态并写入缓冲区:
int g_key_cnt = 0; // 记录有效按键次数(用于显示)
// 定时器超时回调函数(消抖完成后执行)
void key_timeout_func(void *args)
{
uint8_t key_val; // 存储按键状态:按下为0x11,松开为0x81
g_key_cnt++; // 有效按键次数+1
key_timer.timeout = ~0; // 重置定时器
// 读取PB14电平,判断按键状态
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_SET)
key_val = 0x11; // 按下(高电平)
else
key_val = 0x81; // 松开(低电平)
// 将按键状态写入环形缓冲区
circle_buf_write(&g_key_bufs, key_val);
}
3. 主程序读取缓冲区并显示
在main函数的循环中,从缓冲区读取数据,通过 OLED 显示按键计数和状态:
int main(void)
{
// ... 前面的初始化代码
int len; // 存储字符串显示的列数,用于对齐显示
// 在OLED第0行显示"cnt : "(按键计数标签)
OLED_PrintString(0, 0, "cnt : ");
// 在OLED第2行显示"Key val : "(按键状态标签),并获取显示长度
len = OLED_PrintString(0, 2, "Key val : ");
while (1)
{
// 显示按键计数(在"cnt : "后方)
OLED_PrintSignedVal(len, 0, g_key_cnt);
// 从缓冲区读取按键状态
uint8_t key_val = 0;
if (0 == circle_buf_read(&g_key_bufs, &key_val))
{
// 若读取成功,显示按键状态(十六进制,在"Key val : "后方)
OLED_PrintHex(len, 2, key_val, 1);
}
// 模拟耗时操作(延时10秒,测试缓冲区是否丢失数据)
HAL_Delay(10000);
}
}
三、关键函数解析
1. circle_buf_write与circle_buf_read
- 写入:
circle_buf_write(&g_key_bufs, key_val)将按键状态存入缓冲区,若未满则返回 0; - 读取:
circle_buf_read(&g_key_bufs, &key_val)从缓冲区取出数据,若未空则返回 0,按键状态存入key_val。
2. OLED 显示函数
OLED_PrintSignedVal(len, 0, g_key_cnt):在第 0 行、第len列显示按键计数(十进制);OLED_PrintHex(len, 2, key_val, 1):在第 2 行、第len列显示按键状态(十六进制,1 位字节)。
四、实验结果与验证
1. 正常操作效果
- 按下按键:OLED 第 0 行计数递增,第 2 行显示
0x11
- 松开按键:第 2 行显示
0x81
2. 耗时操作下的验证
主程序加入HAL_Delay(10000)(10 秒延时),模拟执行耗时任务:
- 延时期间按下 3 次按键:屏幕无更新(主程序被阻塞);

- 延时结束后:屏幕正确显示 3 次按键的计数和状态,证明缓冲区成功保存了所有数据,无丢失。

五、总结
环形缓冲区通过 “先入先出” 的存储逻辑和循环指针设计,完美解决了 “中断快速写入、主程序慢速读取” 场景下的数据丢失问题。其核心优势在于:
- 数据不覆盖:新数据仅在缓冲区未满时写入,避免覆盖旧数据;
- 高效存取:指针移动无需搬运数组元素,操作效率高;
- 易于扩展:可用于串口接收、传感器数据采集等多种场景。
本次实战中,结构体和指针的灵活运用是关键 —— 结构体封装了缓冲区的核心信息(读写指针、长度、数组),指针操作实现了高效的环形逻辑,这也体现了 C 语言在嵌入式开发中的强大优势。
结尾
本文通过环形缓冲区成功解决了按键数据丢失问题,验证了其在异步数据处理中的价值。下一篇笔记,我们将学习 UART 协议 —— 一种常用的串行通信协议,结合环形缓冲区可实现高效的串口数据收发。
Hello_Embed 继续带你探索 STM32 的数据通信与处理技巧,敬请期待~
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐



所有评论(0)