环形缓冲区(Ring Buffer)又称为循环缓冲区或圆形队列,是一种数据结构,它用于管理固定大小的数据存储空间。环形缓冲区本质是一个一维数组,不过是收尾相连的,类比一条蛇咬自己尾巴。
环形缓冲区重要性:避免数据被覆盖。比如,使用中断函数或者定时器函数记录按键,如果只能记录一个键值的话,如果不能及时读走出来,再次发生中断时新值就会覆盖旧值。使用环形缓冲区可以避免数据被覆盖。
本文将详细介绍环形缓冲区的概念、工作原理,并以STM32F407微控制器结合HAL库为例,探讨如何利用环形缓冲区有效防止按键输入丢失。可参考定时器处理按键抖动
一、开发环境
硬件:正点原子探索者 V3 STM32F407开发板
单片机:STM32F407ZGT6
Keil版本:5.32
STM32CubeMX版本:6.9.2
STM32Cube MCU Packges版本:STM32F4xx_DFP.2.14.0
按键引脚:PE2
串口:USART1(PA9,PA10)
二、配置STM32CubeMX
-
启动STM32CubeMX,新建STM32CubeMX项目 :
-
选择MCU :在软件中选择你的STM32型号-STM32F407ZGT6。
-
选择时钟源:
-
配置时钟:
-
使能Debug功能:Serial Wire
-
HAL库时基选择:SysTick
-
USART1配置: 选择异步模式。
8.配置工程参数 :在Project标签页中,配置项目名称和位置,选择工具链MDK-ARM。 9.生成代码 :在Code Generator标签页中,配置工程外设文件与HAL库,勾选头文件.c和.h文件分开,然后点击Project > Generate Code生成代码。
三、代码实现与部署
-
添加circle_buffer.c,circle_buffer.h:
cpp#ifndef _CIRCLE_BUF_H #define _CIRCLE_BUF_H #include <stdint.h> typedef struct circle_buf { uint32_t r; uint32_t w; uint32_t len; uint8_t *buf; }circle_buf, *p_circle_buf; void circle_buf_init(p_circle_buf pCircleBuf, uint32_t len, uint8_t *buf); int circle_buf_read(p_circle_buf pCircleBuf, uint8_t *pVal); int circle_buf_write(p_circle_buf pCircleBuf, uint8_t val); #endif /* _CIRCLE_BUF_H */
cpp#include <stdint.h> #include "circle_buffer.h" void circle_buf_init(p_circle_buf pCircleBuf, uint32_t len, uint8_t *buf) { pCircleBuf->r = pCircleBuf->w = 0; pCircleBuf->len = len; pCircleBuf->buf = buf; } int circle_buf_read(p_circle_buf pCircleBuf, uint8_t *pVal) { if (pCircleBuf->r != pCircleBuf->w) { *pVal = pCircleBuf->buf[pCircleBuf->r]; pCircleBuf->r++; if (pCircleBuf->r == pCircleBuf->len) pCircleBuf->r = 0; return 0; } else { return -1; } } int circle_buf_write(p_circle_buf pCircleBuf, uint8_t val) { uint32_t next_w; next_w = pCircleBuf->w + 1; if (next_w == pCircleBuf->len) next_w = 0; if (next_w != pCircleBuf->r) { pCircleBuf->buf[pCircleBuf->w] = val; pCircleBuf->w = next_w; return 0; } else { return -1; } }
-
main.c增加代码:
cpp/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2024 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "usart.h" #include "gpio.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include <stdio.h> #include "circle_buffer.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ struct soft_timer { uint32_t timeout; void * args; void (*func)(void *); }; /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ int g_key_cnt = 0; void key_timeout_func(void *args); struct soft_timer key_timer = {~0, NULL, key_timeout_func}; static uint8_t g_data_buf[100]; static circle_buf g_key_bufs; void key_timeout_func(void *args) { uint8_t key_val; /* 按下是0x1, 松开 0x2 */ g_key_cnt++; key_timer.timeout = ~0; /* read gpio */ if (HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2 ) == GPIO_PIN_RESET) key_val = 0x1; else key_val = 0x2; /* put key val into circle buf */ circle_buf_write(&g_key_bufs, key_val); } void mod_timer(struct soft_timer *pTimer, uint32_t timeout) { pTimer->timeout = HAL_GetTick() + timeout; } void check_timer(void) { if (key_timer.timeout <= HAL_GetTick()) { key_timer.func(key_timer.args); } } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == GPIO_PIN_2) { mod_timer(&key_timer, 10); } } /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ circle_buf_init(&g_key_bufs, 100, g_data_buf); /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_USART1_UART_Init(); /* USER CODE BEGIN 2 */ HAL_Delay(5000); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ uint8_t key_val = 0; if (0 == circle_buf_read(&g_key_bufs, &key_val)) { printf("key_cnt:%d\r\n",g_key_cnt); //按下和松开累计次数 if(key_val == 0x1) printf("The key is pressed\r\n"); //按键按下 else if (key_val == 0x2) printf("Key is released\r\n");//按键松开 } } /* USER CODE END 3 */ }
-
stm32f4xx_it.c增加代码:
cpp/** * @brief This function handles System tick timer. */ void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ extern void check_timer(void); check_timer(); /* USER CODE END SysTick_IRQn 1 */ }
-
usart.c增加代码 :usart.c的第1行添加头文件#include <stdio.h>
#include <string.h>,在末尾用户代码区增加如下代码。printf调用"fputc()","fgetc()",该函数会使用HAL_UART_Transmit发送数据。cpp/* * 添加如下代码,可不在工程设置中勾选Use MicroLIB */ #pragma import(__use_no_semihosting) struct __FILE { int a; }; FILE __stdout; FILE __stdin; void _sys_exit(int x) { } /***************************************************** *function: 写字符文件函数 *param1: 输出的字符 *param2: 文件指针 *return: 输出字符的ASCII码 ******************************************************/ int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 10); return ch; } /***************************************************** *function: 读字符文件函数 *param1: 文件指针 *return: 读取字符的ASCII码 ******************************************************/ int fgetc(FILE *f) { uint8_t ch = 0; HAL_UART_Receive(&huart1, (uint8_t*)&ch, 1, 10); return (int)ch; }
-
连接USART1 :用USB转TTL工具连接当前硬件USART1的PA9、PA10,GND。
-
打开串口助手:
-
编译代码:Keil编译生成的代码。
-
烧录程序:将编译好的程序用ST-LINK烧录到STM32微控制器中。
四、运行结果
观察结果:一旦程序烧录完成并运行,复位3秒后,按下4次,按下的4次(8次按下松开)每次都被记录下来。
复位3秒内,按下4次,按下的4次(8次按下松开)也被记录下来了,说明按下的键值被环形缓冲区记录下来了。
五、注意事项
1.确保你的开发环境和工具已经正确安装和配置。
2.如果没有打印电压值,按一下复位键,检查连接和电源是否正确,注意根据你所用的硬件来接线,不要接错线。
3.在串口打印数据时,要确保波特率等参数与串口助手设置一致。
通过上述步骤,你就可以在STM32F407 HAL库利用环形缓冲区有效防止按键输入丢失。