本帖最后由 eltonchang2001 于 2022-7-21 11:56 编辑
前言
在机械按键的触点闭合和断开时,都会产生抖动,为了保证系统能正确识别按键的开关,就必须对按键的抖动进行处理。 按键的抖动对于人类来说是感觉不到的,但对单片机来说,则是完全可以感应到的,而且还是一个很“漫长”的过程,因为单片机处理的速度在“微秒”级,而按键抖动的时间至少在“毫秒”级。 单片机如果在触点抖动期间检测按键的通断状态,则可能导致判断出错,即按键一次按下或释放被错误地认为是多次操作,从而引起误处理。因此,为了确保单片机对一次按键动作只作—次响应,就必须考虑如何消除按键抖动的影响。
一、按键实现
1.硬件电路
我的开发板按键一端连接到MCU,另一端是连接到地。所以当检测到低电平时,代表按键按下。为了防止电平不稳定,一般连接MCU的I/O口我们会选择上拉。
假如按键一端是连接Vdd的时候,此时I/O口就不再需要上拉。(ps:记得加限流电阻!防止电流过大,烧毁MCU)。
2.简单版(遍历)这是简单的按键检测,无脑遍历。只能实现按键单击功能。下文有稍微复杂版。程序功能:按下按键LED灯状态发生改变。 #include "HT66F0185.h" /******************************************************************************* * @fn delayMs * @brief 延时函数 * @param 延时时间 单位为ms * @return 无 *******************************************************************************/ void delayMs(unsigned long int ms){ while(ms--) GCC_DELAY(2000);//主频8Mhz,执行一条指令为0.5us。一条指令周期等于四条机器周期——》 1/8Mhz * 4 = 0.5us }
/******************************************************************************* * @fn keyInit * @brief 按键初始化函数 * @param 无 * @return 无 *******************************************************************************/ void keyInit(void){ /*配置PC0*/ _pcc0=1; //设置为输入 _pcpu0=1; //引脚上拉 /*配置PC1*/ _pcc1=1; //设置为输入 _pcpu1=1; //引脚上拉 /*配置PC3*/ _pcc3=1; //设置为输入 _pcpu3=1; //引脚上拉 /*配置PC4*/ _pcc4=1; //设置为输入 _pcpu4=1; //引脚上拉 } /******************************************************************************* * @fn main * @brief 主函数 * @param 无 * @return 无 *******************************************************************************/ void main(void) { _wdtc = 0b10101000;//关闭看门狗。直接配置看门狗寄存器,0b代表二进制。
/*按键初始化*/ keyInit(); /*LED设置*/ _pac3 = 0;//设置PA3口为输出 _pa3 = 1; //开机时灯亮 _cos=1;//设置pa3管脚为IO,而不是比较器输出 while(1){ /*按键检测*/ if(_pc0 == 0){ delayMs(100);//延时100ms if(_pc0 == 0) _pa3 = ~_pa3;//取反 } /*按键检测*/ if(_pc1 == 0){ delayMs(100);//延时100ms if(_pc1 == 0) _pa3 = ~_pa3;//取反 } /*按键检测*/ if(_pc3 == 0){ delayMs(100);//延时100ms if(_pc3 == 0) _pa3 = ~_pa3;//取反 } /*按键检测*/ if(_pc4 == 0){ delayMs(100);//延时100ms if(_pc4 == 0) _pa3 = ~_pa3;//取反 } } }
3.稍复杂版(状态机)以下是用状态机思想实现的按键检测,可以检测到单击和长按。推荐裸机采用这种方式。当然这个也是有缺点的:太依赖遍历,程序过多的话就会导致按键检测不灵敏。怎么解决呢?答案是加入定时器和中断。这个会在后面的章节提到。程序功能:长按LED灯亮,短按LED灯灭。 #include "HT66F0185.h"
#define WaitStatus 0 //等待状态 #define PressStatus 1 //按下状态 #define ReleaseStatus 2 //等待释放状态 #define IDEStatus 3 //空闲状态 #define KeyTime 600 //长按超时时间
#define ShortPress 1 //短按 #define LongPress 2 //长按
#define KEY1 0x01 #define KEY2 0x02 #define KEY3 0x04 #define KEY4 0x08
unsigned char KeyStatus = 0; //按键状态 unsigned char KeyVal = 0; //按键值 unsigned char press = 0; //按键按下状态 1单击,2长按 unsigned int KeyCnt = 0; //长按计时
/******************************************************************************* * @fn delayMs * @brief 延时函数 * @param 延时时间 单位为ms * @return 无 *******************************************************************************/ void delayMs(unsigned long int ms){ while(ms--) GCC_DELAY(2000);//主频8Mhz,执行一条指令为0.5us。一条指令周期等于四条机器周期——》 1/8Mhz * 4 = 0.5us }
/******************************************************************************* * @fn keyInit * @brief 按键初始化函数 * @param 无 * @return 无 *******************************************************************************/ void keyInit(void){ /*配置PC0*/ _pcc0=1; //设置为输入 _pcpu0=1; //引脚上拉 /*配置PC1*/ _pcc1=1; //设置为输入 _pcpu1=1; //引脚上拉 /*配置PC3*/ _pcc3=1; //设置为输入 _pcpu3=1; //引脚上拉 /*配置PC4*/ _pcc4=1; //设置为输入 _pcpu4=1; //引脚上拉 }
/******************************************************************************* * @fn getkey * @brief 获取按键值 * @param 无 * @return 按键值 *******************************************************************************/ unsigned char getkey(void){ unsigned char temp=0; /*获取PC4状态*/ if(_pc4==0)temp|=0x01;else temp&=~(0x01); /*获取PC3状态*/ if(_pc3==0)temp|=0x02;else temp&=~(0x02); /*获取PC1状态*/ if(_pc1==0)temp|=0x04;else temp&=~(0x04); /*获取PC0状态*/ if(_pc0==0)temp|=0x08;else temp&=~(0x08); /*返回按键值*/ return temp; }
/******************************************************************************* * @fn keyscan * @brief 按键扫描 * @param 无 * @return 无 *******************************************************************************/ void keyscan(void){ static unsigned char i=0;//消抖计时 unsigned char temp;//按键值 /*获取按键值*/ temp = getkey(); /*判断按键状态*/ switch(KeyStatus){ /*等待状态*/ case WaitStatus: if(temp != 0x00){ if(++i >= 3){//按键按下后 第一段消抖 防止误触发 i = 0; KeyStatus = PressStatus; //改变按键状态为按下状态 } }else{ i = 0;//清空消抖计时 } break; /*按下状态*/ case PressStatus: if(temp != 0x00){ KeyVal = temp;//获取是哪个按键按下 KeyStatus = ReleaseStatus;//改变按键状态为等待释放状态 }else{ KeyStatus = WaitStatus;//改变按键状态为等待状态 } break; /*等待释放状态*/ case ReleaseStatus: if(temp != 0x00){ if(++KeyCnt == KeyTime) {//长按检测 press = LongPress; KeyStatus = IDEStatus;//改变按键状态为空闲状态 KeyCnt = 0;//清空长按计时 } }else{ KeyCnt = 0;//清空长按计时 KeyStatus = WaitStatus;//改变按键状态为等待状态 press = ShortPress;//按键短按标志位 } break; /*空闲状态*/ case IDEStatus: if(temp == 0x00){ KeyStatus = WaitStatus;//改变按键状态为等待状态 } break; /*为保持switch语句完整*/ default: ;//空语句 什么都不执行 } }
/******************************************************************************* * @fn main * @brief 主函数 * @param 无 * @return 无 *******************************************************************************/ void main(void) { _wdtc = 0b10101000;//关闭看门狗。直接配置看门狗寄存器,0b代表二进制。
/*按键初始化*/ keyInit(); /*LED设置*/ _pac3 = 0;//设置PA3口为输出 _pa3 = 1; //开机时灯亮 _cos = 1; //设置pa3管脚为IO,而不是比较器输出
while(1){ /*按键扫描*/ keyscan(); /*延时3ms*/ delayMs(3); /*检测到长按开灯*/ if(press == LongPress) _pa3 = 1; /*检测到短按关灯*/ if(press == ShortPress) _pa3 = 0; } }
总结
一个按键实现的方式有很多,我是一步一步学过来了的。除了上面说的状态机实现,其实还有更高级的实现方式,能判断长按、短按、双击、单击等等。当然这个就需要RTOS(操作系统)和更高位的处理器,是一种面向对象的实现方式。后面我会单独开一篇文章介绍,实验的环境为:搭载FreeRTOS的STM32。
———————————————
版权声明:本文为CSDN博主「猫想飞」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
|