☰
基于掌上实验实室V9的按键程序设计
2026/11/6 7:46:44 网站建设 项目流程

从简单的按键电路原理开始,到采用软件延时去抖的高级应用,一篇全部讲完。

1 按键检测原理


按键未按下,KEY1为高电平;按键按下,KEY1为低电平。单片机通过检测KEY1状态来判断按键按下与否。

2 单片机引脚配置

3 简单测试程序

下面我们用一个简单程序来测试一下,KEY1按下,LED4状态翻转。需要注意的是,掌上实验室V9按键没有上拉电阻,因此单片机对应按键输入的引脚应该配置成上拉输入。

#include "at32f403a_407_conf.h" /** * GPIO初始化:按键上拉输入 + LED推挽输出 * 按键:PD6 PD7 PB3 PB4 * LED: PB6 PB7 PB8 PB9 */ void key_led_gpio_init(void) { crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_GPIOD_PERIPH_CLOCK, TRUE); gpio_init_type gpio_init_struct; // PB6~PB9 LED推挽输出 gpio_init_struct.gpio_pins = GPIO_PINS_6 | GPIO_PINS_7 | GPIO_PINS_8 | GPIO_PINS_9; gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT; gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL; gpio_init_struct.gpio_pull = GPIO_PULL_NONE; gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER; gpio_init(GPIOB, &gpio_init_struct); // PB3 PB4 按键上拉输入 gpio_init_struct.gpio_pins = GPIO_PINS_3 | GPIO_PINS_4; gpio_init_struct.gpio_mode = GPIO_MODE_INPUT; gpio_init_struct.gpio_pull = GPIO_PULL_UP; gpio_init(GPIOB, &gpio_init_struct); // PD6 PD7 按键上拉输入 gpio_init_struct.gpio_pins = GPIO_PINS_6 | GPIO_PINS_7; gpio_init_struct.gpio_mode = GPIO_MODE_INPUT; gpio_init_struct.gpio_pull = GPIO_PULL_UP; gpio_init(GPIOD, &gpio_init_struct); } /* * 获取按键状态 * 返回非0表示下降沿有效 */ int get_key_down() { static int last_key = 1; int cur_key = gpio_input_data_bit_read(GPIOD, GPIO_PINS_6); if(cur_key != last_key){ last_key = cur_key; if(cur_key == 0) //下降沿有效 return 1; else return 0; }else{ return 0; } } /** * 主函数 */ int main(void) { //按键、LED相关IO初始化 key_led_gpio_init(); //主循环 while(1) { if(get_key_down()){ gpio_bits_toggle(GPIOB, GPIO_PINS_9); } } }

上述程序测试时发现,按键时灵时不灵,问题在哪儿呢?这个还是要回到按键的机械特性,就是按键的机械抖动。

4 按键抖动引起的问题

通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了不产生这种现象而作的措施就是按键消抖。
抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。

5 软件去抖

有了上述的波形,我们就可以进行针对性的硬件或软件改进。

1)硬件电路可以增加双稳态触发器或电容滤波来解决。

2)软件可以通过延时去抖程序来解决。

下面我们采用软件去抖,每次检测到有键按下后,延时20ms,再次确认按键状态,消除抖动影响。程序如下。

