找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 11846|回复: 32
打印 上一主题 下一主题
收起左侧

小玩意,3个普通IO识别22个按键试验。有实物和程序

  [复制链接]
跳转到指定楼层
楼主
在51hei论坛吸取各位前辈的经验,将之前二极管用量多的问题优化一下,目前不用二极管能接6键,2只二极管能接12键,6只二极管能接18键,9只二极管能接21键,第22键要单独占用3只二极管最不化算。

实验用89S51作试验,电路接线就是P1.2,P1.3,P1.4接键盘,P1.0接显示器。







  1. /*==================================================================*

  2. * 3个IO接识别22键测试程序 *

  3. * ------------------------------------------------ *

  4. * MCU: AT89C2051 *

  5. * OSC: 12M cysytel *

  6. * 程序设计:Cowboy *

  7. * 程序版本:V1.0 *

  8. *==================================================================*/



  9. #include<reg52.h>



  10. //================== IO口线连接 ==================

  11. sbit Bus = P1^0;

  12. sbit IO_a = P1^4;

  13. sbit IO_b = P1^3;

  14. sbit IO_c = P1^2;



  15. //================== 变量声明 ====================

  16. unsigned char Disp_buf[3];

  17. unsigned char Dig;

  18. unsigned char Key_count;

  19. unsigned char bdata Key_state;

  20. sbit KB0 = Key_state^0;

  21. sbit KB1 = Key_state^1;

  22. sbit KB2 = Key_state^2;

  23. sbit KB3 = Key_state^3;

  24. sbit KB4 = Key_state^4;

  25. sbit KB5 = Key_state^5;



  26. //================== 表格数据 ====================

  27. code unsigned char LED_font[24]=

  28. {

  29. 0x84,0x9f,0xa2,0x8a,0x99,0xc8,0xc0,0x9e,0x80, //012345678

  30. 0x88,0x90,0xc1,0xe4,0x83,0xe0,0xf0,0xff,0xfb, //9abcdef -

  31. };



  32. code unsigned char Key_tab[64]= //键码映射表

  33. {// 0 1 2 3 4 5 6 7 8 9

  34. 22, 0, 2, 0, 0, 0, 0, 0, 4, 0, //0

  35. 0, 0, 0, 0, 0,18, 0, 0, 0, 0, //1X

  36. 0, 0, 0, 0, 0, 0, 3,14, 0, 0, //2X

  37. 20,10, 6, 0, 0, 0, 0, 0, 1,19, //3X

  38. 0, 5, 0, 0, 0,15, 0,11, 0, 0, //4X

  39. 0,17, 0, 0,13, 8, 0,21, 0, 9, //5X

  40. 16,12, 7, 0 //6X

  41. };



  42. //=============== 检测按键 =================

  43. void Key_scan()

  44. {

  45. unsigned char i;

  46. Key_count --; //扫描次序

  47. Key_count &= 3;

  48. switch (Key_count) //按次序处理

  49. {

  50. case 2: //第一轮扫描

  51. KB0 = IO_b;

  52. KB1 = IO_c;

  53. IO_a = 1;

  54. IO_b = 0;

  55. break;



  56. case 1: //每二轮扫描

  57. KB2 = IO_c;

  58. KB3 = IO_a;

  59. IO_b = 1;

  60. IO_c = 0;

  61. break;



  62. case 0: //每三轮扫描

  63. KB4 = IO_a;

  64. KB5 = IO_b;

  65. IO_c = 1;

  66. break;



  67. default: //每四轮扫描

  68. if (!IO_a) KB0 = 0;

  69. if (!IO_b) KB2 = 0;

  70. if (!IO_c) KB4 = 0;

  71. IO_a = 0;



  72. //======更新显示缓冲区=======

  73. i = Key_tab[Key_state];

  74. if (i == 0)

  75. {

  76. Disp_buf[2] = 0x11; //显示三横

  77. Disp_buf[1] = 0x11;

  78. Disp_buf[0] = 0x11;

  79. }

  80. else

  81. {

  82. Disp_buf[2] = 0x0c; //字符"C"

  83. Disp_buf[1] = i / 10; //键码十位

  84. Disp_buf[0] = B;于 //键码个位

  85. }

  86. Key_state = 0;

  87. }

  88. }





  89. /*===================================================================

  90. ONE WIRE 显示总线驱动程序

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



  92. //=============== 发送一位 =================

  93. void Send_bit(bit Dat)

  94. {

  95. unsigned char i = 3;

  96. if (!Dat) Bus = 0;

  97. else

  98. {

  99. Bus = 0;

  100. Bus = 1;

  101. }

  102. while(--i); //延时8us

  103. Bus = 1;

  104. }



  105. //=============== 总线驱动 =================

  106. void Bus_drive()

  107. {

  108. unsigned char i = 0;

  109. unsigned char Sdat;

  110. Send_bit(1); //Bit6消隐

  111. do Bus = 1; while(--i); //延时768us

  112. do Bus = 0; while(--i); //延时768us

  113. Bus = 1;

  114. Sdat = LED_font[Disp_buf[Dig++]]; //获取显示数据

  115. Send_bit(Sdat & 0x01); //发送位0

  116. Send_bit(Sdat & 0x02); //发送位1

  117. Send_bit(Sdat & 0x04); //发送位2

  118. Send_bit(Sdat & 0x08); //发送位3

  119. Send_bit(Sdat & 0x10); //发送位4

  120. Send_bit(Sdat & 0x20); //发送位5

  121. Send_bit(Dig & 0x01); //发送位选1

  122. Send_bit(Dig & 0x02); //发送位选2

  123. while(--i); //延时512us

  124. Send_bit(Sdat & 0x40); //发送位6

  125. for (i = 7;i> 0;i--) Send_bit(1); //位6移至Dout

  126. if (Dig == 3) Dig = 0;

  127. }



  128. /*===================================================================

  129. 延时 5ms 程序

  130. ===================================================================*/

  131. void Delay_5ms()

  132. {

  133. while(!TF1);

  134. TF1 = 0;

  135. TH1 = (- 5000) / 256;

  136. TL1 = (- 5000) % 256;

  137. }



  138. /*===================================================================

  139. 主程序

  140. ===================================================================*/

  141. void main()

  142. {

  143. TMOD = 0x10; //定时器1,16位模式

  144. TCON = 0xc0; //TR1=1;TF1=1;

  145. while(1) //主循环

  146. {

  147. Bus_drive(); //显示总线驱动

  148. Key_scan(); //检测按键

  149. Delay_5ms(); //延时5MS

  150. }

  151. }
