历史上的今天
返回首页

历史上的今天

今天是:2025年05月06日(星期二)

正在发生

2018年05月06日 | 单片机C语言实现独立按键检测与矩阵键盘操作

2018-05-06 来源:eefocus

所有的电子产品几乎到涉及到按键操作。所以微控制器是如何识别一个按键是否被按下,按下后又该如何做出反应,又如何防止按键抖动呢?更深入一点,微控制器又是如何识别矩阵键盘的?本文将详细阐述如何用C语言实现独立按键的检测和矩阵键盘操作。

完成本文所需硬件:基于C51系列单片机的开发板(本文是基于STC12C5A60S2处理器的一款开发板),带中文版windows操作系统的电脑。

完成本文所需软件:KEIL系列平台(本文选取Keil uVision4), STC烧写软件-ISP-V6.82E 。




一、独立按键检测

       这里我要实现用按键K1去控制发光二极管LD4。同时为了试验按键过程中与其他事件的冲突性,引入两个事件即LD1与LD8分别以不同频率闪烁。先上程序吧。另外利用keil软件新建工程和文件部分这里就略过了,总之所有的代码都放在main文件里执行。

代码:

-----------------------------------------------------------------------------------------------


  1. #include   

  2. sbit K1=P2^4;       //定义按键K1的检测口  

  3. sbit LD4=P1^3;    //定义控制LD4的输出口  

  4. sbit LD1=P1^0;   //定义控制LD1的输出口  

  5. sbit LD8=P1^7;   //定义控制LD8的输出口  


  1. void LD1_flash()  //LD1闪烁  

  2. {  

  3.     static unsigned int a; //定义a为静态局部变量  

  4.     a = a + 1;  

  5.     if(a == 20000) //当a达到20000次CPU计数次数  

  6.     {  

  7.         a=0;  //a归零  

  8.         LD1 =! LD1; //LD1取反,如果先前是灭,则取反后亮,反之。  

  9.     }  

  10. }  

  11.   

  12. void LD8_flash()//LD8 flash  

  13. {  

  14.     static unsigned int b;  

  15.     b++;  

  16.     if(b==40000)  

  17.     {  

  18.         b=0;  

  19.         LD8 =! LD8;  

  20.     }  

  21. }  

  22. void delay(unsigned int x)   

  23. {  

  24.     while(x)  

  25.     {  

  26.         x = x - 1;  

  27.     }  

  28. }  

  29. void Key1()  

  30. {  

  31.     static char st;  

  32.     if(K1==0) //按键K1是否被按下  

  33.     {  

  34.         if(st==0) //按键K1是否是被刚刚按下,假设st为1,则按键还处于被按下的状态,则不用执行if里面语句。  

  35.         {  

  36.             delay(5000); //延时防抖动  

  37.             if(K1==0) //查看按键K1是否还处于被按下状态  

  38.             {  

  39.                 LD4=!LD4;  

  40.                 st=1;  //等待放手  

  41.             }  

  42.                   

  43.         }  

  44.     }  

  45.     else  

  46.     {  

  47.         st=0;  

  48.     }  

  49. }  

  50. void main() //C语言执行入口  

  51. {  

  52.     while(1)  

  53.     {  

  54.         LD1_flash(); //ld1 flash   

  55.         LD8_flash(); //ld8 flash  

  56.         Key1();  

  57.     }  

  58. }  


要理解上面的代码,我们需要结合C语言的一些特性来分析。

1、static unsigned int a,为什么不把变量a与b定义成局部变量,却定义成静态局部变量?

     我们知道局部变量在函数执行完时候其值归零,而static关键字修饰的局部变量在函数结束时,其值会保留到下一次该函数被调用。

2、为什么a和b没被定义成全局变量?

    全局变量是指该变量可被任何函数使用,而局部变量只有定义该变量的函数可以使用。局部变量的优点是安全,但缺点是函数结束其值随之归零。与之相对,全局变量则不安全。

