[51单片机] 从单片机基础到程序框架(连载)

[复制链接]
362971|721
 楼主| jianhong_wu 发表于 2017-10-29 10:57 | 显示全部楼层
本帖最后由 jianhong_wu 于 2017-10-29 11:49 编辑

第九十三节: 独立按键鼠标式的单击与双击。

【93.1   鼠标式的单击与双击。】




                上图93.1.1  独立按键电路


     
                上图93.1.2  LED电路

              
                上图93.1.3  有源蜂鸣器电路

        鼠标的左键,可以触发单击,也可以触发双击。双击的规则是这样的,两次单击,如果第1次单击与第2次单击的时间比较“短”的时候,则这两次单击就构成双击。编写这个程序的最大亮点是如何控制好第1次单击与第2次单击的时间间隔。程序例程要实现的功能是:(1)单击改变LED灯的显示状态,单击一次LED从原来“灭”的状态变成“亮”的状态,或者从原来“亮”的状态变成“灭”的状态,依次循环切换。(2)双击则蜂鸣器发出“嘀”的一声。代码如下:
  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50     //按键触发后发出的声音长度  
  3. #define KEY_FILTER_TIME  25     //按键滤波的“稳定时间”25ms
  4. #define KEY_INTERVAL_TIME  250  //连续两次单击之间的最大有效时间250ms

  5. void T0_time();
  6. void SystemInitial(void) ;
  7. void Delay(unsigned long u32DelayTime) ;
  8. void PeripheralInitial(void) ;

  9. void BeepOpen(void);   
  10. void BeepClose(void);
  11. void LedOpen(void);   
  12. void LedClose(void);

  13. void VoiceScan(void);
  14. void KeyScan(void);    //按键识别的驱动函数,放在定时中断里
  15. void SingleKeyTask(void);   //单击按键任务函数,放在主函数内
  16. void DoubleKeyTask(void);   //双击按键任务函数,放在主函数内

  17. sbit P3_4=P3^4;       //蜂鸣器
  18. sbit P1_4=P1^4;       //LED

  19. sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。

  20. volatile unsigned char vGu8BeepTimerFlag=0;  
  21. volatile unsigned int vGu16BeepTimerCnt=0;  

  22. unsigned char Gu8LedStatus=0; //记录LED灯的状态,0代表灭,1代表亮
  23. volatile unsigned char vGu8SingleKeySec=0;  //单击按键的触发序号
  24. volatile unsigned char vGu8DoubleKeySec=0;  //双击按键的触发序号

  25. void main()
  26. {
  27. SystemInitial();            
  28. Delay(10000);               
  29. PeripheralInitial();      
  30.     while(1)  
  31. {  
  32.    SingleKeyTask();    //单击按键任务函数
  33.    DoubleKeyTask();    //双击按键任务函数
  34.     }
  35. }

  36. void T0_time() interrupt 1     
  37. {
  38. VoiceScan();  
  39. KeyScan();    //按键识别的驱动函数

  40. TH0=0xfc;   
  41. TL0=0x66;   
  42. }


  43. void SystemInitial(void)
  44. {
  45. TMOD=0x01;  
  46. TH0=0xfc;   
  47. TL0=0x66;   
  48. EA=1;      
  49. ET0=1;      
  50. TR0=1;      
  51. }

  52. void Delay(unsigned long u32DelayTime)
  53. {
  54.     for(;u32DelayTime>0;u32DelayTime--);
  55. }

  56. void PeripheralInitial(void)
  57. {
  58. /* 注释一:
  59. * 把LED的初始化放在PeripheralInitial而不是放在SystemInitial,是因为LED显示内容对上电
  60. * 瞬间的要求不高。但是,如果是控制继电器,则应该把继电器的输出初始化放在SystemInitial。
  61. */

  62.     //根据Gu8LedStatus的值来初始化LED当前的显示状态,0代表灭,1代表亮
  63. if(0==Gu8LedStatus)
  64. {
  65.     LedClose();  //关闭LED
  66. }
  67. else
  68. {
  69.     LedOpen();   //点亮LED
  70. }
  71. }

  72. void BeepOpen(void)
  73. {
  74. P3_4=0;  
  75. }

  76. void BeepClose(void)
  77. {
  78. P3_4=1;  
  79. }
  80. void LedOpen(void)
  81. {
  82. P1_4=0;  
  83. }

  84. void LedClose(void)
  85. {
  86. P1_4=1;  
  87. }

  88. void VoiceScan(void)
  89. {

  90.           static unsigned char Su8Lock=0;  

  91. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  92.           {
  93.                   if(0==Su8Lock)
  94.                   {
  95.                    Su8Lock=1;  
  96. BeepOpen();
  97.      }
  98.     else  
  99. {     

  100.                        vGu16BeepTimerCnt--;         

  101.                    if(0==vGu16BeepTimerCnt)
  102.                    {
  103.                            Su8Lock=0;     
  104. BeepClose();  
  105.                    }

  106. }
  107.           }         
  108. }

  109. /* 注释二:
  110. * 双击按键扫描的详细过程:
  111. * 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。
  112. *         如果之前已经有按键触发过1次单击,那么启动时间间隔计数器Su16KeyIntervalCnt1,
  113. *         在KEY_INTERVAL_TIME这个允许的时间差范围内,如果一直没有第2次单击触发,
  114. *         则把累加按键触发的次数Su8KeyTouchCnt1也清零,上一次累计的单击数被清零,
  115. *         就意味着下一次新的双击必须重新开始累加两次单击数。
  116. * 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
  117. *         阀值KEY_FILTER_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使
  118. *         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyTimeCnt1
  119. *         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰,以后凡是用到开关感应器的时候,
  120. *         都可以用类似这样的方法去干扰。
  121. * 第三步:如果按键按下的时间超过了阀值KEY_FILTER_TIME,马上把自锁标志Su8KeyLock1置1,
  122. *         防止按住按键不松手后一直触发。与此同时,累加1次按键次数,如果按键次数累加有2次,
  123. *         则认为触发双击按键,并把编号vGu8DoubleKeySec赋值。
  124. * 第四步:等按键松开后,自锁标志Su8KeyLock1及时清零解锁,为下一次自锁做准备。并且累加间隔时间,
  125. *         防止两次按键的间隔时间太长。如果连续2次单击的间隔时间太长达到了KEY_INTERVAL_TIME
  126. *         的长度,立即清零当前按键次数的计数器,这样意味着上一次的累加单击数无效,下一次双击
  127. *         必须重新累加新的单击数。
  128. */

  129. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  130. {
  131.    static unsigned char Su8KeyLock1;           //1号按键的自锁
  132.    static unsigned int  Su16KeyCnt1;           //1号按键的计时器
  133.    static unsigned char Su8KeyTouchCnt1;       //1号按键的次数记录
  134.    static unsigned int  Su16KeyIntervalCnt1;   //1号按键的间隔时间计数器

  135.    //1号按键
  136.    if(0!=KEY_INPUT1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  137.    {
  138.        Su8KeyLock1=0; //按键解锁
  139.        Su16KeyCnt1=0; //按键去抖动延时计数器清零,此行非常巧妙。      
  140.        if(Su8KeyTouchCnt1>=1) //之前已经有按键触发过一次,再来一次就构成双击
  141.        {
  142.            Su16KeyIntervalCnt1++; //按键间隔的时间计数器累加
  143.            if(Su16KeyIntervalCnt1>=KEY_INTERVAL_TIME) //达到最大允许的间隔时间
  144.            {
  145.                Su16KeyIntervalCnt1=0; //时间计数器清零
  146.                Su8KeyTouchCnt1=0;     //清零按键的按下的次数
  147.            }
  148.        }
  149.    }
  150.    else if(0==Su8KeyLock1)//有按键按下,且是第一次被按下。此行如有疑问,请看第92节的讲解。
  151.    {
  152.       Su16KeyCnt1++; //累加定时中断次数
  153.       if(Su16KeyCnt1>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME,长度是25ms。
  154.       {
  155.          Su8KeyLock1=1;  //按键的自锁,避免一直触发
  156.          Su16KeyIntervalCnt1=0;   //按键有效间隔的时间计数器清零
  157.          Su8KeyTouchCnt1++;       //记录当前单击的次数
  158.          if(1==Su8KeyTouchCnt1)   //只按了1次
  159.          {
  160.               vGu8SingleKeySec=1;     //单击任务
  161.          }
  162.          else if(Su8KeyTouchCnt1>=2)  //连续按了两次以上
  163.          {
  164.               Su8KeyTouchCnt1=0;    //统计按键次数清零
  165.               vGu8SingleKeySec=1;   //单击任务
  166. vGu8DoubleKeySec=1;   //双击任务
  167.          }
  168.       }
  169.    }

  170. }

  171. void SingleKeyTask(void)    //单击按键任务函数,放在主函数内
  172. {
  173. if(0==vGu8SingleKeySec)
  174. {
  175. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  176. }

  177. switch(vGu8SingleKeySec) //根据不同的按键触发序号执行对应的代码
  178. {
  179.    case 1:     //单击任务
  180.         //通过Gu8LedStatus的状态切换,来反复切换LED的“灭”与“亮”的状态
  181.         if(0==Gu8LedStatus)
  182.         {
  183.             Gu8LedStatus=1; //标识并且更改当前LED灯的状态。0就变成1。
  184.             LedOpen();   //点亮LED
  185. }
  186.         else
  187.         {
  188.             Gu8LedStatus=0; //标识并且更改当前LED灯的状态。1就变成0。
  189.             LedClose();  //关闭LED
  190. }

  191. vGu8SingleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  192. break;
  193. }

  194. }

  195. void DoubleKeyTask(void)    //双击按键任务函数,放在主函数内
  196. {
  197. if(0==vGu8DoubleKeySec)
  198. {
  199. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  200. }

  201. switch(vGu8DoubleKeySec) //根据不同的按键触发序号执行对应的代码
  202. {
  203.    case 1:     //双击任务

  204.         vGu8BeepTimerFlag=0;  
  205. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发双击后,发出“嘀”一声
  206.         vGu8BeepTimerFlag=1;  
  207. vGu8DoubleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  208. break;
  209. }

  210. }



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
wangxueq 发表于 2017-10-31 22:24 | 显示全部楼层
鼓励 加油!
 楼主| jianhong_wu 发表于 2017-11-5 11:24 | 显示全部楼层
第九十四节: 两个独立按键构成的组合按键。

【94.1   组合按键。】




                上图94.1.1  独立按键电路


  
                上图94.1.2  LED电路

              
                上图94.1.3  有源蜂鸣器电路

        组合按键的触发,是指两个按键同时按下时的“非单击”触发。一次组合按键的产生,必然包含了三类按键的触发。比如,K1与K2两个独立按键,当它们产生一次组合按键的操作时,就包含了三类触发:K1单击触发,K2单击触发,K1与K2的组合触发。这三类触发可以看作是底层的按键驱动程序,在按键应用层的任务函数SingleKeyTask和CombinationKeyTask中,可以根据项目的实际需要进行响应。本节程序例程要实现的功能是:(1)K1单击让LED变成“亮”的状态。(2)K2单击让LED变成“灭”的状态。(3)K1与K2的组合按键触发让蜂鸣器发出“嘀”的一声。代码如下:

  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50     //组合按键触发后发出的声音长度  
  3. #define KEY_FILTER_TIME  25     //按键滤波的“稳定时间”25ms

  4. void T0_time();
  5. void SystemInitial(void) ;
  6. void Delay(unsigned long u32DelayTime) ;
  7. void PeripheralInitial(void) ;

  8. void BeepOpen(void);   
  9. void BeepClose(void);
  10. void LedOpen(void);   
  11. void LedClose(void);

  12. void VoiceScan(void);
  13. void KeyScan(void);    //按键识别的驱动函数,放在定时中断里
  14. void SingleKeyTask(void);   //单击按键任务函数,放在主函数内
  15. void CombinationKeyTask(void);   //组合按键任务函数,放在主函数内

  16. sbit P3_4=P3^4;       //蜂鸣器
  17. sbit P1_4=P1^4;       //LED

  18. sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
  19. sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。

  20. volatile unsigned char vGu8BeepTimerFlag=0;  
  21. volatile unsigned int vGu16BeepTimerCnt=0;  

  22. volatile unsigned char vGu8SingleKeySec=0;  //单击按键的触发序号
  23. volatile unsigned char vGu8CombinationKeySec=0;  //组合按键的触发序号

  24. void main()
  25. {
  26. SystemInitial();            
  27. Delay(10000);               
  28. PeripheralInitial();      
  29.     while(1)  
  30. {  
  31.    CombinationKeyTask();  //组合按键任务函数
  32.    SingleKeyTask();    //单击按键任务函数
  33.     }
  34. }

  35. void T0_time() interrupt 1     
  36. {
  37. VoiceScan();  
  38. KeyScan();    //按键识别的驱动函数

  39. TH0=0xfc;   
  40. TL0=0x66;   
  41. }


  42. void SystemInitial(void)
  43. {
  44. TMOD=0x01;  
  45. TH0=0xfc;   
  46. TL0=0x66;   
  47. EA=1;      
  48. ET0=1;      
  49. TR0=1;      
  50. }

  51. void Delay(unsigned long u32DelayTime)
  52. {
  53.     for(;u32DelayTime>0;u32DelayTime--);
  54. }

  55. void PeripheralInitial(void)
  56. {
  57. LedClose();  //初始化关闭LED
  58. }

  59. void BeepOpen(void)
  60. {
  61. P3_4=0;  
  62. }

  63. void BeepClose(void)
  64. {
  65. P3_4=1;  
  66. }
  67. void LedOpen(void)
  68. {
  69. P1_4=0;  
  70. }

  71. void LedClose(void)
  72. {
  73. P1_4=1;  
  74. }

  75. void VoiceScan(void)
  76. {

  77.           static unsigned char Su8Lock=0;  

  78. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  79.           {
  80.                   if(0==Su8Lock)
  81.                   {
  82.                    Su8Lock=1;  
  83. BeepOpen();
  84.      }
  85.     else  
  86. {     

  87.                        vGu16BeepTimerCnt--;         

  88.                    if(0==vGu16BeepTimerCnt)
  89.                    {
  90.                            Su8Lock=0;     
  91. BeepClose();  
  92.                    }

  93. }
  94.           }         
  95. }


  96. /* 注释一:
  97. * 组合按键扫描的详细过程:
  98. * 第一步:平时只要K1与K2两个按键中有一个没有被按下时,按键的自锁标志,去抖动延时计数器
  99. * 一直被清零。
  100. * 第二步:一旦两个按键都处于被按下的状态,去抖动延时计数器开始在定时中断函数里累加,在还没
  101. *         累加到阀值KEY_FILTER_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使其中一个
  102. *         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16CombinationKeyTimeCnt
  103. *         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。
  104. * 第三步:如果两个按键按下的时间超过了阀值KEY_FILTER_TIME,马上把自锁标志Su8CombinationKeyLock
  105. *         置1,防止按住两个按键不松手后一直触发。并把按键编号vGu8CombinationKeySec赋值,
  106. *         触发一次组合按键。
  107. * 第四步:等其中一个按键松开后,自锁标志Su8CombinationKeyLock及时清零,为下一次自锁做准备。
  108. */

  109. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  110. {
  111.    static unsigned char Su8KeyLock1;        
  112.    static unsigned int  Su16KeyCnt1;         
  113.    static unsigned char Su8KeyLock2;         
  114.    static unsigned int  Su16KeyCnt2;           

  115.    static unsigned char Su8CombinationKeyLock;  //组合按键的自锁
  116.    static unsigned int  Su16CombinationKeyCnt;  //组合按键的计时器


  117.    //K1按键与K2按键的组合触发
  118.    if(0!=KEY_INPUT1||0!=KEY_INPUT2)//两个按键只要有一个按键没有按下,处于“非组合按键”的状态。
  119.    {
  120.       Su8CombinationKeyLock=0; //组合按键解锁
  121.       Su16CombinationKeyCnt=0;  //组合按键去抖动延时计数器清零,此行非常巧妙,是全场的亮点。      
  122.    }
  123.    else if(0==Su8CombinationKeyLock)//两个按键被同时按下,且是第一次被按下。此行请看专题分析。
  124.    {
  125.       Su16CombinationKeyCnt++; //累加定时中断次数
  126.       if(Su16CombinationKeyCnt>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME。
  127.       {
  128.          Su8CombinationKeyLock=1;  //组合按键的自锁,避免一直触发
  129.          vGu8CombinationKeySec=1;   //触发K1与K2的组合键操作
  130.       }
  131.    }

  132.    //K1按键的单击
  133.    if(0!=KEY_INPUT1)
  134.    {
  135.       Su8KeyLock1=0;
  136.       Su16KeyCnt1=0;      
  137.    }
  138.    else if(0==Su8KeyLock1)
  139.    {
  140.       Su16KeyCnt1++;
  141.       if(Su16KeyCnt1>=KEY_FILTER_TIME)
  142.       {
  143.          Su8KeyLock1=1;  
  144.          vGu8SingleKeySec=1;    //触发K1的单击键
  145.       }
  146.    }

  147.    //K2按键的单击
  148.    if(0!=KEY_INPUT2)
  149.    {
  150.       Su8KeyLock2=0;
  151.       Su16KeyCnt2=0;      
  152.    }
  153.    else if(0==Su8KeyLock2)
  154.    {
  155.       Su16KeyCnt2++;
  156.       if(Su16KeyCnt2>=KEY_FILTER_TIME)
  157.       {
  158.          Su8KeyLock2=1;  
  159.          vGu8SingleKeySec=2;    //触发K2的单击键
  160.       }
  161.    }

  162. }

  163. void CombinationKeyTask(void)    //组合按键任务函数,放在主函数内
  164. {
  165. if(0==vGu8CombinationKeySec)
  166. {
  167. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  168. }

  169. switch(vGu8CombinationKeySec) //根据不同的按键触发序号执行对应的代码
  170. {
  171.    case 1:     //K1与K2的组合按键任务

  172.         vGu8BeepTimerFlag=0;  
  173. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发一次组合按键后,发出“嘀”一声
  174.         vGu8BeepTimerFlag=1;  
  175. vGu8CombinationKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  176. break;
  177. }

  178. }

  179. void SingleKeyTask(void)    //单击按键任务函数,放在主函数内
  180. {
  181. if(0==vGu8SingleKeySec)
  182. {
  183. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  184. }

  185. switch(vGu8SingleKeySec) //根据不同的按键触发序号执行对应的代码
  186. {
  187.    case 1:     //K1单击任务
  188.         LedOpen();    //LED亮

  189. vGu8SingleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  190. break;

  191.    case 2:     //K2单击任务
  192.         LedClose();   //LED灭

  193. vGu8SingleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  194. break;

  195. }

  196. }


【94.2   专题分析:else if(0==Su8CombinationKeyLock)。】

        疑问:
  1.    if(0!=KEY_INPUT1||0!=KEY_INPUT2)
  2.    {
  3.       Su8CombinationKeyLock=0;
  4.       Su16CombinationKeyCnt=0;      
  5.    }
  6.    else if(0==Su8CombinationKeyLock)//两个按键被同时按下,且是第一次被按下。为什么?
  7.    {
  8.       Su16CombinationKeyCnt++;
  9.       if(Su16CombinationKeyCnt>=KEY_FILTER_TIME)
  10.       {
  11.          Su8CombinationKeyLock=1;  
  12.          vGu8CombinationKeySec=1;   
  13.       }
  14.    }


       解答:
       首先,我们要明白C语言的语法中,
  1. if(条件1)
  2. {

  3. }
  4. else if(条件2)
  5. {

  6. }

       以上语句是一对组合语句,不能分开来看。当(条件1)成立的时候,它是绝对不会判断(条件2)的。当(条件1)不成立的时候,才会判断(条件2)。
       回到刚才的问题,当程序执行到(条件2) else if(0==Su8CombinationKeyLock)的时候,就已经默认了(条件1) if(0!=KEY_INPUT1||0!=KEY_INPUT2)不成立,这个条件不成立,就意味着0==KEY_INPUT1和0==KEY_INPUT2,也就是有两个按键被同时按下,因此,这里的else if(0==Su8CombinationKeyLock)等效于else if(0==Su8CombinationKeyLock&&0==KEY_INPUT1&&0==KEY_INPUT2),而Su8CombinationKeyLock是一个自锁标志位,一旦组合按键被触发后,这个标志位会变1,防止两个按键按住不松手的时候不断触发组合按键。这样,组合按键只能同时按下一次触发一次,任意松开其中一个按键后再同时按下一次两个按键,又触发一次新的组合按键。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
 楼主| jianhong_wu 发表于 2017-11-12 15:31 | 显示全部楼层
第九十五节: 两个独立按键的“电脑键盘式”组合按键。

【95.1   “电脑键盘式”组合按键。】




                上图95.1.1  独立按键电路



                上图95.1.2  LED电路

               
                上图95.1.3  有源蜂鸣器电路

        上一节也讲了由K1和K2构成的组合按键,但是这种组合按键是普通的组合按键,因为它们的K1和K2是不分先后顺序的,你先按住K1然后再按K2,或者你先按住K2然后再按K1,效果都一样。本节讲的组合按键是分先后顺序的,比如,像电脑的复制快捷键(Ctrl+C),你必须先按住Ctrl再按住C此时“复制快捷键”才有效,如果你先按住C再按住Ctrl此时“复制快捷键”无效。本节讲的例程就是要实现这个功能,用K1代表C这类“字符数字键”,用K2代表Ctrl这类“辅助按键”,因此,要触发组合键(K2+K1),必须先按住K2再按K1才有效。本节讲的例程功能如下:(1)K1每单击一次,LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)如果先按住K2再按K1,就认为构造了“电脑键盘式”组合键,蜂鸣器发出“嘀”的一声。代码如下:
  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50     //组合按键触发后发出的声音长度 50ms  
  3. #define KEY_FILTER_TIME  25     //按键滤波的“稳定时间”25ms

  4. void T0_time();
  5. void SystemInitial(void) ;
  6. void Delay(unsigned long u32DelayTime) ;
  7. void PeripheralInitial(void) ;

  8. void BeepOpen(void);   
  9. void BeepClose(void);
  10. void LedOpen(void);   
  11. void LedClose(void);

  12. void VoiceScan(void);
  13. void KeyScan(void);    //按键识别的驱动函数,放在定时中断里
  14. void SingleKeyTask(void);   //单击按键任务函数,放在主函数内
  15. void CombinationKeyTask(void);   //组合按键任务函数,放在主函数内

  16. sbit P3_4=P3^4;       //蜂鸣器
  17. sbit P1_4=P1^4;       //LED

  18. sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
  19. sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。

  20. volatile unsigned char vGu8BeepTimerFlag=0;  
  21. volatile unsigned int vGu16BeepTimerCnt=0;  

  22. unsigned char Gu8LedStatus=0; //记录LED灯的状态,0代表灭,1代表亮

  23. volatile unsigned char vGu8SingleKeySec=0;  //单击按键的触发序号
  24. volatile unsigned char vGu8CombinationKeySec=0;  //组合按键的触发序号

  25. void main()
  26. {
  27. SystemInitial();            
  28. Delay(10000);               
  29. PeripheralInitial();      
  30.     while(1)  
  31. {  
  32.    CombinationKeyTask();  //组合按键任务函数
  33.    SingleKeyTask();       //单击按键任务函数
  34.     }
  35. }

  36. void T0_time() interrupt 1     
  37. {
  38. VoiceScan();  
  39. KeyScan();    //按键识别的驱动函数

  40. TH0=0xfc;   
  41. TL0=0x66;   
  42. }


  43. void SystemInitial(void)
  44. {
  45. TMOD=0x01;  
  46. TH0=0xfc;   
  47. TL0=0x66;   
  48. EA=1;      
  49. ET0=1;      
  50. TR0=1;      
  51. }

  52. void Delay(unsigned long u32DelayTime)
  53. {
  54.     for(;u32DelayTime>0;u32DelayTime--);
  55. }

  56. void PeripheralInitial(void)
  57. {
  58. if(0==Gu8LedStatus)
  59. {
  60. LedClose();  
  61. }
  62. else
  63. {
  64. LedOpen();  
  65. }
  66. }

  67. void BeepOpen(void)
  68. {
  69. P3_4=0;  
  70. }

  71. void BeepClose(void)
  72. {
  73. P3_4=1;  
  74. }
  75. void LedOpen(void)
  76. {
  77. P1_4=0;  
  78. }

  79. void LedClose(void)
  80. {
  81. P1_4=1;  
  82. }

  83. void VoiceScan(void)
  84. {

  85.           static unsigned char Su8Lock=0;  

  86. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  87.           {
  88.                   if(0==Su8Lock)
  89.                   {
  90.                    Su8Lock=1;  
  91. BeepOpen();
  92.      }
  93.     else  
  94. {     

  95.                        vGu16BeepTimerCnt--;         

  96.                    if(0==vGu16BeepTimerCnt)
  97.                    {
  98.                            Su8Lock=0;     
  99. BeepClose();  
  100.                    }

  101. }
  102.           }         
  103. }


  104. /* 注释一:
  105. * “电脑键盘式”组合按键扫描的详细过程:
  106. * 第一步:K2与K1构成的组合按键触发是融合在K1单击按键程序里的,只需稍微更改一下K1单击的程序
  107. *         ,就可以兼容到K2与K1构成的“电脑键盘式”组合按键。平时只要K1没有被按下时,按
  108. *         键的自锁标志Su8KeyLock1和去抖动延时计数器Su16KeyCnt1一直被清零。
  109. * 第二步:一旦K1按键被按下,去抖动延时计数器Su16KeyCnt1开始在定时中断函数里累加,在还没
  110. *         累加到阀值KEY_FILTER_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使
  111. *         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt1清零了,
  112. *         这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。
  113. * 第三步:如果K1按键按下的时间超过了阀值KEY_FILTER_TIME,马上把自锁标志Su8KeyLock1置1,
  114. *         防止按住按键不松手后一直触发,此时才开始判断一次K2按键的电平状态,如果K2为低电
  115. *         平就认为是组合按键,并给按键编号vGu8CombinationKeySec赋值,否则,就认为是K1的单击
  116. *         按键,并给按键编号vGu8SingleKeySec赋值。
  117. * 第四步:等K1按键松开后,自锁标志Su8KeyLock1及时清零,为下一次自锁做准备。
  118. */

  119. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  120. {
  121.    static unsigned char Su8KeyLock1;        
  122.    static unsigned int  Su16KeyCnt1;         


  123.    //K1的单击,或者K2与K1构成的“电脑键盘式组合按键”。
  124.    if(0!=KEY_INPUT1)//单个K1按键没有按下,及时清零一些标志。
  125.    {
  126.       Su8KeyLock1=0; //按键解锁
  127.       Su16KeyCnt1=0;  //去抖动延时计数器清零,此行非常巧妙,是全场的亮点。      
  128.    }
  129.    else if(0==Su8KeyLock1)//单个按键K1被按下
  130.    {
  131.       Su16KeyCnt1++; //累加定时中断次数
  132.       if(Su16KeyCnt1>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME。
  133.       {
  134.          if(0==KEY_INPUT2)  //此时才开始判断一次K2的电平状态,为低电平则是组合按键。
  135.          {
  136.             Su8KeyLock1=1;  
  137.             vGu8CombinationKeySec=1;  //组合按键的触发
  138. }
  139. else   
  140.          {
  141.             Su8KeyLock1=1;  
  142.             vGu8SingleKeySec=1;    //K1单击按键的触发
  143. }
  144.       }
  145.    }
  146. }

  147. void CombinationKeyTask(void)    //组合按键任务函数,放在主函数内
  148. {
  149. if(0==vGu8CombinationKeySec)
  150. {
  151. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  152. }

  153. switch(vGu8CombinationKeySec) //根据不同的按键触发序号执行对应的代码
  154. {
  155.    case 1:     //K1与K2的组合按键任务

  156.         vGu8BeepTimerFlag=0;  
  157. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发一次组合按键后,发出“嘀”一声
  158.         vGu8BeepTimerFlag=1;  
  159. vGu8CombinationKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  160. break;
  161. }

  162. }

  163. void SingleKeyTask(void)    //单击按键任务函数,放在主函数内
  164. {
  165. if(0==vGu8SingleKeySec)
  166. {
  167. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  168. }

  169. switch(vGu8SingleKeySec) //根据不同的按键触发序号执行对应的代码
  170. {
  171.    case 1:     //K1单击任务
  172. if(0==Gu8LedStatus)
  173. {
  174. Gu8LedStatus=1;
  175. LedOpen();    //LED亮   
  176. }
  177. else
  178. {
  179. Gu8LedStatus=0;
  180. LedClose();    //LED灭   
  181. }

  182. vGu8SingleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  183. break;
  184. }

  185. }



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×

评论

应该在KeyScan()函数中在加一个K2是在K1按下前或者按下后按下的一个状态变量来进行判断。  发表于 2018-12-8 21:36
此处代码如果是K2先提前按下,在按K1,蜂鸣器也是会响的  发表于 2018-12-8 21:21

评分

参与人数 1威望 +1 收起 理由
龙魔123 + 1 很给力!

查看全部评分

 楼主| jianhong_wu 发表于 2017-11-26 11:40 | 显示全部楼层
第九十七节: 独立按键按住不松手的连续均匀触发。

【97.1   按住不松手的连续均匀触发。】




                上图97.1.1  独立按键电路


                 
                上图97.1.2  灌入式驱动8个LED

               
                上图97.1.3  有源蜂鸣器电路

        在电脑上删除某个文件某行文字的时候,单击一次“退格按键[Backspace]”,就删除一个文字,如果按住“退格按键[Backspace]”不松手,就会“连续均匀”的触发“删除”的功能,自动逐个把整行文字删除清空,这就是“按住不松手的连续均匀触发”应用案例之一。除此之外,在很多需要人机交互的项目中都有这样的功能,为了快速加减某个数值,按住某个按键不松手,某个数值有节奏地快速往上加或者快速往下减。这种“按住不松手连续均匀触发”的按键识别,在程序上有“3个时间”需要留意,第1个是按键单击的“滤波”时间,第2个是按键“从单击进入连击”的间隔时间(此时间是“单击”与“连击”的分界线),第3个是按键“连击”的间隔时间,
        本节例程实现的功能如下:(1)8个受按键控制的跑马灯在某一时刻只有1个LED亮,每触发一次K1按键,“亮的LED”就“往左边跑一步”;相反,每触发一次K2按键,“亮的LED”就“往右边跑一步”。如果按住K1或者K2不松手就连续触发,“亮的LED”就“连续跑”,一直跑到左边或者右边的尽头。(2)按键每“单击”一次蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。

  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50     

  3. #define KEY_SHORT_TIME  25      //按键单击的“滤波”时间25ms
  4. #define KEY_ENTER_CONTINUITY_TIME    300  //按键“从单击进入连击”的间隔时间300ms
  5. #define KEY_CONTINUITY_TIME    80  //按键“连击”的间隔时间80ms

  6. #define BUS_P0    P0     //8个LED灯一一对应单片机的P0口总线

  7. void T0_time();
  8. void SystemInitial(void) ;
  9. void Delay(unsigned long u32DelayTime) ;
  10. void PeripheralInitial(void) ;

  11. void BeepOpen(void);   
  12. void BeepClose(void);

  13. void VoiceScan(void);
  14. void KeyScan(void);   
  15. void KeyTask(void);   
  16. void DisplayTask(void);   //显示的任务函数(LED显示状态)

  17. sbit P3_4=P3^4;       //蜂鸣器

  18. sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
  19. sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。

  20. volatile unsigned char vGu8BeepTimerFlag=0;  
  21. volatile unsigned int vGu16BeepTimerCnt=0;  

  22. unsigned char Gu8LedStatus=0; //LED灯的状态
  23. unsigned char Gu8DisplayUpdate=1; //显示的刷新标志

  24. volatile unsigned char vGu8KeySec=0;  //按键的触发序号
  25. volatile unsigned char vGu8ShieldVoiceFlag=0;  //屏蔽声音的标志

  26. void main()
  27. {
  28. SystemInitial();            
  29. Delay(10000);               
  30. PeripheralInitial();      
  31.     while(1)  
  32. {  
  33.     KeyTask();       //按键的任务函数
  34. DisplayTask();   //显示的任务函数(LED显示状态)
  35.     }
  36. }


  37. /* 注释一:
  38. * Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。
  39. * 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
  40. */

  41. void DisplayTask(void)   //显示的任务函数(LED显示状态)
  42. {
  43. if(1==Gu8DisplayUpdate)  //需要刷新一次显示
  44. {
  45. Gu8DisplayUpdate=0;  //及时清零,避免主函数“不断去执行显示代码”而影响程序效率

  46. //Gu8LedStatus是左移的位数,范围(0至7),决定了跑马灯的显示状态。
  47. BUS_P0=~(1<<Gu8LedStatus);  //“左移<<”之后的“取反~”,因为LED电路是灌入式驱动方式。
  48. }
  49. }

  50. /* 注释二:
  51. * 按键“连续均匀触发”的识别过程:
  52. * 第一步:平时只要K1没有被按下,按键的自锁标志Su8KeyLock1、去抖动延时计数器Su16KeyCnt1、
  53. *         连击计数器Su16KeyContinuityCnt1,一直被清零。
  54. * 第二步:一旦K1按键被按下,去抖动延时计数器Su16KeyCnt1开始在定时中断函数里累加,在还没
  55. *         累加到阀值KEY_SHORT_TIME时,如果在这期间由于受外界干扰或者按键抖动,
  56. *         而使IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt1清零,
  57. *         这个过程非常巧妙,非常有效地去除瞬间的杂波干扰。
  58. * 第三步:如果K1按键按下的时间超过了阀值KEY_SHORT_TIME,则触发一次“单击”, 同时,马上把自锁
  59. *         标志Su8KeyLock1置1防止按住按键不松手后一直触发,并且把计数器Su16KeyCnt1清零为了下
  60. *         一步用来累加“从单击进入连击的间隔时间1000ms”。如果此时还没有松手,直到发现按下的时
  61. *         间超过“从单击进入连击的间隔时间”阀值KEY_ENTER_CONTINUITY_TIME时,从此进入“连击”
  62. *         的模式,连击计数器Su16KeyContinuityCnt1开始累加,每到达一次阀值
  63. *         KEY_CONTINUITY_TIME就触发1次按键,为了屏蔽按键声音及时把vGu8ShieldVoiceFlag也置1,
  64. *         同时,Su16KeyContinuityCnt1马上清零为继续连击作准备。
  65. * 第四步:等K1按键松手后,自锁标志Su8KeyLock1、去抖动延时计数器Su16KeyCnt1、
  66. *         连击计数器Su16KeyContinuityCnt1,及时清零,为下一次按键触发做准备。
  67. */

  68. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  69. {
  70.    static unsigned char Su8KeyLock1;        
  71.    static unsigned int  Su16KeyCnt1;  
  72.    static unsigned int  Su16KeyContinuityCnt1;  //连击计数器

  73.    static unsigned char Su8KeyLock2;        
  74.    static unsigned int  Su16KeyCnt2;  
  75.    static unsigned int  Su16KeyContinuityCnt2;  //连击计数器

  76.    //K1按键
  77.    if(0!=KEY_INPUT1)//单个K1按键没有按下,及时清零一些标志。
  78.    {
  79.       Su8KeyLock1=0; //按键解锁
  80.       Su16KeyCnt1=0;  //去抖动延时计数器清零,此行非常巧妙,是全场的亮点。
  81. Su16KeyContinuityCnt1=0;  //连击计数器  
  82.    }
  83.    else if(0==Su8KeyLock1)//单个按键K1被按下
  84.    {
  85.       Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
  86.       if(Su16KeyCnt1>=KEY_SHORT_TIME) //按键的“滤波”时间25ms
  87.       {
  88.             Su8KeyLock1=1;      //“自锁”
  89.             vGu8KeySec=1;       //触发一次K1按键      
  90.             Su16KeyCnt1=0;      //清零,为了下一步用来累加“从单击进入连击的间隔时间300ms”
  91.       }
  92.    }
  93.    else if(Su16KeyCnt1<=KEY_ENTER_CONTINUITY_TIME)//按住不松手累加到300ms
  94.    {
  95.       Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
  96.    }
  97.    else  //按住累加到300ms后仍然不放手,这个时候进入有节奏的连续触发
  98.    {
  99.        Su16KeyContinuityCnt1++; //连击计数器开始累加,每一次累加额度是1ms
  100.        if(Su16KeyContinuityCnt1>=KEY_CONTINUITY_TIME)  //按住没松手,每0.08秒就触发一次
  101.        {
  102.             Su16KeyContinuityCnt1=0; //清零,为了继续连击。
  103.             vGu8KeySec=1;       //触发一次K1按键   
  104. vGu8ShieldVoiceFlag=1;  //把当前按键触发的声音屏蔽掉
  105.        }

  106.   }

  107.    //K2按键
  108.    if(0!=KEY_INPUT2)
  109.    {
  110.       Su8KeyLock2=0;
  111.       Su16KeyCnt2=0;  
  112. Su16KeyContinuityCnt2=0;   
  113.    }
  114.    else if(0==Su8KeyLock2)
  115.    {
  116.       Su16KeyCnt2++;
  117.       if(Su16KeyCnt2>=KEY_SHORT_TIME)
  118.       {
  119.             Su8KeyLock2=1;     
  120.             vGu8KeySec=2;       //触发一次K2按键      
  121.             Su16KeyCnt2=0;      
  122.       }
  123.    }
  124.    else if(Su16KeyCnt2<=KEY_ENTER_CONTINUITY_TIME)
  125.    {
  126.       Su16KeyCnt2++;
  127.    }
  128.    else
  129.    {
  130.        Su16KeyContinuityCnt2++;
  131.        if(Su16KeyContinuityCnt2>=KEY_CONTINUITY_TIME)  
  132.        {
  133.             Su16KeyContinuityCnt2=0;
  134.             vGu8KeySec=2;       //触发一次K2按键   
  135. vGu8ShieldVoiceFlag=1;  //把当前按键触发的声音屏蔽掉
  136.        }

  137.   }
  138. }

  139. void KeyTask(void)    //按键任务函数,放在主函数内
  140. {
  141. if(0==vGu8KeySec)
  142. {
  143. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  144. }

  145. switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
  146. {
  147.    case 1:     //K1触发的任务
  148.         if(Gu8LedStatus>0)
  149. {
  150. Gu8LedStatus--;  //控制LED“往左边跑”
  151. Gu8DisplayUpdate=1;  //刷新显示
  152. }

  153.         if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
  154. {
  155.                 vGu8BeepTimerFlag=0;  
  156. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发一次“长按”后,发出“嘀”一声
  157.                 vGu8BeepTimerFlag=1;  
  158.             }

  159. vGu8ShieldVoiceFlag=0;  //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
  160. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  161. break;

  162.    case 2:     //K2触发的任务
  163.         if(Gu8LedStatus<7)
  164. {
  165. Gu8LedStatus++;  //控制LED“往右边跑”
  166. Gu8DisplayUpdate=1;  //刷新显示
  167. }

  168.         if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
  169. {
  170.                 vGu8BeepTimerFlag=0;  
  171. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发一次“长按”后,发出“嘀”一声
  172.                 vGu8BeepTimerFlag=1;  
  173.             }

  174. vGu8ShieldVoiceFlag=0;  //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
  175. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  176. break;

  177. }
  178. }



  179. void T0_time() interrupt 1     
  180. {
  181. VoiceScan();  
  182. KeyScan();    //按键识别的驱动函数

  183. TH0=0xfc;   
  184. TL0=0x66;   
  185. }


  186. void SystemInitial(void)
  187. {
  188. TMOD=0x01;  
  189. TH0=0xfc;   
  190. TL0=0x66;   
  191. EA=1;      
  192. ET0=1;      
  193. TR0=1;      
  194. }

  195. void Delay(unsigned long u32DelayTime)
  196. {
  197.     for(;u32DelayTime>0;u32DelayTime--);
  198. }

  199. void PeripheralInitial(void)
  200. {

  201. }

  202. void BeepOpen(void)
  203. {
  204. P3_4=0;  
  205. }

  206. void BeepClose(void)
  207. {
  208. P3_4=1;  
  209. }

  210. void VoiceScan(void)
  211. {

  212.           static unsigned char Su8Lock=0;  

  213. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  214.           {
  215.                   if(0==Su8Lock)
  216.                   {
  217.                    Su8Lock=1;  
  218. BeepOpen();
  219.      }
  220.     else  
  221. {     

  222.                        vGu16BeepTimerCnt--;         

  223.                    if(0==vGu16BeepTimerCnt)
  224.                    {
  225.                            Su8Lock=0;     
  226. BeepClose();  
  227.                    }

  228. }
  229.           }         
  230. }


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
一路向北lm 发表于 2017-11-26 12:42 | 显示全部楼层
感谢分享,这么全的资料,大家有福了。
arima 发表于 2017-11-26 23:26 | 显示全部楼层
看着这么详细的资料,也是一种享受....

持之以恒,佩服!!!!

加油!!!!
 楼主| jianhong_wu 发表于 2017-12-7 11:40 | 显示全部楼层
第九十八节: 独立按键按住不松手的“先加速后匀速”的触发。

【98.1   “先加速后匀速”的触发。】




                上图98.1.1  独立按键电路


                 
                上图98.1.2  灌入式驱动8个LED

               
                上图98.1.3  有源蜂鸣器电路

        当“连续加”或者“连续减”的数据范围很大的时候,就需要按键的加速与匀速相结合的触发方式。“加速”是指按住按键不松手,按键刚开始触发是从慢到快的渐进过程,当“加速”到某个特别快的速度的时候,就“不再加速”,而是以该“恒定高速”进行“连续匀速”触发。这种触发方式,“加速”和“匀速”是相辅相成缺一不可的,为什么?假如没有“加速”只有“匀速”,那么刚按下按键就直接以最高速的“匀速”进行,就会跑过头,缺乏微调功能;而假如没有“匀速”只有“加速”,那么按下按键不松手后,速度就会一直不断飙升,最后失控过冲。
        本节例程实现的功能如下:
       (1)要更改一个“设置参数”(一个全局变量),参数的范围是0到800。
       (2)8个受“设置参数”控制的跑马灯在某一时刻只有1个LED亮,每触发一次K1按键,该“设置参数”就自减1,最小值为0;相反,每触发一次K2按键,该“设置参数”就自加1,最大值为800。
       (3)LED灯实时显示“设置参数”的范围状态:
                只有第0个LED灯亮:0<=“设置参数”<100。
                只有第1个LED灯亮:100<=“设置参数”<200。
                只有第2个LED灯亮:200<=“设置参数”<300。
                只有第3个LED灯亮:300<=“设置参数”<400。
                只有第4个LED灯亮:400<=“设置参数”<500。
                只有第5个LED灯亮:500<=“设置参数”<600。
                只有第6个LED灯亮:600<=“设置参数”<700。
                只有第7个LED灯亮:700<=“设置参数”<=800。
       (4)按键每“单击”一次蜂鸣器就鸣叫一次,但是,当按键“从单击进入连击”后,蜂鸣器就不鸣叫。

  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50     

  3. #define KEY_SHORT_TIME  25      //按键单击的“滤波”时间
  4. #define KEY_ENTER_CONTINUITY_TIME    300  //按键“从单击进入连击”的间隔时间
  5. #define KEY_CONTINUITY_INITIAL_TIME    80  //按键“连击”起始的预设间隔时间
  6. #define KEY_SUB_DT_TIME     8      //按键在“加速”时每次减小的时间。
  7. #define KEY_CONTINUITY_MIN_TIME   10  //按键时间减小到最后的“匀速”间隔时间。


  8. #define BUS_P0    P0     //8个LED灯一一对应单片机的P0口总线

  9. void T0_time();
  10. void SystemInitial(void) ;
  11. void Delay(unsigned long u32DelayTime) ;
  12. void PeripheralInitial(void) ;

  13. void BeepOpen(void);   
  14. void BeepClose(void);

  15. void VoiceScan(void);
  16. void KeyScan(void);   
  17. void KeyTask(void);   
  18. void DisplayTask(void);   //显示的任务函数(LED显示状态)

  19. sbit P3_4=P3^4;       //蜂鸣器

  20. sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。
  21. sbit KEY_INPUT2=P2^1;  //K2按键识别的输入口。

  22. volatile unsigned char vGu8BeepTimerFlag=0;  
  23. volatile unsigned int vGu16BeepTimerCnt=0;  

  24. unsigned int Gu16SetData=0; //“设置参数”。范围从0到800。LED灯反映该当前值的范围状态
  25. unsigned char Gu8DisplayUpdate=1; //显示的刷新标志

  26. volatile unsigned char vGu8KeySec=0;  //按键的触发序号
  27. volatile unsigned char vGu8ShieldVoiceFlag=0;  //屏蔽声音的标志

  28. void main()
  29. {
  30. SystemInitial();            
  31. Delay(10000);               
  32. PeripheralInitial();      
  33.     while(1)  
  34. {  
  35.     KeyTask();       //按键的任务函数
  36. DisplayTask();   //显示的任务函数(LED显示状态)
  37.     }
  38. }


  39. /* 注释一:
  40. * Gu8DisplayUpdate这类“显示刷新变量”在“显示框架”里是很常见的,而且屡用屡爽。
  41. * 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
  42. */
  43. void DisplayTask(void)   //显示的任务函数(LED显示状态)
  44. {
  45. if(1==Gu8DisplayUpdate)  //需要刷新一次显示
  46. {
  47. Gu8DisplayUpdate=0;  //及时清零,避免主函数“不断去执行显示代码”而影响程序效率

  48.         if(Gu16SetData<100)
  49. {
  50. BUS_P0=~(1<<0);  //第0个灯亮
  51. }
  52.         else if(Gu16SetData<200)
  53. {
  54. BUS_P0=~(1<<1);  //第1个灯亮
  55. }
  56.         else if(Gu16SetData<300)
  57. {
  58. BUS_P0=~(1<<2);  //第2个灯亮
  59. }
  60.         else if(Gu16SetData<400)
  61. {
  62. BUS_P0=~(1<<3);  //第3个灯亮
  63. }
  64.         else if(Gu16SetData<500)
  65. {
  66. BUS_P0=~(1<<4);  //第4个灯亮
  67. }
  68.         else if(Gu16SetData<600)
  69. {
  70. BUS_P0=~(1<<5);  //第5个灯亮
  71. }
  72.         else if(Gu16SetData<700)
  73. {
  74. BUS_P0=~(1<<6);  //第6个灯亮
  75. }
  76.         else
  77. {
  78. BUS_P0=~(1<<7);  //第7个灯亮
  79. }
  80. }
  81. }

  82. /* 注释二:
  83. * 按键“先加速后匀速”的识别过程:
  84. * 第一步:每次按一次就触发一次“单击”,如果按下去到松手的时间不超过1秒,则不会进入
  85. *        “连击”模式。
  86. * 第二步:如果按下去不松手的时间超过1秒,则进入“连击”模式。按键触发的节奏
  87. *         不断加快,直至到达某个极限值,然后以此极限值间隔匀速触发。这就是“先加速后匀速”。
  88. */

  89. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  90. {
  91.    static unsigned char Su8KeyLock1;        
  92.    static unsigned int  Su16KeyCnt1;  
  93.    static unsigned int  Su16KeyContinuityCnt1;  //连击计数器
  94.    static unsigned int  Su16KeyContinuityTime1=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值

  95.    static unsigned char Su8KeyLock2;        
  96.    static unsigned int  Su16KeyCnt2;  
  97.    static unsigned int  Su16KeyContinuityCnt2;  //连击计数器
  98.    static unsigned int  Su16KeyContinuityTime2=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值

  99.    //K1按键
  100.    if(0!=KEY_INPUT1)//单个K1按键没有按下,及时清零一些标志。
  101.    {
  102.       Su8KeyLock1=0; //按键解锁
  103.       Su16KeyCnt1=0;  //去抖动延时计数器清零,此行非常巧妙,是全场的亮点。
  104. Su16KeyContinuityCnt1=0;  //连击计数器  
  105. Su16KeyContinuityTime1=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值。重装初始值。
  106.    }
  107.    else if(0==Su8KeyLock1)//单个按键K1被按下
  108.    {
  109.       Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
  110.       if(Su16KeyCnt1>=KEY_SHORT_TIME) //按键的“滤波”时间25ms
  111.       {
  112.             Su8KeyLock1=1;      //“自锁”
  113.             vGu8KeySec=1;       //触发一次K1按键      
  114.             Su16KeyCnt1=0;      //清零,为了下一步用来累加“从单击进入连击的间隔时间300ms”
  115.       }
  116.    }
  117.    else if(Su16KeyCnt1<=KEY_ENTER_CONTINUITY_TIME)//按住不松手累加到300ms
  118.    {
  119.       Su16KeyCnt1++; //累加定时中断次数,每一次累加额度是1ms
  120.    }
  121.    else  //按住累加到300ms后仍然不放手,这个时候进入有节奏的连续触发
  122.    {
  123.        Su16KeyContinuityCnt1++; //连击计数器开始累加,每一次累加额度是1ms
  124.        if(Su16KeyContinuityCnt1>=Su16KeyContinuityTime1)  //按住没松手,每隔一会就触发一次
  125.        {
  126.             Su16KeyContinuityCnt1=0; //清零,为了继续连击。
  127.             vGu8KeySec=1;       //触发一次K1按键   
  128. vGu8ShieldVoiceFlag=1;  //把当前按键触发的声音屏蔽掉
  129. if(Su16KeyContinuityTime1>=KEY_SUB_DT_TIME)
  130. {
  131.     //此数值不断被减小,按键的触发速度就不断变快
  132. Su16KeyContinuityTime1=Su16KeyContinuityTime1-KEY_SUB_DT_TIME;//变快节奏
  133. }

  134. if(Su16KeyContinuityTime1<KEY_CONTINUITY_MIN_TIME) //最小间隔时间就是“高速匀速”
  135. {
  136. Su16KeyContinuityTime1=KEY_CONTINUITY_MIN_TIME; //最后以此最高速进行“匀速”
  137. }

  138.        }

  139.   }

  140.    //K2按键
  141.    if(0!=KEY_INPUT2)
  142.    {
  143.       Su8KeyLock2=0;
  144.       Su16KeyCnt2=0;  
  145. Su16KeyContinuityCnt2=0;
  146. Su16KeyContinuityTime2=KEY_CONTINUITY_INITIAL_TIME;
  147.    }
  148.    else if(0==Su8KeyLock2)
  149.    {
  150.       Su16KeyCnt2++;
  151.       if(Su16KeyCnt2>=KEY_SHORT_TIME)
  152.       {
  153.             Su8KeyLock2=1;      
  154.             vGu8KeySec=2;       //触发一次K2按键      
  155.             Su16KeyCnt2=0;           
  156.       }
  157.    }
  158.    else if(Su16KeyCnt2<=KEY_ENTER_CONTINUITY_TIME)
  159.    {
  160.       Su16KeyCnt2++;
  161.    }
  162.    else
  163.    {
  164.        Su16KeyContinuityCnt2++;
  165.        if(Su16KeyContinuityCnt2>=Su16KeyContinuityTime2)
  166.        {
  167.             Su16KeyContinuityCnt2=0;
  168.             vGu8KeySec=2;       //触发一次K2按键   
  169. vGu8ShieldVoiceFlag=1;
  170. if(Su16KeyContinuityTime2>=KEY_SUB_DT_TIME)
  171. {
  172. Su16KeyContinuityTime2=Su16KeyContinuityTime2-KEY_SUB_DT_TIME;
  173. }

  174. if(Su16KeyContinuityTime2<KEY_CONTINUITY_MIN_TIME)
  175. {
  176. Su16KeyContinuityTime2=KEY_CONTINUITY_MIN_TIME;
  177. }
  178.        }
  179.   }
  180. }

  181. void KeyTask(void)    //按键任务函数,放在主函数内
  182. {
  183. if(0==vGu8KeySec)
  184. {
  185. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  186. }

  187. switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
  188. {
  189.    case 1:     //K1触发的任务
  190.         if(Gu16SetData>0)
  191. {
  192. Gu16SetData--;     //“设置参数”
  193. Gu8DisplayUpdate=1;  //刷新显示
  194. }

  195.         if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
  196. {
  197.                 vGu8BeepTimerFlag=0;  
  198. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  199.                 vGu8BeepTimerFlag=1;  
  200.             }

  201. vGu8ShieldVoiceFlag=0;  //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
  202. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  203. break;

  204.    case 2:     //K2触发的任务
  205.         if(Gu16SetData<800)
  206. {
  207. Gu16SetData++;       //“设置参数”
  208. Gu8DisplayUpdate=1;  //刷新显示
  209. }

  210.         if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
  211. {
  212.                 vGu8BeepTimerFlag=0;  
  213. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  214.                 vGu8BeepTimerFlag=1;  
  215.             }

  216. vGu8ShieldVoiceFlag=0;  //及时把屏蔽标志清零,避免平时正常的单击声音也被淹没。
  217. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  218. break;

  219. }
  220. }



  221. void T0_time() interrupt 1     
  222. {
  223. VoiceScan();  
  224. KeyScan();    //按键识别的驱动函数

  225. TH0=0xfc;   
  226. TL0=0x66;   
  227. }


  228. void SystemInitial(void)
  229. {
  230. TMOD=0x01;  
  231. TH0=0xfc;   
  232. TL0=0x66;   
  233. EA=1;      
  234. ET0=1;      
  235. TR0=1;      
  236. }

  237. void Delay(unsigned long u32DelayTime)
  238. {
  239.     for(;u32DelayTime>0;u32DelayTime--);
  240. }

  241. void PeripheralInitial(void)
  242. {

  243. }

  244. void BeepOpen(void)
  245. {
  246. P3_4=0;  
  247. }

  248. void BeepClose(void)
  249. {
  250. P3_4=1;  
  251. }

  252. void VoiceScan(void)
  253. {

  254.           static unsigned char Su8Lock=0;  

  255. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  256.           {
  257.                   if(0==Su8Lock)
  258.                   {
  259.                    Su8Lock=1;  
  260. BeepOpen();
  261.      }
  262.     else  
  263. {     

  264.                        vGu16BeepTimerCnt--;         

  265.                    if(0==vGu16BeepTimerCnt)
  266.                    {
  267.                            Su8Lock=0;     
  268. BeepClose();  
  269.                    }

  270. }
  271.           }         
  272. }


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
一路向北lm 发表于 2017-12-7 11:54 | 显示全部楼层
赵成玉1 发表于 2017-12-8 08:22 | 显示全部楼层
先赞一下再说
as791026747 发表于 2017-12-8 15:57 | 显示全部楼层
厉害厉害
家长不监护 发表于 2017-12-9 14:00 | 显示全部楼层
embassy 发表于 2016-10-8 13:59
很简单的 c语言, 被你用的多么多么牛叉的样子.  大部分在工作中根本不需要这么复杂,      c语言本身就是很 ...

对您可能没帮助,但是对别人也许是有帮助的。您也可以写一部集成芯片手册来,帮助大家。
 楼主| jianhong_wu 发表于 2017-12-10 11:15 | 显示全部楼层
第九十九节: “行列扫描式”矩阵按键的单个触发(原始版)。

【99.1   “行列扫描式”矩阵按键。】

                 
                上图99.1.1  有源蜂鸣器电路


         
                上图99.1.2  3*3矩阵按键的电路

       上图是3*3的矩阵按键电路,其它4*4或者8*8的矩阵电路原理是一样的,编程思路也是一样的。相对独立按键,矩阵按键因为采用动态行列扫描的方式,能更加节省IO口,比如3*3的3行3列,1行占用1根IO口,1列占用1根IO口,因此3*3矩阵按键占用6个IO口(3+3=6),但是能识别9个按键(3*3=9)。同理,8*8矩阵按键占用16个IO口(8+8=16),但是能识别64个按键(8*8=64)。
       矩阵按键的编程原理。如上图3*3矩阵按键的电路,行IO口(P2.2,P2.1,P2.0)定为输入,列IO口(P2.5,P2.4,P2.3)定为输出。同一时刻,列输出的3个IO口只能有1根是输出L(低电平),其它2根必须全是H(高电平),然后依次轮番切换输出状态,列输出每切换一次,就分别读取一次行输入的3个IO口,这样一次就能识别到3个按键的状态,如果列连续切换3次就可以读取全部9个按键的状态。列的3种输出状态分别是:(P2.5为L,P2.4为H,P2.3为H),(P2.5为H,P2.4为L,P2.3为H),(P2.5为H,P2.4为H,P2.3为L)。为什么列输出每切换一次就能识别到3个按键的状态?因为,首先要明白一个前提,在没有任何按键“被按下”的时候,行输入的3个IO口因为内部上拉电阻的作用,默认状态都是H电平。并且,H与H相互短接输出为H,H与L相互短接输出L,也就是,L(低电平)的优先级最大,任何H(高电平)碰到L(低电平)输出的结果都是L(低电平)。L(低电平)就像数学乘法运算里的数字0,任何数跟0相乘必然等于0。多说一句,这个“L最高优先级”法则是有前提的,就是H(高电平)的产生必须是纯粹靠上拉电阻拉高的H(高电平)才行,比如刚好本教程所用的51单片机内部IO口输出的H(高电平)是依靠内部的上拉电阻产生,如果是其它“非上拉电阻产生的高电平”与“低电平”短接就有“短路烧坏芯片”的风险,这时就需要额外增加“三极管开漏式输出”电路或者外挂“开漏式输出集成芯片”电路。继续回到正题,为什么列输出每切换一次就能识别到3个按键的状态?举个例子,比如当列输出状态处于(P2.5为L,P2.4为H,P2.3为H)下,我们读取行输入的P2.2口,行输入的P2.2与列输出P2.5,P2.4,P2.3的“交叉处”有3个按键S1,S2,S3,此时,如果P2.2口是L(低电平),那么必然是S1“被按下”,因为想让P2.2口是L,只有S1有这个能力,而如果S1没有“被按下”,另外两个S2,S3即使“被按下”,P2.2口也是H而绝对不会为L,因为S2,S3的列输出P2.4为H,P2.3为H,H与H相互短接输出的结果必然为H。
        本节例程实现的功能:9个矩阵按键,每按下1个按键都触发一次蜂鸣器鸣叫。

  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50     

  3. #define KEY_SHORT_TIME  20     //按键去抖动的“滤波”时间

  4. void T0_time();
  5. void SystemInitial(void) ;
  6. void Delay(unsigned long u32DelayTime) ;
  7. void PeripheralInitial(void) ;

  8. void BeepOpen(void);   
  9. void BeepClose(void);

  10. void VoiceScan(void);
  11. void KeyScan(void);   
  12. void KeyTask(void);   

  13. sbit P3_4=P3^4;       //蜂鸣器

  14. sbit ROW_INPUT1=P2^2;  //第1行输入口。
  15. sbit ROW_INPUT2=P2^1;  //第2行输入口。
  16. sbit ROW_INPUT3=P2^0;  //第3行输入口。

  17. sbit COLUMN_OUTPUT1=P2^5;  //第1列输出口。
  18. sbit COLUMN_OUTPUT2=P2^4;  //第2列输出口。
  19. sbit COLUMN_OUTPUT3=P2^3;  //第3列输出口。

  20. volatile unsigned char vGu8BeepTimerFlag=0;  
  21. volatile unsigned int vGu16BeepTimerCnt=0;  


  22. volatile unsigned char vGu8KeySec=0;  //按键的触发序号

  23. void main()
  24. {
  25. SystemInitial();            
  26. Delay(10000);               
  27. PeripheralInitial();      
  28.     while(1)  
  29. {  
  30.     KeyTask();       //按键的任务函数
  31.     }
  32. }

  33. /* 注释一:
  34. *  矩阵按键扫描的详细过程:
  35. *  先输出某1列低电平,其它2列输出高电平,延时等待2ms后(等此3列输出同步稳定),
  36. *  再分别判断3行的输入IO口, 如果发现哪一行是低电平,就说明对应的某个按键被触发。
  37. *  依次循环切换输出的3种状态,并且分别判断输入的3行,就可以检测完9个按键。矩阵按键的
  38. *  去抖动处理方法跟我前面讲的独立按键去抖动方法是一样的,不再重复多讲。
  39. */

  40. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  41. {
  42.    static unsigned char Su8KeyLock=0;        
  43.    static unsigned int  Su16KeyCnt=0;  
  44.    static unsigned char Su8KeyStep=1;  

  45.    switch(Su8KeyStep)
  46.    {
  47.      case 1:   //按键扫描输出第一列低电平
  48.           COLUMN_OUTPUT1=0;      
  49.           COLUMN_OUTPUT2=1;
  50.           COLUMN_OUTPUT3=1;   

  51.           Su16KeyCnt=0;  //延时计数器清零
  52.           Su8KeyStep++;  //切换到下一个运行步骤
  53.           break;

  54.      case 2:     //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
  55.           Su16KeyCnt++;
  56.           if(Su16KeyCnt>=2)
  57.           {
  58.              Su16KeyCnt=0;
  59.              Su8KeyStep++;     //切换到下一个运行步骤
  60.           }
  61.           break;

  62.      case 3:
  63.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  64.           {  
  65.              Su8KeyStep++;  //如果没有按键按下,切换到下一个运行步骤
  66.              Su8KeyLock=0;  //按键自锁标志清零
  67.              Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        
  68.           }
  69.           else if(0==Su8KeyLock)  //有按键按下,且是第一次触发
  70.           {
  71.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  72.               {
  73.                   Su16KeyCnt++;  //去抖动延时计数器
  74.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  75.                   {
  76.                       Su16KeyCnt=0;
  77.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  78.                       vGu8KeySec=1;  //触发1号键 对应S1键
  79.                   }

  80.               }
  81.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  82.               {
  83.                   Su16KeyCnt++;  //去抖动延时计数器
  84.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  85.                   {
  86.                       Su16KeyCnt=0;
  87.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  88.                       vGu8KeySec=2;  //触发2号键 对应S2键
  89.                   }   
  90.               }
  91.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  92.               {
  93.                   Su16KeyCnt++;  //去抖动延时计数器
  94.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  95.                   {
  96.                       Su16KeyCnt=0;
  97.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  98.                       vGu8KeySec=3;  //触发3号键 对应S3键
  99.                   }   
  100.               }

  101.           }
  102.           break;
  103.      case 4:   //按键扫描输出第二列低电平
  104.           COLUMN_OUTPUT1=1;      
  105.           COLUMN_OUTPUT2=0;
  106.           COLUMN_OUTPUT3=1;   

  107.           Su16KeyCnt=0;  //延时计数器清零
  108.           Su8KeyStep++;  //切换到下一个运行步骤
  109.           break;

  110.      case 5:     //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
  111.           Su16KeyCnt++;
  112.           if(Su16KeyCnt>=2)
  113.           {
  114.              Su16KeyCnt=0;
  115.              Su8KeyStep++;     //切换到下一个运行步骤
  116.           }
  117.           break;

  118.      case 6:
  119.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  120.           {  
  121.              Su8KeyStep++;  //如果没有按键按下,切换到下一个运行步骤
  122.              Su8KeyLock=0;  //按键自锁标志清零
  123.              Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        
  124.           }
  125.           else if(0==Su8KeyLock)  //有按键按下,且是第一次触发
  126.           {
  127.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  128.               {
  129.                   Su16KeyCnt++;  //去抖动延时计数器
  130.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  131.                   {
  132.                       Su16KeyCnt=0;
  133.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  134.                       vGu8KeySec=4;  //触发4号键 对应S4键
  135.                   }

  136.               }
  137.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  138.               {
  139.                   Su16KeyCnt++;  //去抖动延时计数器
  140.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  141.                   {
  142.                       Su16KeyCnt=0;
  143.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  144.                       vGu8KeySec=5;  //触发5号键 对应S5键
  145.                   }   
  146.               }
  147.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  148.               {
  149.                   Su16KeyCnt++;  //去抖动延时计数器
  150.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  151.                   {
  152.                       Su16KeyCnt=0;
  153.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  154.                       vGu8KeySec=6;  //触发6号键 对应S6键
  155.                   }   
  156.               }

  157.           }
  158.           break;
  159.      case 7:   //按键扫描输出第三列低电平
  160.           COLUMN_OUTPUT1=1;      
  161.           COLUMN_OUTPUT2=1;
  162.           COLUMN_OUTPUT3=0;   

  163.           Su16KeyCnt=0;  //延时计数器清零
  164.           Su8KeyStep++;  //切换到下一个运行步骤
  165.           break;

  166.      case 8:     //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
  167.           Su16KeyCnt++;
  168.           if(Su16KeyCnt>=2)
  169.           {
  170.              Su16KeyCnt=0;
  171.              Su8KeyStep++;     //切换到下一个运行步骤
  172.           }
  173.           break;

  174.      case 9:
  175.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  176.           {  
  177.              Su8KeyStep=1;  //如果没有按键按下,返回到第一步,重新开始扫描!!!!!!
  178.              Su8KeyLock=0;  //按键自锁标志清零
  179.              Su16KeyCnt=0; //按键去抖动延时计数器清零,此行非常巧妙        
  180.           }
  181.           else if(0==Su8KeyLock)  //有按键按下,且是第一次触发
  182.           {
  183.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  184.               {
  185.                   Su16KeyCnt++;  //去抖动延时计数器
  186.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  187.                   {
  188.                       Su16KeyCnt=0;
  189.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  190.                       vGu8KeySec=7;  //触发7号键 对应S7键
  191.                   }

  192.               }
  193.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  194.               {
  195.                   Su16KeyCnt++;  //去抖动延时计数器
  196.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  197.                   {
  198.                       Su16KeyCnt=0;
  199.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  200.                       vGu8KeySec=8;  //触发8号键 对应S8键
  201.                   }   
  202.               }
  203.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  204.               {
  205.                   Su16KeyCnt++;  //去抖动延时计数器
  206.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  207.                   {
  208.                       Su16KeyCnt=0;
  209.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  210.                       vGu8KeySec=9;  //触发9号键 对应S9键
  211.                   }   
  212.               }

  213.           }
  214.           break;
  215.    }

  216. }

  217. void KeyTask(void)    //按键任务函数,放在主函数内
  218. {
  219. if(0==vGu8KeySec)
  220. {
  221. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  222. }

  223. switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
  224. {
  225.    case 1:     //S1触发的任务

  226.             vGu8BeepTimerFlag=0;  
  227. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  228.             vGu8BeepTimerFlag=1;  

  229. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  230. break;

  231.    case 2:     //S2触发的任务

  232.             vGu8BeepTimerFlag=0;  
  233. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  234.             vGu8BeepTimerFlag=1;  

  235. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  236. break;

  237.    case 3:     //S3触发的任务

  238.             vGu8BeepTimerFlag=0;  
  239. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  240.             vGu8BeepTimerFlag=1;  

  241. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  242. break;

  243.    case 4:     //S4触发的任务

  244.             vGu8BeepTimerFlag=0;  
  245. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  246.             vGu8BeepTimerFlag=1;  

  247. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  248. break;

  249.    case 5:     //S5触发的任务

  250.             vGu8BeepTimerFlag=0;  
  251. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  252.             vGu8BeepTimerFlag=1;  

  253. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  254. break;

  255.    case 6:     //S6触发的任务

  256.             vGu8BeepTimerFlag=0;  
  257. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  258.             vGu8BeepTimerFlag=1;  

  259. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  260. break;

  261.    case 7:     //S7触发的任务

  262.             vGu8BeepTimerFlag=0;  
  263. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  264.             vGu8BeepTimerFlag=1;  

  265. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  266. break;

  267.    case 8:     //S8触发的任务

  268.             vGu8BeepTimerFlag=0;  
  269. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  270.             vGu8BeepTimerFlag=1;  

  271. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  272. break;

  273.    case 9:     //S9触发的任务

  274.             vGu8BeepTimerFlag=0;  
  275. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  276.             vGu8BeepTimerFlag=1;  

  277. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  278. break;

  279. }
  280. }



  281. void T0_time() interrupt 1     
  282. {
  283. VoiceScan();  
  284. KeyScan();    //按键识别的驱动函数

  285. TH0=0xfc;   
  286. TL0=0x66;   
  287. }


  288. void SystemInitial(void)
  289. {
  290. TMOD=0x01;  
  291. TH0=0xfc;   
  292. TL0=0x66;   
  293. EA=1;      
  294. ET0=1;      
  295. TR0=1;      
  296. }

  297. void Delay(unsigned long u32DelayTime)
  298. {
  299.     for(;u32DelayTime>0;u32DelayTime--);
  300. }

  301. void PeripheralInitial(void)
  302. {

  303. }

  304. void BeepOpen(void)
  305. {
  306. P3_4=0;  
  307. }

  308. void BeepClose(void)
  309. {
  310. P3_4=1;  
  311. }

  312. void VoiceScan(void)
  313. {

  314.           static unsigned char Su8Lock=0;  

  315. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  316.           {
  317.                   if(0==Su8Lock)
  318.                   {
  319.                    Su8Lock=1;  
  320. BeepOpen();
  321.      }
  322.     else  
  323. {     

  324.                        vGu16BeepTimerCnt--;         

  325.                    if(0==vGu16BeepTimerCnt)
  326.                    {
  327.                            Su8Lock=0;     
  328. BeepClose();  
  329.                    }

  330. }
  331.           }         
  332. }


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
ifxz0123 发表于 2017-12-11 09:11 | 显示全部楼层
好贴,必须支持下
avensun 发表于 2017-12-11 21:33 | 显示全部楼层
深入浅出,讲的真好!
Dalarl2008 发表于 2017-12-11 22:07 | 显示全部楼层
Dalarl2008 发表于 2017-12-11 22:07 | 显示全部楼层
谢谢 下载完毕
李劲均 发表于 2017-12-13 11:49 | 显示全部楼层
谢谢吴老师,你的无私奉献,造就了你的功德无量。
李劲均 发表于 2017-12-15 13:58 | 显示全部楼层
谢谢分享
laosun9911 发表于 2017-12-16 14:27 | 显示全部楼层
楼主以前是不是在**论坛?
现在来21IC了?
那个控制欲极强的**有时候确实不可理喻。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部