复制代码
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏18 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:51654 发表于 2013-7-9 23:15 | 只看该作者
下次焊个四面体的4IO玩玩看

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

二极管数量

6条楞,每楞两个,12个

中间星型,4个

共16个
回复

使用道具 举报

板凳
ID:51654 发表于 2013-7-9 23:15 | 只看该作者

作为试验目的,没有接按键,只焊了个键盘框架,用镊子短路相应的节点来当按键。图中接二极管阵列的三根线是3个IO,单独的一根是地线。MCU发送串行数据给HC595驱动数码管作键码显示。
回复

使用道具 举报

地板
ID:51654 发表于 2013-7-9 23:21 | 只看该作者
对于这种方式的按键识别方法,很多朋友担心编程会很复杂,其实仔细分析后也很简单.比如上面例子,其本的思路是依次把三个IO拉低,然后记录另外两个IO的状态,最后三个IO都不下拉,再记录一次,就可得出的结果.对于按下不同的按键,就有不同的结果.如果只扫18键,那么最后一次扫描可以省掉,即扫描三次即可.实际应用时5MS的扫描间隔可以用定时中断来实现,这样就只占用很少的MCU时间.
回复

使用道具 举报

5#
ID:50574 发表于 2013-7-10 00:21 | 只看该作者

楼主,两点请求:
1、能否给我单总线数码管显示那块的电路图
2、我移植到AVR下,按键值不变,错在哪里呢?
   //=============== 检测按键 =================
void Key_scan()
{   
    unsigned char i;
    Key_count --;                        //扫描次序
    Key_count &= 3;
    switch (Key_count)                //按次序处理
    {
        case 2:                                //第一轮扫描
        KB0 = PINA&0X02;  
        KB1 = PINA&0X04;  
        PORTA |= (1<<0);
        PORTA &= ~(1<<1);
        break;
     
        case 1:                                //每二轮扫描
        KB2 = PINA&0X04;
        KB3 = PINA&0X01;
        PORTA |= (1<<1);  
        PORTA &= ~(1<<2);
        break;
     
        case 0:                                //每三轮扫描
        KB4 = PINA&0X01;
        KB5 = PINA&0X02;
        PINA &= ~(1<<0);
        PORTA |= (1<<0);  
        break;
     
        default:                        //每四轮扫描
        if (!(PINA&0X01)) KB0 = 0;
        if (!(PINA&0X02)) KB2 = 0;
        if (!(PINA&0X04)) KB4 = 0;
         

        //======更新显示缓冲区=======
        Key_state = KB0<<5 + KB1<<4 + KB2<<3 + KB3<<2 + KB4<<1 + KB5;
        i = Key_tab[Key_state];
        if (i == 0)
        {
            Disp_buf[2] = 0x11;                //显示三横
            Disp_buf[1] = 0x11;
            Disp_buf[0] = 0x11;
        }
        else
        {
            Disp_buf[2] = 0x0c;     //字符"C"
            Disp_buf[1] = i / 10;   //键码十位
            Disp_buf[0] = i % 10;      //键码个位
        }
        Key_state = 0;
    }
}

