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