完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
这个教程是在电协培训新生的一点点心得,旨在解决许多人学习单片机,对按键的一个困惑:按键的非阻塞式消抖。如何在不消耗很多时间的情况下,有效地消抖,具有非常实际的意义。
本文就把一个循序渐进的对按键处理的理解记录下来,方便大家学习,由于本人已投入半导体器件的学习,很久没有接触数字电路,如有错误,还请读者指出。 本文注重方法的连续演化,遵循事物的发展规律,如需要计数式非阻塞矩阵按键读取方法,直接翻到最后即可,最后我将给出本文所讲的所有代码,屏蔽掉,稍加修改端口可以在51单片机上实现,体会每种方法的利弊,和更好的方法的思想。 另外我将给出一个应用,证明非阻塞,特征次数可变的按键检测的优势。 1、按键的基本读取 如上图,是单片机常用的两种开关,其内部结构,由于弹性金属触点的接触,进而触发了两个引脚的开路和短路。 独立按键的检测原理: 从单片机核心板的原理图上可以看到,在按键未按下时,KEY是断开的,单片机IO口通过限流电阻与VCC相连,则IO口处于被拉高的状态。 按键被按下时,则KEY接通,电阻右端为理想的0V状态,IO口接在电阻右端,处于被拉低的状态。 单片机通过读这些引脚状态就能够判断按键处于什么状态。 根据这个思想,代码如下: void main(void) { while (1) { if (key0 == 0) { led = !led; } } } 2、delay延时消抖:解决抖动问题 上面的代码,效果不好?那一定是按键消抖的问题 通常按键所用的开关都是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹 性作用,一个按键开关在闭合时不会马上就稳定的接通,在断开时也不会一下子彻底断开, 而是在闭合和断开的瞬间伴随了一连串的抖动。 那么,如何消抖呢? 一种是硬件消抖,用电容导走信号的高频分量,也就是抖动部分。 另一种就是软件消抖: void main(void) { while (1) { if (!key0) { delay_ms(10); if (!key0) { led = !led; //while (!key0); } } } } 这种方法也有弊端,不用while卡住,那么按住按键,则每次都会进入循环,大多数应用情景,我们都希望按一次按键,单片机做一次响应,用了while,那么主循环就会卡在这里不能往下进行,这对于大多数情况下都是致命的,应该尽量避免。 3、标志位:解决单次按下难题 这种方法基于上面提到的问题做了改进,主要思想是定义一个变量,储存按键的状态,只有在弹起状态的时候按下,才算做一次有效按下,在else if中更新为按键弹起状态。 void main(void) { unsigned char key0_stat; while (1) { if (key0_stat == 1 && key0 == 0) { delay_ms(10); key0_stat = 0; led = !led; } else if (key0_stat == 0 && key0 == 1) key0_stat = 1; } } 4、 计数:解决阻塞问题 上一种方法已经能用,但是远远称不上好用,为什么?因为那个10ms的延时,我们说延时的目的是为了避开那个我们处理不了的抖动过程,但是延时必然有弊端,尤其是这种延时这么长时间,什么事都不干,无论从系统实时性角度,还是资源利用率角度,都是不能允许存在的。那么有没有别的方法避免呢? 上面的图,展示了按键从按下到松开,单片机以较高频率采集IO口状态的过程,那么,我们能从中提取出标志着按下并稳定的一种关键特征吗? 没错,就是能够在一段时间内持续、稳定保持在低电平状态,也就是按下的前几个0。 这样是不是可以避免那个恼火的延时过程呢? 先看代码: void main(void) { unsigned int count = 0; while (1) { if (!key0) { if (count <= 100) count++; } else count = 0; if (count == 100) { led = !led; } } } 根据上面的理论分析,我们想到了连续多次读引脚状态,只有在稳定按下的时候,才判断为真正按下了按键。 第一个if,判断按键按下的过程,只要检测到按下,就会进入,只要count小于101,就+1,这个变量就通过值来反应按下的时间,中间有任何抖动,一旦被读入高电平,这个count就会被重新置0,打断了连续计数的状态,只有按键稳定,count才能被加到101,并且只要按键不松开,count的值就会稳定在101。 然后在主循环中另外判断,只要conut等于100,就说明经历了一次按下的状态,并且一次按下,只会触发一次,这样就避免了疯狂按下的bug,这样的程序也几乎不会干扰主循环中的其他程序。 5、扩展到矩阵按键 矩阵按键的特点: 电路结构复杂,但提高了I/O引脚的利用率,软件编程较复杂,适用于所需按键较多的场合。 矩阵按键程序实现的两种方法: 一是行扫描法,二是线反转法。两种方法大同小异,我们这里用最常用的行扫描法。 可以看到,矩阵按键通过8个IO口,就能够驱动16个按键,这是因为分别动用了4个IO口来操作4行4列的线,通过这样的排布,以及扫描软件,就能实现读。那么在程序中我们又如何进行这项操作呢? void main(void) { int i, j; unsigned int cnt[16] = {0}; while (1) { P2 = 0xff; //P2口全部置高 for (i = 0; i < 4; i++) //循环,每次拉低一列 { set_row(i, 0); //拉低一列 for (j = 0; j < 4; j++) //循环,每次检测一行 { if (get_line() == j) //检测到行被拉低 { if(cnt[4*i+j] <= 100) cnt[4*i+j]++; //对应的按键计数加一 } else cnt[4*i+j] = 0; //低电平,按键松开或抖动过程,对应的按键计数清零 } set_row(i, 1); //重新拉高这一列 } //if (cnt[0] == 100) led = !led; for (i = 0; i < 4; i++) //检测计数值经过100的按键,并用LED显示 { for (j = 0; j < 4; j++) { if (cnt[4*i+j] == 100) P0 = ~(4*i+j); } } } } 为了方便使用,两个函数定义为: void set_row(unsigned char row, unsigned char stat) { switch (row) { case 0: row0 = stat; break; case 1: row1 = stat; break; case 2: row2 = stat; break; case 3: row3 = stat; break; } } unsigned char get_line(void) { unsigned char line = 4; if (line0 == 0) line = 0; if (line1 == 0) line = 1; if (line2 == 0) line = 2; if (line3 == 0) line = 3; return line; } 这就是按键扫描检测的核心代码,可以看到,代码形式与上面刚刚讲过的最优的独立按键检测方式非常相似。 不同就在于两点:
6、全部代码 为了方便大家学习,我贴出全部代码,可以在STC89C52平台跑通,请自行屏蔽注释不需要的main函数。 #include "reg52.h" #include ***it led = P0^0; ***it key0 = P3^3; ***it key1 = P2^3; ***it key2 = P2^4; ***it row0 = P2^3; ***it row1 = P2^2; ***it row2 = P2^1; ***it row3 = P2^0; ***it line0 = P2^7; ***it line1 = P2^6; ***it line2 = P2^5; ***it line3 = P2^4; void delay_ms(unsigned int a); //@11.0592MHz void set_row(unsigned char row, unsigned char stat); unsigned char get_line(void); void main(void) { while (1) { if (key0 == 0) { led = !led; } } } //void main(void) //{ // while (1) // { // if (!key0) // { // delay_ms(10); // if (!key0) // { // led = !led; // //while (!key0); // } // } // } //} //void main(void) //{ // unsigned char key0_stat; // while (1) // { // if (key0_stat == 1 && key0 == 0) // { // delay_ms(10); // key0_stat = 0; // led = !led; // } // else if (key0_stat == 0 && key0 == 1) key0_stat = 1; // } //} //void main(void) //{ // unsigned int count = 0; // while (1) // { // if (!key0) // { // if (count <= 100) count++; // } // else count = 0; // if (count == 100) // { // led = !led; // } // } //} //void main(void) //{ // int i, j; // unsigned int cnt[16] = {0}; // while (1) // { // P2 = 0xff; //P2口全部置高 // for (i = 0; i < 4; i++) //循环,每次拉低一列 // { // set_row(i, 0); //拉低一列 // for (j = 0; j < 4; j++) //循环,每次检测一行 // { // if (get_line() == j) //检测到行被拉低 // { // if(cnt[4*i+j] <= 100) cnt[4*i+j]++; //对应的按键计数加一 // } // else cnt[4*i+j] = 0; //低电平,按键松开或抖动过程,对应的按键计数清零 // } // set_row(i, 1); //重新拉高这一列 // } // // //if (cnt[0] == 100) led = !led; // for (i = 0; i < 4; i++) //检测计数值经过100的按键,并用LED显示 // { // for (j = 0; j < 4; j++) // { // if (cnt[4*i+j] == 100) P0 = ~(4*i+j); // } // } // } //} void delay_ms(unsigned int a) //@11.0592MHz { unsigned char i, j; unsigned int k; for (k = 0; k < a; k++) { _nop_(); i = 2; j = 199; do { while (--j); } while (--i); } } void set_row(unsigned char row, unsigned char stat) { switch (row) { case 0: row0 = stat; break; case 1: row1 = stat; break; case 2: row2 = stat; break; case 3: row3 = stat; break; } } unsigned char get_line(void) { unsigned char line = 4; if (line0 == 0) line = 0; if (line1 == 0) line = 1; if (line2 == 0) line = 2; if (line3 == 0) line = 3; return line; } |
|
|
|
只有小组成员才能发言,加入小组>>
3314 浏览 9 评论
2995 浏览 16 评论
3494 浏览 1 评论
9059 浏览 16 评论
4088 浏览 18 评论
1178浏览 3评论
605浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
599浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2335浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1896浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-23 06:27 , Processed in 1.317770 second(s), Total 78, Slave 59 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号