还有,你那个笔段代码是BCD码吗?
回复

使用道具 举报

6#
ID:51654 发表于 2013-7-10 00:24 | 只看该作者
h333 发表于 2013-7-10 00:21
楼主,两点请求:
1、能否给我单总线数码管显示那块的电路图
2、我移植到AVR下,按键值不变,错在哪里 ...

首先,AVR的IO不是准双向口,在作为输入时,需要开上拉电阻,当切换到输出0时,需要改变PORTA和DDRA,你程序里没有更改DDRA,因而不能动作。
其次,没看到你对KB0~KB5的变量类型声明,51中声明为bit,AVR中的位操作比较麻烦,建议读入时直接更新Key_state,取消KB0~KB5。
至于显示的问题,我打算迟些开个新帖,因为和这主题关系不大。键码已从 i= Key_tab[Key_state] 获得,需要送显示可自行处理。
回复

使用道具 举报

7#
ID:50574 发表于 2013-7-10 00:42 | 只看该作者

unsigned char KB0,KB1,KB2,KB3,KB4,KB5;


//=============== 检测按键 =================
void Key_scan()
{   
    unsigned char i;
    Key_count --;                        //扫描次序
    Key_count &= 3;
    switch (Key_count)                //按次序处理
    {
        case 2:                                //第一轮扫描
        KB0 = PINA&0X02;  
        KB1 = PINA&0X04;  
        PORTA |= (1<<0);
                DDRA |= (1<<0);
        PORTA &= ~(1<<1);
                DDRA &= ~(1<<1);
        break;
     
        case 1:                                //每二轮扫描
        KB2 = PINA&0X04;
        KB3 = PINA&0X01;
        PORTA |= (1<<1);
                DDRA |= (1<<1);
        PORTA &= ~(1<<2);
                DDRA &= ~(1<<2);
        break;
     
        case 0:                                //每三轮扫描
        KB4 = PINA&0X01;
        KB5 = PINA&0X02;
        PORTA &= ~(1<<0);
                DDRA &= ~(1<<0);
        PORTA |= (1<<2);
                DDRA |= (1<<2);
        break;
     
        default:                        //每四轮扫描
        if (!(PINA&0X01)) KB0 = 0;
        if (!(PINA&0X02)) KB2 = 0;
        if (!(PINA&0X04)) KB4 = 0;
         

        //======更新显示缓冲区=======
        Key_state = (KB0<<5 + KB1<<4 + KB2<<3 + KB3<<2 + KB4<<1 + KB5);
        i = Key_tab[Key_state];
        if (i == 0)
        {
            Disp_buf[2] = 0x11;                //显示三横
            Disp_buf[1] = 0x11;
            Disp_buf[0] = 0x11;
        }
        else
        {
            Disp_buf[2] = 0x0c;     //字符"C"
            Disp_buf[1] = i / 10;   //键码十位
            Disp_buf[0] = i % 10;      //键码个位
        }
        Key_state = 0;
    }
}     

void main()
{
   // TMOD = 0x10;            //定时器1,16位模式
    //TCON = 0xc0;            //TR1=1;TF1=1;
        init_devices();
    while(1)                //主循环
    {
        //Bus_drive();        //显示总线驱动
        PORTB = LED_font[Disp_buf[2]];
        PORTC = LED_font[Disp_buf[1]];
        PORTD = LED_font[Disp_buf[0]];
        Key_scan();         //检测按键
        delay_ms(5);        //延时5MS     
    }
}

怎么按,都是显示C22
回复

使用道具 举报

8#
ID:51654 发表于 2013-7-10 00:43 | 只看该作者
楼上还是没搞明白,举例说明一下

        case 2:                                //第一轮扫描
        if (PINA & 0x02) Key_state |= 0x01;    //读入PINA.1,记录于Key_state.0
        if (PINA & 0x04) Key_state |= 0x02;    //读入PINA.2,记录于Key_state.1
        DDRA  &= ~(1<<0);  //A口位0先改为输入
        PORTA |=  (1<<0);  //A口位0上拉有效
        PORTA &=  (1<<1);  //A口位1设定为0
        DDRA  |=  (1<<1);  //A口位1改为输出0
        break;             //以上端口变化顺序不要更改,否则可以出现短路
