关键词不能为空

当前您在: 主页 > 英语 >

单片机经典长短按程序

作者:高考题库网
来源:https://www.bjmy2z.cn/gaokao
2021-03-03 12:43
tags:

-

2021年3月3日发(作者:3838)


新型的按键扫描程序



不过我在网上游逛了很 久,


也看过不少源程序了,


没有发现这种按键处理办法的


踪迹,所以,我将他共享出来,和广大同僚们共勉。我非常坚信这种按键处理办


法的便捷和高效,


你可以移植到任何一种嵌入式处理器上面,

< br>因为


C


语言强大的


可移植性。< /p>



同时,


这里面用到了一些分层的思想 ,


在单片机当中也是相当有用的,


也是本文

的另外一个重点。



对于老鸟,


我建议直接看那两个表达式,


然后自己想想就会懂的了,


也不需 要听


我后面的自吹自擂了,


我可没有班门弄斧的意思,


hoho


~~但是对于新手,


我建


议将全文看完。因为这是实际项目中总结出来的经验,学校里面学不到的东西。



以下假设你懂


C


语言,


因为纯粹的


C


语言描述,


所以和处理器平台无关,


你可以



M CS-51



AVR



PIC


,甚至是


ARM


平台上 面测试这个程序性能。当然,我自己


也是在多个项目用过,效果非常好的。



好了,工程人员的习惯,废话就应该少说,开始吧。以下我以

< p>
AVR



MEGA8


作为


平台讲解,没有其它原因,因为我手头上只有


AVR

< p>
的板子而已没有


51


的。用


51


也可以,只是芯片初始化部分不同,还有寄存器名字不同而已。

< br>


核心算法:



unsigned char Trg;


unsigned char Cont;


void KeyRead( void )


{


unsigned char ReadData = PINB^0xff; // 1


Trg = ReadData & (ReadData ^ Cont); // 2


Cont = ReadData; // 3


}


完了。


有没有一种 不可思议的感觉?当然,


没有想懂之前会那样,


想懂之后就会< /p>


惊叹于这算法的精妙!!



下面是程序解释:



Trg



triger



< /p>


代表的是触发,


Cont



continue


)代表的是连续按下。



1


:读


PORTB


的 端口数据,取反,然后送到


ReadData


临时变量里面保存起来。



2


:算法


1


,用来计算触发变量的。一个位与操 作,一个异或操作,我想学过


C


语言都应该懂吧?


Trg


为全局变量,其它程序可以直接引用。


< /p>


3


:算法


2


,用 来计算连续变量。



看到这里,有种“知其然,不知其所以然 ”的感觉吧?代码很简单,但是它到底


是怎么样实现我们的目的的呢?好,下面就让我们 绕开云雾看青天吧。



我们最常用的按键接法如下:


AVR


是有内部上拉功能的,但是为了说明问题,我

是特意用外部上拉电阻。那么,按键没有按下的时候,读端口数据为


1


,如果按


键按下,


那么端口读到

0



下面就看看具体几种情况之下,


这算法是怎么一回事。




1




没有按键的时候



端口为

< p>
0xff



ReadData

读端口并且取反,很显然,就是


0x00


了。



Trg = ReadData & (ReadData ^ Cont);


(初始状态下,


Cont


也是为


0


的)很


简单的数学计算,因为


ReadData



0


,则它和任何数“相与”,结果也是为

< p>
0


的。



Cont = ReadData;


保存


Cont


其实就是等于


ReadData


,为


0




结果就是:



ReadData



0




Trg



0




Cont



0





2




第一次


PB0


按下的情况



端口数据为


0xfe



ReadData


读端口并且取反,很显然,就是


0x01


了。



Trg = ReadData & (ReadData ^ Cont);


因为这是第一次按下,所以


Cont


是上


次的值,应为为


0


。那么这个式子的值也不 难算,也就是


Trg = 0x01 & (0x01^


0x00) = 0x01


Cont = ReadData = 0x01




结果就是:



ReadData



0x01




Trg



0x01



Trg


只会在这个时候对应位的值为


1


,其它时候都为


0


Cont



0x01





3



PB0


按着不松(长按键)的情况



端口数据为


0xfe



ReadDat a


读端口并且取反是


0x01


