STC 通用的 【按键: 长按 / 单击 / 双击 / 三击】识别代码
STC 通用的 【按键: 长按 / 单击 / 双击 / 三击】识别代码本代码参考了https://blog.csdn.net/m0_52596850/article/details/126776765#,
在原代码的基础上按我的代码风格重写了,添加了三击功能。
具体原理在注释中已经给出,通读一遍便能理解,
简单的概括就是通过switch在各种不同的状态之间切换实现按键的消抖,
长短按识别,相当的巧妙。
其中消抖直接使用了定时器中断的间隔进行消抖,我认为这是这个方案最巧思的一点。
如果要复用,直接根据你的按键IO口改变第一行的keyinput就行了,其他的不需要改变,
然后在主循环中或者定时器中断调用就可以了。
目前为P32低电平为有效按键输入,如有需求可以改为高电平。
#define KEYINPUT P32//按键输入为P32
#define NOKEY 0//无
#define SINGLEKEY 1//单键
#define DOUBLEKEY 2//双键
#define TRIPLEKEY 3//三键
#define LONGKEY 4//长键
#define KEYSTATE0 0
#define KEYSTATE1 1
#define KEYSTATE2 2
#define KEYSTATE3 3
unsigned char KEY_DRIVER(void){
static unsigned char keystate = KEYSTATE0;
static unsigned char keytime = 0;
unsigned char keypress;
unsigned char keyreturn = NOKEY;
keypress = KEYINPUT;//读取P32电平
switch(keystate){
case KEYSTATE0://按键初始状态,按下后转换到消抖与确认态,用定时器中断间隔实现消抖
if(!keypress){//P32==0
keystate = KEYSTATE1;//如果无按键按下就始终返回为NOKEY
}
break;
case KEYSTATE1:
if(!keypress){//P32==0
keytime = 0;
keystate = KEYSTATE2;}//按键仍然处于按下,消抖完成,状态转换到计时
else{
keystate = KEYSTATE0;//低电平持续时间过小,只有一个定时器间隔
} //认为是无效按键,清零状态,实现消抖
break;
case KEYSTATE2:
if(keypress){//P32==1,按键释放,且间隔2个定时器中断,认为是无抖动的按键输入。
keyreturn = SINGLEKEY;//返回单击
keystate = KEYSTATE0;//清空状态
}
else if(++keytime >= 64){//P32=0,继续按下,计时加一个定时器中断间隔时间,
keyreturn = LONGKEY;//在下次定时器中断直接输出为长按,不需要等待
keystate = KEYSTATE3;//进入状态3,等待按键释放
}
break;
case KEYSTATE3://等待按键释放,释放后清空状态
if(keypress){//P32==1,按键已经抬起
keystate = KEYSTATE0;//清空状态
}
break;
}
return keyreturn;
}
//////////////////////////////////////////////////
unsigned char KEY_READ(void){
static unsigned char key1 = KEYSTATE0;
static unsigned char keytime1 = 0;//多次按键计数器
unsigned char keyreturn = NOKEY;
unsigned char keytemp;
keytemp = KEY_DRIVER();//读取按键状态
switch(key1){
case KEYSTATE0:
if(keytemp == SINGLEKEY){
keytime1 = 0;//第一次单击,无返回值,到下个状态判断之后是否有再次单击
key1 = KEYSTATE1;//切换单击
}
else{
keyreturn = keytemp;//对于无键,长按时间返回原事件
}
break;
case KEYSTATE1:
if(keytemp == SINGLEKEY){//再次单击,间隔小于640ms
key1 = KEYSTATE2;//切换到状态3,等待三击
}//不清空计数器,因为要实现总间隔检测
else{
if(++keytime1 >= 32){//在这里实现等待双击
keyreturn = SINGLEKEY;//返回单击
key1 = KEYSTATE0;//清空状态
}
}
break;
case KEYSTATE2:
if(keytemp == SINGLEKEY){//第三次单击,总间隔小于640ms,沿用state1中的计数器
keyreturn = TRIPLEKEY;//输出为三击
key1 = KEYSTATE0;//返回初始状态
}
else{
if(++keytime1 >= 32){//沿用之前的计数器值,继续计数
keyreturn = DOUBLEKEY;//超时,输出双击
key1 = KEYSTATE0;
}
}
}
return keyreturn;
}
下面是测试代码及调用功能示范,长按带点亮led,三击熄灭led,可以用于测试功能,需要手动
bit timer20msok = 0;
void TIMER0_ROUTINE(void) interrupt 1{
timer20msok = 1;
}
unsigned char keyevent = NOKEY;
void main(void){
TH0 = 0XD8;
TL0 = 0XF0;
IE = 0X8F;//允许中断
TR0 = 1;//打开定时器电源
while(1){
if(timer20msok){
timer20msok = 0;
keyevent = KEY_READ();
if(keyevent == LONGKEY){
P30 = 0;
}
else if(keyevent == TRIPLEKEY)
P30 = 1;
}
}
}
https://www.stcaimcu.com/data/download/Datasheet/AI8051U.pdf
{:4_250:}
强大,我有个加上单击加长按的,不过老是有点小小问题 vb2002 发表于 2025-7-4 00:06
强大,我有个加上单击加长按的,不过老是有点小小问题
我们数据手册中有, 深圳大学,同学们调试通过的 参考例程
参考现成的 90分的程序,再去完善
vb2002 发表于 2025-7-4 00:06
强大,我有个加上单击加长按的,不过老是有点小小问题
我在人家大佬的代码的基础上扩展出的三击功能,
只能说是人家思路确实牛,感觉不是嵌入式老手想不出这么精妙的方案,
我自己也折腾了好久按键逻辑,
但就是没想到像人家这样在不同的状态之间转换的方案,太牛了,
我看了他的方案之后就想到这写的简直就是我
按键程序调用举例:下面这段代码通过一个ledmode变量来进入不同的状态,限制最大档位,需要在定时器中断内设置keydet =1 允许检测,在主循环中添加
if(keydet){
KEY_HANDLER();
}
这个函数会调用中层和底层的按键处理代码。
如果只是测试就把ledmode变量初始化为0就可以了
const unsigned char ccapvalues[] = {0x36, 0x33, 0x2E, 0x27, 0x20, 0x16, 0x10, 0x00};
unsigned char ccapcounter;
void PCA_CONFIG(void){
// P_SW1 = 0x10; // P3.1切换为PWM输出
CCON = 0x00; // 复位PCA
CMOD = 0x0A; // 系统时钟/4,6MHZ下为23.5khz
CL = 0x00; // 复位低字节
CH = 0x00; // 复位高字节
CCAPM0 = 0x42;// PCA0 PWM模式
PCA_PWM0 = 0x80;// 6位PWM模式
CCAP0H = ccapvalues;//在唤醒后读取ram中的ccapcounter值
CR = 1; //初始化不开启电源
}
void TM0_ROUTINE(void) interrupt 1{//timer0中断服务函数,20ms一次
keydet = 1;
}
bit keydet = 0;//允许按键检测
unsigned char keyevent = NOKEY;//初始化为0
void KEY_HANDLER(void){
keyevent = KEY_READ();//调用按键读取函数
switch(keyevent){
case LONGKEY:
poweron ^= 1;
break;
case SINGLEKEY:
if(poweron && ledmode<=2){
if(ccapcounter <= 7) ccapcounter++;//如果小于7就增加,等于7后不再增加
}
else if(poweron && ledmode>2){
if(ccapcounter <= 5) ccapcounter++;
}
break;
case DOUBLEKEY:
if(poweron && ledmode<=2){
if(ccapcounter > 0) ccapcounter--;//如果大于0就减小,等于0后不再减小
}
else if(poweron && ledmode>2){//不能写成>=0,否则当为0时再减1...好吧这是无符号字符,不会小于0
if(ccapcounter > 0) ccapcounter--;
}
break;
case TRIPLEKEY:
switch(ledmode){
case 0:
case 1:
case 2:
if(ccapcounter == 7){
ccapcounter = 0;
}
else{
ccapcounter = 7;
}
break;
case 3:
case 4:
case 5:
if(ccapcounter == 5){
ccapcounter = 0;
}
else{
ccapcounter =5;
}
}
break;
}
} 学习了 最近正好有项目要用 老师,我问一下组合按键有示例程序吗? 请问是24mkz的吗
页:
[1]