单片机的按键扫描方法以及原理,百度有太多的案例解释,我这里就不做太多的赘述,只教大家如何利用XC8P9530配置输入上拉下拉,然后配合时基检测的方法。一般IO口按键检测要高电平有效,那IO口就要配置成输入下拉,反之则配置成输入上拉,一般我们单片机检测按键低电平有效的比较多,我们就以此来介绍。
按键的消抖程序需要定义消抖的标志位,这个标志位的最终结果就是按键的最终状态。直接上一次的工程加上定义:
#define BIT_1MS BIT_FLAG.b0 //1ms的时基
#define BIT_FADE_HL BIT_FLAG.b1 //呼吸方向
#define BIT_KEY_HL BIT_FLAG.b2 //按键的状态 0:按下 1:松开
再定义好按键的IO口位号,这里定义在PORT6_3;
/****************************************/
// --------端口定义--------------------
/****************************************/
#define io_ledout PORT6_2
#define io_keyin PORT6_3
再将PORT6_3配置为输入上拉,P6PH = 0xf7;P6CON = 0X08;
按键消抖的时间一般是10ms左右,通常我们实际产品直接消抖20ms就可以了,消抖范围一般10ms~100ms都是可以的。按键扫描的程序我们可以在1ms的时基里面写,再用一个变量进行消抖的计数,从松开的状态(BIT_KEY_HL = 1)检测到io_keyin为低电平的时候或者从按下的状态(BIT_KEY_HL = 0)检测到io_keyin为高电平的时候进行计数,超过20就认为消抖完成,将上次的BIT_KEY_HL翻转就可以了。下面直接展示代码:
unsigned char key_count; //用于消抖的计数
void ScanKey(void)
{
if(((BIT_KEY_HL == 1)&&(io_keyin == 0))||((BIT_KEY_HL == 0)&&(io_keyin == 1)))//松开的状态检测到io_keyin为低电平的时候或者从按下的状态检测到io_keyin为高电平的时候
{
key_count++; //消抖计数
if(key_count >= 20) //消抖计数满20
{
if(BIT_KEY_HL) //如果上次是松开的状态,
{
BIT_KEY_HL = 0; //消抖之后就是按下的状态
}
else //如果上次是按下的状态,
{
BIT_KEY_HL = 1; //消抖之后就是松开的状态
}
}
}
else
{
key_count = 0; //当前按键IO口io_keyin的电平和状态标志位BIT_KEY_HL的高低一致消抖计数清零(可能是一直松开的状态,也可能是一直按下的状态)
}
}
需要把此函数放在1ms的时基里扫描,这样keycount消抖20次才是20ms。
如何验证按键扫描的正确性呢?我们可以在消抖完成后加一个IO输出,反应当前的按键状态。我们用io_ledout(PORT6_2)来验证,验证代码如下:
通过仿真验证可以看到当按键一直按下的时候,io_ledout为低电平输出,当按键松开的时候,io_ledout为高电平输出。说明按键的判断是OK的。
一般的产品应用按键扫描包括长按和短按,那么长按是如何判断的呢?其实也很简单,按键的状态标志位BIT_KEY_HL反应了当前的按键状态,如果判断BIT_KEY_HL一直为0,就意味着按键一直处于按下状态,我们从BIT_KEY_HL由1转向0开始计时,超过一段时间就认为是长按,从而触发某一个动作。基于这个思路,代码如下:
定义一个标志位BIT_KEY_HOLD,BIT_KEY_HL由 1 转向 0 的时候置 1
#define BIT_KEY_HOLD BIT_FLAG.b3 //长按的维持计时 1:开始计时,0:不计时
unsigned char key_count; //用于消抖的计数
unsigned int key_long_count; //用于长按的计数
void ScanKey(void)
{
if(((BIT_KEY_HL == 1)&&(io_keyin == 0))||((BIT_KEY_HL == 0)&&(io_keyin == 1)))//松开的状态检测到io_keyin为低电平的时候或者从按?碌淖刺觳獾絠o_keyin为高电平的时候
{
key_count++; //消抖计数
if(key_count >= 20) //消抖计数满20
{
if(BIT_KEY_HL) //如果上次是松开的状态,
{
BIT_KEY_HL = 0; //消抖之后就是按下的状态
BIT_KEY_HOLD = 1;
key_long_count = 0; //重新计时
}
else //如果上次是按下的状态,
{
BIT_KEY_HL = 1; //消抖之后就是松开的状态
if(BIT_KEY_HOLD) //按下长按计时到不到2秒认为是短按
{
BIT_KEY_HOLD = 0;
/*
可以放置短按的处理代码
*/
io_ledout = !io_ledout;
}
}
}
}
else
{
key_count = 0; //当前按键IO口io_keyin的电平和状态标志位BIT_KEY_HL的高低一致消抖计数清零(可能是一直松开的状态,也可能是一直?聪碌淖刺?
}
if(BIT_KEY_HOLD) //没有触发长按的时候
{
key_long_count ++;
if(key_long_count >= 2000) //长按小于2秒
{
BIT_KEY_HOLD = 0; //计时到2秒清零
/*
可以放置长按的处理代码
*/
io_ledout = !io_ledout;
}
}
}
为了验证长按和短按的区分判断有没有问题,我们可以在长按和短按之后,将io_ledout翻转,来验证代码的正确性。经过验证,是正确的。
为了巩固之前学习内容,接下来可以延申一下做一个小项目,先看功能需求:
下面是完整的参考代码:
.C文件代码
#include "XC8P9530.h"
#include "XJ_Define.h"
//===================================//
//void file_clrRam(void);
//void file_init(void);
//void file_project_init(void);
//===================================//
//中断服务程序
//===================================//
unsigned char time_1ms;//计数产生1ms时基
unsigned char cyc_count; //PWM周期满量程
unsigned char pwm_duty; //pwm占空比
unsigned char fade_time; //用于改变呼吸的周期
unsigned char sys_mode; //挡位变量
void int_isr(void) __interrupt
{
__asm__("org 0x08"); //中断入口地址
PUSH(_A_BUFF,_R3_BUFF); //中断入栈保护
//=========TC0中断程序===============//
if(TC0IF) //判断TC0IF是否为1
{
TC0C += 156; // 100us
INTF = 0xfe; //清TC0中断标志位
time_1ms++;
if(time_1ms>=10)
{
time_1ms = 0;
BIT_1MS = 1;
}
cyc_count++;
if(cyc_count>=100)
{
cyc_count = 0;
}
if(cyc_count<pwm_duty)
{
io_ledout = 1;
}
else
{
io_ledout = 0;
}
}
INTF = 0x09; //清未用到的标志位,置1无效,只可清零
POP(_A_BUFF,_R3_BUFF); //中断出栈保护恢复
}
//===================================//
//初始化清RAM:10H~3FH
//===================================//
void file_clrRam()
{
for(RSR=0xD0;RSR<0xFF;RSR++) //RAM清零
//RSR的高两位默认只读为1
{IAR = 0;}
IAR = 0;
}
//===================================//
//端口初始化
//===================================//
void file_init(void)
{
IOCP_W(WDTCON,0x00); //WDT 使能控制寄存器
CONTW(0x80); //TC0使能 8分频
TC0C = 156; //1/2 * 8 * (256-6) = 1000us 公式:1/IRC频率 * 预分频 *(256-初值)
PORT6 = 0; //P6口输出低
IOCP_W(P6CON,0x08); //P6口设为输出,1输入
IOCP_W(P6PH,0xf7); //bit5-bit0对应P65-P60端口上拉,1禁止
IOCP_W(P6PD,0xff); //bit5-bit0对应P65-P60端口下拉,1禁止
IOCP_W(INTE,0x01); //中断使能控制寄存器
INTF = 0x0; //清TC0中断标志位
}
/*系统变量的初始化*/
void Sys_init(void)
{
sys_mode = 3;
}
//===================================//
//MAIN主程序
//===================================//
unsigned char key_count; //用于消抖的计数
unsigned int key_long_count; //用于长按的计数
/*按键的长短按扫描函数*/
void ScanKey(void)
{
if(((BIT_KEY_HL == 1)&&(io_keyin == 0))||((BIT_KEY_HL == 0)&&(io_keyin == 1)))//松开的状态检测到io_keyin为低电平的时候或者从按?碌淖刺觳獾絠o_keyin为高电平的时候
{
key_count++; //消抖计数
if(key_count >= 20) //消抖计数满20
{
if(BIT_KEY_HL) //如果上次是松开的状态,
{
BIT_KEY_HL = 0; //消抖之后就是按下的状态
BIT_KEY_HOLD = 1;
key_long_count = 0; //重新计时
}
else //如果上次是按下的状态,
{
BIT_KEY_HL = 1; //消抖之后就是松开的状态
if(BIT_KEY_HOLD) //按下长按计时到不到2秒认为是短按
{
BIT_KEY_HOLD = 0;
/*
可以放置短按的处理代码
*/
if(!BIT_SYS_OFF) //如果是开机的状态,才能操作
{
sys_mode++; //切换下一挡位
if(sys_mode >= 4) //如果挡位超过第四挡,循环到第一挡
{
sys_mode = 0;
}
}
}
}
}
}
else
{
key_count = 0; //当前按键IO口io_keyin的电平和状态标志位BIT_KEY_HL的高低一致消抖计数清零(可能是一直松开的状态,也可能是一直?聪碌淖刺?
}
if(BIT_KEY_HOLD) //没有触发长按的时候
{
key_long_count ++;
if(key_long_count >= 2000) //长按小于2秒
{
BIT_KEY_HOLD = 0; //计时到2秒清零
/*
可以放置长按的处理代码
*/
if(BIT_SYS_OFF) //如果长按之前上次是关机的状态
{
BIT_SYS_OFF = 0; //开机
if(sys_mode == 3) //如果是呼吸挡
{
pwm_duty = 0; //初始化呼吸模式从最低亮度开始
BIT_FADE_HL = 0; //呼吸方向为增长趋势
}
}
else //如果长按之前上次是开机的状态
{
BIT_SYS_OFF = 1; //关机
}
}
}
}
/*led输出的处理函数*/
void HandleLedOut(void)
{
if(BIT_SYS_OFF) //关机
{
pwm_duty = 0; //灭灯
}
else //开机
{
if(sys_mode == 0) //第一挡
{
pwm_duty = 80; //80%
}
else if(sys_mode == 1) //第二挡
{
pwm_duty = 40; //40%
}
else if(sys_mode == 2) //第三挡
{
pwm_duty = 5; //5%
}
else if(sys_mode == 3) //第四挡
{
fade_time++;
if(fade_time >= 20) //40ms加减一次占空比
{
fade_time = 0;
if(!BIT_FADE_HL)
{
if(pwm_duty<100)
{
pwm_duty++;
}
else
{
BIT_FADE_HL = 1; //改变呼吸方向
}
}
else
{
if(pwm_duty>0)
{
pwm_duty--;
}
else
{
BIT_FADE_HL = 0; //改变呼吸方向
}
}
}
}
}
}
/*主函数*/
void main()
{
file_clrRam(); //清RAM
file_init(); //io寄存器初始化
Sys_init(); //系统变量初始化
EI(); //打开总中断
while(1)
{
if(BIT_1MS)
{
BIT_1MS = 0;
ScanKey();
HandleLedOut();
}
}
}
.h文件代码
typedef union{
struct{
unsigned b0 :1;
unsigned b1 :1;
unsigned b2 :1;
unsigned b3 :1;
unsigned b4 :1;
unsigned b5 :1;
unsigned b6 :1;
unsigned b7 :1;
};
}ob8;
ob8 BIT_FLAG;
#define BIT_1MS BIT_FLAG.b0 //1ms的时基
#define BIT_FADE_HL BIT_FLAG.b1 //呼吸方向 0:增长趋势 1:下降趋势
#define BIT_KEY_HL BIT_FLAG.b2 //按键的状态 0:按下 1:松开
#define BIT_KEY_HOLD BIT_FLAG.b3 //长按的维持计时 1:开始计时,0:不计时
#define BIT_SYS_OFF BIT_FLAG.b4 //开关机的标志位定义 0:开机 1:关机
#define EI() __asm__(" ei ")
#define DI() __asm__(" di ")
#define NOP() __asm__(" nop ")
#define CWDT() __asm__(" CWDT ")
#define SLEEP() __asm__(" sleep ")
/************************************/
//-----------寄存器读写示例----------
/************************************/
#define CONTW(VAL) __asm__("mov a,@"#VAL"\n ctw") //CTW = VAL:CONT寄存器赋值
#define IOCP_W(REG,VAL) __asm__("mov a,@"#VAL"\n iw "#REG) //REG = VAL:IOC页寄存器赋值
#define IOCP_R(RAM,REG) __asm__("ir "#REG"\n mov "#RAM",a") //RAM = REG:IOC页寄存器读值
#define IOCP_W_AND(REG,VAL) __asm__("ir "#REG"\n and a,@"#VAL"\n iw "#REG)
#define IOCP_W_OR(REG,VAL) __asm__("ir "#REG"\n or a,@"#VAL"\n iw "#REG)
#define PUSH(A_REG,R3_REG) __asm__("mov "#A_REG",a\n swap "#A_REG"\n swapa STATUS\n mov "#R3_REG",a") //中断入栈保护
#define POP(A_REG,R3_REG) __asm__("swapa "#R3_REG"\n mov STATUS,a\n swapa "#A_REG) //中断出栈保护恢复
/****************************************/
// ---------数据定义--------------------
/****************************************/
#define data_tc0 6 //;加到256溢出,1ms进一次中断
/****************************************/
// --------端口定义--------------------
/****************************************/
#define io_ledout PORT6_2
#define io_keyin PORT6_3
/****************************************/
// ---------寄存器定义--------------------
/****************************************/
volatile __at(0x10) unsigned char A_BUFF; //中断ACC保护RAM
volatile __at(0x11) unsigned char R3_BUFF; //中断STATUS保护RAM
这期的教学就到这里,下一期我们来讲解单片机在关机之后,如何做低功耗休眠的处理。
标签:__,XC8P9530,KEY,消抖,单片机,HL,新手,BIT,define From: https://blog.csdn.net/Nzyz180/article/details/145165650