3、为什么a和b并未赋初始值?

    这是因为startup.A51程序执行时使其赋值为0。C语言执行是从函数开始的,但是真正的程序运行是从汇编语言开始,即startup.A51文件。因为C语言无法访问寄存器如R0, 所以只能由汇编程序来执行。下面截取一段startup.A51的程序进行分析:

  1. IF IDATALEN <> 0  

  2.                 MOV     R0,#IDATALEN - 1  

  3.                 CLR     A  

  4. IDATALOOP:      MOV     @R0,A  

  5.                 DJNZ    R0,IDATALOOP  

  6. ENDIF  


    这段程序会将内存区域清零,其中A代表累加器ACC,即内存中224号地址。

     另外解释下赋初值的情况,例如unsigned int a = 8, 我们知道RAM在断电后数据丢失,因此单片机就利用ROM来保存这个初始值。在下次上电时,进入C语言之前,汇编程序将ROM中的a调到RAM中。这样也就保证了a的初值。

4、LD1与LD8的闪烁为什么不适用delay函数?

     如果使用delay函数,那么单片机在执行到LD1_flash函数时候,只能停滞在delay的这个时间段内,浪费时钟资源。而引入a和b后,程序进入LD1_flash函数时候,只需判断a的值,然后再加一或者清零,几乎不占用CPU时间。

5、key1函数中st的作用?

     首先,st可以判断程序在每次被调用的前后按键状态,这样,可以保证键被按住的时候,发光二极管不会闪烁。另外,也可以避免在操作K1键的时候影响LD1和LD8的运行。另外如果不想在key1函数中使用delay函数,可以采用如下代码:

  1. void Key1()  

  2. {  

  3.     static unsigned int a,b;  

  4.     if(K1==0)  

  5.     {  

  6.         if(a==0) //检查按键是否被刚刚按下  

  7.         {  

  8.             b++;  

  9.             if(b >= 1000) //看看按键时间是否维持了1000次CPU执行周期  

  10.             {  

  11.                 LD4=!LD4;  

  12.                 //...  

  13.                 a=1;  

  14.             }  

  15.         }  

  16.     }  

  17.     else  

  18.     {  

  19.         a=0;  

  20.         b=0;  

  21.     }  

  22. }  



6、如何将不同的函数独立到不同的文件?

    为了保证代码的可维护性,如果要将不同的代码区块分配到不同文件,可以使用extern,它的作用是实现不同文件间的函数调用。例如:

    本例中可将main函数精简成:

  1. #include   

  2.   

  3. extern void delay(unsigned int x);  

  4. extern void LD1_flash();  

  5. extern void LD8_flash();  

  6. extern void Key1();  

  7.   

  8. void main()  

  9. {  

  10.     while(1)  

  11.     {  

  12.         LD1_flash(); //ld1 flash   

  13.         LD8_flash(); //ld8 flash  

  14.         Key1();  

  15.     }  

  16. }  


二、矩阵键盘





上图中,P1口初始值从高位到低位为11111110,当2和3键被按下,此时P1.0口连通P1.5和P1.6口,即P1口状态变为10011110。由此我们可以用P1口的高四位来判断有没有键被按下,如果没有那么其值为1111,也就是0x0F。那如果被按下了,具体是哪个键呢?这时候我们可以建立一个矩阵表,然后通过每次读取P1口的状态,与矩阵表相对应,便可以知道哪个键被按下。这个矩阵表见下(选用了P0口代替P1口):




原理就解释到这吧,直接上程序:


