系列文章目录
前言
在机械按键的触点闭合和断开时,都会产生抖动,为了保证系统能正确识别按键的开关,就必须对按键的抖动进行处理。 按键的抖动对于人类来说是感觉不到的,但对单片机来说,则是完全可以感应到的,而且还是一个很“漫长”的过程,因为单片机处理的速度在“微秒”级,而按键抖动的时间至少在“毫秒”级。 单片机如果在触点抖动期间检测按键的通断状态,则可能导致判断出错,即按键一次按下或释放被错误地认为是多次操作,从而引起误处理。因此,为了确保单片机对一次按键动作只作—次响应,就必须考虑如何消除按键抖动的影响。一、新建工程
请参照第二章第一节新建工程
——》第二章 点亮第一个LED灯
二、按键实现
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;
}
}
工程链接—》状态机版