回复

使用道具 举报

9#
ID:50574 发表于 2013-7-10 00:43 | 只看该作者
为什么 不是:
        case 2:                //第一轮扫描  
        DDRA  &= ~(1<<0);  //A口位0先改为输入
        PORTA |=  (1<<0);  //A口位0上拉有效
        PORTA &=  ~(1<<1);  //A口位1设定为0
        DDRA  |=  (1<<1);  //A口位1改为输出0                               //第一轮扫描  
        if (PINA & 0x02) Key_state |= 0x01;    //读入PINA.1,记录于Key_state.0
        if (PINA & 0x04) Key_state |= 0x02;    //读入PINA.2,记录于Key_state.1        
        break;  
       先改变IO口状态,再做键值判断?
       我那个 Key_state = (KB5<<5 + KB4<<4 + KB3<<3 + KB2<<2 + KB1<<1 + KB0);效果也应该是一样呀,只不过繁琐了一些
回复

使用道具 举报

10#
ID:50574 发表于 2013-7-10 00:43 | 只看该作者

4个IO口,能扫多少键呢? ^_^
回复

使用道具 举报

11#
ID:51654 发表于 2013-7-10 01:06 | 只看该作者
先改变IO状态,立刻回读IO,有时会出错,IO外部受分布参数影响会有延时,特别是AVR上拉电阻较大,如果立该回读可能出错。
这里看似先读再改变IO输出,其实读的时候,上一轮扫描中已经把IO改变,到现在已过了5ms,IO状态足够稳定了,这时读入就可靠。
读完了,再改变IO状态,其实是为下一次做好准备。

Key_state用组合生成,效果是一样,只是处理繁琐且占内存也多。你先前的程序IO切换过程不正确才可能导致没效果。
回复

使用道具 举报

12#
ID:51654 发表于 2013-7-10 01:07 | 只看该作者
h333 发表于 2013-7-10 00:43
4个IO口,能扫多少键呢? ^_^

51的四个IO能扫65个键(或可以再多6个,但这6个可靠性不太好).
AVR的,2个IO就可以扫15键,4个IO可能超100了,你可以搜索一下h2feo4的贴子。
回复

使用道具 举报

13#
ID:50574 发表于 2013-7-10 01:11 | 只看该作者
四个IO扫3次,那Key_state就不止8位了,咋办呢?
另外,default语句是不是漏了break?
楼主,能否把QQ号给我?我直接把仿真文件给你,就能直观地看到错误了
回复

使用道具 举报

14#
ID:51654 发表于 2013-7-10 01:14 | 只看该作者
你没细看程序,Key_state只用了6位,defalt那次的位用压缩方或与前面6位中的3位相与。如果真的用到9位,键码表占512字节就不好了。
至于dafalt后的break,在这里可用可不用,因为defalt后已经是switch的未端。
你可以把文件传到这里,或发至cowboy3@163.com
回复

使用道具 举报

15#
ID:50574 发表于 2013-7-10 01:28 | 只看该作者

const unsigned char Key_tab[]=     //键码映射表
{//  0  1  2  3  4  5  6  7  8  9   
        22, 0, 2, 0, 0, 0, 0, 0, 4, 0, //0
         0, 0, 0, 0, 0,18, 0, 0, 0, 0, //1X
         0, 0, 0, 0, 0, 0, 3,14, 0, 0, //2X
        20,10, 6, 0, 0, 0, 0, 0, 1,19, //3X
         0, 5, 0, 0, 0,15, 0,11, 0, 0, //4X
         0,17, 0, 0,13, 8, 0,21, 0, 9, //5X
        16,12, 7, 0                    //6X
};