-----------------------------------------------------------------------------------------------------------------

  1. #include "reg51.h"  

  2.   

  3. void delay(unsigned int x)  

  4. {  

  5.     while(x)  

  6.     {  

  7.         x=x-1;  

  8.     }  

  9. }  

  10.   

  11. void key_exc(unsigned char k)//unsigned 表示最高位不当做符号用  

  12. {  

  13.     switch(k)  

  14.     {  

  15.         case 0xee: P1=~P1; break; //1键按下  

  16.         case 0xed:  break;        //2键按下  

  17.         case 0xeb:  break;  

  18.         case 0xe7:  break;        

  19.           

  20.         case 0xde:  break;  

  21.         case 0xdd:  break;  

  22.         case 0xdb:  break;  

  23.         case 0xd7:  break;  

  24.           

  25.         case 0xbe:  break;  

  26.         case 0xbd:  break;  

  27.         case 0xbb:  break;  

  28.         case 0xb7:  break;  

  29.           

  30.         case 0x7e:  break;  

  31.         case 0x7d:  break;  

  32.         case 0x7b:  break;  

  33.         case 0x77:  P1=~P1; break;  

  34.     }  

  35. }  

  36. void key_scan()  

  37. {  

  38.     char i;  

  39.     char t; //用来存放读取P0口的状态  

  40.       

  41. //  if((P0>>4) != 15)  

  42. //  {  

  43. //      return; //直接跳出函数  

  44. //  }  

  45.       

  46.     for(i=0;i<4;i++) //for循环执行,首先i赋值,再判断i是否小于4,如果是的话,开始执行下面的程序,程序执行完加1,再判断i是否小于4,这样一直到结束  

  47.     {  

  48.         P0 = ~(1<

  49.         t=P0; //将P0的值赋给t  

  50.         if((t>>4)!=0x0F) //本行是否有按钮被按下  

  51.         {  

  52.             delay(5000);  

  53.             if(P0==t) //如果相等则表示延时前后的按键状态是一致的  

  54.             {  

  55.                 key_exc(t);//将局部变量传给函数key-exc,并分析按下的按钮  

  56.                 while((P0>>4)!=15)//等待放手  

  57.                 {  

  58.                       

  59.                 }  

  60.             }  

  61.         }  

  62.     }  

  63. }  

  64. void main()  

  65. {  

  66.     while(1)  

  67.     {  

  68.         key_scan();  

  69.     }  

  70. }  

-----------------------------------------------------------------------------------------------------------------------------------

利用上面的程序,可以判断哪个按钮被按下。例如当K1按下时候,P1口的发光二极管都被点亮。


推荐阅读

史海拾趣

Everbuild公司的发展小趣事

为了进一步扩大市场份额,Everbuild开始积极拓展国际市场。公司派遣专业的团队参加国际电子产品展览会,与国际知名厂商进行技术交流和合作。同时,Everbuild还针对不同国家和地区的市场需求,推出了定制化的电子产品解决方案。这些努力使Everbuild的产品逐渐进入国际市场,并赢得了越来越多客户的信任和支持。

Asia Electronics Ind Co Ltd公司的发展小趣事

随着公司产品的不断成熟和市场的不断扩大,Asia Electronics Ind Co Ltd积极寻求市场拓展的机会。公司加强与国际知名企业的合作,通过参加国际电子展、建立海外销售渠道等方式,不断提升品牌知名度和影响力。同时,公司还注重品牌形象的塑造,通过统一的品牌标识、宣传资料等,增强消费者对品牌的认知度和信任度。

ESS [ESS Technology,Inc]公司的发展小趣事

ESS不仅在个人电脑音频领域取得了显著成就,还在HIFI领域实现了突破。ESS的HIFI传承可以追溯到其创始人对音频技术的深刻理解和对市场需求的敏锐把握。通过多年的技术积累和不断创新,ESS成功推出了多款高性能的HIFI音频芯片,如ES9018,这些产品凭借其卓越的性能和音质赢得了广泛好评。

德芯半导体(Doeshare)公司的发展小趣事

德芯半导体深知人才是企业发展的核心。因此,公司注重人才队伍的建设,吸引了一批具有丰富经验和专业知识的资深工程师和管理人员。这些人才不仅为公司带来了先进的技术和管理经验,还为公司的发展提供了源源不断的动力。公司设立技术委员会,布局先进封装项目,推动公司在高端封装技术上的突破。

FUJITSU(富士通)公司的发展小趣事

德芯半导体在发展过程中,积极拓展合作伙伴关系。公司与国内外多家知名企业建立了长期稳定的合作关系,共同开展技术研发和市场推广。这些合作伙伴不仅为公司提供了技术支持和市场渠道,还为公司带来了更多的商业机会和发展空间。通过与合作伙伴的紧密合作,德芯半导体在半导体行业中树立了良好的口碑和形象。

Heidenhain Corp公司的发展小趣事

面对电子行业日益增长的自动化需求,海德汉公司凭借其丰富的自动化解决方案,助力多家电子企业实现了生产线的智能化升级。某大型电子制造厂采用海德汉的自动化控制系统和光栅尺技术,对生产线进行了全面改造。改造后的生产线实现了从原材料上料到成品下线的全自动化作业,大大提高了生产效率和产品质量稳定性。同时,通过集成海德汉的MES系统,实现了生产数据的实时监控和追溯,为企业的精细化管理提供了有力支持。

问答坊 | AI 解惑

freescale MCF52259 SD卡演示例程

这是在我们的MCF52259开发板上跑的一个简单的SD卡读写程序,SD卡是用MCF52259的QSPI模块操作的。MCF52259是飞思卡尔coldfire v2 core的mcu,具有以太网控制器,CAN, USB OTG,UART,RTC,QSPI,IIC等功能,比较全。 例子在codewarrior for coldfire v7 ...…

查看全部问答>

三端稳压LM317设计的甲类功放

本帖最后由 jameswangsynnex 于 2015-3-3 19:59 编辑 用稳压集成功放制作的功率放大器,对电子爱好者来说,作为开拓思路的一种尝试不无积极意义。该电路为纯甲类工作,又用低噪声管作电压放大,所以THD,NF等指标都不错,输出功率可达到30W 电路 ...…

查看全部问答>

新产品快递Allegro’s LED Backlight Driver IC for Large Displays

Allegro’s LED Backlight Driver IC for Large Displays The new device integrates a scalable-output boost controller operating in constant-frequency current mode control – adjustable between 300kHz and 1MHz - drivin ...…

查看全部问答>

中国智能家电的春天在哪里?

本帖最后由 jameswangsynnex 于 2015-3-3 20:01 编辑 今年以来,随着海尔推出物联网冰箱、小天鹅推出物联网洗衣机以及美的在世博会上展示全套的物联网家电产品,家电的智能化热潮正悄然袭来。4月底,中国轻工业联合会嵌入式系统应用委员会也宣布成 ...…

查看全部问答>

关于WINCE应用程序

我把整个的NAND FLASH读出来,然后通过一些办法把NK文件读出来了。。 但是我发现里面的都是系统文件,里面原先写入NAND FLASH文件的文件不能导出来。请问有什么办法把这些导出来。。 我写个示意图: 00000000H:xx xx xx xx xx xx xx xx xx xx ...…

查看全部问答>

embedded visual c++4.0安装过程中 sp4安装不成功

尝试很多次,一直安不成功。 本人使用电脑为联想的笔记本,自带操作系统,是不是因为他里面装了一些软件导致冲突造成。 错误提示 Internal Error2349! 非常感谢各位大虾! 另,我是一初学者,还请各位指教本书来尽快上手。…

查看全部问答>

红外线遥控器控制继电器的程序+电路图

红外线遥控器控制继电器的程序 #include #include #define uchar unsigned char #define uint unsigned int    #pragma interrupt_handler IceInt:6 中断程序说明 uint bitcnt,data0=0,data1=0; void ICEInit(void) //T/C1 初 ...…

查看全部问答>

【M4开发板Hanker试用狂-Shower.Xu】9、有容乃大-基于USB设备的SD读卡器实现

失败了无数次之后,一瞬间竟然可以了。成功和失败只隔了一层纸,但是个中滋味却相差甚远...简单记录一下移植过程:1、在SD实现读写的移植基础上展开。2、usb端口初始化和USB鼠标一样,无需改变。3、在此基础上加入下图几个文件usb_msc_structs.c是 ...…

查看全部问答>

菜鸟疑问

各位老鸟们:         我在ccs3.1中定义了一个数组,是全局变量。然后对他赋值,数组前面的元素赋对了,可后面的赋不上,在watch window中后面的元素也不能修改。有没有老鸟遇到过此类情况啊,求分析!!…

查看全部问答>

msp430比较器的使用

#include typedef unsigned char uchar; void Bcstime() {      WDTCTL=WDTPW+WDTHOLD; } /************定时器A初始化*******************/ void  TimerA() {      P2DIR |=0X04; ...…

查看全部问答>