即使使用中断函数或者定时器函数记录按键,如果只能记录一个键值的话,如果不能
及时读走出来,再次发生中断时新值就会覆盖旧值。要解决数据被覆盖的问题,可以使用
一个稍微大点的缓冲区,这就涉及数据的写入、读出,可以使用环形缓冲区。
环形缓冲区特别适合这种场景:
- 一方写buffer
- 另一方读buffer
环形缓冲区实际上还是一维数组,假设有N 个数组项,从第0 个数组项开始遍历,访
问完第N-1 个数组项后,再从0 开始——这就是“环形”的含义,如下图所示:
环形缓冲区的工作原理如下图所示:
① 读位置、写位置:r、w,它们表示“下一个要读的位置”、“下一个要写的位置”,初始值都是0。这两个变量概念非常重要,记住写下标永远在读下标前面。
② 写数据时:把数据写入buffer[w],然后调整w指向下一个位置(当w 越界后要从0
开始),先写数据再调整下标位置。
③ 读数据时:从buffer[r]读出数据,然后调整r 指向下一个位置(当r 越界后要从0
开始),先读数据再调整下标位置。
④ 判断buffer 为空:r 等于w 时表示空,此时读下标位置追上了写下标位置。
⑤判断buffer为满:“下一个写位置”等于当前读位置,注意空出了一个元素未使用的,作为区分标志。
缓冲区的实现代码
circual_buffer.h
#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 */
circual.c
#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)
{//环形缓冲区非空,r等于w表示空if (pCircleBuf->r != pCircleBuf->w){*pVal = pCircleBuf->buf[pCircleBuf->r];pCircleBuf->r++;//r下标应该在0--len-1,如果下一个读位置等于len,下标从0开始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;}
}
以下是stm32应用,使用缓冲区记录按键的值,防止丢失
#include "main.h"
#include "i2c.h"
#include "gpio.h"
#include "driver_oled.h"
#include "circle_buffer.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes *//* 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, 松开 0x81 */g_key_cnt++; //统计按键中断次数key_timer.timeout = ~0; //定时器计数值清0,防止反复触发按键计数/* read gpio */if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14) == GPIO_PIN_RESET)key_val = 0x1;elsekey_val = 0x81;/* 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;
}//定时器中断处理函数,每1ms触发一次
void check_timer(void)
{if (key_timer.timeout <= HAL_GetTick()){key_timer.func(key_timer.args);}
}//按键中断处理函数,每次修改定时器的值推迟10ms
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{if (GPIO_Pin == GPIO_PIN_14){ 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 */int len;/* 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_I2C1_Init();/* USER CODE BEGIN 2 */// Init OLEDOLED_Init();// 清屏OLED_Clear();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE *///HAL_Delay(10000);OLED_PrintString(0, 0, "Cnt : "); //在第一行打印,第二个参数决定len = OLED_PrintString(0, 2, "Key val : "); //在第二行打印,第二个参数决定while (1){OLED_PrintSignedVal(len, 0, g_key_cnt);uint8_t key_val = 0;if (0 == circle_buf_read(&g_key_bufs, &key_val)){OLED_ClearLine(len, 2); //清除一行指定位置后的数据OLED_PrintHex(len, 2, key_val, 1);}/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}