环形缓冲区(Ring Buffer)在STM32 HAL库中的应用:防止按键丢失

环形缓冲区(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

  1. 启动STM32CubeMX,新建STM32CubeMX项目

  2. 选择MCU :在软件中选择你的STM32型号-STM32F407ZGT6。

  3. 选择时钟源:

  4. 配置时钟:

  5. 使能Debug功能:Serial Wire

  6. HAL库时基选择:SysTick

  7. USART1配置: 选择异步模式。

8.配置工程参数 :在Project标签页中,配置项目名称和位置,选择工具链MDK-ARM。 9.生成代码 :在Code Generator标签页中,配置工程外设文件与HAL库,勾选头文件.c和.h文件分开,然后点击Project > Generate Code生成代码。

三、代码实现与部署

  1. 添加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;
    	}
    }
  2. 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 */
    }
  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 */
    }
  4. 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;
    }
  5. 连接USART1 :用USB转TTL工具连接当前硬件USART1的PA9、PA10,GND。

  6. 打开串口助手:

  7. 编译代码:Keil编译生成的代码。

  8. 烧录程序:将编译好的程序用ST-LINK烧录到STM32微控制器中。

四、运行结果

观察结果:一旦程序烧录完成并运行,复位3秒后,按下4次,按下的4次(8次按下松开)每次都被记录下来。

复位3秒内,按下4次,按下的4次(8次按下松开)也被记录下来了,说明按下的键值被环形缓冲区记录下来了。

​五、注意事项

1.确保你的开发环境和工具已经正确安装和配置。

2.如果没有打印电压值,按一下复位键,检查连接和电源是否正确,注意根据你所用的硬件来接线,不要接错线。
3.在串口打印数据时,要确保波特率等参数与串口助手设置一致。

通过上述步骤,你就可以在STM32F407 HAL库利用环形缓冲区有效防止按键输入丢失。

相关推荐
Mortal_hhh21 分钟前
VScode的C/C++点击转到定义,不是跳转定义而是跳转声明怎么办?(内附详细做法)
ide·vscode·stm32·编辑器
深圳市青牛科技实业有限公司34 分钟前
【青牛科技】应用方案|D2587A高压大电流DC-DC
人工智能·科技·单片机·嵌入式硬件·机器人·安防监控
朱一头zcy1 小时前
C语言复习第9章 字符串/字符/内存函数
c语言
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
Mr.谢尔比2 小时前
电赛入门之软件stm32keil+cubemx
stm32·单片机·嵌入式硬件·mcu·信息与通信·信号处理
LightningJie2 小时前
STM32中ARR(自动重装寄存器)为什么要减1
stm32·单片机·嵌入式硬件
何曾参静谧2 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
鹿屿二向箔2 小时前
STM32外设之SPI的介绍
stm32
西瓜籽@2 小时前
STM32——毕设基于单片机的多功能节能窗控制系统
stm32·单片机·课程设计
lulu_gh_yu2 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法