//=============== 检测按键 =================
void Key_scan()
{   
    unsigned char i;
    Key_count --;                        //扫描次序
    Key_count &= 3;
    switch (Key_count)                //按次序处理
    {
        case 2:                //第一轮扫描                                   
        if (PINA & 0x02) Key_state |= 0x01;    //读入PINA.1,记录于Key_state.0  
        if (PINA & 0x04) Key_state |= 0x02;    //读入PINA.2,记录于Key_state.1
                DDRA  &= ~(1<<0);  //A口位0先改为输入  
        PORTA |=  (1<<0);  //A口位0上拉有效
                DDRA  &= ~(1<<2);  //A口位2先改为输入  
        PORTA |=  (1<<2);  //A口位2上拉有效  
        PORTA &=  ~(1<<1);  //A口位1设定为0  
        DDRA  |=  (1<<1);  //A口位1改为输出0         
        break;
     
        case 1:                                //每二轮扫描
        if (PINA & 0x01) Key_state |= 0x04;    //读入PINA.0,记录于Key_state.2  
        if (PINA & 0x04) Key_state |= 0x08;    //读入PINA.2,记录于Key_state.3
        PORTA |= (1<<1);
                DDRA &= ~(1<<1);
        PORTA &= ~(1<<2);
                DDRA  |= (1<<2);
                DDRA  &= ~(1<<0);   
        PORTA |=  (1<<0);  
        break;
     
        case 0:                                //每三轮扫描
        if (PINA & 0x02) Key_state |= 0x10;    //读入PINA.1,记录于Key_state.4  
        if (PINA & 0x01) Key_state |= 0x20;    //读入PINA.0,记录于Key_state.5
        PORTA |= (1<<2);
                DDRA &= ~(1<<2);
        PORTA &= ~(1<<0);
                DDRA  |= (1<<0);
                DDRA  &= ~(1<<1);   
        PORTA |=  (1<<1);  
        break;
     
        default:                        //每四轮扫描
        if (!(PINA&0X01)) Key_state &= ~(1<<0);
        if (!(PINA&0X02)) Key_state &= ~(1<<1);
        if (!(PINA&0X04)) Key_state &= ~(1<<2);
               
        break;
     }

        //======更新显示缓冲区=======
        i = Key_tab[Key_state];                 
        if (i == 0)
        {
            Disp_buf[2] = 0x11;                //显示三横
            Disp_buf[1] = 0x11;
            Disp_buf[0] = 0x11;
        }
        else
        {
            Disp_buf[2] = 0x0c;     //字符"C"
            Disp_buf[1] = i / 10;   //键码十位
            Disp_buf[0] = i % 10;      //键码个位                        
        }
                //Key_state = 0;                        
}
Key_state什么时候清零才合适?要是4IO口的话,键码又如何扩展呢?
回复

使用道具 举报

16#
ID:51654 发表于 2013-7-10 01:31 | 只看该作者
今天太晚了,下次再弄吧
回复

使用道具 举报

17#
ID:51777 发表于 2013-7-13 20:32 | 只看该作者
由于还是新人 只有跟在高手后面慢慢做了!
回复

使用道具 举报

18#
ID:52177 发表于 2013-7-23 22:24 | 只看该作者
有套件卖吗?我要
回复

使用道具 举报

19#
ID:53315 发表于 2013-8-21 02:07 | 只看该作者
楼主,我没看见按键啊?
回复

使用道具 举报

20#
ID:55734 发表于 2013-10-9 02:12 | 只看该作者
看看学习学习。。
回复

使用道具 举报

21#
ID:52286 发表于 2013-10-13 23:52 | 只看该作者
支持支持!!
回复

使用道具 举报

22#
ID:58267 发表于 2013-12-29 20:49 | 只看该作者
支持一下
回复

使用道具 举报

23#
ID:58796 发表于 2014-2-14 00:00 | 只看该作者
学习。。。。。。。。。。。。。
回复

使用道具 举报

24#
ID:59151 发表于 2014-2-18 01:59 | 只看该作者
好东西,揽下试试,谢谢!
回复

使用道具 举报

25#
ID:59341 发表于 2014-3-1 15:55 | 只看该作者
高手呀,收藏一下
回复

使用道具 举报

26#
ID:60246 发表于 2014-4-2 09:50 | 只看该作者
谢谢分享
回复

使用道具 举报

27#
ID:91611 发表于 2015-10-5 11:18 | 只看该作者
这真是大神啊
回复

使用道具 举报

28#
ID:92052 发表于 2015-10-14 07:20 | 只看该作者
高手啊,掌握了精髓,所以可以随心所欲了。
回复

使用道具 举报

29#
ID:89386 发表于 2015-10-14 19:48 | 只看该作者
超级赞! 新手 目前写个 点亮一个LED都带写错的。。。。直接膜拜!
回复

使用道具 举报

30#
ID:297381 发表于 2018-3-26 17:17 来自手机 | 只看该作者
6膜拜大佬
回复

使用道具 举报

31#
ID:368541 发表于 2018-7-10 11:21 | 只看该作者
楼主辛苦  回复的很仔细
回复

使用道具 举报

32#
ID:74252 发表于 2019-6-9 03:28 | 只看该作者
这是深夜逛论坛翻到宝了!!!
回复

使用道具 举报

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

本版积分规则

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

Powered by 单片机教程网

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