单片机:STM32F407
开发板:DMF407电机开发板
平台:keil V5.31
HSE 为8MHZ
HSI为16MHZ
原理图:

IO配置:
void key_init(void)
{
GPIO_InitTypeDef gpio_init_struct; /* GPIO配置参数存储变量 */
KEY0_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */
KEY1_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */
KEY2_GPIO_CLK_ENABLE(); /* KEY2时钟使能 */
gpio_init_struct.Pin = KEY0_GPIO_PIN; /* KEY0引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct); /* KEY0引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY1_GPIO_PIN; /* KEY1引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_init_struct); /* KEY1引脚模式设置,上拉输入 */
gpio_init_struct.Pin = KEY2_GPIO_PIN; /* KEY2引脚 */
gpio_init_struct.Mode = GPIO_MODE_INPUT; /* 输入 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(KEY2_GPIO_PORT, &gpio_init_struct); /* KEY2引脚模式设置,上拉输入 */
}
按键扫描:
uint8_t key_scan(uint8_t mode)
{
static uint8_t key_up = 1; /* 按键按松开标志 */
uint8_t keyval = 0;
if (mode) key_up = 1; /* 支持连按 */
if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0)) /* 按键松开标志为1, 且有任意一个按键按下了 */
{
delay_ms(10); /* 去抖动 */
key_up = 0;
if (KEY0 == 0) keyval = KEY0_PRES;
if (KEY1 == 0) keyval = KEY1_PRES;
if (KEY2 == 0) keyval = KEY2_PRES;
}
else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1) /* 没有任何按键按下, 标记按键松开 */
{
key_up = 1;
}
return keyval; /* 返回键值 */
}
按键操作使用:
while(1)
{
key = key_scan(0); /* 得到键值 */
if (key)
{
switch (key)
{
case KEY0_PRES: /* 控制LED0(RED)翻转 */
LED0_TOGGLE(); /* LED0状态取反 */
break;
case KEY1_PRES: /* 控制LED1(GREEN)翻转 */
LED1_TOGGLE(); /* LED1状态取反 */
break;
case KEY2_PRES: /* 同时控制LED0, LED1翻转 */
LED0_TOGGLE(); /* LED0状态取反 */
LED1_TOGGLE(); /* LED1状态取反 */
break;
default : break;
}
}
else
{
delay_ms(10);
}
}
按键实验是很简单的,在使用的时候可能会碰到很复杂的情况。曾经本人就碰到非常棘手的按键需求。是这样的,一个不算复杂的产品,随着开发的深入,增加的功能越来越多,5个按键分布在3个MCU上,MCU之间通过串口和IIC通讯,这5个按键要完成几十个功能,短按,长按,超长按,双击,三击,四击,五击,六击,七击,八击,还要增加几个组合按键,搞得最后拍案而起,拂袖而去,至今记忆犹新。
某人写的一个按键单击的代码:
while (1) {
if(KEY0 == 0)
{
g_nKey0_presstime++;
if(g_bKey0_pressflag==1)
{
if(g_nKey0_releasetime<10)
{
}
else
{
g_nKey0_releasetime=0;
g_bKey0_releaseflag=1;
}
}
}
else
{
g_nKey0_releasetime++;
if(g_nKey0_presstime<10)
{
}
else
{
g_nKey0_presstime=0;
g_bKey0_pressflag=1;
g_bKey0_res=1;
LED0_TOGGLE();
}
}
delay_ms(10);
}
实现按键0的单击、双击、三击、四击、五击和长按的代码:
/* 按键事件枚举 */
typedef enum {
KEY_EVENT_NONE = 0,
KEY_EVENT_SINGLE_CLICK = 1, // 单击
KEY_EVENT_DOUBLE_CLICK = 2, // 双击
KEY_EVENT_TRIPLE_CLICK = 3, // 三击
KEY_EVENT_QUAD_CLICK = 4, // 四击
KEY_EVENT_QUINT_CLICK = 5, // 五击
KEY_EVENT_LONG_PRESS = 6 // 长按
} Key_Event_t;
/* 按键配置结构体 */
typedef struct {
GPIO_TypeDef* Port; // GPIO端口
uint16_t Pin; // GPIO引脚
uint8_t ActiveLevel; // 有效电平 (GPIO_PIN_RESET 或 GPIO_PIN_SET)
// 内部状态变量 (用户无需手动修改)
uint8_t State; // 当前状态: 0-空闲, 1-按下, 2-释放等待
uint8_t ClickCount; // 连续点击计数
uint32_t LastPressTime; // 上次按下时间戳
uint32_t LastReleaseTime; // 上次释放时间戳
uint8_t LongPressFlag; // 长按标志位
} Key_Handle_t;
/* 时间阈值定义 (单位: ms) */
#define DEBOUNCE_TIME 20 // 消抖时间
#define CLICK_INTERVAL_MAX 300 // 两次点击最大间隔 (判断连击)
#define LONG_PRESS_TIME 1000 // 长按判定时间
/* 全局时间基准,需由用户在中断或主循环中更新 */
static volatile uint32_t SystemTickMs = 0;
void Key_Update_Tick(void) {
SystemTickMs++;
}
static uint32_t Get_Current_Tick(void) {
return SystemTickMs;
}
void Key_Init(Key_Handle_t* key, GPIO_TypeDef* port, uint16_t pin, uint8_t active_level) {
key->Port = port;
key->Pin = pin;
key->ActiveLevel = active_level;
key->State = 0;
key->ClickCount = 0;
key->LastPressTime = 0;
key->LastReleaseTime = 0;
key->LongPressFlag = 0;
}
static uint8_t Read_Key_Level(Key_Handle_t* key) {
return HAL_GPIO_ReadPin(KEY0_GPIO_PORT, KEY0_GPIO_PIN);//HAL_GPIO_ReadPin(key->Port, key->Pin);
}
Key_Event_t Key_Scan(Key_Handle_t* key) {
uint8_t current_level = Read_Key_Level(key);
uint32_t current_time = Get_Current_Tick();
Key_Event_t event = KEY_EVENT_NONE;
switch (key->State) {
case 0: // 空闲状态
if (current_level == key->ActiveLevel) {
// 检测到按下,记录时间,进入按下状态
key->LastPressTime = current_time;
key->State = 1;
key->LongPressFlag = 0;
}
break;
case 1: // 按下状态 (检测长按或等待释放)
if (current_level != key->ActiveLevel) {
// 按键释放
uint32_t press_duration = current_time - key->LastPressTime;
if (press_duration < LONG_PRESS_TIME) {
// 短按释放,增加点击计数
key->ClickCount++;
key->LastReleaseTime = current_time;
key->State = 2; // 进入释放等待状态,看是否有后续点击
} else {
// 长按触发
key->LongPressFlag = 1;
//event = KEY_EVENT_LONG_PRESS;
// 重置状态
key->State = 0;
key->ClickCount = 0;
}
} else {
// 持续按下,检查是否达到长按时间且未触发过
if (!key->LongPressFlag && (current_time - key->LastPressTime >= LONG_PRESS_TIME)) {
event = KEY_EVENT_LONG_PRESS;
key->LongPressFlag = 1;
// 注意:长按触发后,通常不再响应后续的点击,直到释放
}
}
break;
case 2: // 释放等待状态 (判断是连击还是结束)
if (current_level == key->ActiveLevel) {
// 再次按下
uint32_t interval = current_time - key->LastReleaseTime;
if (interval < CLICK_INTERVAL_MAX) {
// 有效连击,回到按下状态继续计数
key->LastPressTime = current_time;
key->State = 1;
} else {
// 间隔太久,视为新的单次点击序列开始,重置计数
key->ClickCount = 1;
key->LastPressTime = current_time;
key->State = 1;
}
} else {
// 保持释放,检查是否超时
uint32_t release_duration = current_time - key->LastReleaseTime;
if (release_duration >= CLICK_INTERVAL_MAX) {
// 超时,输出当前的点击次数事件
switch (key->ClickCount) {
case 1: event = KEY_EVENT_SINGLE_CLICK; break;
case 2: event = KEY_EVENT_DOUBLE_CLICK; break;
case 3: event = KEY_EVENT_TRIPLE_CLICK; break;
case 4: event = KEY_EVENT_QUAD_CLICK; break;
case 5: event = KEY_EVENT_QUINT_CLICK; break;
default: event = KEY_EVENT_NONE; break; // 超过5次忽略或自定义
}
// 重置状态
key->State = 0;
key->ClickCount = 0;
}
}
break;
default:
key->State = 0;
break;
}
return event;
}
/* 定义按键实例 */
Key_Handle_t Key1;
Key_Event_t event;
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
delay_init(168); /* 延时初始化 */
led_init(); /* 初始化LED */
/* 初始化按键: 假设按键连接在 PA0, 低电平有效 */
Key_Init(&Key1, GPIOE, GPIO_PIN_2, GPIO_PIN_RESET);
while (1) {
/* 扫描按键事件 */
event = Key_Scan(&Key1);
if (event != KEY_EVENT_NONE) {
switch (event) {
case KEY_EVENT_SINGLE_CLICK:
LED0_TOGGLE();
break;
case KEY_EVENT_DOUBLE_CLICK:
LED1_TOGGLE();
break;
case KEY_EVENT_TRIPLE_CLICK:
LED0_TOGGLE();
LED1_TOGGLE();
break;
case KEY_EVENT_QUAD_CLICK:
LED0_TOGGLE();
break;
case KEY_EVENT_QUINT_CLICK:
LED1_TOGGLE();
break;
case KEY_EVENT_LONG_PRESS:
LED0_TOGGLE();
LED1_TOGGLE();
break;
default:
break;
}
}
delay_ms(10);
Key_Update_Tick();
}
}