实例
通过配置按键和 LED 灯,使按键的长按和短按控制对应 LED 灯亮灭。
电路分析
打开蓝桥开发板的电路图,可以看到板载的 4 个可编程按键 K1-K4 通过 N K1-N K4 经由跳线帽连接到 M PA0、M PA8、M PB1 和 M PB2
工程建立
本次工程在上一次的工程上继续修改,复制上一次的工程作为本次的工程基础。
初始化按键
编写按键初始化函数,程序代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void Key_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2; GPIO_Init(GPIOB,&GPIO_InitStructure); }
|
特别提醒
结合电路原理图,此处将 PA0、PA8、PB1、PB2 的模式均设置为上拉输入(GPIO_Mode_IPU),在按键松开状态下,由上拉电阻拉高,读取到高电平,当按键按下时,读取到引脚电平为低电平。
配置定时器
为消除抖动和能够准确的进行按键扫描,使用定时器以 50ms 为周期进行按键扫描,在上一例的基础上,修改定时器 1 程序:
声明 50ms 定时的全局变量:
配置定时器 1 中断:
1 2 3 4 5 6 7 8 9 10 11 12 13
| void TIM1_UP_IRQHandler(void) { static u16 cnt=0; if(TIM_GetITStatus(TIM1,TIM_IT_Update)==SET) { TIM_ClearITPendingBit(TIM1,TIM_IT_Update); if(++cnt>=50) { cnt=0; Flag50ms=1; } } }
|
定义按键输入变量
为方便后续获取各个按键的电平状态,此处利用宏定义来定义 KeyX 对应为各引脚读取到的电平状态。
1 2 3 4
| #define Key1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) #define Key2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8) #define Key3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1) #define Key4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)
|
按键扫描程序
编写按键扫描程序,扫描识别各个按键的状态,通过定时器每 50ms 调用一次按键扫描程序,定时进行扫描,此处我们将按键超过 2 秒的按键动作定义为长按操作,否则为短按操作,通过松手后对按键的计数变量进行判断完成计时检测。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| void Key_Scan(void) { static u16 key1_count=0,key2_count=0,key3_count=0,key4_count=0; if(!Key1) { key1_count++; } else if(!Key2) { key2_count++; } else if(!Key3) { key3_count++; } else if(!Key4) { key4_count++; } else { if(key1_count>=40) { } else if(key1_count>=1) { } else if(key2_count>=40) { } else if(key2_count>=1) { } else if(key3_count>=40) { } else if(key3_count>=1) { }
else if(key4_count>=40) { } else if(key4_count>=1) { } key1_count=0; key2_count=0; key3_count=0; key4_count=0; } }
|
本初处以 K1 按钮来详细讲解代码,当 K1 按下后,计数器 key1_count 执行 ++ 操作,由于按键扫描频率为 50ms,当超过 2s 时,key1_count 此时也超过了 40,若没有超过 2s,则 key1_count 应位于 1-40 之间,当按键松开后,没有任何按键按下的情况时,进入 else 内的松手程序,比较 key1_count 是否为大于 40,若大于则执行 K1 按键的长按功能,若位于 1-40 间,则执行短按功能,若没有按下,则 key1_count 为 0,继续判断 key_2count 的功能,当所有按键判断和操作执行完毕后,清空计数,执行下一轮扫描操作。
特别提醒
为什么要在此处使用 else if 结构?
通常情况来讲,在读取按键这个动作中只会有一个按键按下,使用 else if 结构可以减少部分程序判断流程,提升代码运行效率。
该代码编写方式的优缺点?
使用如上结构进行判断按键长短按的操作时,可以发现我们的动作均是在按键松开后执行的,其主要的优点在于可以独立区别长按和短按操作,即长按执行时不会触发短按的操作,但也存在一定的缺点,无法执行长按连续的动作,比如要求长按按钮过程中对某一个变量进行跨度更大的加或者减,若需要实现此功能,则要对按键判断部分进行修改,具体修改方法请参见拓展延伸部分。
继续以 K1 为例,为 K1 按钮添加如下功能:
- 短按 K1 控制 L1 开关
- 长按 K1 控制 L2 开关
在按键扫描程序内定义 L1 和 L2 的状态变量:
1 2
| u8 L1_Status=1; u8 L2_Status=1;
|
修改 K1 的功能代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ...... else { if(key1_count>=40) { L2_Status=!L2_Status; GPIO_SetBits(GPIOD,GPIO_Pin_2); GPIO_WriteBits(GPIOC,GPIO_Pin_9,(BitAction)L2_Status); GPIO_ResetBits(GPIOD,GPIO_Pin_2); } else if(key1_count>=1) { L1_Status=~L1_Status; GPIO_SetBits(GPIOD,GPIO_Pin_2); GPIO_WriteBit(GPIOC,GPIO_Pin_8,(BitAction)L1_Status); GPIO_ResetBits(GPIOD,GPIO_Pin_2); } ......
|
修改主程序
结合功能要求,修改主程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| int main(void) { SysTick_Config(SystemCoreClock/1000); Delay_Ms(200); STM3210B_LCD_Init(); LCD_Clear(Black); LCD_SetBackColor(Black); LCD_SetTextColor(White);
Timer1_Init(); LED_Init(); Key_Init();
while(1) { if(Flag50ms) { Flag50ms=0; Key_Scan(); } } }
|
延伸拓展
按键长按过程中若要执行操作,以 K1 为例,则应修改代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void Key_Scan(void) { static u16 key1_count=0; if(!Key1) { key1_count++ if(key1_count==1) { } else if(key1_count>=40) { key1_count=40; } } eles { key1_count1=0; } }
|