单片机矩阵键盘扫描驱动程序与电路分析

最新更新时间:2023-01-05来源: zhihu关键字:单片机  矩阵键盘  扫描驱动 手机看文章 扫描二维码
随时随地手机看文章

以4X4键盘为例,首先按照下图制作电路。

然后将HOR1-HOR4连接到单片机的输入引脚上去;LON1-LON4连接到单片机的开漏输出引脚上去,注意这4个引脚必须设置为开漏模式!

程序上首先将LON1所连接的IO输出低电平其余3个IO输出高电平,同时检测HOR1-HOR4的电平来获取K1-K4的按键状态;然后将LON2所连接的IO输出低电平其余3个IO输出高电平,同时检测HOR1-HOR4的电平来获取K5-K8的按键状态;依次类推。

但是这个电路是有BUG的,比如同时按下K1、K5和K6,当LON1为低电平的时HOR1检测到是低电平没有问题;因为K2没有被按下所以我们希望HOR2是高电平,但是由于K1、K5、K6同时按下电流从VCC通过R2再通过K6再通过K5再通过K1流到LON1,所以实际上HOR2也是低电平这时候程序就认为K2被按下了导致出错。


解决这个问题很简单只需要在合适的位置加一个二极管,利用其单向导电性阻挡电流跨列流动就行了。

好了,接下来是单片机代码时间:

  1. #ifndef __Key_matrix_H

  2. #define __Key_matrix_H


  3. #include "gpio.h"

  4. #include "gpio_bool.h"


  5. /*务必把这4个输出IO设置为上拉输入*/

  6. #define KEY_HOR1 PAin(7)

  7. #define KEY_HOR2 PAin(6)

  8. #define KEY_HOR3 PAin(5)

  9. #define KEY_HOR4 PAin(4)

  10. /*务必把这4个输出IO设置为开漏*/

  11. #define KEY_LON1 PBout(0)

  12. #define KEY_LON2 PCout(5)

  13. #define KEY_LON3 PCout(4)

  14. #define KEY_LON4 PCout(3)


  15. #define KEY_PRESS_TIME 20//消抖常数

  16. #define KEY_LONG_PRESS_TIME 3000//单个按键长按阈值3s

  17. /*通过读取(只读)这三个变量即可获得按键的单按、长按和组合键信息*/

  18. extern volatile uint16_t Key_Phy_Num;

  19. extern volatile uint8_t Key_Pulse_Num;

  20. extern volatile uint16_t Key_LP_Num;


  21. typedef enum

  22. {

  23. KPL_DISABLE=0,

  24. KPL_ENABLE

  25. }K_L_P;//按键的长按状态

  26. typedef struct

  27. {

  28. K_L_P KEY_LONG_PRESS;

  29. uint16_t KeyOpenCount;

  30. uint8_t KOC_EN;

  31. uint16_t KeyCloseCount;

  32. uint8_t KCC_EN;

  33. }Key_Para;


  34. exter Key_Par Key_1,Key_2,Key_3,Key_4,Key_5,Key_6,Key_7,Key_8,Key_9,Key_10,Key_11,Key_12,Key_13,Key_14,Key_15,Key_16;


  35. void Clear_Key_Pulse_Num(void);//当读取完Key_Pulse_Num后调用

  36. void KeyCount_Run(void);//在1ms滴答里调用

  37. void Key_Scan(void);//大循环或者滴答里边都行


  38. #endif


