程序中需要使用到STM32官方库V3.5.0版本,当然如果使用其他版本可以根据要求自行更改,这里不啰嗦了,直接上程序
首先在头文件中定义按键事件和宏定义,用于表示当前的按键状态:
#define MY_BUTTON_DOWN_MS 50 //按下超过MY_BUTTON_DOWN_MS时间,认为按键为有效按下,过小容易出现抖动误判,过大会导致按键不灵敏
#define MY_BUTTON_HOLD_MS 700 //当超过此数值时认为按键长按保持
#define MY_BUTTON_SCAN_SPACE_MS 5 //按键的扫描周期
#define MY_BUTTON_LIST_MAX 10 //支持的按键数量,可以根据实际要求定义
#define PRESS_LEVEL Bit_RESET //表示按键按下时的电平状态,这里使用的是低电平,Bit_RESET为STM32官方库中的定义
#define DEFAULT_COUNT_TIME 50
enum my_button_event
{
BUTTON_EVENT_CLICK_DOWN, /* 按键单击按下事件 */
BUTTON_EVENT_CLICK_UP, /* 按键单击结束事件 */
BUTTON_EVENT_HOLD, /* 按键长按开始事件 */
BUTTON_EVENT_HOLD_CYC, /* 按键长按周期性触发事件 */
BUTTON_EVENT_HOLD_UP, /* 按键长按结束事件 */
BUTTON_EVENT_NONE /* 无按键事件 */
};
//接着,我们定义一下按键逻辑的结构体:
struct my_button
{
uint8_t press_logic_level; /* 按键按下时的电平 */
uint16_t cnt; /* 连续扫描到按下状态的次数 */
uint16_t hold_cyc_period; /* 长按周期回调的周期 */
GPIO_TypeDef* GPIOx; /* 定义按键使用的端口,如GPIOA,基于STM32官方库 */
uint16_t GPIO_Pin; /* 按键对应的 pin 引脚 */
enum my_button_event event; /* 按键的触发的事件 */
my_button_callback cb; /* 按键触发事件回调函数 */
};
接下来编写函数部分:
函数回调宏定义
#define MY_BUTTON_CALL(func, argv) \
do { if ((func) != RT_NULL) func argv; } while (0)
这里需要定义一个my_button_manage结构体,用于统一管理按键
struct my_button_manage
{
uint8_t num; /* 已注册的按键的数目 */
rt_timer_t timer; /* 按键扫描用到的定时器 */
struct my_button *button_list[MY_BUTTON_LIST_MAX]; /* 存储按键指针的数组 */
};
static struct my_button_manage button_manage;
然后进行STM32的硬件引脚初始化:
void Key_Input_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); //使能PA,PD端口时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; //LED0-->PA.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure); //根据设定参数初始化GPIOA.8
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14; //LED0-->PA.8 端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure); //根据设定参数初始化GPIOA.8
Menu_Key_Register(); //这里是完成菜单按键的注册,详细见下面代码
}
再实现按键事件的注册:
//这里可以自己定义按键名称,如下是定义的上下左右OK键和菜单、返回键,并且定义它们使用的引脚
struct my_button UP_Key = {PRESS_LEVEL, 0, 5, GPIOA, GPIO_Pin_7};
struct my_button DOWN_Key = {PRESS_LEVEL, 0, 5, GPIOA, GPIO_Pin_5};
struct my_button LEFT_Key = {PRESS_LEVEL, 0, 5, GPIOA, GPIO_Pin_6};
struct my_button RIGHT_Key = {PRESS_LEVEL, 0, 5, GPIOA, GPIO_Pin_4};
struct my_button OK_Key = {PRESS_LEVEL, 0, 5, GPIOB, GPIO_Pin_12};
struct my_button MENU_Key = {PRESS_LEVEL, 0, 5, GPIOB, GPIO_Pin_13};
struct my_button BACK_Key = {PRESS_LEVEL, 0, 5, GPIOB, GPIO_Pin_14};
int my_button_register(struct my_button *button)
{
button->cnt = 0;
button->event = BUTTON_EVENT_NONE;
button_manage.button_list[button_manage.num++] = button;
return 0;
}
//对按键进行初始化注册
void Menu_Key_Register(void)
{
my_button_register(&UP_Key);
my_button_register(&DOWN_Key);
my_button_register(&LEFT_Key);
my_button_register(&RIGHT_Key);
my_button_register(&OK_Key);
my_button_register(&MENU_Key);
my_button_register(&BACK_Key);
}
//判断按键是否按下
uint8_t Is_Key_Press(struct my_button *btn, enum my_button_event eve)
{
if(btn->event == eve)
{
btn->event = BUTTON_EVENT_NONE;
return 1;
}
return 0;
}
接下来是按键的主要处理逻辑了
void My_Button_Scan(void)是按键的处理函数,可以放在定时中断函数中,本文使用的是5MS定时中断
void My_Button_Scan(void)
{
rt_uint8_t i;
rt_uint16_t cnt_old;
for (i = 0; i < button_manage.num; i++)
{
cnt_old = button_manage.button_list[i]->cnt;
if(GPIO_ReadInputDataBit(button_manage.button_list[i]->GPIOx, button_manage.button_list[i]->GPIO_Pin) == PRESS_LEVEL)
{
button_manage.button_list[i]->cnt ++;
if (button_manage.button_list[i]->cnt == MY_BUTTON_DOWN_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_DOWN */
{
button_manage.button_list[i]->event = BUTTON_EVENT_CLICK_DOWN;
MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i]));
}
else if (button_manage.button_list[i]->cnt == MY_BUTTON_HOLD_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_HOLD */
{
button_manage.button_list[i]->event = BUTTON_EVENT_HOLD;
MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i]));
}
else if (button_manage.button_list[i]->cnt > MY_BUTTON_HOLD_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_HOLD_CYC */
{
button_manage.button_list[i]->event = BUTTON_EVENT_HOLD_CYC;
if (button_manage.button_list[i]->hold_cyc_period && button_manage.button_list[i]->cnt % (button_manage.button_list[i]->hold_cyc_period / MY_BUTTON_SCAN_SPACE_MS) == 0)
MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i]));
}
}
else
{
button_manage.button_list[i]->cnt = 0;
if (cnt_old >= MY_BUTTON_DOWN_MS / MY_BUTTON_SCAN_SPACE_MS && cnt_old < MY_BUTTON_HOLD_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_CLICK_UP */
{
button_manage.button_list[i]->event = BUTTON_EVENT_CLICK_UP;
MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i]));
}
else if (cnt_old >= MY_BUTTON_HOLD_MS / MY_BUTTON_SCAN_SPACE_MS) /* BUTTON_HOLD_UP */
{
button_manage.button_list[i]->event = BUTTON_EVENT_HOLD_UP;
MY_BUTTON_CALL(button_manage.button_list[i]->cb, (button_manage.button_list[i]));
}
}
}
}
//清除所有按键事件,一般用于初始化或切换系统状态时
void Clear_KeyEvent_All(void)
{
rt_uint8_t i;
for (i = 0; i < button_manage.num; i++)
{
button_manage.button_list[i]->event = BUTTON_EVENT_NONE;
}
}