了。



Trg = ReadData & (ReadData ^ Cont);


因为这是连续按下, 所以


Cont


是上次


的值,应为为


0x01


。那么这个式子就变成了


Trg = 0x01 & (0x01^0x01) = 0x0


0


Cont = ReadData = 0x01




结果就是:



ReadData



0x01




Trg



0x00




Cont



0x01




因为现在按键是长按着 ,所以


MCU


会每个一定时间(


20m s


左右)不断的执行这


个函数,那么下次执行的时候情况会是怎 么样的呢?



ReadData



0x01


;这个不会变,因为按键没有松开



Trg



ReadData & (ReadData ^ Cont)



0x01 & (0x01 ^ 0x01) = 0



只要


按键没有松开,这个


Trg


值永远为

< br> 0


!!!



Cont



0x01


;只要按键没有松开,这 个值永远是


0x01


!!




4




按键松开的情况



端口数据为


0xff



ReadData


读端口并且取反是


0x00


了。



Trg = ReadData & (ReadData ^ Cont) = 0x00 & (0x00^0x01) = 0x00


Cont = ReadData = 0x00




结果就是:



ReadData



0x00




Trg



0x00




Cont



0x00




很显然,这个回到了初始状态,也就是没有按键按下的状态。



总结一下,不知道想懂了没有?其实很简单,答案如下:



Trg


表示的就是触发的意思,


也就 是跳变,


只要有按键按下


(电平从


1< /p>



0


的跳


变), 那么


Trg


在对应按键的位上面会置一,我们用了


PB0



Trg


的值为


0x01



类似,


如果我们


PB7


按下的话,


Trg


的值就应该为


0x80



这个很好理解,


还有,


最关键的地方,


Trg


的值每次按下只会出现一次,


然后立刻 被清除,


完全不需要


人工去干预。所以按键功能处理程序不会重 复执行,省下了一大堆的条件判断,


这个可是精粹哦!!


Con t


代表的是长按键,如果


PB0


按着不 放,那么


Cont


的值


就为

< p>
0x01



相对应,


P B7


按着不放,


那么


Cont


的值应该为


0x80



同样很好理解。



如果还是想不懂的话,可以自己演算一下那 两个表达式,应该不难理解的。



因为有了这个支持,那么按键处理就变得很爽了,下面看应用:



应用一:一次触发的按键处理



假设


PB0


为蜂鸣器按键,按一下,蜂鸣器


beep


的响一声。这个很简单,但是大


家以前是怎么做的呢? 对比一下看谁的方便?



#define KEY_BEEP 0x01


void KeyProc(void)


{


if (Trg & KEY_BEEP) //


如果按下的是


KEY_BEEP


{


Beep(); //


执行蜂鸣器处理函数



}


}


怎么样?够和谐不?记得前面解释说

Trg


的精粹是什么?精粹就是只会出现一


次。所以你按下 按键的话,


Trg & KEY_BEEP


为“真”的情况只 会出现一次,所


以处理起来非常的方便,蜂鸣器也不会没事乱叫,


hoho


~~~



或者你会认为这个处理简单,没有问题,我们继续。



应用


2


:长按键的处理



项目中经常会遇到一些要求,例如:一个按键如果短按一下执行功能


A


,如果长



2

< p>
秒不放的话会执行功能


B


,又或者是要求


3


秒按着不放,计数连加什么什么


的功能,很实 际。不知道大家以前是怎么做的呢?我承认以前做的很郁闷。



但是看我们这里怎么处理吧,或许你会大吃一惊,原来程序可以这么简单



这里具个简单例子,为了只是说明原理,


PB0


是模式按键,短按则切换模式,


PB


1


就是加,如果长按的话则连加(玩过电子表吧?没错,就是那个!)



#define KEY_MODE 0x01 //


模式按键



#define KEY_PLUS 0x02 //




void KeyProc(void)


{


if (Trg & KEY_MODE) //

如果按下的是


KEY_MODE


,而且你常按这按键


也没有用,



{ //


它是不会执行第二次的哦





必须先松开再按下



Mode++; //


模式寄存器加


1


,当然,这里只是演示,你可以


执行你想



//


执行的任何代码



}


if (Cont & KEY_PLUS) //


如果“加”按键被按着不放



{


cnt_plus++; //


计时



if (cnt_plus > 100) // 20ms*100 = 2S


如果时间到



{


Func(); //


你需要的执行的程序



}


}


}