复制代码


  1. #include "Key_matrix.h"


  2. Key_Par Key_1,Key_2,Key_3,Key_4,Key_5,Key_6,Key_7,Key_8,Key_9,Key_10,Key_11,Key_12,Key_13,Key_14,Key_15,Key_16;

  3. volatile uint16_t Key_Phy_Num=0; //Key_Phy_Num每一个bit代表一个按键的状态

  4. volatile uint8_t Key_Pulse_Num=0;//当某一个按键从按下到弹起的过程中(非长按)始终只有该按键被操作,则Key_Pulse_Num被修改为该键的序号

  5. volatile uint16_t Key_LP_Num=0; //Key_LP_Num每一个bit代表一个按键的长按状态

  6. uint8_t KeyCom=0;//组合键是否出现


  7. static void Key_Num_Read(Key_Para* Key,uint16_t KPN,uint8_t Pulse,uint8_t Key_Hor)

  8. {

  9. if(Key_Hor == 0)

  10. {

  11. Key->KOC_EN=0;//按键按下立即清除(松开)计数

  12. if(Key->KeyCloseCount > KEY_PRESS_TIME)

  13. {

  14. /*消抖方法为检测到按键被(持续)按下超过20ms*/

  15. Key_Phy_Num|=KPN;//消抖完毕后记录被按下的按键的键值

  16. if(Key->KeyCloseCount > KEY_LONG_PRESS_TIME)

  17. {

  18. /*检测到按键被(持续)按下超过3秒*/

  19. Key->KEY_LONG_PRESS=KPL_ENABLE;

  20. Key_LP_Num|=KPN;

  21. Key->KCC_EN=0;

  22. }

  23. else

  24. {

  25. /*时间不够启动计数*/

  26. Key->KCC_EN=1;

  27. }

  28. }

  29. else

  30. {

  31. /*时间不够启动计数*/

  32. Key->KCC_EN=1;

  33. }

  34. }

  35. else

  36. {

  37. Key->KCC_EN=0;//按键松开立即清除(按下)计数

  38. if(Key->KeyOpenCount > KEY_PRESS_TIME)

  39. {

  40. if((Key_Phy_Num==KPN)&&(KeyCom==0)&&(Key->KEY_LONG_PRESS!=KPL_ENABLE))

  41. {

  42. //按键被按下过&&非长按&&不是在组合键周期,该按键释放时发出生命周期为直到被读取或者直到有新按键被按下的脉冲

  43. Key_Pulse_Num=Pulse;

  44. }

  45. //清除该位

  46. Key_Phy_Num&=(~KPN);

  47. Key_LP_Num&=(~KPN);

  48. /*检测到(持续)松开20ms*/

  49. Key->KEY_LONG_PRESS=KPL_DISABLE;

  50. Key->KOC_EN=0;

  51. }

  52. else

  53. {

  54. Key->KOC_EN=1;

  55. }

  56. }

  57. }

  58. /********************************************************/

  59. static void Key_Count(Key_Para *Key)

  60. {

  61. if(Key->KOC_EN==0)

  62. {

  63. Key->KeyOpenCount=0;

  64. }

  65. else if(Key->KeyOpenCount>=50000)

  66. {

  67. Key->KeyOpenCount=50000;

  68. }

  69. else

  70. {

  71. Key->KeyOpenCount++;

  72. }


  73. if(Key->KCC_EN==0)

  74. {

  75. Key->KeyCloseCount=0;

  76. }

  77. else if(Key->KeyCloseCount>=50000)

  78. {

  79. Key->KeyCloseCount=50000;

  80. }

  81. else

  82. {

  83. Key->KeyCloseCount++;

  84. }

  85. }

  86. /********************************************************/

  87. void Clear_Key_Pulse_Num(void)

  88. {

  89. Key_Pulse_Num=0;

  90. }

  91. /********************************************************/

  92. void KeyCount_Run(void)

  93. {

  94. Key_Count(&Key_1);

  95. Key_Count(&Key_2);

  96. Key_Count(&Key_3);

  97. Key_Count(&Key_4);

  98. Key_Count(&Key_5);

  99. Key_Count(&Key_6);

  100. Key_Count(&Key_7);

  101. Key_Count(&Key_8);

  102. Key_Count(&Key_9);

  103. Key_Count(&Key_10);

  104. Key_Count(&Key_11);

  105. Key_Count(&Key_12);

  106. Key_Count(&Key_13);

  107. Key_Count(&Key_14);

  108. Key_Count(&Key_15);

  109. Key_Count(&Key_16);

  110. }

  111. /********************************************************/

  112. static void Recognition_KeyCombination(void)

  113. {

  114. uint8_t i=0,j=0;

  115. uint16_t Data=0;


  116. Data=Key_Phy_Num;

  117. for(i=0;i<16;i++)

  118. {

  119. if(Data&0x8000)

  120. {

  121. j++;

  122. }

  123. Data<<=1;

  124. }

  125. /*发现多个bit为1,那指定多个按键按下了*/

  126. if(j>1)

  127. {

  128. KeyCom=1;

  129. }

  130. /*一切归于平静,又是一个因果循环*/

  131. if(Key_Phy_Num==0x0)

  132. {

  133. KeyCom=0;

  134. }

  135. }

  136. /********************************************************/

  137. void Key_Scan(void)

  138. {

  139. static uint8_t ScanCount=0;


  140. Recognition_KeyCombination();

  141. switch(ScanCount)

  142. {

  143. case 0:

  144. {

  145. KEY_LON1=0;KEY_LON2=1;KEY_LON3=1;KEY_LON4=1;

  146. Key_Num_Read(&Key_1,(uint16_t)0x0001 ,1,KEY_HOR1);

  147. Key_Num_Read(&Key_2,(uint16_t)0x0001<<1,2,KEY_HOR2);

  148. Key_Num_Read(&Key_3,(uint16_t)0x0001<<2,3,KEY_HOR3);

  149. Key_Num_Read(&Key_4,(uint16_t)0x0001<<3,4,KEY_HOR4);

  150. KEY_LON1=1;KEY_LON2=0;KEY_LON3=1;KEY_LON4=1;

  151. ScanCount++;

  152. }break;

  153. case 1:

  154. {

  155. KEY_LON1=1;KEY_LON2=0;KEY_LON3=1;KEY_LON4=1;

  156. Key_Num_Read(&Key_5,(uint16_t)0x0001<<4,5,KEY_HOR1);

  157. Key_Num_Read(&Key_6,(uint16_t)0x0001<<5,6,KEY_HOR2);

  158. Key_Num_Read(&Key_7,(uint16_t)0x0001<<6,7,KEY_HOR3);

  159. Key_Num_Read(&Key_8,(uint16_t)0x0001<<7,8,KEY_HOR4);

  160. KEY_LON1=1;KEY_LON2=1;KEY_LON3=0;KEY_LON4=1;

  161. ScanCount++;

  162. }break;

  163. case 2:

  164. {

  165. KEY_LON1=1;KEY_LON2=1;KEY_LON3=0;KEY_LON4=1;

  166. Key_Num_Read(&Key_9 ,(uint16_t)0x0001<<8 , 9,KEY_HOR1);

  167. Key_Num_Read(&Key_10,(uint16_t)0x0001<<9 ,10,KEY_HOR2);

  168. Key_Num_Read(&Key_11,(uint16_t)0x0001<<10,11,KEY_HOR3);

  169. Key_Num_Read(&Key_12,(uint16_t)0x0001<<11,12,KEY_HOR4);

  170. KEY_LON1=1;KEY_LON2=1;KEY_LON3=1;KEY_LON4=0;

  171. ScanCount++;

  172. }break;

  173. case 3:

  174. {

  175. KEY_LON1=1;KEY_LON2=1;KEY_LON3=1;KEY_LON4=0;

  176. Key_Num_Read(&Key_13,(uint16_t)0x0001<<12,13,KEY_HOR1);

  177. Key_Num_Read(&Key_14,(uint16_t)0x0001<<13,14,KEY_HOR2);

  178. Key_Num_Read(&Key_15,(uint16_t)0x0001<<14,15,KEY_HOR3);

  179. Key_Num_Read(&Key_16,(uint16_t)0x0001<<15,16,KEY_HOR4);

  180. KEY_LON1=0;KEY_LON2=1;KEY_LON3=1;KEY_LON4=1;

  181. ScanCount=0;

  182. }break;

  183. default:

  184. {

  185. ScanCount=0;

  186. }break;

  187. }

  188. }