#include "at32f403a_407_conf.h" //系统tick,单位ms volatile uint32_t gSysTick = 0; //按键下降沿保存 char key_down[4] = {0,0,0,0}; /** * 延时 */ void delay_ms(int ms) { uint32_t tick = gSysTick; while(gSysTick - tick < ms){} } /** * 系统时钟设置 主频64MHz */ void system_clock_config(void) { crm_reset(); crm_clock_source_enable(CRM_CLOCK_SOURCE_HICK, TRUE); while(crm_flag_get(CRM_HICK_STABLE_FLAG) != SET); crm_pll_config(CRM_PLL_SOURCE_HICK, CRM_PLL_MULT_16, CRM_PLL_OUTPUT_RANGE_LE72MHZ); crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE); while(crm_flag_get(CRM_PLL_STABLE_FLAG) != SET); crm_sysclk_switch(CRM_SCLK_PLL); while(crm_sysclk_switch_status_get() != CRM_SCLK_PLL); crm_ahb_div_set(CRM_AHB_DIV_1); crm_apb2_div_set(CRM_APB2_DIV_2); crm_apb1_div_set(CRM_APB1_DIV_2); system_core_clock_update(); } /** * GPIO初始化:按键上拉输入 + LED推挽输出 * 按键:PD6 PD7 PB3 PB4 * LED: PB6 PB7 PB8 PB9 */ void key_led_gpio_init(void) { crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_GPIOD_PERIPH_CLOCK, TRUE); gpio_init_type gpio_init_struct; // PB6~PB9 LED推挽输出 gpio_init_struct.gpio_pins = GPIO_PINS_6 | GPIO_PINS_7 | GPIO_PINS_8 | GPIO_PINS_9; gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT; gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL; gpio_init_struct.gpio_pull = GPIO_PULL_NONE; gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER; gpio_init(GPIOB, &gpio_init_struct); // PB3 PB4 按键上拉输入 gpio_init_struct.gpio_pins = GPIO_PINS_3 | GPIO_PINS_4; gpio_init_struct.gpio_mode = GPIO_MODE_INPUT; gpio_init_struct.gpio_pull = GPIO_PULL_UP; gpio_init(GPIOB, &gpio_init_struct); // PD6 PD7 按键上拉输入 gpio_init_struct.gpio_pins = GPIO_PINS_6 | GPIO_PINS_7; gpio_init_struct.gpio_mode = GPIO_MODE_INPUT; gpio_init_struct.gpio_pull = GPIO_PULL_UP; gpio_init(GPIOD, &gpio_init_struct); } /** * TMR6初始化 周期为1ms, 开中断 */ void tmr6_init(void) { /* enable tmr6 clock */ crm_periph_clock_enable(CRM_TMR6_PERIPH_CLOCK, TRUE); /* 64000000/64/1000 = 1000Hz*/ tmr_base_init(TMR6, 1000 - 1, 64 - 1); tmr_cnt_dir_set(TMR6, TMR_COUNT_UP); /* overflow interrupt enable */ tmr_interrupt_enable(TMR6, TMR_OVF_INT, TRUE); /* tmr1 overflow interrupt nvic init */ nvic_priority_group_config(NVIC_PRIORITY_GROUP_4); nvic_irq_enable(TMR6_GLOBAL_IRQn, 0, 0); /* enable tmr1 */ tmr_counter_enable(TMR6, TRUE); } /** * TMR6中断响应函数 */ void TMR6_GLOBAL_IRQHandler(void) { if(tmr_flag_get(TMR6, TMR_OVF_FLAG) != RESET) { gSysTick++; tmr_flag_clear(TMR6, TMR_OVF_FLAG); } } /* * 获取按键状态 * 返回非0表示下降沿有效 */ int get_key_down() { static int last_key = 1; int cur_key = gpio_input_data_bit_read(GPIOD, GPIO_PINS_6); if(cur_key != last_key){ delay_ms(20); if(cur_key != last_key){ last_key = cur_key; if(cur_key == 0) //下降沿有效 return 1; } } return 0; } /** * 主函数 */ int main(void) { //按键、LED相关IO初始化 key_led_gpio_init(); //定时器初始化 tmr6_init(); //主循环 while(1) { if(get_key_down()){ gpio_bits_toggle(GPIOB, GPIO_PINS_9); } } }

现在可以正常工作了!但是每次有键按下就会延时去抖,会影响程序执行效率。最关键的是当有多个按键的时候,在两键同时按下时,会导致按键判断错误或延迟。

6 后台工作的按键程序

在定时器中断进行按键处理,是一种高效且准确的按键处理方式。下面的程序给出了以中断方式进行4个按键处理的完整程序。

程序中还修复了PB3,PB4缺省用于JTAG调试引脚导致的按键问题。

