找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 13267|回复: 16
收起左侧

单片机用ADC+电阻扫描键盘的源程序和原理图

  [复制链接]
ID:215340 发表于 2017-6-28 09:36 | 显示全部楼层 |阅读模式
这是我的ADC扫描键盘程序,附原理图,用stc单片机自带的ad和一些电阻构成的简单电路,实现了16个ad的按键.

原理图

原理图


单片机源程序如下:
  1. /*************  本程序功能说明  **************

  2. 用STC的MCU的IO方式控制74HC595驱动8位数码管。

  3. 显示效果为: 数码时钟.

  4. 使用Timer0的16位自动重装来产生1ms节拍,程序运行于这个节拍下, 用户修改MCU主时钟频率时,自动定时于1ms.

  5. 左边4位LED显示时间(小时,分钟), 右边最后两位显示按键值.

  6. ADC按键键码为1~16.

  7. 按键只支持单键按下, 不支持多键同时按下, 那样将会有不可预知的结果.

  8. 键按下超过1秒后,将以10键/秒的速度提供重键输出. 用户只需要检测KeyCode是否非0来判断键是否按下.

  9. 调整时间键:
  10. 键码1: 小时+.
  11. 键码2: 小时-.
  12. 键码3: 分钟+.
  13. 键码4: 分钟-.


  14. ******************************************/


  15. #include    "reg51.h"
  16. #include    "intrins.h"

  17. #define     MAIN_Fosc       11059200L   //定义主时钟

  18. typedef     unsigned char   u8;
  19. typedef     unsigned int    u16;
  20. typedef     unsigned long   u32;

  21. sfr TH2  = 0xD6;
  22. sfr TL2  = 0xD7;
  23. sfr IE2   = 0xAF;
  24. sfr INT_CLKO = 0x8F;
  25. sfr AUXR = 0x8E;
  26. sfr AUXR1 = 0xA2;
  27. sfr P_SW1 = 0xA2;
  28. sfr P_SW2 = 0xBA;
  29. sfr S2CON = 0x9A;
  30. sfr S2BUF = 0x9B;

  31. sfr ADC_CONTR = 0xBC;   //带AD系列
  32. sfr ADC_RES   = 0xBD;   //带AD系列
  33. sfr ADC_RESL  = 0xBE;   //带AD系列
  34. sfr P1ASF = 0x9D;   //只写,模拟输入(AD或LVD)选择

  35. sfr P4   = 0xC0;
  36. sfr P5   = 0xC8;
  37. sfr P6   = 0xE8;
  38. sfr P7   = 0xF8;
  39. sfr P1M1 = 0x91;    //PxM1.n,PxM0.n     =00--->Standard,    01--->push-pull
  40. sfr P1M0 = 0x92;    //                  =10--->pure input,  11--->open drain
  41. sfr P0M1 = 0x93;
  42. sfr P0M0 = 0x94;
  43. sfr P2M1 = 0x95;
  44. sfr P2M0 = 0x96;
  45. sfr P3M1 = 0xB1;
  46. sfr P3M0 = 0xB2;
  47. sfr P4M1 = 0xB3;
  48. sfr P4M0 = 0xB4;
  49. sfr P5M1 = 0xC9;
  50. sfr P5M0 = 0xCA;
  51. sfr P6M1 = 0xCB;
  52. sfr P6M0 = 0xCC;
  53. sfr P7M1 = 0xE1;
  54. sfr P7M0 = 0xE2;

  55. sbit P00 = P0^0;
  56. sbit P01 = P0^1;
  57. sbit P02 = P0^2;
  58. sbit P03 = P0^3;
  59. sbit P04 = P0^4;
  60. sbit P05 = P0^5;
  61. sbit P06 = P0^6;
  62. sbit P07 = P0^7;
  63. sbit P10 = P1^0;
  64. sbit P11 = P1^1;
  65. sbit P12 = P1^2;
  66. sbit P13 = P1^3;
  67. sbit P14 = P1^4;
  68. sbit P15 = P1^5;
  69. sbit P16 = P1^6;
  70. sbit P17 = P1^7;
  71. sbit P20 = P2^0;
  72. sbit P21 = P2^1;
  73. sbit P22 = P2^2;
  74. sbit P23 = P2^3;
  75. sbit P24 = P2^4;
  76. sbit P25 = P2^5;
  77. sbit P26 = P2^6;
  78. sbit P27 = P2^7;
  79. sbit P30 = P3^0;
  80. sbit P31 = P3^1;
  81. sbit P32 = P3^2;
  82. sbit P33 = P3^3;
  83. sbit P34 = P3^4;
  84. sbit P35 = P3^5;
  85. sbit P36 = P3^6;
  86. sbit P37 = P3^7;
  87. sbit P40 = P4^0;
  88. sbit P41 = P4^1;
  89. sbit P42 = P4^2;
  90. sbit P43 = P4^3;
  91. sbit P44 = P4^4;
  92. sbit P45 = P4^5;
  93. sbit P46 = P4^6;
  94. sbit P47 = P4^7;
  95. sbit P50 = P5^0;
  96. sbit P51 = P5^1;
  97. sbit P52 = P5^2;
  98. sbit P53 = P5^3;
  99. sbit P54 = P5^4;
  100. sbit P55 = P5^5;
  101. sbit P56 = P5^6;
  102. sbit P57 = P5^7;



  103. #define Timer0_Reload   (65536UL -(MAIN_Fosc / 1000))       //Timer 0 中断频率, 1000次/秒

  104. #define DIS_DOT     0x20
  105. #define DIS_BLACK   0x10
  106. #define DIS_        0x11




  107. u8 code t_display[]={                       //标准字库
  108. //   0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
  109.     0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
  110. //black  -     H    J    K    L    N    o   P    U     t    G    Q    r   M    y
  111.     0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
  112.     0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46};    //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1

  113. u8 code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};      //位码


  114. sbit    P_HC595_SER   = P4^0;   //pin 14    SER     data input
  115. sbit    P_HC595_RCLK  = P5^4;   //pin 12    RCLk    store (latch) clock
  116. sbit    P_HC595_SRCLK = P4^3;   //pin 11    SRCLK   Shift data clock


  117. u8  LED8[8];        //显示缓冲
  118. u8  display_index;  //显示位索引
  119. bit B_1ms;          //1ms标志

  120. u8  ADC_KeyState,ADC_KeyState1,ADC_KeyState2,ADC_KeyState3; //键状态
  121. u8  ADC_KeyHoldCnt; //键按下计时
  122. u8  KeyCode;    //给用户使用的键码, 1~16有效
  123. u8  cnt10ms;

  124. u8  hour,minute,second; //RTC变量
  125. u16 msecond;

  126. void    CalculateAdcKey(u16 adc);
  127. u16     Get_ADC10bitResult(u8 channel); //channel = 0~7
  128. void    DisplayRTC(void);
  129. void    RTC(void);




  130. /**********************************************/
  131. void main(void)
  132. {
  133.     u8  i;
  134.     u16 j;

  135.     P0M1 = 0;   P0M0 = 0;   //设置为准双向口
  136.     P1M1 = 0;   P1M0 = 0;   //设置为准双向口
  137.     P2M1 = 0;   P2M0 = 0;   //设置为准双向口
  138.     P3M1 = 0;   P3M0 = 0;   //设置为准双向口
  139.     P4M1 = 0;   P4M0 = 0;   //设置为准双向口
  140.     P5M1 = 0;   P5M0 = 0;   //设置为准双向口
  141.     P6M1 = 0;   P6M0 = 0;   //设置为准双向口
  142.     P7M1 = 0;   P7M0 = 0;   //设置为准双向口
  143.    
  144.     display_index = 0;
  145.     P1ASF = 0x10;       //P1.4做ADC
  146.     ADC_CONTR = 0xE0;   //90T, ADC power on
  147.    
  148.     AUXR = 0x80;    //Timer0 set as 1T, 16 bits timer auto-reload,
  149.     TH0 = (u8)(Timer0_Reload / 256);
  150.     TL0 = (u8)(Timer0_Reload % 256);
  151.     ET0 = 1;    //Timer0 interrupt enable
  152.     TR0 = 1;    //Tiner0 run
  153.     EA = 1;     //打开总中断
  154.    
  155.     for(i=0; i<8; i++)  LED8[i] = 0x10; //上电消隐

  156.     hour   = 12;    //初始化时间值
  157.     minute = 0;
  158.     second = 0;
  159.     DisplayRTC();

  160.     ADC_KeyState  = 0;
  161.     ADC_KeyState1 = 0;
  162.     ADC_KeyState2 = 0;
  163.     ADC_KeyState3 = 0;  //键状态
  164.     ADC_KeyHoldCnt = 0; //键按下计时
  165.     KeyCode = 0;    //给用户使用的键码, 1~16有效
  166.     cnt10ms = 0;

  167.     while(1)
  168.     {
  169.         if(B_1ms)   //1ms到
  170.         {
  171.             B_1ms = 0;
  172.             if(++msecond >= 1000)   //1秒到
  173.             {
  174.                 msecond = 0;
  175.                 RTC();
  176.                 DisplayRTC();
  177.             }
  178.             if(msecond == 500)  DisplayRTC();   //小时后的小数点做秒闪

  179.             if(++cnt10ms >= 10) //10ms读一次ADC
  180.             {
  181.                 cnt10ms = 0;
  182.                 j = Get_ADC10bitResult(4);  //参数0~7,查询方式做一次ADC, 返回值就是结果, == 1024 为错误
  183.                 if(j < 1024)    CalculateAdcKey(j); //计算按键
  184.                         
  185.             }

  186.             if(KeyCode > 0)     //有键按下
  187.             {
  188.                 LED8[6] = KeyCode / 10; //显示键码
  189.                 LED8[7] = KeyCode % 10; //显示键码

  190.                 if(KeyCode == 1)    //hour +1
  191.                 {
  192.                     if(++hour >= 24)    hour = 0;
  193.                     DisplayRTC();
  194.                 }
  195.                 if(KeyCode == 2)    //hour -1
  196.                 {
  197.                     if(--hour >= 24)    hour = 23;
  198.                     DisplayRTC();
  199.                 }
  200.                 if(KeyCode == 3)    //minute +1
  201.                 {
  202.                     second = 0;
  203.                     if(++minute >= 60)  minute = 0;
  204.                     DisplayRTC();
  205.                 }
  206.                 if(KeyCode == 4)    //minute -1
  207.                 {
  208.                     second = 0;
  209.                     if(--minute >= 60)  minute = 59;
  210.                     DisplayRTC();
  211.                 }

  212.                 KeyCode = 0;
  213.             }

  214.         }
  215.     }
  216. }
  217. /**********************************************/



  218. /********************** 显示时钟函数 ************************/
  219. void    DisplayRTC(void)
  220. {
  221.     if(hour >= 10)  LED8[0] = hour / 10;
  222.     else            LED8[0] = DIS_BLACK;
  223.     LED8[1] = hour % 10;
  224.     LED8[2] = minute / 10;
  225.     LED8[3] = minute % 10;
  226.     if(msecond >= 500)      LED8[1] |= DIS_DOT; //小时后的小数点做秒闪
  227. }

  228. /********************** RTC演示函数 ************************/
  229. void    RTC(void)
  230. {
  231.     if(++second >= 60)
  232.     {
  233.         second = 0;
  234.         if(++minute >= 60)
  235.         {
  236.             minute = 0;
  237.             if(++hour >= 24)    hour = 0;
  238.         }
  239.     }
  240. }


  241. //========================================================================
  242. // 函数: u16    Get_ADC10bitResult(u8 channel)
  243. // 描述: 查询法读一次ADC结果.
  244. // 参数: channel: 选择要转换的ADC.
  245. // 返回: 10位ADC结果.
  246. // 版本: V1.0, 2012-10-22
  247. //========================================================================
  248. u16 Get_ADC10bitResult(u8 channel)  //channel = 0~7
  249. {
  250.     ADC_RES = 0;
  251.     ADC_RESL = 0;

  252.     ADC_CONTR = (ADC_CONTR & 0xe0) | 0x08 | channel;    //start the ADC
  253.     _nop_();
  254.     _nop_();
  255.     _nop_();
  256.     _nop_();

  257.     while((ADC_CONTR & 0x10) == 0)  ;   //wait for ADC finish
  258.     ADC_CONTR &= ~0x10;     //清除ADC结束标志
  259.     return  (((u16)ADC_RES << 2) | (ADC_RESL & 3));
  260. }

  261. /***************** ADC键盘计算键码 *****************************
  262. 电路和软件算法设计: Coody
  263. 本ADC键盘方案在很多实际产品设计中, 验证了其稳定可靠, 即使按键使用导电膜,都很可靠.
  264. 16个键,理论上各个键对应的ADC值为 (1024 / 16) * k = 64 * k, k = 1 ~ 16, 特别的, k=16时,对应的ADC值是1023.
  265. 但是实际会有偏差,则判断时限制这个偏差, ADC_OFFSET为+-偏差, 则ADC值在 (64*k-ADC_OFFSET) 与 (64*k+ADC_OFFSET)之间为键有效.
  266. 间隔一定的时间,就采样一次ADC,比如10ms.
  267. 为了避免偶然的ADC值误判, 或者避免ADC在上升或下降时误判, 使用连续3次ADC值均在偏差范围内时, ADC值才认为有效.
  268. 以上算法, 能保证读键非常可靠.
  269. **********************************************/
  270. #define ADC_OFFSET  16
  271. void    CalculateAdcKey(u16 adc)
  272. {
  273.     u8  i;
  274.     u16 j;
  275.    
  276.     if(adc < (64-ADC_OFFSET))
  277.     {
  278.         ADC_KeyState = 0;   //键状态归0
  279.         ADC_KeyHoldCnt = 0;
  280.     }
  281.     j = 64;
  282.     for(i=1; i<=16; i++)
  283.     {
  284.         if((adc >= (j - ADC_OFFSET)) && (adc <= (j + ADC_OFFSET)))  break;  //判断是否在偏差范围内
  285.         j += 64;
  286.     }
  287.     ADC_KeyState3 = ADC_KeyState2;
  288.     ADC_KeyState2 = ADC_KeyState1;
  289.     if(i > 16)  ADC_KeyState1 = 0;  //键无效
  290.     else                        //键有效
  291.     {
  292.         ADC_KeyState1 = i;
  293.         if((ADC_KeyState3 == ADC_KeyState2) && (ADC_KeyState2 == ADC_KeyState1) &&
  294.            (ADC_KeyState3 > 0) && (ADC_KeyState2 > 0) && (ADC_KeyState1 > 0))
  295.         {
  296.             if(ADC_KeyState == 0)   //第一次检测到
  297.             {
  298.                 KeyCode  = i;   //保存键码
  299.                 ADC_KeyState = i;   //保存键状态
  300.                 ADC_KeyHoldCnt = 0;
  301.             }
  302.             if(ADC_KeyState == i)   //连续检测到同一键按着
  303.             {
  304.                 if(++ADC_KeyHoldCnt >= 100) //按下1秒后,以10次每秒的速度Repeat Key
  305.                 {
  306. ……………………

  307. …………限于本文篇幅 余下代码请从51黑下载附件…………
复制代码

所有资料51hei提供下载:
ADC键盘.zip (3.92 KB, 下载次数: 216)
回复

使用道具 举报

ID:140183 发表于 2017-6-29 05:40 | 显示全部楼层
很好的资料.多谢分享!
回复

使用道具 举报

ID:274730 发表于 2018-1-11 11:33 | 显示全部楼层
谢谢分享谢谢分享,,,,,,,,,,,,,
回复

使用道具 举报

ID:117047 发表于 2018-3-12 12:18 | 显示全部楼层
感谢分享!!!
回复

使用道具 举报

ID:66287 发表于 2018-3-14 10:14 | 显示全部楼层
程序设计合理,编制简洁,谢谢分享!
回复

使用道具 举报

ID:339788 发表于 2018-6-25 00:21 | 显示全部楼层
谢谢楼主
回复

使用道具 举报

ID:374681 发表于 2018-7-19 11:19 | 显示全部楼层

谢谢楼主
回复

使用道具 举报

ID:446645 发表于 2019-2-27 15:55 | 显示全部楼层
很好的资料.多谢分享!
回复

使用道具 举报

ID:151689 发表于 2019-5-12 13:58 | 显示全部楼层
老大可以下载吗?
回复

使用道具 举报

ID:525189 发表于 2019-8-23 09:56 | 显示全部楼层
不错的资料,谢谢分享!
回复

使用道具 举报

ID:602985 发表于 2019-8-23 10:26 | 显示全部楼层
谢谢分享!
回复

使用道具 举报

ID:151348 发表于 2020-4-2 10:24 | 显示全部楼层
可用,已经成功移植
回复

使用道具 举报

ID:292359 发表于 2020-6-10 17:08 | 显示全部楼层
感谢分享,正在搞ADC多键去switch不同的功能区,代替原来的独立键盘,还没搞定T0去扫
回复

使用道具 举报

ID:775405 发表于 2020-6-10 19:35 | 显示全部楼层
这资料不错啊,谢谢分享
回复

使用道具 举报

ID:716797 发表于 2020-6-16 12:40 | 显示全部楼层
谢谢楼主无私分享、
回复

使用道具 举报

ID:774633 发表于 2020-6-17 08:38 | 显示全部楼层
利用一个ADC口,就实现多按键扫描,唯独不能识别同时按。不过很好了。谢谢分享
回复

使用道具 举报

ID:65956 发表于 2020-6-17 08:50 | 显示全部楼层
感谢分享思路,但我个人建议大家在作时,那些电阻可配大点,这样每个按键值之间的差别会大点,方便程序对按键值的判断范围宽点,减少误判
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表