即使使用中断函数或者定时器函数记录按键,如果只能记录一个键值的话,如果不能
 及时读走出来,再次发生中断时新值就会覆盖旧值。要解决数据被覆盖的问题,可以使用
 一个稍微大点的缓冲区,这就涉及数据的写入、读出,可以使用环形缓冲区。
 环形缓冲区特别适合这种场景:
- 一方写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;
	else
		key_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 OLED
    OLED_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 */
}



















