单片机按键实验

单片机: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();
	}
}
相关推荐
踏着七彩祥云的小丑10 小时前
嵌入式测试学习第 16 天:复位电路、电源电路基础原理
单片机·嵌入式硬件
小手智联老徐10 小时前
Arduino IDE环境搭建与点亮ESP32 D1板载LED
嵌入式硬件·esp32·arduino
坤坤藤椒牛肉面11 小时前
stm32学习1--新建工程
stm32·单片机·学习
yong999011 小时前
STM32 LoRaWAN Ping-Pong 节点方案
stm32·单片机·嵌入式硬件
模拟IC攻城狮11 小时前
(最新)华为 2025届秋招-硬件技术工程师-单板硬件开发—机试题—(共12套)(每套四十题)
嵌入式硬件·华为·硬件架构·pcb工艺·模拟芯片
我先去打把游戏先11 小时前
Ubuntu虚拟机(服务器版本)Git安装教程(附常用命令)——从零开始掌握版本控制
服务器·c语言·c++·git·嵌入式硬件·物联网·ubuntu
安生生申11 小时前
uni-app 连接 JDY-31 蓝牙串口模块实践
c语言·前端·javascript·stm32·单片机·嵌入式硬件·uni-app
熙芯XiChip11 小时前
CPLD核心原理与结构
单片机
于小猿Sup1 天前
VMware在Ubuntu22.04驱动Livox Mid360s
linux·c++·嵌入式硬件·自动驾驶