< p>
不知道各位感觉如何?我觉得还是挺简单的完成了任务,


当然,

< p>
作为演示用代码。



应用


3


:点触型按键和开关型按键的混合使用



点触形按键估计用的最多,


特别是单片机。


开关型其实 也很常见,


例如家里的电


灯,那些按下就不松开,除非关。这是 两种按键形式的处理原理也没啥特别,但


是你有没有想过,


如果 一个系统里面这两种按键是怎么处理的?我想起了我以前


的处理,


分开两个非常类似的处理程序,


现在看起来真的是笨的不行了,


但是也


没有办法啊,结构决定了程序。不过现在好了,用上面介绍的办法,很轻松就可


以搞定。



原理么?可能你也会想到 ,


对于点触开关,


按照上面的办法处理一次按下和长按,


对于开关型,


我们只需要处理


Cont



OK


了,


为什么? 很简单嘛,


把它当成是一


个长按键,这样就找到了共同点,屏蔽 了所有的细节。程序就不给了,完全就是


应用


2


的内容,在这里提为了就是说明原理~~



好了,< /p>


这个好用的按键处理算是说完了。


可能会有朋友会问,

< p>
为什么不说延时消


抖问题?哈哈,被看穿了。果然不能偷懒。下面谈谈这个 问题,顺便也就非常简


单的谈谈我自己用时间片轮办法,以及是如何消抖的。

< p>


延时消抖的办法是非常传统,也就是



第一次判断有按键,延时一定的时间(一


般习惯是

20ms


)再读端口,如果两次读到的数据一样,说明了是真正的按键,

< p>
而不是抖动,则进入按键处理程序。



当然,不 要跟我说你


delay



20


)那样去死循环去,真是那样的话,我衷心的建


议你先放下手上所有的东 西,


好好的去了解一下操作系统的分时工作原理,


大概


知道思想就可以,不需要详细看原理,否则你永远逃不出“菜鸟”这个圈子。当

< br>然我也是菜鸟。我的意思是,真正的单片机入门,是从学会处理多任务开始的,


这 个也是学校程序跟公司程序的最大差别。


当然,


本文不是专门说 这个的,


所以


也不献丑了。



我的主程序架构是这样的:



volatile unsigned char Intrcnt;


void InterruptHandle() //


中断服务程序



{


Intrcnt++; // 1ms

< p>
中断


1


次,可变



}


void main(void)


{


SysInit();


while(1) //



20ms


执行一次大循环



{


KeyRead(); //


将每个子程序都扫描一遍



KeyProc();


Func1();


Funt2();



?




?



while(1)


{


if (Intrcnt>20) //


一直在等,直到

20ms


时间到



{


Intrcnt=


break; //


返回主循环



}


}


}


}


貌似扯远了,


回到我们刚才的问 题,


也就是怎么做按键消抖处理。


我们将读按键


的程序放在了主循环,也就是说,每


20ms


我们会执 行一次


KeyRead()


函数来得


到 新的


Trg



Cont


值。好了,下面是我的消抖部分:很简单


< br>基本架构如上,我自己比较喜欢的,一直在用。当然,和这个配合,每个子程序


必 须执行时间不长,


更加不能死循环,


一般采用有限状态机的办法 来实现,


具体


参考其它资料咯。


< /p>


懂得基本原理之后,


至于怎么用就大家慢慢思考了,


我想也难不到聪明的工程师


们。例如还有一些处理,



怎么判断按键释放?很简单,


Trg



Cont


都为


0


则肯定已经释放了。




这个需要有定时


(按键间隔)


调用函数,

< p>
完成去抖,


区别单次和长按,


好的思路。


我想矩阵键盘也可以处理,只有键盘返回的码是唯一的,把


PINB


换成


getkey


之类的函数。我想 这个可能用来分析脉冲信号,比如红外遥控信号











最简单矩阵键盘扫描程序



这是站长初 学者写的最简单、


最详细、


效率最高的矩阵键盘扫描程序,


只用了四条常用命令



MOV/

< p>
送数、


JB/


