历史上的今天
返回首页

历史上的今天

今天是:2026年01月05日(星期一)

正在发生

2023年01月05日 | 单片机矩阵键盘扫描驱动程序与电路分析

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. }


推荐阅读

史海拾趣

Amphenol Nexus公司的发展小趣事

2008年,Amphenol公司看中了Nexus, Inc.在连接器领域的潜力,决定对其进行收购。收购完成后,Amphenol成立了提供全球销售支持的Amphenol Nexus Technologies,同时仍保持了对客户支持和开发的坚定承诺。这一举措使Amphenol Nexus Technologies得以借助Amphenol的全球性的资源和网络,进一步拓展其业务范围和市场影响力。

东晨(DC)公司的发展小趣事

在电子行业的激烈竞争中,东晨(DC)公司凭借一项革命性的技术突破——高效能低功耗的芯片设计,迅速崛起。该公司投入大量研发资源,成功开发出了一款性能卓越、能耗极低的芯片,这一创新成果为智能手机、平板电脑等移动设备提供了更长的续航时间和更快的运行速度。凭借这一技术优势,东晨(DC)公司迅速获得了市场的认可,产品销量节节攀升。

CLARE公司的发展小趣事

CLARE公司始终坚持品质至上的原则,从原材料采购到生产制造、质量检测等各个环节都严格把控,确保产品的品质和性能达到客户的期望。同时,公司还建立了完善的售后服务体系,为客户提供及时、专业的技术支持和服务。这些举措赢得了客户的信赖和好评,为CLARE公司的长期发展奠定了坚实的基础。

Concord Semiconductor Corp公司的发展小趣事

Concord Semiconductor Corp自创立之初,便专注于半导体技术的研发与创新。公司在早期阶段成功开发出一种高效能、低功耗的半导体芯片,这一创新成果迅速在行业内引起关注。随着技术的不断完善和市场的广泛认可,公司的产品线逐渐丰富,客户群体也不断扩大。技术创新成为Concord Semiconductor Corp发展的核心驱动力,推动公司不断向前发展。

Comchip Technology公司的发展小趣事

面对日益激烈的市场竞争和不断变化的客户需求,Comchip Technology始终保持着创新精神。公司不断投入研发资源,开发新产品、新技术,以满足市场的不断变化。同时,公司还积极关注行业发展趋势和未来技术动向,为公司的长远发展做好战略布局。

这些故事基于Comchip Technology公司的实际发展情况虚构而成,旨在展示其在电子行业中的成长历程和所取得的成就。虽然这些故事可能并不完全准确或详尽,但它们能够提供一个关于该公司发展的大致框架和背景。

CINCH公司的发展小趣事

随着业务的不断发展,CINCH公司开始寻求市场拓展和战略合作的机会。公司积极与国内外知名企业建立合作关系,共同开发新产品、拓展新市场。同时,CINCH公司还加强了对新兴市场的关注,通过参加国际展会、举办技术研讨会等方式,提升品牌知名度和影响力。这些举措有效地推动了公司的市场拓展和业务发展。

问答坊 | AI 解惑

三端稳压器管脚判断

简单的三端稳压器管脚判断,也许你知道!给初学者…

查看全部问答>

求助该电路存在的问题

不好意思,不知道怎么直接插入图片,麻烦大侠们点一下。 该图是一种长按式开关电路,暂时不考虑电阻值和电容的选值,原理上可以实现 长按开关灯亮,再次长按灯灭。但是我实际搭出电路后,一上电,灯直接就亮了。 请教是哪里有问题。…

查看全部问答>

大家要加工资了,等着吧。

胡锦涛:提高劳动报酬 让群众实现体面劳动。…

查看全部问答>

51单片机向PC电脑发送一组汉字的方法

最近看到有朋友在网上问单片机怎么向电脑发送一串汉字这个问题,其实这个问题也不难.呵呵.只要把相关的参数设置好一些,然后把汉字定义成一组字符向电脑发送就可以. 为了让大家更明确的了解,我就写了下面的一个程序来演示一下. 操作条件: ...…

查看全部问答>

找ise foundation 10.1 的开发软件

我现在找了很久ise foundation 10.1或webpack的开发软件,有谁有的可以给我提供以下吗,我在学校,去xilinx网站下要花很大的流量,下不起,图书馆借了个光盘也不能用,哪位好心人能帮我一下,感激涕零啊!邮箱:ffxsppan@163.com…

查看全部问答>

问个关于STM32的ADC的问题

现在有3个AD通道,隔一段时间就要刷新一次,但读3个通道的时间间隔要很短。我的想法是把这3个通道设置为注入模式。然后设置ADON位,使它们连续的采样,并使用扫描模式。这样我什么时候想读,就直接读相关的注入数据寄存器就行了。参考手册也说 ...…

查看全部问答>

求助

这是5438 datasheet 中的一段话,其中Each sector can be completely powered down to save leakage,however all data is lost.是什么意思呀~·…

查看全部问答>

GPIO设置点灯M3学习笔记

#define  DATA_PERIPH            SYSCTL_PERIPH_GPIOG   #define  GPIO_PORTG_BASE        0x40026000  #define  LED ...…

查看全部问答>

如何 设计开关电源

如何设计一个简单的开关电源,输出5V,3A。输入220V…

查看全部问答>

PADS2007专业教程分享

给大家分享一款最好用最实用的PCB画板专业教程。…

查看全部问答>