关键字:单片机  矩阵键盘  扫描驱动 编辑:什么鱼 引用地址:单片机矩阵键盘扫描驱动程序与电路分析

上一篇:8051单片机快速入门--我的第一盏灯
下一篇:at89c51 8个LED 如何循环亮灭?

推荐阅读

单片机烧录程序的几种常见方法
最近看到一个小伙伴问了一个比较基础的问题,大概就是问:单片机烧录程序常见方法有哪些?下面就来说下常见的三种烧录程序的方法:ISP、IAP和ICP,以及它们的区别。ISPISP:In System Programing,在系统编程。ISP 是指可以在板级上进行编程,也就是不用拆芯片下来,写的是整个程序,一般是通过 ISP 接口线来写。支持ISP的芯片一般在芯片内部固化了一段(用ISP升级的)boot程序。比如:使用STC-ISP对STC芯片编程,利用Flash loader对STM32编程等。ICPICP:In Circuit Programing,在电路编程。ICSP:In-Circuit Serial Programming,在电
发表于 2023-03-27
<font color='red'>单片机</font>烧录程序的几种常见方法
玩转单片机得弄明白DMA原理
DMA,全称Direct Memory Access,即直接存储器访问。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理, DMA
发表于 2023-03-27
原来单片机main函数在这里执行
最近看了硬汉分享的一个内容:为什么复位中断服务程序里面直接调用的main函数,难道所有程序都在复位中断里面执行的?首先,Reset_Handler 是单片机的一个中断,其次,main 函数也确实被 Reset_Handler 中断调用了。那不是,main函数在中断里执行?看到这个问题,你是否也曾想过这个问题,难道我们以前的认识错了?说实话,我都没曾想过这个问题,我觉得绝大多数人都没有想过这个问题。所以,这里顺便分享一下这个问题的内容。单片机的操作模式这里的单片机,主要指 ARM Cortex-M 内核单片机。要回答开篇那个问题,就要提到单片机的操作模式,这里以 Cortex‐M3 单片机为例,Cortex‐M3 支持两种模式和两个特
发表于 2023-03-27
单片机固件中加入版本信息的方法
前言开发完MCU软件后,通常都会生成hex文件或者bin文件,用来做固件烧录或者升级,如果用来做产品开发,就涉及到固件版本的问题,初学者通常采用固件文件重命名来区分版本。如果需要每次上电开机通过串口或者OLED等显示版本信息,那么这种方式就无法实现下面介绍如何在程序中加入版本信息,以MDK为例介绍。实现方式1.定义一个结构体,里面定义一些软件版本相关的信息typedef struct{ char szVersion[32]; // 软件版本 char szBuildDate[32]; // 程序编译日期 char szBuildTime[32]; // 程序编译时间}AppInfo_t;2.定义一个只读结构体变量(只读的目的
发表于 2023-03-27
<font color='red'>单片机</font>固件中加入版本信息的方法
单片机ADC常见的几种滤波方法
如今传感器的种类越来越多,数量也越来越多,而这些传感器很多都会用到模拟量,模拟量就离不开ADC。然而,我们单片机ADC采集的模拟量基本都会经过“滤波”处理才能使用,下面给大家分享一些常见的ADC滤波算法。一、限幅滤波1、方法根据经验判断两次采样允许的最大偏差值A每次采新值时判断:若本次值与上次值之差<=A,则本次有效;若本次值与上次值之差>A,本次无效,用上次值代替本次。2、优缺点克服脉冲干扰,无法抑制周期性干扰,平滑度差。3、代码/* A值根据实际调,Value有效值,new_Value当前采样值,程序返回有效的实际值 */#define A 10char Value;char filter(){ char new_V
发表于 2023-03-27
3个字让你记住单片机的大小端模式
今天,我们来讲解一下单片机的大小端模式,目录如下:1、什么是大小端?2、怎么区分大端模式和小端模式?3、如何判断单片机的大小端模式?4、大端模式与小端模式怎么转换?5、STM32是大端还是小端模式?一、什么是大小端?我们常常提到的大小端,其英文名字为“endianness”,直译过来就是“字节序”的意思,是内存中存储数据的字节顺序(注意:一定要记住是“字节的顺序”,因为在计算机系统中都是以字节为单位的,每个地址单元都对应一个字节,即8bit)。在C语言系统中,除了8bit的char类型,还有16bit的short类型、32bit的long类型。对于超过8bit的数据的存储,必然存在存在如何将多个字节排序的问题,因此就导致了大端存储模
发表于 2023-03-27
3个字让你记住<font color='red'>单片机</font>的大小端模式
小广播
设计资源 培训 开发板 精华推荐

何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2023 EEWORLD.com.cn, Inc. All rights reserved