高电平转移、


JMP/


直接转移、


RET/


子程序 返回)


,保证初学者一看就懂!


本程序已经在本站电子实验板上 验证通过,


占用CPU时间少,


效率高,


被选作


单片机


的测


试程序!





矩阵按键扫描程 序是一种节省


IO


口的方法


,


按键数目越多节省


IO


口就越可观,本程序


的思路跟书上一样:先判断某一列(行)是否有按键按下,再判断该行(列)是那一只键按< /p>


下。但是,在程序的写法上,站长采用了最简单的方法,使得程序效率最高。





本程序中,


如果检测到某键按下了,


就不再检测其它的按键,


这完全能满足绝大多数需


要,又能节省大量的


CPU


时间。另外,本人认为键盘用延时程序来消除抖动,完全是浪费


时间。试 想,如果不用中断执行(用中断执行需要更多的硬件资源)的方法来扫描键盘,每


秒钟扫 描20-100次,


每次都要延时10-20MS的话,


我们的


单片机


还有多少时间


做正事呢?





其实,延时的 这段时间,


CPU


可以做其它的事呀。所以,本键盘扫描程序的 前面后面


都可以加入少少代码,


既可以达到完美的消抖动效果,


又可以扩展其它的功能


(例如按键封


锁 、按键长按等按键功能复用!




字串


2




本键盘扫描子程序名叫


key


,每次要 扫描时用


call


key


调用即可。以下子程序内容:




key:mov


p0,#0000 1111b;


上四位和下四位分别为行和列


,

< br>所以送出高低电压检查有没有按键


按下



jmp


k10;


跳到


K10


处开始扫描,这里可以改成其它条件转移指令来决定本次扫描是否要继< /p>


续,


例如减


1



0


转移或者位为


1


0


才转移,


这主要用来增加功能 ,


确认上一按键功能是


否完成?是否相当于经过了延时?是否要 封锁键盘?



goend:jmp


k end;


如果上面判断本次不执行键盘扫描程序,


则立即转到程 序尾部,


不要浪费


C


PU


的时间



k10:jb


p 0.0,k20;


扫描正式开始,先检查列


1

< br>四个键是否有键按下,如果没有,则跳到


K20


检查列< /p>


2


k11:mov


p0,#1110 1111b;



1


有键按下时


,P0.0


变低


,


到底 是那一个键按下?现在分别输出各行


低电平



jb


p0.0,k12;


该行的键不 按下时,


p0.0


为高电平


,


跳到到


K12,


检查其它的行

< br>


mov


r1,#1;


如果正 好是这行的键按下,将寄存器


R0


写下


1


,表示


1


号键按下了



k12:mov


p0,#11011111b


jb


p0.0,k13


mov < /p>


r1,#2;


如果正好是这行的键按下,将寄存器


R0


写下


2


,表示

< p>
2


号键按下了



k13:mov


p0,#10111111b


jb


p0.0,k14


mov < /p>


r1,#3;


如果正好是这行的键按下,将寄存器


R0


写下


3


,表示

< p>
3


号键按下了



字串


3



k14:mov


p0,#01111111b


jb


p0.0,kend;


如果现在 四个键都没有按下,可能按键松开或干扰,退出扫描(以后相同)



mov


r1,#4


如果正好是这行的 键按下,将寄存器


R0


写下


4


,表示


4


号键按下了



jmp


kend;


已经找到按下的键 ,跳到结尾吧




k20:jb


p0.1,k30;



2

< br>检查为高电平再检查列


3



4



k21:mov


p0,#1110 1111b;



2


有健按下时,


P0.0


会变低,到底是那一行的键按下呢?分别输

< br>出行的低电平



jb


p0.1 ,k22;


该行的键不按下时


p0.0


为高电平,跳到到


K22


,检查另外三行



mov


r1,#5;


如果正好是 这行的键按下,将寄存器


R0


写下


5< /p>


,表示


5


号键按下了(以后相同,


不再重复了)



k22:mov


p0,#11011111b

-


-


-


-


-


-


-


-



本文更新与2021-03-03 12:43,由作者提供,不代表本网站立场,转载请注明出处:https://www.bjmy2z.cn/gaokao/700007.html

单片机经典长短按程序的相关文章