前言

大家好,这里是 Hello_Embed。上一篇我们介绍了环形缓冲区的原理与实现,本篇将通过实战验证其效果 —— 将按键状态(按下 / 松开)写入环形缓冲区,再在主程序中读取并显示,即使主程序执行耗时操作,也能确保按键数据不丢失。这一方案完美解决了 “中断写入快、主程序读取慢” 的矛盾,下一篇我们将学习 UART 协议,进一步拓展数据通信的场景。

一、实战目标与思路

目标:通过环形缓冲区存储按键状态(按下为0x11,松开为0x81),在主程序中读取并显示,验证即使主程序延时 10 秒,按键数据也不会丢失。
思路

  1. 中断 / 定时器消抖后,将按键状态写入环形缓冲区;
  2. 主程序从缓冲区读取数据,通过 OLED 显示按键计数和状态;
  3. 在主程序中加入 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_writecircle_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 的数据通信与处理技巧,敬请期待~

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