文章目录
- 开发板
- 开发环境
- 前言
- 按键消抖
- 按键硬件原理图
- 软件延时实现思路
- 实验目的
- 代码
- 按键状态
- 按键信息
- 按键相关定义
- 按键底层配置及状态获取
- 总结
开发板
正点原子STM32F103ZET6战舰
开发环境
stm32cubeMX Clion
前言
在单片机使用按键时,为了消除按键的抖动,往往采用delay的方法消除,但是这种方式使CPU处于空等的状态,不能进行其他任务,直到结束delay延时函数,这种阻塞的方式不利于多任务的情形。因此采用状态机就能实现非阻塞,从而让CUP更高效执行任务。
按键消抖
按键硬件原理图
本次用到KEY0 KEY1 KEY2
由原理图可知当我们按键按下呈现低电平,理想波形如下:
实际波形:
因此我们要消除按下和抬起时抖动。这个抖动我们软件上延迟个5-10ms再次确认电平状态,如果任然是低电平则认为按键按下,抬起同上。
软件延时实现思路
首先我们摒弃直接delay_ms(10)这种方法,我们既要达到延时10ms左右时间又要不阻塞CPU,可采取以下方法:
可以开启一个定时器,10为周期,在回调函数里面内进行判断。
我这里采用获取滴答定时器中断的tick计数,当两次获取tick差值为10时也就实现了以上效果,本实验还将实现长按检测,当差值达到设定值时,即可判断当前处于长按状态。因此采用系统滴答定时器中断计数这种方式会更加方便。具体实现可查看代码。
实验目的
实现采用状态机方式检测各个按键状态并实现对应功能(支持长按)
KEY0按下 红灯亮,松开红灯灭,长按红灯一直亮直至松开
KEY1按下 绿灯亮,松开绿灯灭,长按绿灯一直亮直至松开
KEY0按下 蜂鸣器响,蜂鸣器关,长按蜂鸣器一直响亮直至松开
代码
按键状态
// 按键状态机
typedef enum _KeyState
{
KEY_NULL = 0, //表示无按键按下
KEY_DOWN_JUDDER, //按下抖动
KEY_DOWN, //按下
KEY_LONG, //长按
KEY_UP_JUDDER, //抬起抖动
KEY_UP, //抬起
}KeyState;
按键信息
本实验采用三个按键
// 按键ID
typedef enum _KeyId
{
KEY_ID_0 = 0,
KEY_ID_1 ,
KEY_ID_2 ,
KEY_ID_MAX
}KeyId;
// 按键结构体
typedef struct _keyInfo{
KeyState keyState ; //按键状态机
uint8_t preKeyState; //上一次按键的状态
int32_t preTime; //上次时间
int32_t curTime; //当前时间
}KeyInfo;
按键相关定义
可自行修改
#define KEYPRESS 1 //按下电平
#define KEYRELEASE 0 //抬起电平
#define KEYPRESSTIME 10 //消除抖动10ms
#define KEYLONGPRESSTIME 1000 //短按超过1秒算长按
按键底层配置及状态获取
bsp_key.c
/********************************************************************************
* @author: majj
* @email: 1289856135@qq.com
* @date: 2023/9/8 21:13
* @version: 2.0
* @description:
********************************************************************************/
#include "bsp_key.h"
KeyInfo keyInfo[KEY_ID_MAX];
KeyValueBuffer keyValueBuffer[KEY_ID_MAX];
/**
* 获取按键电平值
* @param keyID
* @return
*/
GPIO_PinState GetKeyValue(uint8_t keyID)
{
GPIO_PinState value = GPIO_PIN_RESET;
switch (keyID) {
case KEY_ID_0:
value = HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin);
break;
case KEY_ID_1:
value = HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin);
break;
case KEY_ID_2:
value = HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin);
break;
default:
break;
}
return value;
}
/**
* 写入一次状态
* @param keypad_status
*/
void KeyValueWrite(uint8_t id,KeyState keypad_status)
{
keyValueBuffer[id].valBuffer[keyValueBuffer[id].write] = keypad_status;
if (++keyValueBuffer[id].write >= KEY_ID_MAX)
{
keyValueBuffer[id].write = 0;
}
}
/**
* 读取一次状态
* @return
*/
KeyState KeyValueRead(uint8_t id)
{
KeyState key_event;
if (keyValueBuffer[id].read == keyValueBuffer[id].write)
{
return KEY_NULL;
}
else
{
key_event = keyValueBuffer[id].valBuffer[keyValueBuffer[id].read];
if (++keyValueBuffer[id].read >= KEY_ID_MAX)
{
keyValueBuffer[id].read = 0;
}
return key_event;
}
}
/**
* 判断案件是否被按下
* @param keyId
* @return
*/
uint8_t GetKeyIOState(KeyId keyId)
{
GPIO_PinState key_value = GetKeyValue(keyId);
if (key_value == GPIO_PIN_RESET){
return KEYPRESS; // 按键被按下
}else{
return KEYRELEASE; // 按键松开
}
}
/**
*
*/
void KeyInfoInit()
{
for (int i = 0; i < KEY_ID_MAX; i++) {
keyInfo[i].keyState = KEY_NULL;
keyInfo[i].preKeyState = KEY_NULL;
keyInfo[i].preTime = 0;
keyInfo[i].curTime = 0;
}
}
bsp_key.h
/********************************************************************************
* @author: majj
* @email: 1289856135@qq.com
* @date: 2023/9/8 21:13
* @version: 2.0
* @description:
********************************************************************************/
#ifndef KEY_STATE_BSP_KEY_H
#define KEY_STATE_BSP_KEY_H
#include "gpio.h"
#include "Retarget.h"
#define KEYPRESS 1 //按下电平
#define KEYRELEASE 0 //抬起电平
#define KEYPRESSTIME 10 //消除抖动10ms
#define KEYLONGPRESSTIME 1000 //短按超过1秒算长按
// 按键ID
typedef enum _KeyId
{
KEY_ID_0 = 0,
KEY_ID_1 ,
KEY_ID_2 ,
KEY_ID_MAX
}KeyId;
// 按键状态机
typedef enum _KeyState
{
KEY_NULL = 0, //表示无按键按下
KEY_DOWN_JUDDER, //按下抖动
KEY_DOWN, //按下
KEY_LONG, //长按
KEY_UP_JUDDER, //抬起抖动
KEY_UP, //抬起
}KeyState;
//buffer结构体
typedef struct _KeyValueBuffer
{
GPIO_PinState valBuffer[KEY_ID_MAX]; //键值缓冲区
uint8_t read; //缓冲区读指针1
uint8_t write; //缓冲区写指针1
} KeyValueBuffer;
// 按键结构体
typedef struct _keyInfo{
KeyState keyState ; //按键状态机
uint8_t preKeyState; //上一次按键的状态
int32_t preTime; //上次时间
int32_t curTime; //当前时间
}KeyInfo;
extern KeyInfo keyInfo[KEY_ID_MAX];
void KeyInfoInit(void);
uint8_t GetKeyIOState(KeyId keyId);
void KeyValueWrite(uint8_t id,KeyState keypad_status);
KeyState KeyValueRead(uint8_t id);
#endif //KEY_STATE_BSP_KEY_H
bsp_led.c
/********************************************************************************
* @author: majj
* @email: 1289856135@qq.com
* @date: 2023/9/8 21:13
* @version: 2.0
* @description:
********************************************************************************/
#include "bsp_led.h"
/**
* 设置LED0状态
* @param flag
*/
void SetLed0State(uint8_t flag)
{
if(flag){
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET);
}else{
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET);
}
}
/**
* 设置LED1状态
* @param flag
*/
void SetLed1State(uint8_t flag)
{
if(flag){
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
}else{
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
}
}
bsp_beep.c
/********************************************************************************
* @author: majj
* @email: 11289856135@qq.com
* @date: 2023/9/8 21:14
* @version: 2.0
* @description:
********************************************************************************/
#include "bsp_beep.h"
void SetBeepState(uint8_t flag)
{
if(flag){
HAL_GPIO_WritePin(BEEP_GPIO_Port,BEEP_Pin,GPIO_PIN_SET);
} else{
HAL_GPIO_WritePin(BEEP_GPIO_Port,BEEP_Pin,GPIO_PIN_RESET);
}
}
按键状态机轮询任务
/********************************************************************************
* @author: majj
* @email: 1289856135@qq.com
* @date: 2023/9/8 22:36
* @version: 2.0
* @description:
********************************************************************************/
#include "key_status_controller.h"
/**
* 执行按键任务
* @param keyID
* @param keyState
*/
void OperateKeyTask(KeyId keyID,KeyState keyState)
{
switch (keyID) {
case KEY_ID_0:
if(keyState == KEY_DOWN){
SetLed0State(1);
}else if(keyState == KEY_LONG){
printf("KEY0 --长按\r\n");
SetLed0State(1);
} else if(keyState ==KEY_NULL){
SetLed0State(0);
}
break;
case KEY_ID_1:
if(keyState == KEY_DOWN){
SetLed1State(1);
}else if(keyState == KEY_LONG){
SetLed1State(1);
} else if(keyState ==KEY_NULL){
SetLed1State(0);
}
break;
case KEY_ID_2:
if(keyState == KEY_DOWN){
SetBeepState(1);
}else if(keyState == KEY_LONG){
printf("KEY2 --长按\r\n");
SetBeepState(1);
} else if(keyState ==KEY_NULL ){
SetBeepState(0);
}
break;
default:
break;
}
}
/**
* 检测按键
* @param keyID
*/
void DetectKeyState(KeyId keyID)
{
//读取按键状态信息
KeyState keyState = keyInfo[keyID].preKeyState;
//读取按键电平变化
uint8_t keyValue = GetKeyIOState(keyID);
switch (keyState) {
case KEY_NULL:
if(keyValue == KEYPRESS){
//检测按键按下
keyInfo[keyID].keyState = KEY_DOWN_JUDDER;
keyInfo[keyID].preTime = keyInfo[keyID].curTime;
} else{
keyInfo[keyID].curTime = 0;
keyInfo[keyID].preTime = 0;
}
break;
case KEY_DOWN_JUDDER:
if(keyValue == KEYPRESS){
keyInfo[keyID].curTime = HAL_GetTick();
if((keyInfo[keyID].curTime - keyInfo[keyID].preTime) > KEYPRESSTIME){
//确认按下
keyInfo[keyID].keyState = KEY_DOWN;
keyInfo[keyID].preTime = keyInfo[keyID].curTime;
}
} else{
keyInfo[keyID].keyState = KEY_NULL;
}
break;
case KEY_DOWN:
keyInfo[keyID].curTime = HAL_GetTick();
if((keyInfo[keyID].curTime - keyInfo[keyID].preTime) > KEYLONGPRESSTIME){
//确认按下
keyInfo[keyID].keyState = KEY_LONG;
keyInfo[keyID].preTime = keyInfo[keyID].curTime;
}else{
//短按
if(keyValue == KEYRELEASE){
keyInfo[keyID].keyState = KEY_UP_JUDDER;
keyInfo[keyID].preTime = keyInfo[keyID].curTime;
}
}
break;
case KEY_LONG:
keyInfo[keyID].curTime = HAL_GetTick();
if(keyValue == KEYRELEASE){
//抬起抖动
keyInfo[keyID].keyState = KEY_UP_JUDDER;
keyInfo[keyID].curTime = HAL_GetTick();
keyInfo[keyID].preTime = keyInfo[keyID].curTime;
}
break;
case KEY_UP_JUDDER:
if(keyValue == KEYRELEASE){
keyInfo[keyID].curTime = HAL_GetTick();
if((keyInfo[keyID].curTime - keyInfo[keyID].preTime) > KEYPRESSTIME){
//确认抬起
keyInfo[keyID].keyState = KEY_UP;
KeyValueWrite(keyID,keyInfo[keyID].keyState);
}
}
break;
case KEY_UP:
keyInfo[keyID].keyState = KEY_NULL;
KeyValueWrite(keyID,keyInfo[keyID].keyState);
break;
default:
break;
}
//执行动作任务
OperateKeyTask(keyID,keyState);
//记录本次状态
keyInfo[keyID].preKeyState = keyInfo[keyID].keyState;
}
/**
* 扫描按键
*/
void KeyScanTask()
{
for(int i = 0; i < KEY_ID_MAX; i++){
DetectKeyState((KeyId)i);
}
}
总结
以上以实现了一个简单粗略的方式的按键状态机,有很多地方相对不足,仅供参考。本例程本人采用stm32cube+clion的方式,无法在keil5上直接跑,因此不上传整个文件了。