#include "at32f403a_407_conf.h" //系统tick,单位ms volatile uint32_t gSysTick = 0; //按键下降沿保存 char key_down[4] = {0,0,0,0}; /** * 系统时钟设置 主频64MHz */ void system_clock_config(void) { crm_reset(); crm_clock_source_enable(CRM_CLOCK_SOURCE_HICK, TRUE); while(crm_flag_get(CRM_HICK_STABLE_FLAG) != SET); crm_pll_config(CRM_PLL_SOURCE_HICK, CRM_PLL_MULT_16, CRM_PLL_OUTPUT_RANGE_LE72MHZ); crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE); while(crm_flag_get(CRM_PLL_STABLE_FLAG) != SET); crm_sysclk_switch(CRM_SCLK_PLL); while(crm_sysclk_switch_status_get() != CRM_SCLK_PLL); crm_ahb_div_set(CRM_AHB_DIV_1); crm_apb2_div_set(CRM_APB2_DIV_2); crm_apb1_div_set(CRM_APB1_DIV_2); system_core_clock_update(); } /** * GPIO初始化:按键上拉输入 + LED推挽输出 * 按键:PD6 PD7 PB3 PB4 * LED: PB6 PB7 PB8 PB9 */ void key_led_gpio_init(void) { crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE); crm_periph_clock_enable(CRM_GPIOD_PERIPH_CLOCK, TRUE); gpio_init_type gpio_init_struct; // PB6~PB9 LED推挽输出 gpio_init_struct.gpio_pins = GPIO_PINS_6 | GPIO_PINS_7 | GPIO_PINS_8 | GPIO_PINS_9; gpio_init_struct.gpio_mode = GPIO_MODE_OUTPUT; gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL; gpio_init_struct.gpio_pull = GPIO_PULL_NONE; gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER; gpio_init(GPIOB, &gpio_init_struct); // PB3 PB4 按键上拉输入 gpio_init_struct.gpio_pins = GPIO_PINS_3 | GPIO_PINS_4; gpio_init_struct.gpio_mode = GPIO_MODE_INPUT; gpio_init_struct.gpio_pull = GPIO_PULL_UP; gpio_init(GPIOB, &gpio_init_struct); // PD6 PD7 按键上拉输入 gpio_init_struct.gpio_pins = GPIO_PINS_6 | GPIO_PINS_7; gpio_init_struct.gpio_mode = GPIO_MODE_INPUT; gpio_init_struct.gpio_pull = GPIO_PULL_UP; gpio_init(GPIOD, &gpio_init_struct); } /** * TMR6初始化 周期为1ms, 开中断 */ void tmr6_init(void) { /* enable tmr6 clock */ crm_periph_clock_enable(CRM_TMR6_PERIPH_CLOCK, TRUE); /* 64000000/64/1000 = 1000Hz*/ tmr_base_init(TMR6, 1000 - 1, 64 - 1); tmr_cnt_dir_set(TMR6, TMR_COUNT_UP); /* overflow interrupt enable */ tmr_interrupt_enable(TMR6, TMR_OVF_INT, TRUE); /* tmr1 overflow interrupt nvic init */ nvic_priority_group_config(NVIC_PRIORITY_GROUP_4); nvic_irq_enable(TMR6_GLOBAL_IRQn, 0, 0); /* enable tmr1 */ tmr_counter_enable(TMR6, TRUE); } /* * 获取按键状态 * 返回非0表示下降沿有效 */ char get_key_down(int which_key) { char key = 0; if(which_key>=0 && which_key<=3){ key = key_down[which_key]; } key_down[which_key] = 0; return key; } /* * 按键扫描程序 * 建议放在ms中断中 */ void key_scan() { static char last_key[4]= {1,1,1,1}; //记录上次按键状态 static uint32_t tickcount_key[4] = {0,0,0,0}; //按键状态变化起始时间 char cur_key[4]; //当前按键状态 cur_key[0] = gpio_input_data_bit_read(GPIOD, GPIO_PINS_6); cur_key[1] = gpio_input_data_bit_read(GPIOD, GPIO_PINS_7); cur_key[2] = gpio_input_data_bit_read(GPIOB, GPIO_PINS_3); cur_key[3] = gpio_input_data_bit_read(GPIOB, GPIO_PINS_4); for(int i=0;i<4;i++){ if(cur_key[i]!=last_key[i]){ //判断按键变化 if(gSysTick - tickcount_key[i] > 20){ //20ms 去抖动 if(cur_key[i]!=last_key[i]){ //再次确认按键变化 last_key[i] = cur_key[i]; if(cur_key[i]==0) //下降沿判断 key_down[i] = 1; } } }else{ tickcount_key[i] = gSysTick; //记录按键变化前时间 } } } /** * TMR6中断响应函数 */ void TMR6_GLOBAL_IRQHandler(void) { if(tmr_flag_get(TMR6, TMR_OVF_FLAG) != RESET) { gSysTick++; tmr_flag_clear(TMR6, TMR_OVF_FLAG); key_scan(); } } /** * 主函数 */ int main(void) { //PB3 PB4 缺省定义为jtag调试口。调试改为SW-JTAG,PB3,PB4变成普通IO。 crm_periph_clock_enable(CRM_IOMUX_PERIPH_CLOCK, TRUE); gpio_pin_remap_config(SWJTAG_GMUX_010, TRUE); //设置系统时钟 system_clock_config(); //按键、LED相关IO初始化 key_led_gpio_init(); //1ms定时器初始化 tmr6_init(); //主循环 while(1) { for(int i=0;i<4;i++){ //有键按下,对应LED翻转 if(get_key_down(i)){ gpio_bits_toggle(GPIOB, GPIO_PINS_9>>i); } } } }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询