独闷闷网

 找回密码
 立即注册
搜索
楼主: jianhong_wu
打印 上一主题 下一主题
收起左侧

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

[复制链接]
101#
 楼主| 发表于 2017-11-26 11:32:35 | 只看该作者
本帖最后由 jianhong_wu 于 2017-11-26 11:46 编辑

第九十七节: 独立按键按住不松手的连续均匀触发。
第九十七节_pdf文件.pdf (117.81 KB, 下载次数: 1311)
【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. }
复制代码



乐于分享,勇于质疑!
102#
 楼主| 发表于 2017-12-7 11:32:42 | 只看该作者
本帖最后由 jianhong_wu 于 2017-12-7 11:50 编辑

第九十八节: 独立按键按住不松手的“先加速后匀速”的触发。
第九十八节_pdf文件.pdf (119.05 KB, 下载次数: 1297)
【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. }
复制代码





乐于分享,勇于质疑!
103#
 楼主| 发表于 2017-12-10 11:09:03 | 只看该作者
本帖最后由 jianhong_wu 于 2017-12-10 11:18 编辑

第九十九节: “行列扫描式”矩阵按键的单个触发(原始版)。
第九十九节_pdf文件.pdf (117.38 KB, 下载次数: 1310)
【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. }
复制代码


乐于分享,勇于质疑!
104#
 楼主| 发表于 2017-12-17 11:28:54 | 只看该作者
本帖最后由 jianhong_wu 于 2017-12-17 11:50 编辑

第一百节: “行列扫描式”矩阵按键的单个触发(优化版)。
第一百节_pdf文件.pdf (109.4 KB, 下载次数: 1517)
【100.1   “行列扫描式”矩阵按键。】

                  
                上图100.1.1  有源蜂鸣器电路


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

       写程序,凡是出现“重复性、相似性”的代码,都可以加入“循环,判断,数组”这类语句对代码进行压缩优化。上一节讲的矩阵按键,代码是记流水账式的,出现很多“重复性、相似性”的代码,是没有经过优化的“原始版”,本节的目的是对上一节的代码进行优化,让大家从中发现一些技巧。
       多说一句,我一直认为,只要单片机容量够,代码多一点少一点并不重要,只要不影响运行效率就行。而且有时候,代码写多一点,可读性非常强,修改起来也非常方便。如果一味的追求压缩代码,就会刻意用很多“循环,判断,数组”等元素,代码虽然紧凑了,但是可分离性,可更改性,可阅读性就没那么强。因此,做项目的时候,某些代码要不要进行压缩,是没有绝对标准的,能因敌而取胜者谓之神。
       本节例程实现的功能: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.    static unsigned char Su8ColumnRecord=0;  //本节多增加此变量用来切换当前列的输出

  46.    switch(Su8KeyStep)
  47.    {
  48.      case 1:   
  49.           if(0==Su8ColumnRecord)  //按键扫描输出第一列低电平
  50. {
  51.           COLUMN_OUTPUT1=0;      
  52.           COLUMN_OUTPUT2=1;
  53.           COLUMN_OUTPUT3=1;  
  54. }
  55.           else if(1==Su8ColumnRecord)  //按键扫描输出第二列低电平
  56. {
  57.           COLUMN_OUTPUT1=1;      
  58.           COLUMN_OUTPUT2=0;
  59.           COLUMN_OUTPUT3=1;  
  60. }
  61.           else     //按键扫描输出第三列低电平
  62. {
  63.           COLUMN_OUTPUT1=1;      
  64.           COLUMN_OUTPUT2=1;
  65.           COLUMN_OUTPUT3=0;  
  66. }
  67.           Su16KeyCnt=0;  //延时计数器清零
  68.           Su8KeyStep++;  //切换到下一个运行步骤
  69.           break;

  70.      case 2:     //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
  71.           Su16KeyCnt++;
  72.           if(Su16KeyCnt>=2)
  73.           {
  74.              Su16KeyCnt=0;
  75.              Su8KeyStep++;     //切换到下一个运行步骤
  76.           }
  77.           break;

  78.      case 3:
  79.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  80.           {  
  81.              Su8KeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描!!!!!!
  82.              Su8KeyLock=0;  //按键自锁标志清零
  83.              Su16KeyCnt=0;  //按键去抖动延时计数器清零,此行非常巧妙   
  84.              Su8ColumnRecord++;  //输出下一列
  85.              if(Su8ColumnRecord>=3)  
  86.              {
  87.                 Su8ColumnRecord=0; //依次输出完第3列之后,继续从第1列开始输出低电平
  88.              }     
  89.           }
  90.           else if(0==Su8KeyLock)  //有按键按下,且是第一次触发
  91.           {
  92.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  93.               {
  94.                   Su16KeyCnt++;  //去抖动延时计数器
  95.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  96.                   {
  97.                       Su16KeyCnt=0;
  98.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零

  99.                       if(0==Su8ColumnRecord)  //第1列输出低电平
  100.                       {
  101.                            vGu8KeySec=1;  //触发1号键 对应S1键
  102.                       }
  103.                       else if(1==Su8ColumnRecord)  //第2列输出低电平
  104.                       {
  105.                            vGu8KeySec=2;  //触发2号键 对应S2键
  106.                       }
  107.                       else if(2==Su8ColumnRecord)  //第3列输出低电平
  108.                       {
  109.                            vGu8KeySec=3;  //触发3号键 对应S3键
  110.                       }
  111.                   }

  112.               }
  113.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  114.               {
  115.                   Su16KeyCnt++;  //去抖动延时计数器
  116.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  117.                   {
  118.                       Su16KeyCnt=0;
  119.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零

  120.                       if(0==Su8ColumnRecord)  //第1列输出低电平
  121.                       {
  122.                            vGu8KeySec=4;  //触发4号键 对应S4键
  123.                       }
  124.                       else if(1==Su8ColumnRecord)  //第2列输出低电平
  125.                       {
  126.                            vGu8KeySec=5;  //触发5号键 对应S5键
  127.                       }
  128.                       else if(2==Su8ColumnRecord)  //第3列输出低电平
  129.                       {
  130.                            vGu8KeySec=6;  //触发6号键 对应S6键
  131.                       }
  132.                   }   
  133.               }
  134.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  135.               {
  136.                   Su16KeyCnt++;  //去抖动延时计数器
  137.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  138.                   {
  139.                       Su16KeyCnt=0;
  140.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  141.                       if(0==Su8ColumnRecord)  //第1列输出低电平
  142.                       {
  143.                            vGu8KeySec=7;  //触发7号键 对应S7键
  144.                       }
  145.                       else if(1==Su8ColumnRecord)  //第2列输出低电平
  146.                       {
  147.                            vGu8KeySec=8;  //触发8号键 对应S8键
  148.                       }
  149.                       else if(2==Su8ColumnRecord)  //第3列输出低电平
  150.                       {
  151.                            vGu8KeySec=9;  //触发9号键 对应S9键
  152.                       }
  153.                   }   
  154.               }

  155.           }
  156.           break;
  157.    }

  158. }

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

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

  168.             vGu8BeepTimerFlag=0;  
  169. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  170.             vGu8BeepTimerFlag=1;  

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

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

  174.             vGu8BeepTimerFlag=0;  
  175. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  176.             vGu8BeepTimerFlag=1;  

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

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

  180.             vGu8BeepTimerFlag=0;  
  181. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  182.             vGu8BeepTimerFlag=1;  

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

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

  186.             vGu8BeepTimerFlag=0;  
  187. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  188.             vGu8BeepTimerFlag=1;  

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

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

  192.             vGu8BeepTimerFlag=0;  
  193. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  194.             vGu8BeepTimerFlag=1;  

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

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

  198.             vGu8BeepTimerFlag=0;  
  199. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  200.             vGu8BeepTimerFlag=1;  

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

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

  204.             vGu8BeepTimerFlag=0;  
  205. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  206.             vGu8BeepTimerFlag=1;  

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

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

  210.             vGu8BeepTimerFlag=0;  
  211. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  212.             vGu8BeepTimerFlag=1;  

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

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

  216.             vGu8BeepTimerFlag=0;  
  217. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  218.             vGu8BeepTimerFlag=1;  

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

  221. }
  222. }



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

  227. TH0=0xfc;   
  228. TL0=0x66;   
  229. }


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

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

  243. void PeripheralInitial(void)
  244. {

  245. }

  246. void BeepOpen(void)
  247. {
  248. P3_4=0;  
  249. }

  250. void BeepClose(void)
  251. {
  252. P3_4=1;  
  253. }

  254. void VoiceScan(void)
  255. {

  256.           static unsigned char Su8Lock=0;  

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

  266.                        vGu16BeepTimerCnt--;         

  267.                    if(0==vGu16BeepTimerCnt)
  268.                    {
  269.                            Su8Lock=0;     
  270. BeepClose();  
  271.                    }

  272. }
  273.           }         
  274. }
复制代码






乐于分享,勇于质疑!
105#
 楼主| 发表于 2017-12-31 16:36:01 | 只看该作者
本帖最后由 jianhong_wu 于 2017-12-31 17:49 编辑

第一百零一节: 矩阵按键鼠标式的单击与双击。
第一百零一节_pdf文件.pdf (127.59 KB, 下载次数: 1475)
【101.1   矩阵按键鼠标式的单击与双击。】

               
                上图101.1.1  有源蜂鸣器电路

      
                上图101.1.2  LED电路


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

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

  2. #define KEY_VOICE_TIME   50     
  3. #define KEY_SHORT_TIME  20     //按键去抖动的“滤波”时间
  4. #define KEY_INTERVAL_TIME  80  //连续两次单击之间的最大有效时间。因为是矩阵,80不一定是80ms

  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 ROW_INPUT1=P2^2;  //第1行输入口。
  20. sbit ROW_INPUT2=P2^1;  //第2行输入口。
  21. sbit ROW_INPUT3=P2^0;  //第3行输入口。

  22. sbit COLUMN_OUTPUT1=P2^5;  //第1列输出口。
  23. sbit COLUMN_OUTPUT2=P2^4;  //第2列输出口。
  24. sbit COLUMN_OUTPUT3=P2^3;  //第3列输出口。

  25. volatile unsigned char vGu8BeepTimerFlag=0;  
  26. volatile unsigned int vGu16BeepTimerCnt=0;  

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

  30. void main()
  31. {
  32. SystemInitial();            
  33. Delay(10000);               
  34. PeripheralInitial();      
  35.     while(1)  
  36. {  
  37.         SingleKeyTask();    //单击按键任务函数
  38.         DoubleKeyTask();    //双击按键任务函数
  39.     }
  40. }

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

  48. /* 注释二:
  49. * 双击按键扫描的详细过程:
  50. * 第一步:平时没有按键被触发时,按键的自锁标志,去抖动延时计数器一直被清零。
  51. *         如果之前已经有按键触发过1次单击,那么启动时间间隔计数器Su16KeyIntervalCnt1,
  52. *         在KEY_INTERVAL_TIME这个允许的时间差范围内,如果一直没有第2次单击触发,
  53. *         则把累加按键触发的次数Su8KeyTouchCnt1也清零,上一次累计的单击数被清零,
  54. *         就意味着下一次新的双击必须重新开始累加两次单击数。
  55. * 第二步:一旦有按键被按下,去抖动延时计数器开始在定时中断函数里累加,在还没累加到
  56. *         阀值KEY_SHORT_TIME时,如果在这期间由于受外界干扰或者按键抖动,而使
  57. *         IO口突然瞬间触发成高电平,这个时候马上把延时计数器Su16KeyCnt
  58. *         清零了,这个过程非常巧妙,非常有效地去除瞬间的杂波干扰,以后凡是用到开关感应器的时候,
  59. *         都可以用类似这样的方法去干扰。
  60. * 第三步:如果按键按下的时间超过了阀值KEY_SHORT_TIME,马上把自锁标志Su8KeyLock置1,
  61. *         防止按住按键不松手后一直触发。与此同时,累加1次按键次数,如果按键次数累加有2次,
  62. *         则认为触发双击按键,并把编号vGu8DoubleKeySec赋值。
  63. * 第四步:等按键松开后,自锁标志Su8KeyLock及时清零解锁,为下一次自锁做准备。并且累加间隔时间,
  64. *         防止两次按键的间隔时间太长。如果连续2次单击的间隔时间太长达到了KEY_INTERVAL_TIME
  65. *         的长度,立即清零当前按键次数的计数器,这样意味着上一次的累加单击数无效,下一次双击
  66. *         必须重新累加新的单击数。
  67. */

  68. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  69. {
  70.    static unsigned char Su8KeyLock=0;        
  71.    static unsigned int  Su16KeyCnt=0;  
  72.    static unsigned char Su8KeyStep=1;  

  73.    static unsigned char Su8ColumnRecord=0;  //用来切换当前列的输出

  74.    static unsigned char Su8KeyTouchCnt1;       //S1按键的次数记录
  75.    static unsigned int  Su16KeyIntervalCnt1;   //S1按键的间隔时间计数器

  76.    switch(Su8KeyStep)
  77.    {
  78.      case 1:   
  79.           if(0==Su8ColumnRecord)  //按键扫描输出第一列低电平
  80. {
  81.           COLUMN_OUTPUT1=0;      
  82.           COLUMN_OUTPUT2=1;
  83.           COLUMN_OUTPUT3=1;  
  84. }
  85.           else if(1==Su8ColumnRecord)  //按键扫描输出第二列低电平
  86. {
  87.           COLUMN_OUTPUT1=1;      
  88.           COLUMN_OUTPUT2=0;
  89.           COLUMN_OUTPUT3=1;  
  90. }
  91.           else     //按键扫描输出第三列低电平
  92. {
  93.           COLUMN_OUTPUT1=1;      
  94.           COLUMN_OUTPUT2=1;
  95.           COLUMN_OUTPUT3=0;  
  96. }
  97.           Su16KeyCnt=0;  //延时计数器清零
  98.           Su8KeyStep++;  //切换到下一个运行步骤
  99.           break;

  100.      case 2:     //延时等待2ms后(等此3列输出同步稳定)。不是按键的去抖动延时。
  101.           Su16KeyCnt++;
  102.           if(Su16KeyCnt>=2)
  103.           {
  104.              Su16KeyCnt=0;
  105.              Su8KeyStep++;     //切换到下一个运行步骤
  106.           }
  107.           break;

  108.      case 3:
  109.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  110.           {  
  111.              Su8KeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描!!!!!!
  112.              Su8KeyLock=0;  //按键自锁标志清零
  113.              Su16KeyCnt=0;  //按键去抖动延时计数器清零,此行非常巧妙

  114. if(Su8KeyTouchCnt1>=1) //之前已经有按键触发过一次,启动间隔时间的计数器
  115. {
  116. Su16KeyIntervalCnt1++; //按键间隔的时间计数器累加
  117. if(Su16KeyIntervalCnt1>=KEY_INTERVAL_TIME) //达到最大允许的间隔时间,溢出无效
  118. {
  119. Su16KeyIntervalCnt1=0; //时间计数器清零
  120. Su8KeyTouchCnt1=0;     //清零按键的按下的次数,因为间隔时间溢出无效
  121. }
  122. }

  123.              Su8ColumnRecord++;  //输出下一列
  124.              if(Su8ColumnRecord>=3)  
  125.              {
  126.                 Su8ColumnRecord=0; //依次输出完第3列之后,继续从第1列开始输出低电平
  127.              }     
  128.           }
  129.           else if(0==Su8KeyLock)  //有按键按下,且是第一次触发
  130.           {
  131.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  132.               {
  133.                   Su16KeyCnt++;  //去抖动延时计数器
  134.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  135.                   {
  136.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零

  137.                       if(0==Su8ColumnRecord)  //第1列输出低电平
  138.                       {
  139.                            Su16KeyIntervalCnt1=0;   //按键有效间隔的时间计数器清零
  140.                            Su8KeyTouchCnt1++;       //记录当前单击的次数
  141.                            if(1==Su8KeyTouchCnt1)   //只按了1次
  142.                            {
  143.                                vGu8SingleKeySec=1;     //单击任务,触发1号键 对应S1键
  144.                            }
  145.                            else if(Su8KeyTouchCnt1>=2)  //连续按了两次以上
  146.                            {
  147.                                 Su8KeyTouchCnt1=0;    //统计按键次数清零
  148.                                 vGu8SingleKeySec=1;   //单击任务,触发1号键 对应S1键
  149. vGu8DoubleKeySec=1;   //双击任务,触发1号键 对应S1键
  150.                            }
  151.                       }
  152.                       else if(1==Su8ColumnRecord)  //第2列输出低电平
  153.                       {
  154.                            vGu8SingleKeySec=2;  //触发2号键 对应S2键
  155.                       }
  156.                       else if(2==Su8ColumnRecord)  //第3列输出低电平
  157.                       {
  158.                            vGu8SingleKeySec=3;  //触发3号键 对应S3键
  159.                       }
  160.                   }

  161.               }
  162.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  163.               {
  164.                   Su16KeyCnt++;  //去抖动延时计数器
  165.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  166.                   {
  167.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零

  168.                       if(0==Su8ColumnRecord)  //第1列输出低电平
  169.                       {
  170.                            vGu8SingleKeySec=4;  //触发4号键 对应S4键
  171.                       }
  172.                       else if(1==Su8ColumnRecord)  //第2列输出低电平
  173.                       {
  174.                            vGu8SingleKeySec=5;  //触发5号键 对应S5键
  175.                       }
  176.                       else if(2==Su8ColumnRecord)  //第3列输出低电平
  177.                       {
  178.                            vGu8SingleKeySec=6;  //触发6号键 对应S6键
  179.                       }
  180.                   }   
  181.               }
  182.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  183.               {
  184.                   Su16KeyCnt++;  //去抖动延时计数器
  185.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  186.                   {
  187.                       Su8KeyLock=1;//自锁置1,避免一直触发,只有松开按键,此标志位才会被清零
  188.                       if(0==Su8ColumnRecord)  //第1列输出低电平
  189.                       {
  190.                            vGu8SingleKeySec=7;  //触发7号键 对应S7键
  191.                       }
  192.                       else if(1==Su8ColumnRecord)  //第2列输出低电平
  193.                       {
  194.                            vGu8SingleKeySec=8;  //触发8号键 对应S8键
  195.                       }
  196.                       else if(2==Su8ColumnRecord)  //第3列输出低电平
  197.                       {
  198.                            vGu8SingleKeySec=9;  //触发9号键 对应S9键
  199.                       }
  200.                   }   
  201.               }

  202.           }
  203.           break;
  204.    }

  205. }

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

  212. switch(vGu8SingleKeySec) //根据不同的按键触发序号执行对应的代码
  213. {
  214.    case 1:     //S1按键的单击任务

  215.             //通过Gu8LedStatus的状态切换,来反复切换LED的“灭”与“亮”的状态
  216.             if(0==Gu8LedStatus)
  217.             {
  218.                 Gu8LedStatus=1; //标识并且更改当前LED灯的状态。0就变成1。
  219.                 LedOpen();   //点亮LED
  220. }
  221.             else
  222.             {
  223.                 Gu8LedStatus=0; //标识并且更改当前LED灯的状态。1就变成0。
  224.                 LedClose();  //关闭LED
  225. }

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

  228.    default:  //其它按键触发的单击

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

  231. }
  232. }

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

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

  242.               vGu8BeepTimerFlag=0;  
  243. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发双击后,发出“嘀”一声
  244.               vGu8BeepTimerFlag=1;  

  245. vGu8DoubleKeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  246. break;
  247. }

  248. }

  249. void T0_time() interrupt 1     
  250. {
  251. VoiceScan();  
  252. KeyScan();    //按键识别的驱动函数

  253. TH0=0xfc;   
  254. TL0=0x66;   
  255. }


  256. void SystemInitial(void)
  257. {
  258. TMOD=0x01;  
  259. TH0=0xfc;   
  260. TL0=0x66;   
  261. EA=1;      
  262. ET0=1;      
  263. TR0=1;      
  264. }

  265. void Delay(unsigned long u32DelayTime)
  266. {
  267.     for(;u32DelayTime>0;u32DelayTime--);
  268. }

  269. void PeripheralInitial(void)
  270. {
  271. /* 注释三:
  272. * 把LED的初始化放在PeripheralInitial而不是放在SystemInitial,是因为LED显示内容对上电
  273. * 瞬间的要求不高。但是,如果是控制继电器,则应该把继电器的输出初始化放在SystemInitial。
  274. */

  275.     //根据Gu8LedStatus的值来初始化LED当前的显示状态,0代表灭,1代表亮
  276. if(0==Gu8LedStatus)
  277. {
  278.     LedClose();  //关闭LED
  279. }
  280. else
  281. {
  282.     LedOpen();   //点亮LED
  283. }
  284. }

  285. void BeepOpen(void)
  286. {
  287. P3_4=0;  
  288. }

  289. void BeepClose(void)
  290. {
  291. P3_4=1;  
  292. }

  293. void LedOpen(void)
  294. {
  295. P1_4=0;  
  296. }

  297. void LedClose(void)
  298. {
  299. P1_4=1;  
  300. }

  301. void VoiceScan(void)
  302. {

  303.           static unsigned char Su8Lock=0;  

  304. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  305.           {
  306.                   if(0==Su8Lock)
  307.                   {
  308.                    Su8Lock=1;  
  309. BeepOpen();
  310.      }
  311.     else  
  312. {     

  313.                        vGu16BeepTimerCnt--;         

  314.                    if(0==vGu16BeepTimerCnt)
  315.                    {
  316.                            Su8Lock=0;     
  317. BeepClose();  
  318.                    }

  319. }
  320.           }         
  321. }
复制代码






乐于分享,勇于质疑!
106#
 楼主| 发表于 2018-1-7 12:24:28 | 只看该作者
本帖最后由 jianhong_wu 于 2018-1-7 12:39 编辑

第一百零二节: 两个“任意行输入”矩阵按键的“有序”组合触发。
第一百零二节_pdf文件.pdf (114.93 KB, 下载次数: 1437)
【102.1   “异行输入”“同行输入”“有序”。】

               
                上图102.1.1  有源蜂鸣器电路

      
                上图102.1.2  LED电路


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

       “任意行输入”是指能兼容“异行输入”与“同行输入”这两种按键状态。
        何谓“异行输入”何谓“同行输入”?如上图矩阵按键,P2.2,P2.1,P2.0是输入行,P2.5,P2.4,P2.3是输出列。以S1按键为例,很明显,S2和S3都是属于S1的“同行输入”,都是属于P2.2的输入行。除了S2和S3以外,其它所有的按键都是S1的“异行输入”,比如S5按键就是S1的“异行输入”,因为S1是属于P2.2的输入行,而S5是属于P2.1的输入行。
何谓“有序”组合触发?就是两个按键的触发必须遵守“先后顺序”才能构成“组合触发”。比如,像电脑的复制快捷键(Ctrl+C),你必须先按住Ctrl再按住C此时“复制快捷键”才有效,如果你先按住C再按住Ctrl此时“复制快捷键”无效。
       “异行输入”与“同行输入”,相比之下,“同行输入”更难更有代表性,如果把“同行输入”的程序写出来了,那么完全按“同行输入”的思路,就可以把“异行输入”的程序写出来。因此,只要把“同行输入”的程序写出来了,也就意味着“任意行输入”的程序也就实现了。本节以S1和S2的“同行输入”按键为例,S1是主键,类似复制快捷键的Ctrl键;S2是从键,类似复制快捷键的C键。要触发组合键(S1+S2),必须先按住S1再按S2才有效。功能如下:(1)S1每单击一次,LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)如果先按住S1再按S2,就认为构造了“有序”组合键,蜂鸣器发出“嘀”的一声。
  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 LedOpen(void);   
  11. void LedClose(void);

  12. void VoiceScan(void);
  13. void KeyScan(void);   
  14. void SingleKeyTask(void);   
  15. void DoubleKeyTask(void);   

  16. sbit P3_4=P3^4;      
  17. sbit P1_4=P1^4;      

  18. sbit ROW_INPUT1=P2^2;  //第1行输入口。
  19. sbit ROW_INPUT2=P2^1;  //第2行输入口。
  20. sbit ROW_INPUT3=P2^0;  //第3行输入口。

  21. sbit COLUMN_OUTPUT1=P2^5;  //第1列输出口。
  22. sbit COLUMN_OUTPUT2=P2^4;  //第2列输出口。
  23. sbit COLUMN_OUTPUT3=P2^3;  //第3列输出口。

  24. volatile unsigned char vGu8BeepTimerFlag=0;  
  25. volatile unsigned int vGu16BeepTimerCnt=0;  

  26. unsigned char Gu8LedStatus=0;
  27. volatile unsigned char vGu8SingleKeySec=0;  
  28. volatile unsigned char vGu8DoubleKeySec=0;  

  29. void main()
  30. {
  31. SystemInitial();            
  32. Delay(10000);               
  33. PeripheralInitial();      
  34.     while(1)  
  35. {  
  36.         SingleKeyTask();   
  37.         DoubleKeyTask();   
  38.     }
  39. }

  40. /* 注释一:
  41. *  两个“任意行输入”矩阵按键“有序”触发的两个最关键地方:
  42. *  (1)当S1按键被按下单击触发之后, “马上更新输出列的信号状态”,然后切换到后面的步骤。
  43. *  (2)在后面的步骤里,进入到S1和S2两个按键的轮番循环监控之中,如果发现S1按键率先
  44. *   被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
  45. *  (3)按照这个模板,只需“更改不同的列输出,判断不同的行输入”,就可以实现“任意行输入”
  46. *   矩阵按键的“有序”组合触发。
  47. */

  48. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  49. {
  50.    static unsigned char Su8KeyLock=0;        
  51.    static unsigned int  Su16KeyCnt=0;  
  52.    static unsigned char Su8KeyStep=1;  

  53.    static unsigned char Su8ColumnRecord=0;  

  54.    switch(Su8KeyStep)
  55.    {
  56.      case 1:   
  57.           if(0==Su8ColumnRecord)  
  58. {
  59.           COLUMN_OUTPUT1=0;      
  60.           COLUMN_OUTPUT2=1;
  61.           COLUMN_OUTPUT3=1;  
  62. }
  63.           else if(1==Su8ColumnRecord)  
  64. {
  65.           COLUMN_OUTPUT1=1;      
  66.           COLUMN_OUTPUT2=0;
  67.           COLUMN_OUTPUT3=1;  
  68. }
  69.           else     
  70. {
  71.           COLUMN_OUTPUT1=1;      
  72.           COLUMN_OUTPUT2=1;
  73.           COLUMN_OUTPUT3=0;  
  74. }
  75.           Su16KeyCnt=0;
  76.           Su8KeyStep++;  
  77.           break;

  78.      case 2:      //等待列输出稳定,但不是去抖动延时
  79.           Su16KeyCnt++;
  80.           if(Su16KeyCnt>=2)
  81.           {
  82.              Su16KeyCnt=0;
  83.              Su8KeyStep++;     
  84.           }
  85.           break;

  86.      case 3:
  87.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  88.           {  
  89.              Su8KeyStep=1;  
  90.              Su8KeyLock=0;
  91.              Su16KeyCnt=0;  

  92.              Su8ColumnRecord++;  
  93.              if(Su8ColumnRecord>=3)  
  94.              {
  95.                 Su8ColumnRecord=0;
  96.              }     
  97.           }
  98.           else if(0==Su8KeyLock)
  99.           {
  100.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  101.               {
  102.                   Su16KeyCnt++;  
  103.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  104.                   {
  105.                       Su8KeyLock=1;

  106.                       if(0==Su8ColumnRecord)  
  107.                       {
  108.                            vGu8SingleKeySec=1;     //单击任务,触发1号键 对应S1键

  109.                            //“马上更新输出列的信号状态”
  110.                        COLUMN_OUTPUT1=1;   
  111.                        COLUMN_OUTPUT2=0;   //列2也输出0,非常关键的代码!
  112.                        COLUMN_OUTPUT3=1;  

  113.                        Su16KeyCnt=0;  //去抖动延时清零,为下一步计时做准备
  114.                            Su8KeyStep++;   //切换到下一步步骤
  115.                       }
  116.                       else if(1==Su8ColumnRecord)  
  117.                       {
  118.                            vGu8SingleKeySec=2;  
  119.                       }
  120.                       else if(2==Su8ColumnRecord)  
  121.                       {
  122.                            vGu8SingleKeySec=3;  
  123.                       }
  124.                   }

  125.               }
  126.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  127.               {
  128.                   Su16KeyCnt++;  
  129.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  130.                   {
  131.                       Su8KeyLock=1;

  132.                       if(0==Su8ColumnRecord)  
  133.                       {
  134.                            vGu8SingleKeySec=4;  
  135.                       }
  136.                       else if(1==Su8ColumnRecord)  
  137.                       {
  138.                            vGu8SingleKeySec=5;  
  139.                       }
  140.                       else if(2==Su8ColumnRecord)  
  141.                       {
  142.                            vGu8SingleKeySec=6;  
  143.                       }
  144.                   }   
  145.               }
  146.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  147.               {
  148.                   Su16KeyCnt++;
  149.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  150.                   {
  151.                       Su8KeyLock=1;
  152.                       if(0==Su8ColumnRecord)  
  153.                       {
  154.                            vGu8SingleKeySec=7;  
  155.                       }
  156.                       else if(1==Su8ColumnRecord)  
  157.                       {
  158.                            vGu8SingleKeySec=8;  
  159.                       }
  160.                       else if(2==Su8ColumnRecord)  
  161.                       {
  162.                            vGu8SingleKeySec=9;  
  163.                       }
  164.                   }   
  165.               }

  166.           }
  167.           break;
  168.      case 4:             //等待列输出稳定,但不是去抖动延时
  169.           Su16KeyCnt++;
  170.           if(Su16KeyCnt>=2)   
  171.           {
  172.               Su16KeyCnt=0;
  173. Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
  174.               Su8KeyStep++;     
  175.           }
  176.           break;

  177.      case 5:    //判断S2按键
  178.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键没有被按下
  179.           {  
  180.              Su8KeyLock=0;
  181.              Su16KeyCnt=0;  

  182.              //“马上更新输出列的信号状态”
  183.          COLUMN_OUTPUT1=0;     //列1输出0,非常关键的代码!
  184.          COLUMN_OUTPUT2=1;  
  185.          COLUMN_OUTPUT3=1;  

  186.              Su8KeyStep++;    //切换到下一个步骤,监控S1是否率先已经松开
  187.           }
  188. else if(0==Su8KeyLock)
  189. {   
  190.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键被按下
  191.               {
  192.                   Su16KeyCnt++;
  193.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  194.                   {
  195.                         Su8KeyLock=1;  //组合按键的自锁
  196. vGu8DoubleKeySec=1;   //触发组合按键(S1+S2)
  197.                   }
  198.               }



  199. }
  200.           break;

  201.      case 6:       //等待列输出稳定,但不是去抖动延时
  202.           Su16KeyCnt++;
  203.           if(Su16KeyCnt>=2)   
  204.           {
  205.               Su16KeyCnt=0;
  206. Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
  207.               Su8KeyStep++;     
  208.           }
  209.           break;

  210.      case 7:    //监控S1按键是否率先已经松开
  211.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  212. {
  213.               Su16KeyCnt=0;
  214. Su8KeyLock=0;   
  215.               Su8KeyStep=1;   //如果S1按键已经松开,返回到第一个运行步骤重新开始扫描

  216.               Su8ColumnRecord++;  
  217.               if(Su8ColumnRecord>=3)  
  218.               {
  219.                   Su8ColumnRecord=0;
  220.               }  
  221. }
  222. else
  223. {
  224.               //“马上更新输出列的信号状态”
  225.           COLUMN_OUTPUT1=1;   
  226.           COLUMN_OUTPUT2=0;     //列2输出0,非常关键的代码!
  227.           COLUMN_OUTPUT3=1;  
  228.               Su8KeyStep=4;   //如果S1按键没有松开,继续返回判断S2是否已按下
  229. }
  230.           break;
  231.    }

  232. }

  233. void SingleKeyTask(void)   
  234. {
  235. if(0==vGu8SingleKeySec)
  236. {
  237. return;
  238. }

  239. switch(vGu8SingleKeySec)
  240. {
  241.    case 1:     //S1按键的单击任务,更改LED灯的显示状态

  242.             if(0==Gu8LedStatus)
  243.             {
  244.                 Gu8LedStatus=1;
  245.                 LedOpen();   
  246. }
  247.             else
  248.             {
  249.                 Gu8LedStatus=0;
  250.                 LedClose();  
  251. }

  252. vGu8SingleKeySec=0;  
  253. break;

  254.    default:  

  255. vGu8SingleKeySec=0;  
  256. break;

  257. }
  258. }

  259. void DoubleKeyTask(void)   
  260. {
  261. if(0==vGu8DoubleKeySec)
  262. {
  263. return;
  264. }

  265. switch(vGu8DoubleKeySec)
  266. {
  267.         case 1:     //S1与S2的组合按键触发,发出“嘀”一声

  268.               vGu8BeepTimerFlag=0;  
  269. vGu16BeepTimerCnt=KEY_VOICE_TIME;
  270.               vGu8BeepTimerFlag=1;  

  271. vGu8DoubleKeySec=0;  
  272. break;
  273. }

  274. }

  275. void T0_time() interrupt 1     
  276. {
  277. VoiceScan();  
  278. KeyScan();   

  279. TH0=0xfc;   
  280. TL0=0x66;   
  281. }


  282. void SystemInitial(void)
  283. {
  284. TMOD=0x01;  
  285. TH0=0xfc;   
  286. TL0=0x66;   
  287. EA=1;      
  288. ET0=1;      
  289. TR0=1;      
  290. }

  291. void Delay(unsigned long u32DelayTime)
  292. {
  293.     for(;u32DelayTime>0;u32DelayTime--);
  294. }

  295. void PeripheralInitial(void)
  296. {
  297. if(0==Gu8LedStatus)
  298. {
  299.     LedClose();  
  300. }
  301. else
  302. {
  303.     LedOpen();   
  304. }
  305. }

  306. void BeepOpen(void)
  307. {
  308. P3_4=0;  
  309. }

  310. void BeepClose(void)
  311. {
  312. P3_4=1;  
  313. }

  314. void LedOpen(void)
  315. {
  316. P1_4=0;  
  317. }

  318. void LedClose(void)
  319. {
  320. P1_4=1;  
  321. }

  322. void VoiceScan(void)
  323. {

  324.           static unsigned char Su8Lock=0;  

  325. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  326.           {
  327.                   if(0==Su8Lock)
  328.                   {
  329.                    Su8Lock=1;  
  330. BeepOpen();
  331.      }
  332.     else  
  333. {     

  334.                        vGu16BeepTimerCnt--;         

  335.                    if(0==vGu16BeepTimerCnt)
  336.                    {
  337.                            Su8Lock=0;     
  338. BeepClose();  
  339.                    }

  340. }
  341.           }         
  342. }
复制代码






乐于分享,勇于质疑!
107#
 楼主| 发表于 2018-1-14 10:31:53 | 只看该作者
本帖最后由 jianhong_wu 于 2018-1-14 10:47 编辑

第一百零三节: 两个“任意行输入”矩阵按键的“无序”组合触发。
第一百零三节_pdf文件.pdf (113.65 KB, 下载次数: 1460)
【103.1   “无序”组合触发。】

               
                上图103.1.1  有源蜂鸣器电路

   
                上图103.1.2  LED电路


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

       “无序”是指两个组合按键不分先后顺序,都能构成组合触发。比如,要触发组合键(S1+S2),先按S1再按S2,或者先按S2再按S1,功能都是一样的。
       本节程序功能如下:(1)S1每单击一次,P1.4所在的LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)S2每单击一次,P1.5所在的LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(3)如果先按住S1再按S2,或者先按住S2再按S1,都认为构造了“无序”组合键,蜂鸣器发出“嘀”的一声。
  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 LedOpen_P1_4(void);   
  11. void LedClose_P1_4(void);
  12. void LedOpen_P1_5(void);   
  13. void LedClose_P1_5(void);

  14. void VoiceScan(void);
  15. void KeyScan(void);   
  16. void SingleKeyTask(void);   
  17. void DoubleKeyTask(void);   

  18. sbit P3_4=P3^4;      
  19. sbit P1_4=P1^4;    //P1.4所在的LED
  20. sbit P1_5=P1^5;    //P1.5所在的LED

  21. sbit ROW_INPUT1=P2^2;  //第1行输入口。
  22. sbit ROW_INPUT2=P2^1;  //第2行输入口。
  23. sbit ROW_INPUT3=P2^0;  //第3行输入口。

  24. sbit COLUMN_OUTPUT1=P2^5;  //第1列输出口。
  25. sbit COLUMN_OUTPUT2=P2^4;  //第2列输出口。
  26. sbit COLUMN_OUTPUT3=P2^3;  //第3列输出口。

  27. volatile unsigned char vGu8BeepTimerFlag=0;  
  28. volatile unsigned int vGu16BeepTimerCnt=0;  

  29. unsigned char Gu8LedStatus_P1_4=0;  //P1.4所在的LED的状态
  30. unsigned char Gu8LedStatus_P1_5=0;  //P1.5所在的LED的状态
  31. volatile unsigned char vGu8SingleKeySec=0;  
  32. volatile unsigned char vGu8DoubleKeySec=0;  

  33. void main()
  34. {
  35. SystemInitial();            
  36. Delay(10000);               
  37. PeripheralInitial();      
  38.     while(1)  
  39. {  
  40.         SingleKeyTask();   
  41.         DoubleKeyTask();   
  42.     }
  43. }

  44. /* 注释一:
  45. *  矩阵按键“无序”触发的两个最关键地方:
  46. *  (1)如果是S1按键先被按下并且单击触发之后,“马上更新输出列的信号状态”,然后切换到
  47. *   “S1后面所在的步骤里”,进入到S1和S2两个按键的轮番循环监控之中,如果发现S1按键率先
  48. *   被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
  49. *  (2)如果是S2按键先被按下并且单击触发之后,“马上更新输出列的信号状态”,然后切换到
  50. *   “S2后面所在的步骤里”,进入到S1和S2两个按键的轮番循环监控之中,如果发现S2按键率先
  51. *   被松开了,就把步骤切换到开始的第一步,重新开始新一轮的按键扫描。
  52. *  (3)上面两个描述中的两种步骤,“S1后面所在的步骤里”和“S2后面所在的步骤里”是分开的,
  53. *   不共用的,这是本节破题的关键。
  54. */

  55. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  56. {
  57.    static unsigned char Su8KeyLock=0;        
  58.    static unsigned int  Su16KeyCnt=0;  
  59.    static unsigned char Su8KeyStep=1;  

  60.    static unsigned char Su8ColumnRecord=0;  

  61.    switch(Su8KeyStep)
  62.    {
  63.      case 1:   
  64.           if(0==Su8ColumnRecord)  
  65. {
  66.           COLUMN_OUTPUT1=0;      
  67.           COLUMN_OUTPUT2=1;
  68.           COLUMN_OUTPUT3=1;  
  69. }
  70.           else if(1==Su8ColumnRecord)  
  71. {
  72.           COLUMN_OUTPUT1=1;      
  73.           COLUMN_OUTPUT2=0;
  74.           COLUMN_OUTPUT3=1;  
  75. }
  76.           else     
  77. {
  78.           COLUMN_OUTPUT1=1;      
  79.           COLUMN_OUTPUT2=1;
  80.           COLUMN_OUTPUT3=0;  
  81. }
  82.           Su16KeyCnt=0;
  83.           Su8KeyStep++;  
  84.           break;

  85.      case 2:      //等待列输出稳定,但不是去抖动延时
  86.           Su16KeyCnt++;
  87.           if(Su16KeyCnt>=2)
  88.           {
  89.              Su16KeyCnt=0;
  90.              Su8KeyStep++;     
  91.           }
  92.           break;

  93.      case 3:
  94.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  95.           {  
  96.              Su8KeyStep=1;  
  97.              Su8KeyLock=0;
  98.              Su16KeyCnt=0;  

  99.              Su8ColumnRecord++;  
  100.              if(Su8ColumnRecord>=3)  
  101.              {
  102.                 Su8ColumnRecord=0;
  103.              }     
  104.           }
  105.           else if(0==Su8KeyLock)
  106.           {
  107.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  108.               {
  109.                   Su16KeyCnt++;  
  110.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  111.                   {
  112.                       Su8KeyLock=1;

  113.                       if(0==Su8ColumnRecord)  
  114.                       {
  115.                            vGu8SingleKeySec=1;     //单击任务,触发1号键 对应S1键

  116.                            //“马上更新输出列的信号状态”
  117.                        COLUMN_OUTPUT1=1;   
  118.                        COLUMN_OUTPUT2=0;   //列2也输出0,下一步监控S2,非常关键的代码!
  119.                        COLUMN_OUTPUT3=1;  

  120.                        Su16KeyCnt=0;  //去抖动延时清零,为下一步计时做准备
  121.                            Su8KeyStep=4;   //切换到“S1后面所在的步骤里”,破题的关键!!!
  122.                       }
  123.                       else if(1==Su8ColumnRecord)  
  124.                       {
  125.                            vGu8SingleKeySec=2;    //单击任务,触发2号键 对应S2键

  126.                            //“马上更新输出列的信号状态”
  127.                        COLUMN_OUTPUT1=0;  //列1也输出0,下一步监控S1,非常关键的代码!
  128.                        COLUMN_OUTPUT2=1;  
  129.                        COLUMN_OUTPUT3=1;  

  130.                        Su16KeyCnt=0;  //去抖动延时清零,为下一步计时做准备
  131.                            Su8KeyStep=8;   //切换到“S2后面所在的步骤里”,破题的关键!!!
  132.                       }
  133.                       else if(2==Su8ColumnRecord)  
  134.                       {
  135.                            vGu8SingleKeySec=3;  
  136.                       }
  137.                   }

  138.               }
  139.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  140.               {
  141.                   Su16KeyCnt++;  
  142.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  143.                   {
  144.                       Su8KeyLock=1;

  145.                       if(0==Su8ColumnRecord)  
  146.                       {
  147.                            vGu8SingleKeySec=4;  
  148.                       }
  149.                       else if(1==Su8ColumnRecord)  
  150.                       {
  151.                            vGu8SingleKeySec=5;  
  152.                       }
  153.                       else if(2==Su8ColumnRecord)  
  154.                       {
  155.                            vGu8SingleKeySec=6;  
  156.                       }
  157.                   }   
  158.               }
  159.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  160.               {
  161.                   Su16KeyCnt++;
  162.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  163.                   {
  164.                       Su8KeyLock=1;
  165.                       if(0==Su8ColumnRecord)  
  166.                       {
  167.                            vGu8SingleKeySec=7;  
  168.                       }
  169.                       else if(1==Su8ColumnRecord)  
  170.                       {
  171.                            vGu8SingleKeySec=8;  
  172.                       }
  173.                       else if(2==Su8ColumnRecord)  
  174.                       {
  175.                            vGu8SingleKeySec=9;  
  176.                       }
  177.                   }   
  178.               }

  179.           }
  180.           break;

  181. /*--------------“S1后面所在的步骤里”------------------*/
  182.      case 4:             //等待列输出稳定,但不是去抖动延时
  183.           Su16KeyCnt++;
  184.           if(Su16KeyCnt>=2)   
  185.           {
  186.               Su16KeyCnt=0;
  187. Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
  188.               Su8KeyStep++;     
  189.           }
  190.           break;

  191.      case 5:    //判断S2按键
  192.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键没有被按下
  193.           {  
  194.              Su8KeyLock=0;
  195.              Su16KeyCnt=0;  

  196.              //“马上更新输出列的信号状态”
  197.          COLUMN_OUTPUT1=0;     //列1输出0,下一步监控S1,非常关键的代码!
  198.          COLUMN_OUTPUT2=1;  
  199.          COLUMN_OUTPUT3=1;  

  200.              Su8KeyStep++;    //切换到下一个步骤,监控S1是否率先已经松开
  201.           }
  202. else if(0==Su8KeyLock)
  203. {   
  204.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S2按键被按下
  205.               {
  206.                   Su16KeyCnt++;
  207.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  208.                   {
  209.                         Su8KeyLock=1;  //组合按键的自锁
  210. vGu8DoubleKeySec=1;   //触发组合按键(S1+S2)
  211.                   }
  212.               }



  213. }
  214.           break;

  215.      case 6:       //等待列输出稳定,但不是去抖动延时
  216.           Su16KeyCnt++;
  217.           if(Su16KeyCnt>=2)   
  218.           {
  219.               Su16KeyCnt=0;
  220. Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
  221.               Su8KeyStep++;     
  222.           }
  223.           break;

  224.      case 7:    //监控S1按键是否率先已经松开
  225.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  226. {
  227.               Su16KeyCnt=0;
  228. Su8KeyLock=0;   
  229.               Su8KeyStep=1;   //如果S1按键已经松开,返回到第一个运行步骤重新开始扫描

  230.               Su8ColumnRecord++;  
  231.               if(Su8ColumnRecord>=3)  
  232.               {
  233.                   Su8ColumnRecord=0;
  234.               }  
  235. }
  236. else
  237. {
  238.               //“马上更新输出列的信号状态”
  239.           COLUMN_OUTPUT1=1;   
  240.           COLUMN_OUTPUT2=0;     //列2输出0,下一步监控S2,非常关键的代码!
  241.           COLUMN_OUTPUT3=1;  
  242.               Su8KeyStep=4;   //如果S1按键没有松开,继续返回判断S2是否已按下
  243. }
  244.           break;

  245. /*--------------“S2后面所在的步骤里”------------------*/
  246.      case 8:             //等待列输出稳定,但不是去抖动延时
  247.           Su16KeyCnt++;
  248.           if(Su16KeyCnt>=2)   
  249.           {
  250.               Su16KeyCnt=0;
  251. Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
  252.               Su8KeyStep++;     
  253.           }
  254.           break;

  255.      case 9:    //判断S1按键
  256.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S1按键没有被按下
  257.           {  
  258.              Su8KeyLock=0;
  259.              Su16KeyCnt=0;  

  260.              //“马上更新输出列的信号状态”
  261.          COLUMN_OUTPUT1=1;     
  262.          COLUMN_OUTPUT2=0;  //列2输出0,下一步监控S2,非常关键的代码!
  263.          COLUMN_OUTPUT3=1;  

  264.              Su8KeyStep++;    //切换到下一个步骤,监控S2是否率先已经松开
  265.           }
  266. else if(0==Su8KeyLock)
  267. {   
  268.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //S1按键被按下
  269.               {
  270.                   Su16KeyCnt++;
  271.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  272.                   {
  273.                         Su8KeyLock=1;  //组合按键的自锁
  274. vGu8DoubleKeySec=1;   //触发组合按键(S1+S2)
  275.                   }
  276.               }



  277. }
  278.           break;

  279.      case 10:       //等待列输出稳定,但不是去抖动延时
  280.           Su16KeyCnt++;
  281.           if(Su16KeyCnt>=2)   
  282.           {
  283.               Su16KeyCnt=0;
  284. Su8KeyLock=0;   //关键语句!自锁清零,为下一步自锁组合按键做准备
  285.               Su8KeyStep++;     
  286.           }
  287.           break;

  288.      case 11:    //监控S2按键是否率先已经松开
  289.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  290. {
  291.               Su16KeyCnt=0;
  292. Su8KeyLock=0;   
  293.               Su8KeyStep=1;   //如果S2按键已经松开,返回到第一个运行步骤重新开始扫描

  294.               Su8ColumnRecord++;  
  295.               if(Su8ColumnRecord>=3)  
  296.               {
  297.                   Su8ColumnRecord=0;
  298.               }  
  299. }
  300. else
  301. {
  302.               //“马上更新输出列的信号状态”
  303.           COLUMN_OUTPUT1=0;    //列1输出0,下一步监控S1,非常关键的代码!
  304.           COLUMN_OUTPUT2=1;     
  305.           COLUMN_OUTPUT3=1;  
  306.               Su8KeyStep=8;   //如果S2按键没有松开,继续返回判断S1是否已按下
  307. }
  308.           break;


  309.    }

  310. }

  311. void SingleKeyTask(void)   
  312. {
  313. if(0==vGu8SingleKeySec)
  314. {
  315. return;
  316. }

  317. switch(vGu8SingleKeySec)
  318. {
  319.    case 1:     //S1按键的单击任务,更改P1.4所在的LED灯的显示状态

  320.             if(0==Gu8LedStatus_P1_4)
  321.             {
  322.                 Gu8LedStatus_P1_4=1;
  323.                 LedOpen_P1_4();   
  324. }
  325.             else
  326.             {
  327.                 Gu8LedStatus_P1_4=0;
  328.                 LedClose_P1_4();  
  329. }

  330. vGu8SingleKeySec=0;  
  331. break;

  332.    case 2:     //S2按键的单击任务,更改P1.5所在的LED灯的显示状态

  333.             if(0==Gu8LedStatus_P1_5)
  334.             {
  335.                 Gu8LedStatus_P1_5=1;
  336.                 LedOpen_P1_5();   
  337. }
  338.             else
  339.             {
  340.                 Gu8LedStatus_P1_5=0;
  341.                 LedClose_P1_5();  
  342. }

  343. vGu8SingleKeySec=0;  
  344. break;

  345.    default:  

  346. vGu8SingleKeySec=0;  
  347. break;

  348. }
  349. }

  350. void DoubleKeyTask(void)   
  351. {
  352. if(0==vGu8DoubleKeySec)
  353. {
  354. return;
  355. }

  356. switch(vGu8DoubleKeySec)
  357. {
  358.         case 1:     //S1与S2的组合按键触发,发出“嘀”一声

  359.               vGu8BeepTimerFlag=0;  
  360. vGu16BeepTimerCnt=KEY_VOICE_TIME;
  361.               vGu8BeepTimerFlag=1;  

  362. vGu8DoubleKeySec=0;  
  363. break;
  364. }

  365. }

  366. void T0_time() interrupt 1     
  367. {
  368. VoiceScan();  
  369. KeyScan();   

  370. TH0=0xfc;   
  371. TL0=0x66;   
  372. }


  373. void SystemInitial(void)
  374. {
  375. TMOD=0x01;  
  376. TH0=0xfc;   
  377. TL0=0x66;   
  378. EA=1;      
  379. ET0=1;      
  380. TR0=1;      
  381. }

  382. void Delay(unsigned long u32DelayTime)
  383. {
  384.     for(;u32DelayTime>0;u32DelayTime--);
  385. }

  386. void PeripheralInitial(void)
  387. {
  388. if(0==Gu8LedStatus_P1_4)
  389. {
  390.     LedClose_P1_4();  
  391. }
  392. else
  393. {
  394.     LedOpen_P1_4();   
  395. }

  396. if(0==Gu8LedStatus_P1_5)
  397. {
  398.     LedClose_P1_5();  
  399. }
  400. else
  401. {
  402.     LedOpen_P1_5();   
  403. }

  404. }

  405. void BeepOpen(void)
  406. {
  407. P3_4=0;  
  408. }

  409. void BeepClose(void)
  410. {
  411. P3_4=1;  
  412. }

  413. void LedOpen_P1_4(void)
  414. {
  415. P1_4=0;  
  416. }

  417. void LedClose_P1_4(void)
  418. {
  419. P1_4=1;  
  420. }

  421. void LedOpen_P1_5(void)
  422. {
  423. P1_5=0;  
  424. }

  425. void LedClose_P1_5(void)
  426. {
  427. P1_5=1;  
  428. }

  429. void VoiceScan(void)
  430. {

  431.           static unsigned char Su8Lock=0;  

  432. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  433.           {
  434.                   if(0==Su8Lock)
  435.                   {
  436.                    Su8Lock=1;  
  437. BeepOpen();
  438.      }
  439.     else  
  440. {     

  441.                        vGu16BeepTimerCnt--;         

  442.                    if(0==vGu16BeepTimerCnt)
  443.                    {
  444.                            Su8Lock=0;     
  445. BeepClose();  
  446.                    }

  447. }
  448.           }         
  449. }
复制代码






乐于分享,勇于质疑!
108#
 楼主| 发表于 2018-1-22 15:15:05 | 只看该作者
本帖最后由 jianhong_wu 于 2018-1-22 15:32 编辑

第一百零四节: 矩阵按键“一键两用”的短按与长按。
第一百零四节_pdf文件.pdf (111.23 KB, 下载次数: 1326)
【104.1   “一键两用”的短按与长按。】

              
                上图104.1.1  有源蜂鸣器电路

        
                上图104.1.2  LED电路


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

        矩阵按键与前面章节独立按键的“短按与长按”的处理思路是一样的,本节讲矩阵按键的“短按与长按”,也算是重温之前章节讲的内容。“短按与长按”的原理是依赖“按键按下的时间长度”来区分识别。“短按”是指从按下的“下降沿”到松手的“上升沿”时间,“长按”是指从按下的“下降沿”到一直按住不松手的“低电平持续时间”。本节的例程功能如下:(1)S1每“短按”一次,LED要么从“灭”变成“亮”,要么从“亮”变成“灭”,在两种状态之间切换。(2)S1每“长按”一次,蜂鸣器发出“嘀”的一声。代码如下:
  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50     

  3. #define KEY_SHORT_TIME  20    //按键的“短按”兼“滤波”的“稳定时间”
  4. #define KEY_LONG_TIME  400    //按键的“长按”兼“滤波”的“稳定时间”

  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_P1_4(void);   
  12. void LedClose_P1_4(void);

  13. void VoiceScan(void);
  14. void KeyScan(void);   
  15. void KeyTask(void);   

  16. sbit P3_4=P3^4;      
  17. sbit P1_4=P1^4;   

  18. sbit ROW_INPUT1=P2^2;  //第1行输入口。
  19. sbit ROW_INPUT2=P2^1;  //第2行输入口。
  20. sbit ROW_INPUT3=P2^0;  //第3行输入口。

  21. sbit COLUMN_OUTPUT1=P2^5;  //第1列输出口。
  22. sbit COLUMN_OUTPUT2=P2^4;  //第2列输出口。
  23. sbit COLUMN_OUTPUT3=P2^3;  //第3列输出口。

  24. volatile unsigned char vGu8BeepTimerFlag=0;  
  25. volatile unsigned int vGu16BeepTimerCnt=0;  

  26. unsigned char Gu8LedStatus_P1_4=0;
  27. volatile unsigned char vGu8KeySec=0;  //短按与长按共用一个全局变量vGu8KeySec来传递按键信息

  28. void main()
  29. {
  30. SystemInitial();            
  31. Delay(10000);               
  32. PeripheralInitial();      
  33.     while(1)  
  34. {  
  35.         KeyTask();   
  36.     }
  37. }

  38. /* 注释一:
  39. *  本节破题的关键:
  40. *  矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
  41. *  “短按”与“长按”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
  42. *  “大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1。
  43. */

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

  49.    static unsigned char Su8ColumnRecord=0;  

  50.    static unsigned char Su8KeyShortFlag_S1=0;  //S1按键专属的“短按”触发标志  

  51.    switch(Su8KeyStep)
  52.    {
  53.      case 1:   
  54.           if(0==Su8ColumnRecord)  
  55. {
  56.           COLUMN_OUTPUT1=0;      
  57.           COLUMN_OUTPUT2=1;
  58.           COLUMN_OUTPUT3=1;  
  59. }
  60.           else if(1==Su8ColumnRecord)  
  61. {
  62.           COLUMN_OUTPUT1=1;      
  63.           COLUMN_OUTPUT2=0;
  64.           COLUMN_OUTPUT3=1;  
  65. }
  66.           else     
  67. {
  68.           COLUMN_OUTPUT1=1;      
  69.           COLUMN_OUTPUT2=1;
  70.           COLUMN_OUTPUT3=0;  
  71. }
  72.           Su16KeyCnt=0;
  73.           Su8KeyStep++;  
  74.           break;

  75.      case 2:      //等待列输出稳定,但不是去抖动延时
  76.           Su16KeyCnt++;
  77.           if(Su16KeyCnt>=2)
  78.           {
  79.              Su16KeyCnt=0;
  80.              Su8KeyStep++;     
  81.           }
  82.           break;

  83.      case 3:
  84.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  85.           {  
  86.              Su8KeyStep=1;  
  87.              Su8KeyLock=0;
  88.              Su16KeyCnt=0;  

  89.              if(1==Su8KeyShortFlag_S1)  //松手的时候,如果“短按”标志有效就触发一次“短按”
  90.              {
  91. Su8KeyShortFlag_S1=0;     //先清零“短按”标志避免一直触发。
  92. vGu8KeySec=1;    //触发S1的“短按”
  93. }  

  94.              Su8ColumnRecord++;  
  95.              if(Su8ColumnRecord>=3)  
  96.              {
  97.                 Su8ColumnRecord=0;
  98.              }     
  99.           }
  100.           else if(0==Su8KeyLock)
  101.           {
  102.               //以下第1行,直接把S1按键单独扣出来,用“&&0==Su8ColumnRecord”作为筛选条件
  103.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3&&0==Su8ColumnRecord)
  104.               {
  105.                   Su16KeyCnt++;  
  106.                   if(Su16KeyCnt>=KEY_SHORT_TIME) //“短按”兼“滤波”的“稳定时间”      
  107. {
  108.                       //注意,这里不能“自锁”。后面“长按”触发的时候才“自锁”。
  109.                       Su8KeyShortFlag_S1=1;    //S1的“短按”标志有效,待松手时触发。
  110.                  }

  111.                  if(Su16KeyCnt>=KEY_LONG_TIME) //“长按”兼“滤波”的“稳定时间”
  112.                  {
  113.                       Su8KeyLock=1;      //此时“长按”触发才“自锁”
  114. Su8KeyShortFlag_S1=0;  //既然此时“长按”有效,那么就要废除潜在的“短按”。
  115.                       vGu8KeySec=21; //触发S1的“长按”
  116.                  }

  117.               }
  118.               else if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  119.               {
  120.                   Su16KeyCnt++;  
  121.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  122.                   {
  123.                       Su8KeyLock=1;

  124. //既然S1按键已经被上面几行代码单独扣出来,这里就直接从S2按键开始判断
  125.                       if(1==Su8ColumnRecord)  
  126.                       {
  127.                            vGu8KeySec=2;   
  128.                       }
  129.                       else if(2==Su8ColumnRecord)  
  130.                       {
  131.                            vGu8KeySec=3;  
  132.                       }
  133.                   }

  134.               }
  135.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  136.               {
  137.                   Su16KeyCnt++;  
  138.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  139.                   {
  140.                       Su8KeyLock=1;

  141.                       if(0==Su8ColumnRecord)  
  142.                       {
  143.                            vGu8KeySec=4;  
  144.                       }
  145.                       else if(1==Su8ColumnRecord)  
  146.                       {
  147.                            vGu8KeySec=5;  
  148.                       }
  149.                       else if(2==Su8ColumnRecord)  
  150.                       {
  151.                            vGu8KeySec=6;  
  152.                       }
  153.                   }   
  154.               }
  155.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  156.               {
  157.                   Su16KeyCnt++;
  158.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  159.                   {
  160.                       Su8KeyLock=1;
  161.                       if(0==Su8ColumnRecord)  
  162.                       {
  163.                            vGu8KeySec=7;  
  164.                       }
  165.                       else if(1==Su8ColumnRecord)  
  166.                       {
  167.                            vGu8KeySec=8;  
  168.                       }
  169.                       else if(2==Su8ColumnRecord)  
  170.                       {
  171.                            vGu8KeySec=9;  
  172.                       }
  173.                   }   
  174.               }

  175.           }
  176.           break;

  177.    }

  178. }

  179. void KeyTask(void)   
  180. {
  181. if(0==vGu8KeySec)
  182. {
  183. return;
  184. }

  185. switch(vGu8KeySec)
  186. {
  187.    case 1:     //S1按键的“短按”任务,更改P1.4所在的LED灯的显示状态

  188.             if(0==Gu8LedStatus_P1_4)
  189.             {
  190.                 Gu8LedStatus_P1_4=1;
  191.                 LedOpen_P1_4();   
  192. }
  193.             else
  194.             {
  195.                 Gu8LedStatus_P1_4=0;
  196.                 LedClose_P1_4();  
  197. }

  198. vGu8KeySec=0;  
  199. break;

  200. //以下S1按键的“长按”直接选择case 21的“21”,是为了不占用前排其它按键的编号。
  201.    case 21:     //S1按键的“长按”任务,蜂鸣器发出“嘀”一声
  202. vGu8BeepTimerFlag=0;  
  203. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //蜂鸣器发出“嘀”一声
  204. vGu8BeepTimerFlag=1;

  205. vGu8KeySec=0;  
  206. break;

  207.    default:  

  208. vGu8KeySec=0;  
  209. break;

  210. }
  211. }

  212. void T0_time() interrupt 1     
  213. {
  214. VoiceScan();  
  215. KeyScan();   

  216. TH0=0xfc;   
  217. TL0=0x66;   
  218. }


  219. void SystemInitial(void)
  220. {
  221. TMOD=0x01;  
  222. TH0=0xfc;   
  223. TL0=0x66;   
  224. EA=1;      
  225. ET0=1;      
  226. TR0=1;      
  227. }

  228. void Delay(unsigned long u32DelayTime)
  229. {
  230.     for(;u32DelayTime>0;u32DelayTime--);
  231. }

  232. void PeripheralInitial(void)
  233. {
  234. if(0==Gu8LedStatus_P1_4)
  235. {
  236.     LedClose_P1_4();  
  237. }
  238. else
  239. {
  240.     LedOpen_P1_4();   
  241. }

  242. }

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

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

  251. void LedOpen_P1_4(void)
  252. {
  253. P1_4=0;  
  254. }

  255. void LedClose_P1_4(void)
  256. {
  257. P1_4=1;  
  258. }

  259. void VoiceScan(void)
  260. {

  261.           static unsigned char Su8Lock=0;  

  262. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  263.           {
  264.                   if(0==Su8Lock)
  265.                   {
  266.                    Su8Lock=1;  
  267. BeepOpen();
  268.      }
  269.     else  
  270. {     

  271.                        vGu16BeepTimerCnt--;         

  272.                    if(0==vGu16BeepTimerCnt)
  273.                    {
  274.                            Su8Lock=0;     
  275. BeepClose();  
  276.                    }

  277. }
  278.           }         
  279. }
复制代码





乐于分享,勇于质疑!
109#
 楼主| 发表于 2018-1-28 12:42:30 | 只看该作者
本帖最后由 jianhong_wu 于 2018-1-28 13:09 编辑

第一百零五节: 矩阵按键按住不松手的连续均匀触发。
第一百零五节_pdf文件.pdf (130.76 KB, 下载次数: 1272)
【105.1   按住不松手的连续均匀触发。】

               
                上图105.1.1  有源蜂鸣器电路

      
                上图105.1.2  LED电路


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

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

  2. #define KEY_VOICE_TIME   50     

  3. #define KEY_SHORT_TIME  20    //按键单击的“滤波”时间

  4. #define KEY_ENTER_CONTINUITY_TIME    240  //按键“从单击进入连击”的间隔时间
  5. #define KEY_CONTINUITY_TIME    64   //按键“连击”的间隔时间

  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 ROW_INPUT1=P2^2;  //第1行输入口。
  19. sbit ROW_INPUT2=P2^1;  //第2行输入口。
  20. sbit ROW_INPUT3=P2^0;  //第3行输入口。

  21. sbit COLUMN_OUTPUT1=P2^5;  //第1列输出口。
  22. sbit COLUMN_OUTPUT2=P2^4;  //第2列输出口。
  23. sbit COLUMN_OUTPUT3=P2^3;  //第3列输出口。

  24. volatile unsigned char vGu8BeepTimerFlag=0;  
  25. volatile unsigned int vGu16BeepTimerCnt=0;  

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

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

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

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

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

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

  54. /* 注释二:
  55. *  本节破题的关键:
  56. *  矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
  57. *  “单击”与“连续均匀触发”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
  58. *  “大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1和S9。
  59. *  如果觉得本节的讲解不够详细具体,请先阅读一下前面章节“独立按键按住不松手的连续均匀触发”。
  60. */

  61. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  62. {
  63.    static unsigned char Su8KeyLock=0;        
  64.    static unsigned int  Su16KeyCnt=0;  
  65.    static unsigned char Su8KeyStep=1;  

  66.    static unsigned char Su8ColumnRecord=0;  

  67.    switch(Su8KeyStep)
  68.    {
  69.      case 1:   
  70.           if(0==Su8ColumnRecord)  
  71. {
  72.           COLUMN_OUTPUT1=0;      
  73.           COLUMN_OUTPUT2=1;
  74.           COLUMN_OUTPUT3=1;  
  75. }
  76.           else if(1==Su8ColumnRecord)  
  77. {
  78.           COLUMN_OUTPUT1=1;      
  79.           COLUMN_OUTPUT2=0;
  80.           COLUMN_OUTPUT3=1;  
  81. }
  82.           else     
  83. {
  84.           COLUMN_OUTPUT1=1;      
  85.           COLUMN_OUTPUT2=1;
  86.           COLUMN_OUTPUT3=0;  
  87. }
  88.           Su16KeyCnt=0;
  89.           Su8KeyStep++;  
  90.           break;

  91.      case 2:      //等待列输出稳定,但不是去抖动延时
  92.           Su16KeyCnt++;
  93.           if(Su16KeyCnt>=2)
  94.           {
  95.              Su16KeyCnt=0;
  96.              Su8KeyStep++;     
  97.           }
  98.           break;

  99.      case 3:
  100.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  101.           {  
  102.               Su8KeyStep=1;    //返回步骤1继续扫描
  103.               Su8KeyLock=0;
  104.               Su16KeyCnt=0;  

  105.               Su8ColumnRecord++;  
  106.               if(Su8ColumnRecord>=3)  
  107.               {
  108.                  Su8ColumnRecord=0;
  109.               }     
  110.           }
  111.           else if(0==Su8KeyLock)
  112.           {
  113.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  114.               {
  115.                   Su16KeyCnt++;  
  116.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  117.                   {
  118.                       Su8KeyLock=1;

  119.                       if(0==Su8ColumnRecord)  
  120.                       {
  121.                             vGu8KeySec=1;    //触发一次单击
  122. Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
  123.                             Su8KeyStep=4;    //跳到S1按键的专属区,脱离大众按键
  124.                       }
  125.                       else if(1==Su8ColumnRecord)  
  126.                       {
  127.                             vGu8KeySec=2;   
  128.                       }
  129.                       else if(2==Su8ColumnRecord)  
  130.                       {
  131.                             vGu8KeySec=3;  
  132.                       }
  133.                   }

  134.               }
  135.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  136.               {
  137.                   Su16KeyCnt++;  
  138.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  139.                   {
  140.                       Su8KeyLock=1;

  141.                       if(0==Su8ColumnRecord)  
  142.                       {
  143.                            vGu8KeySec=4;  
  144.                       }
  145.                       else if(1==Su8ColumnRecord)  
  146.                       {
  147.                            vGu8KeySec=5;  
  148.                       }
  149.                       else if(2==Su8ColumnRecord)  
  150.                       {
  151.                            vGu8KeySec=6;  
  152.                       }
  153.                   }   
  154.               }
  155.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  156.               {
  157.                   Su16KeyCnt++;
  158.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  159.                   {
  160.                       Su8KeyLock=1;
  161.                       if(0==Su8ColumnRecord)  
  162.                       {
  163.                            vGu8KeySec=7;  
  164.                       }
  165.                       else if(1==Su8ColumnRecord)  
  166.                       {
  167.                            vGu8KeySec=8;  
  168.                       }
  169.                       else if(2==Su8ColumnRecord)  
  170.                       {
  171.                             vGu8KeySec=9;    //触发一次单击
  172. Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
  173.                             Su8KeyStep=6;    //跳到S9按键的专属区,脱离大众按键
  174.                       }
  175.                   }   
  176.               }

  177.           }
  178.           break;

  179. /*---------S1按键的专属区----------------*/
  180.      case 4:  
  181.           if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
  182.           {
  183. Su16KeyCnt++;
  184. if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
  185.               {
  186. Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
  187.                   Su8KeyStep=5;    //S1按键进入有节奏的连续触发        
  188. }
  189. }
  190.           else //如果期间检查到S1按键已经松手
  191.           {  
  192.               Su8KeyStep=1;    //返回步骤1继续扫描
  193.               Su8KeyLock=0;
  194.               Su16KeyCnt=0;  

  195.               Su8ColumnRecord++;  
  196.               if(Su8ColumnRecord>=3)  
  197.               {
  198.                  Su8ColumnRecord=0;
  199.               }     
  200.           }

  201.           break;
  202.      case 5:  //S1按键进入有节奏的连续触发
  203.           if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
  204.           {
  205. Su16KeyCnt++;
  206.               if(Su16KeyCnt>=KEY_CONTINUITY_TIME)  //该时间是“连击”的时间
  207.               {
  208.                   Su16KeyCnt=0;           //清零,为了继续连击。
  209.                   vGu8KeySec=1;           //触发一次S1按键   
  210. vGu8ShieldVoiceFlag=1;  //因为连击,把当前按键触发的声音屏蔽掉     
  211. }
  212. }
  213.           else //如果期间检查到S1按键已经松手
  214.           {  
  215.               Su8KeyStep=1;    //返回步骤1继续扫描
  216.               Su8KeyLock=0;
  217.               Su16KeyCnt=0;  

  218.               Su8ColumnRecord++;  
  219.               if(Su8ColumnRecord>=3)  
  220.               {
  221.                  Su8ColumnRecord=0;
  222.               }     
  223.           }
  224.           break;

  225. /*---------S9按键的专属区----------------*/
  226.      case 6:  
  227.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
  228.           {
  229. Su16KeyCnt++;
  230. if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
  231.               {
  232. Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
  233.                   Su8KeyStep=7;    //S9按键进入有节奏的连续触发        
  234. }
  235. }
  236.           else //如果期间检查到S9按键已经松手
  237.           {  
  238.               Su8KeyStep=1;    //返回步骤1继续扫描
  239.               Su8KeyLock=0;
  240.               Su16KeyCnt=0;  

  241.               Su8ColumnRecord++;  
  242.               if(Su8ColumnRecord>=3)  
  243.               {
  244.                  Su8ColumnRecord=0;
  245.               }     
  246.           }

  247.           break;
  248.      case 7:  //S9按键进入有节奏的连续触发
  249.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
  250.           {
  251. Su16KeyCnt++;
  252.               if(Su16KeyCnt>=KEY_CONTINUITY_TIME)  //该时间是“连击”的时间
  253.               {
  254.                   Su16KeyCnt=0;           //清零,为了继续连击。
  255.                   vGu8KeySec=9;           //触发一次S9按键   
  256. vGu8ShieldVoiceFlag=1;  //因为连击,把当前按键触发的声音屏蔽掉     
  257. }
  258. }
  259.           else //如果期间检查到S9按键已经松手
  260.           {  
  261.               Su8KeyStep=1;    //返回步骤1继续扫描
  262.               Su8KeyLock=0;
  263.               Su16KeyCnt=0;  

  264.               Su8ColumnRecord++;  
  265.               if(Su8ColumnRecord>=3)  
  266.               {
  267.                  Su8ColumnRecord=0;
  268.               }     
  269.           }
  270.           break;


  271.    }

  272. }

  273. void KeyTask(void)   
  274. {
  275. if(0==vGu8KeySec)
  276. {
  277. return;
  278. }

  279. switch(vGu8KeySec)
  280. {
  281.    case 1:     //S1按键的任务
  282. if(Gu8LedStatus>0)
  283. {
  284. Gu8LedStatus--;  //控制LED“往左边跑”
  285. Gu8DisplayUpdate=1;  //刷新显示
  286. }

  287. if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
  288. {
  289.     vGu8BeepTimerFlag=0;  
  290. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  291.     vGu8BeepTimerFlag=1;  
  292. }

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

  296.    case 9:     //S9按键的任务
  297. if(Gu8LedStatus<7)
  298. {
  299. Gu8LedStatus++;  //控制LED“往右边跑”
  300. Gu8DisplayUpdate=1;  //刷新显示
  301. }

  302. if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
  303. {
  304.     vGu8BeepTimerFlag=0;  
  305. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  306.     vGu8BeepTimerFlag=1;  
  307. }

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

  311.    default:  

  312. vGu8KeySec=0;  
  313. break;

  314. }
  315. }

  316. void T0_time() interrupt 1     
  317. {
  318. VoiceScan();  
  319. KeyScan();   

  320. TH0=0xfc;   
  321. TL0=0x66;   
  322. }


  323. void SystemInitial(void)
  324. {
  325. TMOD=0x01;  
  326. TH0=0xfc;   
  327. TL0=0x66;   
  328. EA=1;      
  329. ET0=1;      
  330. TR0=1;      
  331. }

  332. void Delay(unsigned long u32DelayTime)
  333. {
  334.     for(;u32DelayTime>0;u32DelayTime--);
  335. }

  336. void PeripheralInitial(void)
  337. {

  338. }

  339. void BeepOpen(void)
  340. {
  341. P3_4=0;  
  342. }

  343. void BeepClose(void)
  344. {
  345. P3_4=1;  
  346. }

  347. void VoiceScan(void)
  348. {

  349.           static unsigned char Su8Lock=0;  

  350. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  351.           {
  352.                   if(0==Su8Lock)
  353.                   {
  354.                    Su8Lock=1;  
  355. BeepOpen();
  356.      }
  357.     else  
  358. {     

  359.                        vGu16BeepTimerCnt--;         

  360.                    if(0==vGu16BeepTimerCnt)
  361.                    {
  362.                            Su8Lock=0;     
  363. BeepClose();  
  364.                    }

  365. }
  366.           }         
  367. }
复制代码





乐于分享,勇于质疑!
110#
 楼主| 发表于 2018-2-4 12:14:19 | 只看该作者
本帖最后由 jianhong_wu 于 2018-2-4 12:31 编辑

第一百零六节: 矩阵按键按住不松手的“先加速后匀速”触发。
第一百零六节_pdf文件.pdf (137.08 KB, 下载次数: 1275)
【106.1   按住不松手的先加速后匀速触发。】

              
                上图106.1.1  有源蜂鸣器电路

        
                上图106.1.2  LED电路


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

        矩阵按键与前面章节“独立按键按住不松手的先加速后匀速的触发”的处理思路是一样的。 当“连续加”或者“连续减”的数据范围很大的时候,就需要按键的加速与匀速相结合的触发方式。“加速”是指按住按键不松手,按键刚开始触发是从慢到快的渐进过程,当“加速”到某个特别快的速度的时候,就“不再加速”,而是以该“恒定高速”进行“连续匀速”触发。这种触发方式,“加速”和“匀速”是相辅相成缺一不可的,为什么?假如没有“加速”只有“匀速”,那么刚按下按键就直接以最高速的“匀速”进行,就会跑过头,缺乏微调功能;而假如没有“匀速”只有“加速”,那么按下按键不松手后,速度就会一直不断飙升,最后失控过冲。
       本节例程实现的功能如下:
      (1)要更改一个“设置参数”(一个全局变量),参数的范围是0到800。
      (2)8个受“设置参数”控制的跑马灯在某一时刻只有1个LED亮,每触发一次S1按键,该“设置参数”就自减1,最小值为0;相反,每触发一次S9按键,该“设置参数”就自加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  20    //按键单击的“滤波”时间

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

  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 ROW_INPUT1=P2^2;  //第1行输入口。
  21. sbit ROW_INPUT2=P2^1;  //第2行输入口。
  22. sbit ROW_INPUT3=P2^0;  //第3行输入口。

  23. sbit COLUMN_OUTPUT1=P2^5;  //第1列输出口。
  24. sbit COLUMN_OUTPUT2=P2^4;  //第2列输出口。
  25. sbit COLUMN_OUTPUT3=P2^3;  //第3列输出口。

  26. volatile unsigned char vGu8BeepTimerFlag=0;  
  27. volatile unsigned int vGu16BeepTimerCnt=0;  

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

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

  32. void main()
  33. {
  34. SystemInitial();            
  35. Delay(10000);               
  36. PeripheralInitial();      
  37.     while(1)  
  38. {  
  39.         KeyTask();   
  40. DisplayTask();   //显示的任务函数(LED显示状态)
  41.     }
  42. }

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

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

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

  86. /* 注释二:
  87. *  本节破题的关键:
  88. *  矩阵按键涉及的按键数量很多,但是实际项目上一般只需要少数个别按键具备这种
  89. *  “单击”与“先加速后均匀触发”的特殊技能,因此,在代码上,必须把这类“特殊技能按键”与
  90. *  “大众按键”区分开来,才能相互清晰互不干扰。本节的“特殊技能按键”是S1和S9。
  91. *  如果觉得本节的讲解不够详细具体,请先阅读一下前面章节“独立按键按住不松手的先加速后匀速触发”。
  92. */

  93. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  94. {
  95.    static unsigned char Su8KeyLock=0;        
  96.    static unsigned int  Su16KeyCnt=0;  
  97.    static unsigned char Su8KeyStep=1;  
  98.    static unsigned int  Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值

  99.    static unsigned char Su8ColumnRecord=0;  

  100.    switch(Su8KeyStep)
  101.    {
  102.      case 1:   
  103.           if(0==Su8ColumnRecord)  
  104. {
  105.           COLUMN_OUTPUT1=0;      
  106.           COLUMN_OUTPUT2=1;
  107.           COLUMN_OUTPUT3=1;  
  108. }
  109.           else if(1==Su8ColumnRecord)  
  110. {
  111.           COLUMN_OUTPUT1=1;      
  112.           COLUMN_OUTPUT2=0;
  113.           COLUMN_OUTPUT3=1;  
  114. }
  115.           else     
  116. {
  117.           COLUMN_OUTPUT1=1;      
  118.           COLUMN_OUTPUT2=1;
  119.           COLUMN_OUTPUT3=0;  
  120. }
  121.           Su16KeyCnt=0;
  122.           Su8KeyStep++;  
  123.           break;

  124.      case 2:      //等待列输出稳定,但不是去抖动延时
  125.           Su16KeyCnt++;
  126.           if(Su16KeyCnt>=2)
  127.           {
  128.              Su16KeyCnt=0;
  129.              Su8KeyStep++;     
  130.           }
  131.           break;

  132.      case 3:
  133.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  134.           {  
  135.               Su8KeyStep=1;    //返回步骤1继续扫描
  136.               Su8KeyLock=0;
  137.               Su16KeyCnt=0;  
  138. Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值。重装初始值。

  139.               Su8ColumnRecord++;  
  140.               if(Su8ColumnRecord>=3)  
  141.               {
  142.                  Su8ColumnRecord=0;
  143.               }     
  144.           }
  145.           else if(0==Su8KeyLock)
  146.           {
  147.               if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3)
  148.               {
  149.                   Su16KeyCnt++;  
  150.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  151.                   {
  152.                       Su8KeyLock=1;

  153.                       if(0==Su8ColumnRecord)  
  154.                       {
  155.                             vGu8KeySec=1;    //触发一次单击
  156. Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
  157.                             Su8KeyStep=4;    //跳到S1按键的专属区,脱离大众按键
  158.                       }
  159.                       else if(1==Su8ColumnRecord)  
  160.                       {
  161.                             vGu8KeySec=2;   
  162.                       }
  163.                       else if(2==Su8ColumnRecord)  
  164.                       {
  165.                             vGu8KeySec=3;  
  166.                       }
  167.                   }

  168.               }
  169.               else if(1==ROW_INPUT1&&0==ROW_INPUT2&&1==ROW_INPUT3)
  170.               {
  171.                   Su16KeyCnt++;  
  172.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  173.                   {
  174.                       Su8KeyLock=1;

  175.                       if(0==Su8ColumnRecord)  
  176.                       {
  177.                            vGu8KeySec=4;  
  178.                       }
  179.                       else if(1==Su8ColumnRecord)  
  180.                       {
  181.                            vGu8KeySec=5;  
  182.                       }
  183.                       else if(2==Su8ColumnRecord)  
  184.                       {
  185.                            vGu8KeySec=6;  
  186.                       }
  187.                   }   
  188.               }
  189.               else if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3)
  190.               {
  191.                   Su16KeyCnt++;
  192.                   if(Su16KeyCnt>=KEY_SHORT_TIME)
  193.                   {
  194.                       Su8KeyLock=1;
  195.                       if(0==Su8ColumnRecord)  
  196.                       {
  197.                            vGu8KeySec=7;  
  198.                       }
  199.                       else if(1==Su8ColumnRecord)  
  200.                       {
  201.                            vGu8KeySec=8;  
  202.                       }
  203.                       else if(2==Su8ColumnRecord)  
  204.                       {
  205.                             vGu8KeySec=9;    //触发一次单击
  206. Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
  207.                             Su8KeyStep=6;    //跳到S9按键的专属区,脱离大众按键
  208.                       }
  209.                   }   
  210.               }

  211.           }
  212.           break;

  213. /*---------S1按键的专属区----------------*/
  214.      case 4:  
  215.           if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
  216.           {
  217. Su16KeyCnt++;
  218. if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
  219.               {
  220. Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
  221.                   Su8KeyStep=5;    //S1按键进入有节奏的连续触发        
  222. }
  223. }
  224.           else //如果期间检查到S1按键已经松手
  225.           {  
  226.               Su8KeyStep=1;    //返回步骤1继续扫描
  227.               Su8KeyLock=0;
  228.               Su16KeyCnt=0;  
  229. Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值。重装初始值。

  230.               Su8ColumnRecord++;  
  231.               if(Su8ColumnRecord>=3)  
  232.               {
  233.                  Su8ColumnRecord=0;
  234.               }     
  235.           }

  236.           break;
  237.      case 5:  //S1按键进入有节奏的连续触发
  238.           if(0==ROW_INPUT1&&1==ROW_INPUT2&&1==ROW_INPUT3) //仅判断S1按键,避免交叉影响
  239.           {
  240. Su16KeyCnt++;
  241.               if(Su16KeyCnt>=Su16KeyContinuityTime)  //该时间是“刚开始不断减小,最后不变”
  242.               {
  243.                   Su16KeyCnt=0;           //清零,为了继续连击。
  244.                   vGu8KeySec=1;           //触发一次S1按键   
  245. vGu8ShieldVoiceFlag=1;  //因为连击,把当前按键触发的声音屏蔽掉   
  246. if(Su16KeyContinuityTime>=KEY_SUB_DT_TIME)
  247. {
  248.           //Su16KeyContinuityTime数值不断被减小,按键的触发速度就不断变快
  249. Su16KeyContinuityTime=Su16KeyContinuityTime-KEY_SUB_DT_TIME;//变快节奏
  250. }

  251. //最小间隔时间KEY_CONTINUITY_MIN_TIME就是“高速匀速”
  252. if(Su16KeyContinuityTime<KEY_CONTINUITY_MIN_TIME)
  253. {
  254. //最后以KEY_CONTINUITY_MIN_TIME时间为最高速进行“匀速”
  255. Su16KeyContinuityTime=KEY_CONTINUITY_MIN_TIME;
  256. }

  257. }
  258. }
  259.           else //如果期间检查到S1按键已经松手
  260.           {  
  261.               Su8KeyStep=1;    //返回步骤1继续扫描
  262.               Su8KeyLock=0;
  263.               Su16KeyCnt=0;  
  264. Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值。重装初始值。


  265.               Su8ColumnRecord++;  
  266.               if(Su8ColumnRecord>=3)  
  267.               {
  268.                  Su8ColumnRecord=0;
  269.               }     
  270.           }
  271.           break;

  272. /*---------S9按键的专属区----------------*/
  273.      case 6:  
  274.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
  275.           {
  276. Su16KeyCnt++;
  277. if(Su16KeyCnt>=KEY_ENTER_CONTINUITY_TIME)//该时间是“单击”与“连击”的分界线
  278.               {
  279. Su16KeyCnt=0;    //计时器清零,为即将来临的计时做准备
  280.                   Su8KeyStep=7;    //S9按键进入有节奏的连续触发        
  281. }
  282. }
  283.           else //如果期间检查到S9按键已经松手
  284.           {  
  285.               Su8KeyStep=1;    //返回步骤1继续扫描
  286.               Su8KeyLock=0;
  287.               Su16KeyCnt=0;  
  288. Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值。重装初始值。

  289.               Su8ColumnRecord++;  
  290.               if(Su8ColumnRecord>=3)  
  291.               {
  292.                  Su8ColumnRecord=0;
  293.               }     
  294.           }

  295.           break;
  296.      case 7:  //S9按键进入有节奏的连续触发
  297.           if(1==ROW_INPUT1&&1==ROW_INPUT2&&0==ROW_INPUT3) //仅判断S9按键,避免交叉影响
  298.           {
  299. Su16KeyCnt++;
  300.               if(Su16KeyCnt>=Su16KeyContinuityTime)  //该时间是“刚开始不断减小,最后不变”
  301.               {
  302.                   Su16KeyCnt=0;           //清零,为了继续连击。
  303.                   vGu8KeySec=9;           //触发一次S9按键   
  304. vGu8ShieldVoiceFlag=1;  //因为连击,把当前按键触发的声音屏蔽掉   
  305. if(Su16KeyContinuityTime>=KEY_SUB_DT_TIME)
  306. {
  307.           //Su16KeyContinuityTime数值不断被减小,按键的触发速度就不断变快
  308. Su16KeyContinuityTime=Su16KeyContinuityTime-KEY_SUB_DT_TIME;//变快节奏
  309. }

  310. //最小间隔时间KEY_CONTINUITY_MIN_TIME就是“高速匀速”
  311. if(Su16KeyContinuityTime<KEY_CONTINUITY_MIN_TIME)
  312. {
  313. //最后以KEY_CONTINUITY_MIN_TIME时间为最高速进行“匀速”
  314. Su16KeyContinuityTime=KEY_CONTINUITY_MIN_TIME;
  315. }  
  316. }
  317. }
  318.           else //如果期间检查到S9按键已经松手
  319.           {  
  320.               Su8KeyStep=1;    //返回步骤1继续扫描
  321.               Su8KeyLock=0;
  322.               Su16KeyCnt=0;  
  323. Su16KeyContinuityTime=KEY_CONTINUITY_INITIAL_TIME;  //动态时间阀值。重装初始值。

  324.               Su8ColumnRecord++;  
  325.               if(Su8ColumnRecord>=3)  
  326.               {
  327.                  Su8ColumnRecord=0;
  328.               }     
  329.           }
  330.           break;


  331.    }
  332. }

  333. void KeyTask(void)   
  334. {
  335. if(0==vGu8KeySec)
  336. {
  337. return;
  338. }

  339. switch(vGu8KeySec)
  340. {
  341.    case 1:     //S1按键的任务
  342.         if(Gu16SetData>0)
  343. {
  344. Gu16SetData--;     //“设置参数”
  345. Gu8DisplayUpdate=1;  //刷新显示
  346. }

  347. if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
  348. {
  349.     vGu8BeepTimerFlag=0;  
  350. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  351.     vGu8BeepTimerFlag=1;  
  352. }

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

  356.    case 9:     //S9按键的任务

  357.         if(Gu16SetData<800)
  358. {
  359. Gu16SetData++;       //“设置参数”
  360. Gu8DisplayUpdate=1;  //刷新显示
  361. }

  362. if(0==vGu8ShieldVoiceFlag) //声音没有被屏蔽
  363. {
  364.     vGu8BeepTimerFlag=0;  
  365. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //发出“嘀”一声
  366.     vGu8BeepTimerFlag=1;  
  367. }

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

  371.    default:  

  372. vGu8KeySec=0;  
  373. break;

  374. }
  375. }

  376. void T0_time() interrupt 1     
  377. {
  378. VoiceScan();  
  379. KeyScan();   

  380. TH0=0xfc;   
  381. TL0=0x66;   
  382. }


  383. void SystemInitial(void)
  384. {
  385. TMOD=0x01;  
  386. TH0=0xfc;   
  387. TL0=0x66;   
  388. EA=1;      
  389. ET0=1;      
  390. TR0=1;      
  391. }

  392. void Delay(unsigned long u32DelayTime)
  393. {
  394.     for(;u32DelayTime>0;u32DelayTime--);
  395. }

  396. void PeripheralInitial(void)
  397. {

  398. }

  399. void BeepOpen(void)
  400. {
  401. P3_4=0;  
  402. }

  403. void BeepClose(void)
  404. {
  405. P3_4=1;  
  406. }

  407. void VoiceScan(void)
  408. {

  409.           static unsigned char Su8Lock=0;  

  410. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  411.           {
  412.                   if(0==Su8Lock)
  413.                   {
  414.                    Su8Lock=1;  
  415. BeepOpen();
  416.      }
  417.     else  
  418. {     

  419.                        vGu16BeepTimerCnt--;         

  420.                    if(0==vGu16BeepTimerCnt)
  421.                    {
  422.                            Su8Lock=0;     
  423. BeepClose();  
  424.                    }

  425. }
  426.           }         
  427. }
复制代码






乐于分享,勇于质疑!
111#
 楼主| 发表于 2018-2-11 10:58:35 | 只看该作者
本帖最后由 jianhong_wu 于 2018-2-11 11:17 编辑

第一百零七节: 开关感应器的识别与软件滤波。
第一百零七节_pdf文件.pdf (91.16 KB, 下载次数: 1242)
【107.1   开关感应器的识别与软件滤波。】


         
                上图107.1.1  独立按键模拟开关感应器

      
                上图107.1.2  LED电路

       什么叫开关感应器?凡是只能输出0和1这两种状态的感应器都可以统称为开关感应器。前面花了大量的章节讲按键,按键的识别主要是识别电平变化状态的“下降沿”,程序代码中有1个特别的变量标志叫“自锁标志”,还有1个用来消除抖动的“计时器”。本节讲的开关感应器跟按键很相似,差别在于,开关感应器是识别电平变化状态的“电平”,程序代码中没有“自锁标志”,但是多增加了1个用来消除抖动的“计时器”,也就是一共有两个用来消除抖动的“计时器”,这两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,专业术语也叫“软件滤波”。消抖的时间跟按键差不多,我的经验值是20ms到30ms之间,我平时在项目中喜欢用20ms。
       在显示框架方面,除了之前讲过Gu8DisplayUpdate这类“显示刷新变量”,本节介绍另外一种常用的显示框架,原理是“某数值跟上一次对比,如果发生了变化(两数值不一样),则自动刷新显示,并及时记录当前值”。
       本节例程实现的功能如下:用K1独立按键模拟开关感应器,K1独立按键“没有被按下”时是高电平,单片机识别到这种“高电平”,就让P1.4所在的LED灯发亮;K1独立按键“被按下”时是低电平,单片机识别到这种“低电平”,就让P1.4所在的LED灯熄灭。

  1. #include "REG52.H"  

  2. #define SENSOR_TIME  20    //开关感应器的“滤波”时间

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

  7. void VoiceScan(void);
  8. void SensorScan(void);   
  9. void DisplayTask(void);   //显示的任务函数(LED显示状态)

  10. sbit P1_4=P1^4;      
  11. sbit Sensor_K1_sr=P2^2;   //开关感应器K1所在的引脚

  12. volatile unsigned char vGu8Sensor_K1=0;  //K1开关感应器的当前电平状态。

  13. void main()
  14. {
  15. SystemInitial();            
  16. Delay(10000);               
  17. PeripheralInitial();      
  18.     while(1)  
  19. {   
  20. DisplayTask();   //显示的任务函数(LED显示状态)
  21.     }
  22. }

  23. /* 注释一:
  24. * 后缀为_Last这类“对比上一次数值发生变化而自动刷新显示”在“显示框架”里是很常见的,
  25. * 目的是,既能及时刷新显示,又能避免主函数“不断去执行显示代码”而影响程序效率。
  26. */

  27. void DisplayTask(void)   //显示的任务函数(LED显示状态)
  28. {
  29.    // Su8Sensor_K1_Last初始化取值255,只要不为0或者1就行,目的是让上电就发生第一次刷新。
  30. static unsigned char Su8Sensor_K1_Last=255;  //记录K1开关感应器上一次的电平状态。

  31. if(Su8Sensor_K1_Last!=vGu8Sensor_K1)  //如果当前值与上一次值不一样,就自动刷新
  32. {
  33. Su8Sensor_K1_Last=vGu8Sensor_K1;  //及时记录最新值,避免主函数“不断去执行显示代码”

  34.         if(0==vGu8Sensor_K1) //如果当前电平状态为“低电平”,LED熄灭
  35. {
  36. P1_4=1;  //LED熄灭
  37. }
  38.         else  //如果当前电平状态为“高电平”,LED发亮
  39. {
  40. P1_4=0;  //LED发亮
  41. }
  42. }
  43. }

  44. /* 注释二:
  45. *  本节破题的关键:
  46. *  两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,
  47. *  专业术语也叫“软件滤波”。这种滤波方式,不管是从“高转成低”,还是“低转成高”,
  48. *  如果在某个瞬间出现干扰抖动,某个计数器都会及时被“清零”,从而起到非常高效的消抖滤波作用。
  49. */

  50. void SensorScan(void)  //此函数放在定时中断里每1ms扫描一次,用来识别和滤波开关感应器
  51. {
  52.       static unsigned int Su16Sensor_K1_H_Cnt=0;  //判断高电平的计时器
  53.       static unsigned int Su16Sensor_K1_L_Cnt=0;  //判断低电平的计时器

  54.       if(0==Sensor_K1_sr)
  55.           {
  56.                   Su16Sensor_K1_H_Cnt=0;  //在判断低电平的时候,高电平的计时器被清零,巧妙极了!
  57.                   Su16Sensor_K1_L_Cnt++;
  58.                   if(Su16Sensor_K1_L_Cnt>=SENSOR_TIME)
  59.                   {
  60.                       Su16Sensor_K1_L_Cnt=0;
  61.                           vGu8Sensor_K1=0;   //此全局变量反馈当前电平的状态
  62.                   }
  63.          
  64.           }
  65.           else
  66.           {
  67.                   Su16Sensor_K1_L_Cnt=0;   //在判断高电平的时候,低电平的计时器被清零,巧妙极了!
  68.                   Su16Sensor_K1_H_Cnt++;
  69.                   if(Su16Sensor_K1_H_Cnt>=SENSOR_TIME)
  70.                   {
  71.                       Su16Sensor_K1_H_Cnt=0;
  72.                           vGu8Sensor_K1=1;  //此全局变量反馈当前电平的状态
  73.                   }
  74.           }
  75. }

  76. void T0_time() interrupt 1     
  77. {
  78. SensorScan();  //开关感应器的识别与软件滤波处理

  79. TH0=0xfc;   
  80. TL0=0x66;   
  81. }


  82. void SystemInitial(void)
  83. {
  84. TMOD=0x01;  
  85. TH0=0xfc;   
  86. TL0=0x66;   
  87. EA=1;      
  88. ET0=1;      
  89. TR0=1;      
  90. }

  91. void Delay(unsigned long u32DelayTime)
  92. {
  93.     for(;u32DelayTime>0;u32DelayTime--);
  94. }

  95. void PeripheralInitial(void)
  96. {

  97. }
复制代码




乐于分享,勇于质疑!
112#
 楼主| 发表于 2018-2-23 14:15:22 | 只看该作者
本帖最后由 jianhong_wu 于 2018-2-23 14:44 编辑

第一百零八节: 按键控制跑马灯的启动和暂停和停止。
第一百零八节_pdf文件.pdf (104.79 KB, 下载次数: 1212)
【108.1   按键控制跑马灯的启动和暂停和停止。】



                上图108.1.1  独立按键

           
                上图108.1.2  LED电路

                     
                上图108.1.3  有源蜂鸣器的电路

       在我眼里,按键不仅仅是按键,跑马灯不仅仅是跑马灯。按键是输入设备,跑马灯是应用程序。本节表面上讲按键控制跑马灯的简单项目,实际上作者用心良苦立意深远,试图通过按键与跑马灯,来分享一种输入设备如何关联应用程序的程序框架。
       本节例程实现的功能如下:
      (1)【启动暂停】按键K1。按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯从左到右依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
      (2)【停止】按键K2。当跑马灯处于“启动”或者“暂停”或者“停止”的状态时,只要按下【停止】按键K2,当前的运动状态就终止,强制变回初始的“停止”状态,类似“复位”按键的作用。当跑马灯处于“停止”状态时,此时再按下【启动暂停】按键K1之后,跑马灯又处于“启动”状态。
  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50
  3. #define KEY_FILTER_TIME  25  
  4. #define RUN_TIME  200   //跑马灯的跑动速度的时间参数

  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 VoiceScan(void);
  12. void KeyScan(void);   
  13. void KeyTask(void);   
  14. void RunTask(void);   //跑马灯的任务函数

  15. //4个跑马灯的输出口
  16. sbit P1_4=P1^4;  
  17. sbit P1_5=P1^5;  
  18. sbit P1_6=P1^6;  
  19. sbit P3_3=P3^3;  

  20. //蜂鸣器的输出口
  21. sbit P3_4=P3^4;  

  22. sbit KEY_INPUT1=P2^2;  //【启动暂停】按键K1的输入口。
  23. sbit KEY_INPUT2=P2^1;  //【停止】按键K2的输入口。

  24. volatile unsigned char vGu8BeepTimerFlag=0;  
  25. volatile unsigned int vGu16BeepTimerCnt=0;  

  26. volatile unsigned char vGu8KeySec=0;  

  27. unsigned char Gu8RunStart=0;   //控制跑马灯启动的总开关
  28. unsigned char Gu8RunStatus=0;  //标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。

  29. volatile unsigned char vGu8RunTimerFlag=0;   //用于控制跑马灯跑动速度的定时器
  30. volatile unsigned int vGu16RunTimerCnt=0;  

  31. void main()
  32. {
  33. SystemInitial();            
  34. Delay(10000);               
  35. PeripheralInitial();      
  36.     while(1)  
  37. {  
  38.     KeyTask();    //按键的任务函数
  39. RunTask();    //跑马灯的任务函数
  40.     }
  41. }

  42. void T0_time() interrupt 1     
  43. {
  44. VoiceScan();  
  45. KeyScan();   

  46. if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)  //用于控制跑马灯跑动速度的定时器
  47. {
  48. vGu16RunTimerCnt--;
  49. }

  50. TH0=0xfc;   
  51. TL0=0x66;   
  52. }


  53. void SystemInitial(void)
  54. {
  55. TMOD=0x01;  
  56. TH0=0xfc;   
  57. TL0=0x66;   
  58. EA=1;      
  59. ET0=1;      
  60. TR0=1;      
  61. }

  62. void Delay(unsigned long u32DelayTime)
  63. {
  64.     for(;u32DelayTime>0;u32DelayTime--);
  65. }

  66. void PeripheralInitial(void)
  67. {
  68. //跑马灯处于初始化的状态
  69. P1_4=0;   //第1个灯亮
  70. P1_5=1;   //第2个灯灭
  71. P1_6=1;   //第3个灯灭
  72. P3_3=1;   //第4个灯灭

  73. }

  74. void BeepOpen(void)
  75. {
  76. P3_4=0;  
  77. }

  78. void BeepClose(void)
  79. {
  80. P3_4=1;  
  81. }

  82. void VoiceScan(void)
  83. {

  84.           static unsigned char Su8Lock=0;  

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

  94.                        vGu16BeepTimerCnt--;         

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

  100. }
  101.           }         
  102. }

  103. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  104. {
  105.    static unsigned char Su8KeyLock1;
  106.    static unsigned int  Su16KeyCnt1;
  107.    static unsigned char Su8KeyLock2;
  108.    static unsigned int  Su16KeyCnt2;

  109.    //【启动暂停】按键K1的扫描识别
  110.    if(0!=KEY_INPUT1)
  111.    {
  112.       Su8KeyLock1=0;
  113.       Su16KeyCnt1=0;   
  114.    }
  115.    else if(0==Su8KeyLock1)
  116.    {
  117.       Su16KeyCnt1++;
  118.       if(Su16KeyCnt1>=KEY_FILTER_TIME)
  119.       {
  120.          Su8KeyLock1=1;  
  121.          vGu8KeySec=1;    //触发1号键
  122.       }
  123.    }

  124.    //【停止】按键K2的扫描识别
  125.    if(0!=KEY_INPUT2)
  126.    {
  127.       Su8KeyLock2=0;
  128.       Su16KeyCnt2=0;      
  129.    }
  130.    else if(0==Su8KeyLock2)
  131.    {
  132.       Su16KeyCnt2++;
  133.       if(Su16KeyCnt2>=KEY_FILTER_TIME)
  134.       {
  135.          Su8KeyLock2=1;  
  136.          vGu8KeySec=2;    //触发2号键
  137.       }
  138.    }


  139. }

  140. /* 注释一:
  141. *  本节破题的关键:
  142. *  在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart和Gu8RunStatus这两个
  143. *  全局变量来传递信息。
  144. */

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

  151. switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
  152. {
  153.    case 1:     //1号按键。【启动暂停】按键K1
  154.         if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
  155.         {
  156. Gu8RunStart=1;   //总开关“打开”。
  157. Gu8RunStatus=1;  //状态切换到“启动”状态
  158. }
  159.         else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
  160.         {
  161. Gu8RunStatus=2;  //状态切换到“暂停”状态
  162. }
  163.         else  //当跑马灯处于“暂停”状态时
  164.         {
  165. Gu8RunStatus=1;  //状态切换到“启动”状态
  166. }


  167.         vGu8BeepTimerFlag=0;  
  168. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  169.         vGu8BeepTimerFlag=1;  
  170. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  171. break;

  172.    case 2:     //2号按键。【停止】按键K2

  173. Gu8RunStart=0;   //总开关“关闭”。
  174. Gu8RunStatus=0;  //状态切换到“停止”状态

  175.         vGu8BeepTimerFlag=0;  
  176. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  177.         vGu8BeepTimerFlag=1;  
  178. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  179. break;

  180. }
  181. }

  182. void RunTask(void)    //跑马灯的任务函数,放在主函数内
  183. {
  184. static unsigned char Su8RunStep=0; //运行的步骤


  185. //当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
  186. if(0!=Su8RunStep&&0==Gu8RunStart)
  187. {
  188. Su8RunStep=0; //步骤归零

  189. //跑马灯处于初始化的状态
  190. P1_4=0;   //第1个灯亮
  191. P1_5=1;   //第2个灯灭
  192. P1_6=1;   //第3个灯灭
  193. P3_3=1;   //第4个灯灭

  194. }

  195. switch(Su8RunStep) //屡见屡爱的switch又来了
  196. {
  197.    case 0:
  198.        if(1==Gu8RunStart) //总开关“打开”
  199. {
  200. vGu8RunTimerFlag=0;   
  201. vGu16RunTimerCnt=0;  //定时器清零
  202.             Su8RunStep=1;  //切换到下一步,启动
  203. }
  204.        break;
  205.    case 1:
  206.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  207. {
  208. P1_4=0;   //第1个灯亮
  209. P1_5=1;   //第2个灯灭
  210. P1_6=1;   //第3个灯灭
  211. P3_3=1;   //第4个灯灭

  212.     vGu8RunTimerFlag=0;   
  213. vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
  214. vGu8RunTimerFlag=1;   //启动定时器
  215.             Su8RunStep=2;  //切换到下一步
  216. }

  217.        break;
  218.    case 2:
  219.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  220. {
  221. P1_4=1;   //第1个灯灭
  222. P1_5=0;   //第2个灯亮
  223. P1_6=1;   //第3个灯灭
  224. P3_3=1;   //第4个灯灭

  225.     vGu8RunTimerFlag=0;   
  226. vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
  227. vGu8RunTimerFlag=1;   //启动定时器
  228.             Su8RunStep=3;  //切换到下一步
  229. }

  230.        break;
  231.    case 3:
  232.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  233. {
  234. P1_4=1;   //第1个灯灭
  235. P1_5=1;   //第2个灯灭
  236. P1_6=0;   //第3个灯亮
  237. P3_3=1;   //第4个灯灭

  238.     vGu8RunTimerFlag=0;   
  239. vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
  240. vGu8RunTimerFlag=1;   //启动定时器
  241.             Su8RunStep=4;  //切换到下一步
  242. }

  243.        break;
  244.    case 4:
  245.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  246. {
  247. P1_4=1;   //第1个灯灭
  248. P1_5=1;   //第2个灯灭
  249. P1_6=1;   //第3个灯灭
  250. P3_3=0;   //第4个灯亮

  251.     vGu8RunTimerFlag=0;   
  252. vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
  253. vGu8RunTimerFlag=1;   //启动定时器
  254.             Su8RunStep=1;  //返回到第1步,重新开始下一轮的循环!!!
  255. }

  256.        break;

  257. }

  258. }
复制代码






乐于分享,勇于质疑!
113#
 楼主| 发表于 2018-3-1 09:05:48 | 只看该作者
本帖最后由 jianhong_wu 于 2018-3-1 09:28 编辑

第一百零九节: 按键控制跑马灯的方向。
第一百零九节_pdf文件.pdf (109.97 KB, 下载次数: 1224)
【109.1   按键控制跑马灯的方向。】



                上图109.1.1  独立按键

      
                上图109.1.2  LED电路

            
                上图109.1.3  有源蜂鸣器的电路

      之前108节讲到跑马灯的启动、暂停、停止,本节在此基础上,增加一个“方向”的控制,除了加深理解输入设备如何关联应用程序的程序框架之外,还有一个知识点值得一提,就是如何通过灵活切换switch的“步骤变量”来达到随心所欲的过程控制,本节的“方向”的控制就用到这个方法。
      本节例程的功能如下:
     (1)【启动暂停】按键K1。按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯挨个依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
     (2)【停止】按键K2。当跑马灯处于“启动”或者“暂停”或者“停止”的状态时,只要按下【停止】按键K2,当前的运动状态就终止,强制变回初始的“停止”状态,类似“复位”按键的作用。当跑马灯处于“停止”状态时,此时再按下【启动暂停】按键K1之后,跑马灯又处于“启动”状态。
     (3)【方向】按键K3。跑马灯上电后默认处于“往右跑”的方向。每按一次【方向】按键K3,跑马灯就在“往右跑”与“往左跑”两个方向之间切换。
  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50
  3. #define KEY_FILTER_TIME  25  
  4. #define RUN_TIME  200   //跑马灯的跑动速度的时间参数

  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 VoiceScan(void);
  12. void KeyScan(void);   
  13. void KeyTask(void);   
  14. void RunTask(void);   //跑马灯的任务函数

  15. //4个跑马灯的输出口
  16. sbit P1_4=P1^4;  
  17. sbit P1_5=P1^5;  
  18. sbit P1_6=P1^6;  
  19. sbit P3_3=P3^3;  

  20. //蜂鸣器的输出口
  21. sbit P3_4=P3^4;  

  22. sbit KEY_INPUT1=P2^2;  //【启动暂停】按键K1的输入口。
  23. sbit KEY_INPUT2=P2^1;  //【停止】按键K2的输入口。
  24. sbit KEY_INPUT3=P2^0;  //【方向】按键K3的输入口。

  25. volatile unsigned char vGu8BeepTimerFlag=0;  
  26. volatile unsigned int vGu16BeepTimerCnt=0;  

  27. volatile unsigned char vGu8KeySec=0;  

  28. unsigned char Gu8RunStart=0;      //控制跑马灯启动的总开关
  29. unsigned char Gu8RunStatus=0;     //标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。
  30. unsigned char Gu8RunDirection=0;  //标识跑马灯当前的方向。0代表往右跑,1代表往左跑。

  31. volatile unsigned char vGu8RunTimerFlag=0;   //用于控制跑马灯跑动速度的定时器
  32. volatile unsigned int vGu16RunTimerCnt=0;  

  33. void main()
  34. {
  35. SystemInitial();            
  36. Delay(10000);               
  37. PeripheralInitial();      
  38.     while(1)  
  39. {  
  40.     KeyTask();    //按键的任务函数
  41. RunTask();    //跑马灯的任务函数
  42.     }
  43. }

  44. void T0_time() interrupt 1     
  45. {
  46. VoiceScan();  
  47. KeyScan();   

  48. if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)  //用于控制跑马灯跑动速度的定时器
  49. {
  50. vGu16RunTimerCnt--;
  51. }

  52. TH0=0xfc;   
  53. TL0=0x66;   
  54. }


  55. void SystemInitial(void)
  56. {
  57. TMOD=0x01;  
  58. TH0=0xfc;   
  59. TL0=0x66;   
  60. EA=1;      
  61. ET0=1;      
  62. TR0=1;      
  63. }

  64. void Delay(unsigned long u32DelayTime)
  65. {
  66.     for(;u32DelayTime>0;u32DelayTime--);
  67. }

  68. void PeripheralInitial(void)
  69. {
  70. //跑马灯处于初始化的状态
  71. P1_4=0;   //第1个灯亮
  72. P1_5=1;   //第2个灯灭
  73. P1_6=1;   //第3个灯灭
  74. P3_3=1;   //第4个灯灭

  75. }

  76. void BeepOpen(void)
  77. {
  78. P3_4=0;  
  79. }

  80. void BeepClose(void)
  81. {
  82. P3_4=1;  
  83. }

  84. void VoiceScan(void)
  85. {

  86.           static unsigned char Su8Lock=0;  

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

  96.                        vGu16BeepTimerCnt--;         

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

  102. }
  103.           }         
  104. }

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


  113.    //【启动暂停】按键K1的扫描识别
  114.    if(0!=KEY_INPUT1)
  115.    {
  116.       Su8KeyLock1=0;
  117.       Su16KeyCnt1=0;   
  118.    }
  119.    else if(0==Su8KeyLock1)
  120.    {
  121.       Su16KeyCnt1++;
  122.       if(Su16KeyCnt1>=KEY_FILTER_TIME)
  123.       {
  124.          Su8KeyLock1=1;  
  125.          vGu8KeySec=1;    //触发1号键
  126.       }
  127.    }

  128.    //【停止】按键K2的扫描识别
  129.    if(0!=KEY_INPUT2)
  130.    {
  131.       Su8KeyLock2=0;
  132.       Su16KeyCnt2=0;      
  133.    }
  134.    else if(0==Su8KeyLock2)
  135.    {
  136.       Su16KeyCnt2++;
  137.       if(Su16KeyCnt2>=KEY_FILTER_TIME)
  138.       {
  139.          Su8KeyLock2=1;  
  140.          vGu8KeySec=2;    //触发2号键
  141.       }
  142.    }

  143.    //【方向】按键K3的扫描识别
  144.    if(0!=KEY_INPUT3)
  145.    {
  146.       Su8KeyLock3=0;
  147.       Su16KeyCnt3=0;      
  148.    }
  149.    else if(0==Su8KeyLock3)
  150.    {
  151.       Su16KeyCnt3++;
  152.       if(Su16KeyCnt3>=KEY_FILTER_TIME)
  153.       {
  154.          Su8KeyLock3=1;  
  155.          vGu8KeySec=3;    //触发3号键
  156.       }
  157.    }
  158. }

  159. /* 注释一:
  160. *  本节破题的关键:
  161. *  在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart、Gu8RunStatus、Gu8RunDirection
  162. *  这三个全局变量来传递信息。
  163. */

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

  170. switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
  171. {
  172.    case 1:     //1号按键。【启动暂停】按键K1
  173.         if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
  174.         {
  175. Gu8RunStart=1;   //总开关“打开”。
  176. Gu8RunStatus=1;  //状态切换到“启动”状态
  177. }
  178.         else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
  179.         {
  180. Gu8RunStatus=2;  //状态切换到“暂停”状态
  181. }
  182.         else  //当跑马灯处于“暂停”状态时
  183.         {
  184. Gu8RunStatus=1;  //状态切换到“启动”状态
  185. }


  186.         vGu8BeepTimerFlag=0;  
  187. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  188.         vGu8BeepTimerFlag=1;  
  189. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  190. break;

  191.    case 2:     //2号按键。【停止】按键K2

  192. Gu8RunStart=0;   //总开关“关闭”。
  193. Gu8RunStatus=0;  //状态切换到“停止”状态

  194.         vGu8BeepTimerFlag=0;  
  195. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  196.         vGu8BeepTimerFlag=1;  
  197. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  198. break;

  199.    case 3:     //3号按键。【方向】按键K3
  200.         //每按一次K3按键,Gu8RunDirection就在0和1之间切换,从而控制方向
  201.         if(0==Gu8RunDirection)
  202. {
  203. Gu8RunDirection=1;
  204. }
  205. else
  206. {
  207. Gu8RunDirection=0;
  208. }

  209.         vGu8BeepTimerFlag=0;  
  210. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  211.         vGu8BeepTimerFlag=1;  
  212. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  213. break;

  214. }
  215. }

  216. /* 注释二:
  217. * “方向”的控制,是通过Gu8RunDirection的判断,来灵活切换switch的“步骤变量”来达到
  218. *  随心所欲的过程控制。
  219. */

  220. void RunTask(void)    //跑马灯的任务函数,放在主函数内
  221. {
  222. static unsigned char Su8RunStep=0; //运行的步骤


  223. //当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
  224. if(0!=Su8RunStep&&0==Gu8RunStart)
  225. {
  226. Su8RunStep=0; //步骤归零

  227. //跑马灯处于初始化的状态
  228. P1_4=0;   //第1个灯亮
  229. P1_5=1;   //第2个灯灭
  230. P1_6=1;   //第3个灯灭
  231. P3_3=1;   //第4个灯灭

  232. }

  233. switch(Su8RunStep) //屡见屡爱的switch又来了
  234. {
  235.    case 0:
  236.        if(1==Gu8RunStart) //总开关“打开”
  237. {
  238. vGu8RunTimerFlag=0;   
  239. vGu16RunTimerCnt=0;  //定时器清零
  240.             Su8RunStep=1;  //切换到下一步,启动
  241. }
  242.        break;
  243.    case 1:
  244.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  245. {
  246. P1_4=0;   //第1个灯亮
  247. P1_5=1;   //第2个灯灭
  248. P1_6=1;   //第3个灯灭
  249. P3_3=1;   //第4个灯灭

  250.     vGu8RunTimerFlag=0;   
  251. vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
  252. vGu8RunTimerFlag=1;   //启动定时器

  253. //灵活切换“步骤变量”
  254. if(0==Gu8RunDirection) //往右跑
  255. {
  256.                Su8RunStep=2;
  257. }
  258. else  //往左跑
  259. {
  260.                Su8RunStep=4;
  261. }

  262. }

  263.        break;
  264.    case 2:
  265.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  266. {
  267. P1_4=1;   //第1个灯灭
  268. P1_5=0;   //第2个灯亮
  269. P1_6=1;   //第3个灯灭
  270. P3_3=1;   //第4个灯灭

  271.     vGu8RunTimerFlag=0;   
  272. vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
  273. vGu8RunTimerFlag=1;   //启动定时器

  274. //灵活切换“步骤变量”
  275. if(0==Gu8RunDirection) //往右跑
  276. {
  277.                Su8RunStep=3;
  278. }
  279. else  //往左跑
  280. {
  281.                Su8RunStep=1;
  282. }
  283. }

  284.        break;
  285.    case 3:
  286.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  287. {
  288. P1_4=1;   //第1个灯灭
  289. P1_5=1;   //第2个灯灭
  290. P1_6=0;   //第3个灯亮
  291. P3_3=1;   //第4个灯灭

  292.     vGu8RunTimerFlag=0;   
  293. vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
  294. vGu8RunTimerFlag=1;   //启动定时器

  295. //灵活切换“步骤变量”
  296. if(0==Gu8RunDirection) //往右跑
  297. {
  298.                Su8RunStep=4;
  299. }
  300. else  //往左跑
  301. {
  302.                Su8RunStep=2;
  303. }
  304. }

  305.        break;
  306.    case 4:
  307.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  308. {
  309. P1_4=1;   //第1个灯灭
  310. P1_5=1;   //第2个灯灭
  311. P1_6=1;   //第3个灯灭
  312. P3_3=0;   //第4个灯亮

  313.     vGu8RunTimerFlag=0;   
  314. vGu16RunTimerCnt=RUN_TIME;   //用于控制跑马灯跑动速度的定时器
  315. vGu8RunTimerFlag=1;   //启动定时器

  316. //灵活切换“步骤变量”
  317. if(0==Gu8RunDirection) //往右跑
  318. {
  319.                Su8RunStep=1;
  320. }
  321. else  //往左跑
  322. {
  323.                Su8RunStep=3;
  324. }
  325. }

  326.        break;

  327. }

  328. }
复制代码



乐于分享,勇于质疑!
114#
 楼主| 发表于 2018-3-4 10:13:53 | 只看该作者
本帖最后由 jianhong_wu 于 2018-3-4 10:28 编辑

第一百一十节: 按键控制跑马灯的速度。
第一百一十节_pdf文件.pdf (110.92 KB, 下载次数: 1218)
【110.1   按键控制跑马灯的速度。】



                上图110.1.1  独立按键

   
                上图110.1.2  LED电路

                  
                上图110.1.3  有源蜂鸣器的电路

      之前109节讲到跑马灯的启动、暂停、停止、方向,本节在此基础上,把原来的“停止”更改为“速度”,
加深理解输入设备如何关联应用程序的程序框架。
      本节例程的功能如下:
     (1)【启动暂停】按键K1。上电后,按下【启动暂停】按键K1启动之后,跑马灯处于“启动”状态,4个LED灯挨个依次循环的变亮,给人“跑”起来的感觉。此时如果再按一次【启动暂停】按键K1,则跑马灯处于“暂停”状态,如果再按一次【启动暂停】按键K1,跑马灯又变回“启动”状态。因此,【启动暂停】按键K1是专门用来切换“启动”和“暂停”这两种状态。
     (2)【速度】按键K2。每按一次【速度】按键K2,跑马灯就在“慢”、“中”、“快”三档速度之间切换。
     (3)【方向】按键K3。跑马灯上电后默认处于“往右跑”的方向。每按一次【方向】按键K3,跑马灯就在“往右跑”与“往左跑”两个方向之间切换。
  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50
  3. #define KEY_FILTER_TIME  25  

  4. #define RUN_TIME_SLOW    500   //“慢”档速度的时间参数
  5. #define RUN_TIME_MIDDLE  300   //“中”档速度的时间参数
  6. #define RUN_TIME_FAST    100   //“快”档速度的时间参数

  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 RunTask(void);   //跑马灯的任务函数

  17. //4个跑马灯的输出口
  18. sbit P1_4=P1^4;  
  19. sbit P1_5=P1^5;  
  20. sbit P1_6=P1^6;  
  21. sbit P3_3=P3^3;  

  22. //蜂鸣器的输出口
  23. sbit P3_4=P3^4;  

  24. sbit KEY_INPUT1=P2^2;  //【启动暂停】按键K1的输入口。
  25. sbit KEY_INPUT2=P2^1;  //【速度】按键K2的输入口。
  26. sbit KEY_INPUT3=P2^0;  //【方向】按键K3的输入口。

  27. volatile unsigned char vGu8BeepTimerFlag=0;  
  28. volatile unsigned int vGu16BeepTimerCnt=0;  

  29. volatile unsigned char vGu8KeySec=0;  

  30. unsigned char Gu8RunStart=0;      //控制跑马灯启动的总开关
  31. unsigned char Gu8RunStatus=0;     //标识跑马灯当前的状态。0代表停止,1代表启动,2代表暂停。
  32. unsigned char Gu8RunDirection=0;  //标识跑马灯当前的方向。0代表往右跑,1代表往左跑。
  33. unsigned char Gu8RunSpeed=0;      //当前的速度档位。0代表“慢”,1代表“中”,2代表“快”。
  34. unsigned int  Gu16RunSpeedTimeDate=0; //承接各速度档位的时间参数的变量

  35. volatile unsigned char vGu8RunTimerFlag=0;   //用于控制跑马灯跑动速度的定时器
  36. volatile unsigned int vGu16RunTimerCnt=0;  

  37. void main()
  38. {
  39. SystemInitial();            
  40. Delay(10000);               
  41. PeripheralInitial();      
  42.     while(1)  
  43. {  
  44.     KeyTask();    //按键的任务函数
  45. RunTask();    //跑马灯的任务函数
  46.     }
  47. }

  48. void T0_time() interrupt 1     
  49. {
  50. VoiceScan();  
  51. KeyScan();   

  52. if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)  //用于控制跑马灯跑动速度的定时器
  53. {
  54. vGu16RunTimerCnt--;
  55. }

  56. TH0=0xfc;   
  57. TL0=0x66;   
  58. }


  59. void SystemInitial(void)
  60. {
  61. TMOD=0x01;  
  62. TH0=0xfc;   
  63. TL0=0x66;   
  64. EA=1;      
  65. ET0=1;      
  66. TR0=1;      
  67. }

  68. void Delay(unsigned long u32DelayTime)
  69. {
  70.     for(;u32DelayTime>0;u32DelayTime--);
  71. }

  72. void PeripheralInitial(void)
  73. {
  74. //跑马灯处于初始化的状态
  75. P1_4=0;   //第1个灯亮
  76. P1_5=1;   //第2个灯灭
  77. P1_6=1;   //第3个灯灭
  78. P3_3=1;   //第4个灯灭

  79. //根据当前的速度档位Gu8RunSpeed,来初始化速度时间参数Gu16RunSpeedTimeDate
  80. if(0==Gu8RunSpeed)
  81. {
  82. Gu16RunSpeedTimeDate=RUN_TIME_SLOW; //赋值“慢”档的时间参数
  83. }
  84. else if(1==Gu8RunSpeed)
  85. {
  86. Gu16RunSpeedTimeDate=RUN_TIME_MIDDLE; //赋值“中”档的时间参数
  87. }
  88. else
  89. {
  90. Gu16RunSpeedTimeDate=RUN_TIME_FAST; //赋值“快”档的时间参数
  91. }


  92. }

  93. void BeepOpen(void)
  94. {
  95. P3_4=0;  
  96. }

  97. void BeepClose(void)
  98. {
  99. P3_4=1;  
  100. }

  101. void VoiceScan(void)
  102. {

  103.           static unsigned char Su8Lock=0;  

  104. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  105.           {
  106.                   if(0==Su8Lock)
  107.                   {
  108.                    Su8Lock=1;  
  109. BeepOpen();
  110.      }
  111.     else  
  112. {     

  113.                        vGu16BeepTimerCnt--;         

  114.                    if(0==vGu16BeepTimerCnt)
  115.                    {
  116.                            Su8Lock=0;     
  117. BeepClose();  
  118.                    }

  119. }
  120.           }         
  121. }

  122. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  123. {
  124.    static unsigned char Su8KeyLock1;
  125.    static unsigned int  Su16KeyCnt1;
  126.    static unsigned char Su8KeyLock2;
  127.    static unsigned int  Su16KeyCnt2;
  128.    static unsigned char Su8KeyLock3;
  129.    static unsigned int  Su16KeyCnt3;


  130.    //【启动暂停】按键K1的扫描识别
  131.    if(0!=KEY_INPUT1)
  132.    {
  133.       Su8KeyLock1=0;
  134.       Su16KeyCnt1=0;   
  135.    }
  136.    else if(0==Su8KeyLock1)
  137.    {
  138.       Su16KeyCnt1++;
  139.       if(Su16KeyCnt1>=KEY_FILTER_TIME)
  140.       {
  141.          Su8KeyLock1=1;  
  142.          vGu8KeySec=1;    //触发1号键
  143.       }
  144.    }

  145.    //【速度】按键K2的扫描识别
  146.    if(0!=KEY_INPUT2)
  147.    {
  148.       Su8KeyLock2=0;
  149.       Su16KeyCnt2=0;      
  150.    }
  151.    else if(0==Su8KeyLock2)
  152.    {
  153.       Su16KeyCnt2++;
  154.       if(Su16KeyCnt2>=KEY_FILTER_TIME)
  155.       {
  156.          Su8KeyLock2=1;  
  157.          vGu8KeySec=2;    //触发2号键
  158.       }
  159.    }

  160.    //【方向】按键K3的扫描识别
  161.    if(0!=KEY_INPUT3)
  162.    {
  163.       Su8KeyLock3=0;
  164.       Su16KeyCnt3=0;      
  165.    }
  166.    else if(0==Su8KeyLock3)
  167.    {
  168.       Su16KeyCnt3++;
  169.       if(Su16KeyCnt3>=KEY_FILTER_TIME)
  170.       {
  171.          Su8KeyLock3=1;  
  172.          vGu8KeySec=3;    //触发3号键
  173.       }
  174.    }
  175. }

  176. /* 注释一:
  177. *  本节破题的关键:
  178. *  在KeyTask和RunTask两个任务函数之间,主要是靠Gu8RunStart、Gu8RunStatus、Gu8RunDirection、
  179. *  Gu16RunSpeedTimeDate这四个全局变量来传递信息。
  180. */

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

  187. switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
  188. {
  189.    case 1:     //1号按键。【启动暂停】按键K1
  190.         if(0==Gu8RunStatus) //当跑马灯处于“停止”状态时
  191.         {
  192. Gu8RunStart=1;   //总开关“打开”。
  193. Gu8RunStatus=1;  //状态切换到“启动”状态
  194. }
  195.         else if(1==Gu8RunStatus) //当跑马灯处于“启动”状态时
  196.         {
  197. Gu8RunStatus=2;  //状态切换到“暂停”状态
  198. }
  199.         else  //当跑马灯处于“暂停”状态时
  200.         {
  201. Gu8RunStatus=1;  //状态切换到“启动”状态
  202. }

  203.         vGu8BeepTimerFlag=0;  
  204. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  205.         vGu8BeepTimerFlag=1;  
  206. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  207. break;

  208.    case 2:     //2号按键。【速度】按键K2

  209.         //每按一次K2按键,Gu8RunSpeed就在0、1、2三者之间切换,并且根据Gu8RunSpeed的数值,
  210. //对Gu16RunSpeedTimeDate赋值不同的速度时间参数,从而控制速度档位
  211.         if(0==Gu8RunSpeed)
  212. {
  213. Gu8RunSpeed=1;  //“中”档
  214. Gu16RunSpeedTimeDate=RUN_TIME_MIDDLE; //赋值“中”档的时间参数
  215. }
  216.         else if(1==Gu8RunSpeed)
  217. {
  218. Gu8RunSpeed=2;   //“快”档
  219. Gu16RunSpeedTimeDate=RUN_TIME_FAST; //赋值“快”档的时间参数
  220. }
  221. else
  222. {
  223. Gu8RunSpeed=0;   //“慢”档
  224. Gu16RunSpeedTimeDate=RUN_TIME_SLOW; //赋值“慢”档的时间参数
  225. }

  226.         vGu8BeepTimerFlag=0;  
  227. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  228.         vGu8BeepTimerFlag=1;  
  229. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  230. break;

  231.    case 3:     //3号按键。【方向】按键K3
  232.         //每按一次K3按键,Gu8RunDirection就在0和1之间切换,并且对从而控制方向
  233.         if(0==Gu8RunDirection)
  234. {
  235. Gu8RunDirection=1;
  236. }
  237. else
  238. {
  239. Gu8RunDirection=0;
  240. }

  241.         vGu8BeepTimerFlag=0;  
  242. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  243.         vGu8BeepTimerFlag=1;  
  244. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  245. break;

  246. }
  247. }

  248. /* 注释二:
  249. * “速度”是受Gu16RunSpeedTimeDate具体数值大小的影响
  250. */

  251. void RunTask(void)    //跑马灯的任务函数,放在主函数内
  252. {
  253. static unsigned char Su8RunStep=0; //运行的步骤


  254. //当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零,跑马灯初始化。
  255. if(0!=Su8RunStep&&0==Gu8RunStart)
  256. {
  257. Su8RunStep=0; //步骤归零

  258. //跑马灯处于初始化的状态
  259. P1_4=0;   //第1个灯亮
  260. P1_5=1;   //第2个灯灭
  261. P1_6=1;   //第3个灯灭
  262. P3_3=1;   //第4个灯灭

  263. }

  264. switch(Su8RunStep) //屡见屡爱的switch又来了
  265. {
  266.    case 0:
  267.        if(1==Gu8RunStart) //总开关“打开”
  268. {
  269. vGu8RunTimerFlag=0;   
  270. vGu16RunTimerCnt=0;  //定时器清零
  271.             Su8RunStep=1;  //切换到下一步,启动
  272. }
  273.        break;
  274.    case 1:
  275.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  276. {
  277. P1_4=0;   //第1个灯亮
  278. P1_5=1;   //第2个灯灭
  279. P1_6=1;   //第3个灯灭
  280. P3_3=1;   //第4个灯灭

  281.     vGu8RunTimerFlag=0;   
  282. vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
  283. vGu8RunTimerFlag=1;   //启动定时器

  284. //灵活切换“步骤变量”
  285. if(0==Gu8RunDirection) //往右跑
  286. {
  287.                Su8RunStep=2;
  288. }
  289. else  //往左跑
  290. {
  291.                Su8RunStep=4;
  292. }

  293. }

  294.        break;
  295.    case 2:
  296.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  297. {
  298. P1_4=1;   //第1个灯灭
  299. P1_5=0;   //第2个灯亮
  300. P1_6=1;   //第3个灯灭
  301. P3_3=1;   //第4个灯灭

  302.     vGu8RunTimerFlag=0;   
  303. vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
  304. vGu8RunTimerFlag=1;   //启动定时器

  305. //灵活切换“步骤变量”
  306. if(0==Gu8RunDirection) //往右跑
  307. {
  308.                Su8RunStep=3;
  309. }
  310. else  //往左跑
  311. {
  312.                Su8RunStep=1;
  313. }
  314. }

  315.        break;
  316.    case 3:
  317.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  318. {
  319. P1_4=1;   //第1个灯灭
  320. P1_5=1;   //第2个灯灭
  321. P1_6=0;   //第3个灯亮
  322. P3_3=1;   //第4个灯灭

  323.     vGu8RunTimerFlag=0;   
  324. vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
  325. vGu8RunTimerFlag=1;   //启动定时器

  326. //灵活切换“步骤变量”
  327. if(0==Gu8RunDirection) //往右跑
  328. {
  329.                Su8RunStep=4;
  330. }
  331. else  //往左跑
  332. {
  333.                Su8RunStep=2;
  334. }
  335. }

  336.        break;
  337.    case 4:
  338.        if(1==Gu8RunStatus&&0==vGu16RunTimerCnt) //当前处于“启动”状态,并且定时器等于0
  339. {
  340. P1_4=1;   //第1个灯灭
  341. P1_5=1;   //第2个灯灭
  342. P1_6=1;   //第3个灯灭
  343. P3_3=0;   //第4个灯亮

  344.     vGu8RunTimerFlag=0;   
  345. vGu16RunTimerCnt=Gu16RunSpeedTimeDate; //速度时间参数变量的大小,决定了速度
  346. vGu8RunTimerFlag=1;   //启动定时器

  347. //灵活切换“步骤变量”
  348. if(0==Gu8RunDirection) //往右跑
  349. {
  350.                Su8RunStep=1;
  351. }
  352. else  //往左跑
  353. {
  354.                Su8RunStep=3;
  355. }
  356. }

  357.        break;

  358. }

  359. }
复制代码






乐于分享,勇于质疑!
115#
 楼主| 发表于 2018-3-11 13:48:05 | 只看该作者
本帖最后由 jianhong_wu 于 2018-3-11 14:01 编辑

第一百一十一节: 工业自动化设备的开关信号的运动控制。
第一百一十一节_pdf文件.pdf (117.78 KB, 下载次数: 1299)
【111.1   开关信号的运动控制。】



                上图111.1.1  独立按键

     
                上图111.1.2  LED电路

               
                上图111.1.3  有源蜂鸣器的电路

       本节涉及的知识点有,switch的过程控制,时间延时,开关感应器的软件滤波,工件计数器,以及整体的软件框架。
       现在有一台设备,水平方向有一个滑块,能左右移动,滑块上安装了一个能垂直伸缩的“机械手”。按下启动按键后,滑块先从左边往右边移动,移到最右边碰到“右感应器”后,滑块上的“机械手”开始往下移动2秒,移动2秒后开始原路返回,“机械手”向上移动,碰到“上感应器”后,滑块开始往左边移动,移动3秒后默认已经回到原位最左边,此时“计数器”累加1,完成一次过程,如果再按下启动按键,继续重复这个过程。
       这个设备用了2个气缸。1个“水平气缸”驱动滑块水平方向的左右移动,当控制“水平气缸”的输出信号为0时往左边跑,当控制“水平气缸”的输出信号为1时往右边跑。另1个“垂直气缸”驱动“机械手”的上下移动,当控制“垂直气缸”的输出信号为0时往上边跑,当控制“垂直气缸”的输出信号为1时往下边跑。
       这个设备用了2个开关感应器。分别是“右感应器”和“上感应器”。当感应器没有被碰到的时候信号为1,当感应器被碰到的时候信号为0。
       这个设备用了1个独立按键。控制运动的启动。
       2个气缸是输出信号,用P1.4和P1.5所控制的两个LED模拟。2个开关感应器是输入信号,用K2和K3这两个独立按键模拟。1个独立按键用K1按键。如上图。
  1. #include "REG52.H"  

  2. #define KEY_VOICE_TIME   50
  3. #define KEY_FILTER_TIME  25  
  4. #define SENSOR_TIME   20      //开关感应器的“滤波”时间


  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 GoLeft(void) ; //“水平气缸”往左跑
  12. void GoRight(void); //“水平气缸”往右跑
  13. void GoUp(void);    //“垂直气缸”往上跑
  14. void GoDown(void);  //“垂直气缸”往下跑

  15. void VoiceScan(void);
  16. void SensorScan(void);  //开关感应器的消抖,在定时中断里调用处理
  17. void KeyScan(void);   
  18. void KeyTask(void);   
  19. void RunTask(void);    //运动控制的任务函数

  20. sbit P1_4=P1^4;  //水平气缸的输出
  21. sbit P1_5=P1^5;  //垂直气缸的输出

  22. sbit P3_4=P3^4;  //蜂鸣器的输出口

  23. sbit KEY_INPUT1=P2^2;  //【启动】按键K1的输入口。

  24. sbit SensorRight_sr=P2^1;   //右感应器的输入口
  25. sbit SensorUp_sr=P2^0;      //上感应器的输入口

  26. volatile unsigned char vGu8SensorRight=0;  //右感应器经过滤波后的当前电平状态。
  27. volatile unsigned char vGu8SensorUp=0;  //上感应器经过滤波后的当前电平状态。

  28. volatile unsigned char vGu8BeepTimerFlag=0;  
  29. volatile unsigned int vGu16BeepTimerCnt=0;  

  30. volatile unsigned char vGu8KeySec=0;  

  31. unsigned char Gu8RunStart=0;      //启动的总开关
  32. unsigned char Gu8RunStatus=0;     //运动的状态,0为停止,1为运行

  33. unsigned int  Gu16RunCnt=0;       //计数器
  34. unsigned int  Gu16ReturnLeftTime=3000;   //水平往左跑的延时变量,默认为3秒
  35. unsigned int  Gu16GoDownTime=2000;       //垂直往下跑的延时变量,默认为2秒

  36. volatile unsigned char vGu8RunTimerFlag=0;   //用于控制运动过程中的延时的定时器
  37. volatile unsigned int vGu16RunTimerCnt=0;  

  38. void main()
  39. {
  40. SystemInitial();            
  41. Delay(10000);               
  42. PeripheralInitial();      
  43.     while(1)  
  44. {  
  45.     KeyTask();    //按键的任务函数
  46. RunTask();    //运动控制的任务函数
  47.     }
  48. }

  49. /* 注释一:
  50. *  两个“计时器”相互“清零”相互“抗衡”,从而实现了开关感应器的“消抖”处理,
  51. *  专业术语也叫“软件滤波”。这种滤波方式,不管是从“高转成低”,还是“低转成高”,
  52. *  如果在某个瞬间出现干扰抖动,某个计数器都会及时被“清零”,从而起到非常高效的消抖滤波作用。
  53. */

  54. void SensorScan(void)  //此函数放在定时中断里每1ms扫描一次,用来识别和滤波开关感应器
  55. {
  56.       static unsigned int Su16SensorRight_H_Cnt=0;  //判断高电平的计时器
  57.       static unsigned int Su16SensorRight_L_Cnt=0;  //判断低电平的计时器

  58.       static unsigned int Su16SensorUp_H_Cnt=0;  //判断高电平的计时器
  59.       static unsigned int Su16SensorUp_L_Cnt=0;  //判断低电平的计时器

  60.       //右感应器的滤波
  61.       if(0==SensorRight_sr)
  62.       {
  63.            Su16SensorRight_H_Cnt=0;  //在判断低电平的时候,高电平的计时器被清零,巧妙极了!
  64.            Su16SensorRight_L_Cnt++;
  65.            if(Su16SensorRight_L_Cnt>=SENSOR_TIME)
  66.            {
  67.                Su16SensorRight_L_Cnt=0;
  68.                vGu8SensorRight=0;   //此全局变量反馈经过滤波后“右感应器”当前电平的状态
  69.            }

  70.        }
  71.        else
  72.        {
  73.            Su16SensorRight_L_Cnt=0;   //在判断高电平的时候,低电平的计时器被清零,巧妙极了!
  74.            Su16SensorRight_H_Cnt++;
  75.            if(Su16SensorRight_H_Cnt>=SENSOR_TIME)
  76.            {
  77.                Su16SensorRight_H_Cnt=0;
  78.                vGu8SensorRight=1;  //此全局变量反馈经过滤波后“右感应器”当前电平的状态
  79.            }
  80.        }

  81.       //上感应器的滤波
  82.       if(0==SensorUp_sr)
  83.       {
  84.            Su16SensorUp_H_Cnt=0;  
  85.            Su16SensorUp_L_Cnt++;
  86.            if(Su16SensorUp_L_Cnt>=SENSOR_TIME)
  87.            {
  88.                Su16SensorUp_L_Cnt=0;
  89.                vGu8SensorUp=0;   //此全局变量反馈经过滤波后“上感应器”当前电平的状态
  90.            }

  91.        }
  92.        else
  93.        {
  94.            Su16SensorUp_L_Cnt=0;   
  95.            Su16SensorUp_H_Cnt++;
  96.            if(Su16SensorUp_H_Cnt>=SENSOR_TIME)
  97.            {
  98.                Su16SensorUp_H_Cnt=0;
  99.                vGu8SensorUp=1;  //此全局变量反馈经过滤波后“上感应器”当前电平的状态
  100.            }
  101.        }


  102. }

  103. void T0_time() interrupt 1     
  104. {
  105. VoiceScan();  
  106. KeyScan();   
  107. SensorScan();  //用来识别和滤波开关感应器

  108. if(1==vGu8RunTimerFlag&&vGu16RunTimerCnt>0)  //用于控制运动延时的定时器
  109. {
  110. vGu16RunTimerCnt--;
  111. }

  112. TH0=0xfc;   
  113. TL0=0x66;   
  114. }


  115. void SystemInitial(void)
  116. {
  117. TMOD=0x01;  
  118. TH0=0xfc;   
  119. TL0=0x66;   
  120. EA=1;      
  121. ET0=1;      
  122. TR0=1;      
  123. }

  124. void Delay(unsigned long u32DelayTime)
  125. {
  126.     for(;u32DelayTime>0;u32DelayTime--);
  127. }

  128. void PeripheralInitial(void)
  129. {
  130.      //上电初始化气缸的开机位置
  131. GoLeft() ;     //“水平气缸”往左跑,上电初始化时滑块处于左边
  132. GoUp();        //“垂直气缸”往上跑,上电初始化时“机械臂”处于上方

  133. }

  134. void BeepOpen(void)
  135. {
  136. P3_4=0;  
  137. }

  138. void BeepClose(void)
  139. {
  140. P3_4=1;  
  141. }

  142. void GoLeft(void) //“水平气缸”往左跑
  143. {
  144. P1_4=0;   
  145. }

  146. void GoRight(void) //“水平气缸”往右跑
  147. {
  148. P1_4=1;   
  149. }

  150. void GoUp(void)  //“垂直气缸”往上跑
  151. {
  152. P1_5=0;   
  153. }

  154. void GoDown(void) //“垂直气缸”往下跑
  155. {
  156. P1_5=1;   
  157. }


  158. void VoiceScan(void)
  159. {

  160.           static unsigned char Su8Lock=0;  

  161. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  162.           {
  163.                   if(0==Su8Lock)
  164.                   {
  165.                    Su8Lock=1;  
  166. BeepOpen();
  167.      }
  168.     else  
  169. {     

  170.                        vGu16BeepTimerCnt--;         

  171.                    if(0==vGu16BeepTimerCnt)
  172.                    {
  173.                            Su8Lock=0;     
  174. BeepClose();  
  175.                    }

  176. }
  177.           }         
  178. }

  179. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  180. {
  181.    static unsigned char Su8KeyLock1;
  182.    static unsigned int  Su16KeyCnt1;

  183.    //【启动】按键K1的扫描识别
  184.    if(0!=KEY_INPUT1)
  185.    {
  186.       Su8KeyLock1=0;
  187.       Su16KeyCnt1=0;   
  188.    }
  189.    else if(0==Su8KeyLock1)
  190.    {
  191.       Su16KeyCnt1++;
  192.       if(Su16KeyCnt1>=KEY_FILTER_TIME)
  193.       {
  194.          Su8KeyLock1=1;  
  195.          vGu8KeySec=1;    //触发1号键
  196.       }
  197.    }

  198. }

  199. /* 注释二:
  200. *  在KeyTask中只改变Gu8RunStart的值,用于总启动开关。而运动状态Gu8RunStatus是在运动函数
  201. *  RunTask中改变,用于对外反馈实时的运动状态。
  202. */

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

  209. switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
  210. {
  211.    case 1:     //1号按键。【启动】按键K1

  212.         if(0==Gu8RunStatus) //根据当前运动的状态来决定“总开关”是否能受按键的控制
  213.         {
  214. Gu8RunStart=1;   //总开关“打开”。
  215. }

  216.         vGu8BeepTimerFlag=0;  
  217. vGu16BeepTimerCnt=KEY_VOICE_TIME;  //触发按键后,发出固定长度的声音
  218.         vGu8BeepTimerFlag=1;  
  219. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一直触发
  220. break;

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

  224. }
  225. }

  226. /* 注释三:
  227. *  本节故意引入三个变量:计数器Gu16RunCnt,左延时Gu16ReturnLeftTime,下延时Gu16GoDownTime。
  228. *  在人机界面的场合,这三个变量可以用来扩展实现设置参数的功能。比如,如果有数码管,可以通过
  229. *  显示Gu16RunCnt的数值来让客户看到当前设备的计数器。如果有数码管和按键,可以通过切换到某个
  230. *  界面下,修改Gu16ReturnLeftTime和Gu16GoDownTime的数值,让客户对设备进行延时参数的设置。
  231. */

  232. void RunTask(void)    //运动控制的任务函数,放在主函数内
  233. {
  234. static unsigned char Su8RunStep=0; //运行的步骤


  235. //当总开关处于“停止”并且“步骤不为0”时,强制把步骤归零。
  236. if(0!=Su8RunStep&&0==Gu8RunStart)
  237. {
  238. Su8RunStep=0; //步骤归零
  239. }

  240. switch(Su8RunStep) //屡见屡爱的switch又来了
  241. {
  242.    case 0:
  243.        if(1==Gu8RunStart) //总开关“打开”
  244. {
  245.   Gu8RunStatus=1;  //及时设置Gu8RunStatus的运动状态为“运行”

  246. GoRight() ;      //“水平气缸”往右跑。P1.4的LED灯“灭”。

  247.             Su8RunStep=1;  //切换到下一步
  248. }
  249.        break;
  250.    case 1:
  251.        if(0==vGu8SensorRight) //直到碰到了“右感应器”(按下K2),“机械臂”才往下移动。
  252. {
  253. GoDown();  //“垂直气缸”往下跑。P1.5的LED灯“灭”。
  254.     vGu8RunTimerFlag=0;   
  255. vGu16RunTimerCnt=Gu16GoDownTime; //向下移动3秒的延时赋值
  256. vGu8RunTimerFlag=1;   //启动定时器

  257.             Su8RunStep=2;  //切换到下一步
  258. }
  259.        break;
  260.    case 2:
  261.        if(0==vGu16RunTimerCnt) //当定时的3秒时间到,“机械臂”才往上移动,开始原路返回。
  262. {
  263. GoUp();    //“垂直气缸”往上跑。P1.5的LED灯“亮”。
  264.             Su8RunStep=3;  //切换到下一步  
  265. }
  266.        break;
  267.    case 3:
  268.        if(0==vGu8SensorUp) //直到碰到了“上感应器”(按下K3),滑块才往左移动。
  269. {
  270. GoLeft() ; //“水平气缸”往左跑。P1.4的LED灯“亮”。
  271.     vGu8RunTimerFlag=0;   
  272. vGu16RunTimerCnt=Gu16ReturnLeftTime; //向左移动2秒的延时赋值
  273. vGu8RunTimerFlag=1;   //启动定时器

  274.             Su8RunStep=4;  //切换到下一步
  275. }

  276.        break;
  277.    case 4:
  278.        if(0==vGu16RunTimerCnt) //当定时的2秒时间到,完成一次过程。
  279. {
  280.     Gu16RunCnt++;   //计数器加1,统计设备运行的次数
  281.   Gu8RunStatus=0;  //及时设置Gu8RunStatus的运动状态为“停止”
  282. Gu8RunStart=0;  //总开关“关闭”,为下一次启动作准备
  283.             Su8RunStep=0;   //步骤变量清零,为下一次启动作准备
  284. }
  285.        break;
  286. }
  287. }
复制代码






乐于分享,勇于质疑!
116#
 楼主| 发表于 2018-3-30 10:11:35 | 只看该作者
本帖最后由 jianhong_wu 于 2018-3-30 10:23 编辑

第一百一十二节: 数码管显示的基础知识。
第一百一十二节_pdf文件.pdf (104.92 KB, 下载次数: 1229)
【112.1   数码管显示的基础知识。】
   
                上图112.1.1  数码管

     
                上图112.1.2  等效图

       如上图112.1.1,一个数码管内部有8个段码,每个段码内部对应一颗能发光的LED灯,把相关位置的段码点亮或熄灭就可以显示出不同的数字或者小数点。比如,要显示一个数字“1”,只需要点亮b和c这两个段码LED即可,其它6个a,d,e,f,g,h段码LED熄灭,就可以显示一个数字“1”。再进一步深入分析数码管内部的等效图(上图112.1.2),com4是右起第1位数码管内部8个段码LED的公共端,要点亮任何一个段码LED的前提必须是公共端com4为低电平(P1.0输出0信号)。如果公共端com4为高电平(P1.0输出1信号),则不管段码端P0口的8个IO口输出什么信号,8个段码LED都是熄灭的(无正压差,则无电流无回路)。因此,公共端(比如com4,com3,com2,com1)就是某个数码管的“总开关”。比如,右起第1位数码管要显示数字“1”,要点亮b和c,则P0.1和P0.2必须输出“1”高电平,其它P0.0,P0.3,P0.4,P0.5,P0.6,P0.7必须输出“0”低电平,把这8个IO口二进制的信号转换成十六进制,则整个P0口总线只需输出一个十六进制的0x06,最后,“总开关”打开,公共端com4输出“0”,即可显示一个数字“1”。如果需要显示其它的不同数字,只需要改变段码端P0口的十六进制输出数值即可,如果提前把要显示的数字放在一个数组里,这个数组就是编码转换表,类似于一个字库表。现在编写一个程序例子,右起第1个和第3个数码管循环显示从0到9的数字,另外右起第2个和第4个数码管则关闭不显示,程序代码如下:
  1. #include "REG52.H"  

  2. #define CHANGE_TIME  1000    //数码管切换显示数字的时间

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

  7. void DisplayTask(void);    //数码管显示的任务函数

  8. sbit P1_0=P1^0;  //右起第1位数码管的公共端com4
  9. sbit P1_1=P1^1;  //右起第2位数码管的公共端com3
  10. sbit P1_2=P1^2;  //右起第3位数码管的公共端com2
  11. sbit P1_3=P1^3;  //右起第4位数码管的公共端com1

  12. //根据原理图得出的共阴数码管编码转换表,类似于一个字库表
  13. code unsigned char Cu8DigTable[]=
  14. {
  15. 0x3f,  //0       序号0
  16. 0x06,  //1       序号1
  17. 0x5b,  //2       序号2
  18. 0x4f,  //3       序号3
  19. 0x66,  //4       序号4
  20. 0x6d,  //5       序号5
  21. 0x7d,  //6       序号6
  22. 0x07,  //7       序号7
  23. 0x7f,  //8       序号8
  24. 0x6f,  //9       序号9
  25. 0x00,  //不显示  序号10
  26. };
  27. volatile unsigned char vGu8ChangeTimerFlag=0;  //控制切换数字的时间的定时器
  28. volatile unsigned int vGu16ChangeTimerCnt=0;  

  29. unsigned char Gu8Number=0; //从0到9依次循环显示的数字

  30. void main()
  31. {
  32. SystemInitial();            
  33. Delay(10000);               
  34. PeripheralInitial();      
  35.     while(1)  
  36. {  
  37. DisplayTask();    //数码管显示的任务函数
  38.     }
  39. }

  40. void DisplayTask(void)    //数码管显示的任务函数
  41. {
  42. static unsigned char Su8GetCode;  //从编码转换表中提取出来的编码。

  43. if(0==vGu16ChangeTimerCnt)  //定时的时间到,更新显示下一个数字,依次循环显示
  44. {
  45. Su8GetCode=Cu8DigTable[Gu8Number]; //从编码转换表中提取出来的编码。

  46.     P0=Su8GetCode; //段码端输出需要显示的编码
  47. P1_0=0;  //右起第1位数码管的公共端com4,“总开关”打开,输出低电平0
  48. P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
  49. P1_2=0;  //右起第3位数码管的公共端com2,“总开关”打开,输出低电平0
  50. P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1

  51. Gu8Number++;  //显示的数字不断从0到9累加
  52. if(Gu8Number>9)
  53. {
  54. Gu8Number=0;
  55. }

  56. vGu8ChangeTimerFlag=0;
  57. vGu16ChangeTimerCnt=CHANGE_TIME;  
  58. vGu8ChangeTimerFlag=1;  //启动新一轮的定时器
  59. }
  60. }

  61. void T0_time() interrupt 1     
  62. {
  63. if(1==vGu8ChangeTimerFlag&&vGu16ChangeTimerCnt>0)  //数码管显示切换时间的定时器
  64. {
  65. vGu16ChangeTimerCnt--;
  66. }

  67. TH0=0xfc;   
  68. TL0=0x66;   
  69. }


  70. void SystemInitial(void)
  71. {
  72.     //初始化上电瞬间数码管的状态
  73. P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
  74. P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
  75. P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
  76. P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1

  77. TMOD=0x01;  
  78. TH0=0xfc;   
  79. TL0=0x66;   
  80. EA=1;      
  81. ET0=1;      
  82. TR0=1;      
  83. }

  84. void Delay(unsigned long u32DelayTime)
  85. {
  86.     for(;u32DelayTime>0;u32DelayTime--);
  87. }

  88. void PeripheralInitial(void)
  89. {

  90. }
复制代码







乐于分享,勇于质疑!
117#
 楼主| 发表于 2018-4-12 09:24:56 | 只看该作者
本帖最后由 jianhong_wu 于 2018-4-12 19:31 编辑

第一百一十三节: 动态扫描的数码管显示数字。
第一百一十三节_pdf文件.pdf (99.13 KB, 下载次数: 1132)
【113.1   动态扫描的数码管。】
      
                上图113.1.1  数码管

       上一节,看到打开显示的数码管右起第1个(com4)和第3个(com2)在任意时刻显示的数字是一样的,为什么?因为四个数码管的8个段码a,b,c,d,e,f,g,h所连接的单片机IO口是共用的,如果把四个数码管全部打开(com1,com2,com3,com4全部输出低电平),会发现四个数码管在任意时刻显示的四个数字也是一样的!实际应用中,要四个数码管能各自独立显示不同的数字,就需要用到“分时动态扫描”的方式。所谓分时,就是在任意时刻只能显示其中一个数码管(某个com输出低电平),其它三个数码管关闭(其它三个com输出高电平),每个数码管显示停留的时间固定一致并且非常短暂,四个数码管依次循环的切换显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管是同时亮的(实际不是同时亮),跟动画片“1秒钟动态切换显示多少幅画面”的原理一样。现在编写一个程序例子,四个数码管要显示四个不同的数字“1234”,程序代码如下:
  1. #include "REG52.H"  

  2. /* 注释一:
  3. *  SCAN_TIME是每个数码管停留显示的短暂时间。这里称为“扫描时间”。这个时间既不能太长也不能
  4. *  太短,要调试到恰到好处。太长,则影响其它数码管的显示,会让人觉得画面不连贯不是同时亮;
  5. *  太短,又会影响显示的亮度。具体的时间应该根据实际项目不断调试修正而得到最佳显示的数值。
  6. */

  7. #define SCAN_TIME  1   


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

  12. void DisplayScan(void);    //数码管的动态扫描函数,放在定时中断里。

  13. sbit P1_0=P1^0;  //右起第1位数码管的公共端com4
  14. sbit P1_1=P1^1;  //右起第2位数码管的公共端com3
  15. sbit P1_2=P1^2;  //右起第3位数码管的公共端com2
  16. sbit P1_3=P1^3;  //右起第4位数码管的公共端com1

  17. //根据原理图得出的共阴数码管编码转换表,类似于一个字库表
  18. code unsigned char Cu8DigTable[]=
  19. {
  20. 0x3f,  //0       序号0
  21. 0x06,  //1       序号1
  22. 0x5b,  //2       序号2
  23. 0x4f,  //3       序号3
  24. 0x66,  //4       序号4
  25. 0x6d,  //5       序号5
  26. 0x7d,  //6       序号6
  27. 0x07,  //7       序号7
  28. 0x7f,  //8       序号8
  29. 0x6f,  //9       序号9
  30. 0x00,  //不显示  序号10
  31. };

  32. volatile unsigned char vGu8ScanTimerFlag=0;  //动态扫描的定时器
  33. volatile unsigned int vGu16ScanTimerCnt=0;  

  34. /* 注释二:
  35. *  vGu8Display_Righ_4,vGu8Display_Righ_3,vGu8Display_Righ_2,vGu8Display_Righ_1,这四个
  36. *  全局变量用来传递每位数码管需要显示的数字,作为对上面应用层调用的接口变量。
  37. */

  38. volatile unsigned char vGu8Display_Righ_4=1;  //右起第4位数码管显示的变量。这里显示“1”
  39. volatile unsigned char vGu8Display_Righ_3=2;  //右起第3位数码管显示的变量。这里显示“2”
  40. volatile unsigned char vGu8Display_Righ_2=3;  //右起第2位数码管显示的变量。这里显示“3”
  41. volatile unsigned char vGu8Display_Righ_1=4;  //右起第1位数码管显示的变量。这里显示“4”

  42. void main()
  43. {
  44. SystemInitial();            
  45. Delay(10000);               
  46. PeripheralInitial();      
  47.     while(1)  
  48. {  
  49.     }
  50. }

  51. /* 注释三:
  52. *  DisplayScan数码管的动态扫描函数,之所以放在定时中断里,是因为动态扫描数码管对时间均匀度
  53. *  要求很高,如果放在main主函数中,期间稍微出现一些延时滞后或者超前执行的情况,都会导致
  54. *  数码管出现“闪烁”或者“忽暗忽亮”的显示效果。
  55. */

  56. void DisplayScan(void)   
  57. {
  58. static unsigned char Su8GetCode;  //从编码转换表中提取出来的编码。
  59. static unsigned char Su8ScanStep=1;  //扫描步骤

  60. if(0==vGu16ScanTimerCnt)  //定时的时间到,切换显示下一个数码管,依次动态快速循环切换显示
  61. {

  62. /* 注释四:
  63. *  在即将切换显示到下一个新的数码管之前,应该先关闭显示所有的数码管,避免因关闭不彻底而导致
  64. *  数码管某些段位出现“漏光”,也就是数码管因程序处理不善而出现常见的“鬼影”显示情况。
  65. */

  66.     P0=0x00; //输出显示先清零,先关闭显示所有的数码管
  67.     //先关闭所有的com口,先关闭显示所有的数码管
  68. P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
  69. P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
  70. P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
  71. P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1

  72.     switch(Su8ScanStep)
  73. {
  74.        case 1: //显示右起第1个数码管
  75. Su8GetCode=Cu8DigTable[vGu8Display_Righ_1]; //从编码转换表中提取出来的编码。
  76.     P0=Su8GetCode; //段码端输出需要显示的编码
  77. P1_0=0;  //右起第1位数码管的公共端com4,“总开关”打开,输出低电平0
  78. P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
  79. P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
  80. P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
  81. break;

  82.        case 2: //显示右起第2个数码管
  83. Su8GetCode=Cu8DigTable[vGu8Display_Righ_2]; //从编码转换表中提取出来的编码。
  84.     P0=Su8GetCode; //段码端输出需要显示的编码
  85. P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
  86. P1_1=0;  //右起第2位数码管的公共端com3,“总开关”打开,输出低电平0
  87. P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
  88. P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
  89. break;

  90.        case 3: //显示右起第3个数码管
  91. Su8GetCode=Cu8DigTable[vGu8Display_Righ_3]; //从编码转换表中提取出来的编码。
  92.     P0=Su8GetCode; //段码端输出需要显示的编码
  93. P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
  94. P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
  95. P1_2=0;  //右起第3位数码管的公共端com2,“总开关”打开,输出低电平0
  96. P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1            
  97. break;

  98.        case 4: //显示右起第4个数码管
  99. Su8GetCode=Cu8DigTable[vGu8Display_Righ_4]; //从编码转换表中提取出来的编码。
  100.     P0=Su8GetCode; //段码端输出需要显示的编码
  101. P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出高电平1
  102. P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
  103. P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出高电平1
  104. P1_3=0;  //右起第4位数码管的公共端com1,“总开关”打开,输出低电平0            
  105. break;

  106. }

  107. Su8ScanStep++;
  108. if(Su8ScanStep>4) //如果扫描步骤大于4,继续从第1步开始扫描
  109. {
  110. Su8ScanStep=1;

  111. }

  112. vGu8ScanTimerFlag=0;
  113. vGu16ScanTimerCnt=SCAN_TIME;  
  114. vGu8ScanTimerFlag=1;  //启动新一轮的定时器
  115. }
  116. }

  117. void T0_time() interrupt 1     
  118. {
  119. DisplayScan(); //数码管的动态扫描函数

  120. if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  //数码管显示切换时间的定时器
  121. {
  122. vGu16ScanTimerCnt--;
  123. }

  124.    
  125. TH0=0xfc;   
  126. TL0=0x66;   
  127. }


  128. void SystemInitial(void)
  129. {
  130. //初始化上电瞬间数码管的状态,关闭显示所有的数码管
  131. P0=0x00;
  132. P1_0=1;  //右起第1位数码管的公共端com4,“总开关”关闭,输出低电平1
  133. P1_1=1;  //右起第2位数码管的公共端com3,“总开关”关闭,输出高电平1
  134. P1_2=1;  //右起第3位数码管的公共端com2,“总开关”关闭,输出低电平1
  135. P1_3=1;  //右起第4位数码管的公共端com1,“总开关”关闭,输出高电平1
  136.    
  137. TMOD=0x01;  
  138. TH0=0xfc;   
  139. TL0=0x66;   
  140. EA=1;      
  141. ET0=1;      
  142. TR0=1;      
  143. }

  144. void Delay(unsigned long u32DelayTime)
  145. {
  146.     for(;u32DelayTime>0;u32DelayTime--);
  147. }

  148. void PeripheralInitial(void)
  149. {

  150. }


复制代码







乐于分享,勇于质疑!
118#
 楼主| 发表于 2018-4-16 12:47:37 | 只看该作者
本帖最后由 jianhong_wu 于 2018-4-16 12:58 编辑

第一百一十四节: 动态扫描的数码管显示小数点。
第一百一十四节_pdf文件.pdf (78.88 KB, 下载次数: 1063)
【114.1   动态扫描的数码管显示小数点。】
     
                上图114.1.1  数码管

       如上图,小数点的段码是h,对应单片机的P0.7口。数码管编码转换表(类似字库)的11个以字节为单位的数据,把它们从十六进制转换成二进制后,可以发现第7位(对应P0.7口)都是0。因此,从转换表里取数据后,得到的数据默认是让数码管的小数点不显示的。如果想显示这个小数点,就需要用到“或(|)”语句操作。比如,本节程序需要显示“1.234”这个带小数点的数值,代码如下:

  1. #include "REG52.H"  

  2. #define SCAN_TIME  1   

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

  7. void DisplayScan(void);   

  8. sbit P1_0=P1^0;  
  9. sbit P1_1=P1^1;  
  10. sbit P1_2=P1^2;  
  11. sbit P1_3=P1^3;

  12. //转换表,里面的11个数据,转换成二进制后,第7位数据都是0默认不显示小数点
  13. code unsigned char Cu8DigTable[]=
  14. {
  15. 0x3f,  //0       序号0
  16. 0x06,  //1       序号1
  17. 0x5b,  //2       序号2
  18. 0x4f,  //3       序号3
  19. 0x66,  //4       序号4
  20. 0x6d,  //5       序号5
  21. 0x7d,  //6       序号6
  22. 0x07,  //7       序号7
  23. 0x7f,  //8       序号8
  24. 0x6f,  //9       序号9
  25. 0x00,  //不显示  序号10
  26. };

  27. volatile unsigned char vGu8ScanTimerFlag=0;  
  28. volatile unsigned int vGu16ScanTimerCnt=0;  


  29. volatile unsigned char vGu8Display_Righ_4=1;  //右起第4位数码管显示的变量。这里显示“1”
  30. volatile unsigned char vGu8Display_Righ_3=2;  //右起第3位数码管显示的变量。这里显示“2”
  31. volatile unsigned char vGu8Display_Righ_2=3;  //右起第2位数码管显示的变量。这里显示“3”
  32. volatile unsigned char vGu8Display_Righ_1=4;  //右起第1位数码管显示的变量。这里显示“4”

  33. /* 注释一:
  34. *  vGu8Display_Righ_Dot_4,vGu8Display_Righ_Dot_3,vGu8Display_Righ_Dot_2,
  35. *  vGu8Display_Righ_Dot_1,这四个全局变量用来传递每位数码管是否需要显示它的小数点,如果是1
  36. *  代表需要显示其小数点,如果是0则不显示小数点。这四个变量作为对上面应用层调用的接口变量。
  37. */

  38. volatile unsigned char vGu8Display_Righ_Dot_4=1;  //右起第4位数码管的小数点。1代表打开显示。
  39. volatile unsigned char vGu8Display_Righ_Dot_3=0;  //右起第3位数码管的小数点。0代表关闭显示。
  40. volatile unsigned char vGu8Display_Righ_Dot_2=0;  //右起第2位数码管的小数点。0代表关闭显示。
  41. volatile unsigned char vGu8Display_Righ_Dot_1=0;  //右起第1位数码管的小数点。0代表关闭显示。

  42. void main()
  43. {
  44. SystemInitial();            
  45. Delay(10000);               
  46. PeripheralInitial();      
  47.     while(1)  
  48. {  
  49.     }
  50. }


  51. void DisplayScan(void)   
  52. {
  53. static unsigned char Su8GetCode;  
  54. static unsigned char Su8ScanStep=1;  

  55. if(0==vGu16ScanTimerCnt)  
  56. {


  57.     P0=0x00;
  58. P1_0=1;  
  59. P1_1=1;  
  60. P1_2=1;  
  61. P1_3=1;  

  62.     switch(Su8ScanStep)
  63. {
  64.        case 1:
  65. Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

  66. /* 注释二:
  67. *  这里是本节的关键。通过判断全局的接口变量的数值,来决定是否打开显示小数点。
  68. *  从转换表取出字模数据后再跟0x80进行“或”运算即可把第7位数据改为1。
  69. */

  70. if(1==vGu8Display_Righ_Dot_1) //如果打开了需要显示第1个数码管的小数点
  71. {
  72. Su8GetCode=Su8GetCode|0x80;  //把第7位数据改为1,显示小数点
  73. }
  74.     P0=Su8GetCode;
  75. P1_0=0;
  76. P1_1=1;  
  77. P1_2=1;  
  78. P1_3=1;            
  79. break;

  80.        case 2:
  81. Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
  82. if(1==vGu8Display_Righ_Dot_2) //如果打开了需要显示第2个数码管的小数点
  83. {
  84. Su8GetCode=Su8GetCode|0x80;  //把第7位数据改为1,显示小数点
  85. }
  86.     P0=Su8GetCode;
  87. P1_0=1;  
  88. P1_1=0;
  89. P1_2=1;
  90. P1_3=1;         
  91. break;

  92.        case 3:
  93. Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
  94. if(1==vGu8Display_Righ_Dot_3) //如果打开了需要显示第3个数码管的小数点
  95. {
  96. Su8GetCode=Su8GetCode|0x80;  //把第7位数据改为1,显示小数点
  97. }
  98.     P0=Su8GetCode;
  99. P1_0=1;  
  100. P1_1=1;  
  101. P1_2=0;  
  102. P1_3=1;         
  103. break;

  104.        case 4:
  105. Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
  106. if(1==vGu8Display_Righ_Dot_4) //如果打开了需要显示第4个数码管的小数点
  107. {
  108. Su8GetCode=Su8GetCode|0x80;  //把第7位数据改为1,显示小数点
  109. }
  110.     P0=Su8GetCode;
  111. P1_0=1;  
  112. P1_1=1;  
  113. P1_2=1;  
  114. P1_3=0;           
  115. break;

  116. }

  117. Su8ScanStep++;
  118. if(Su8ScanStep>4)
  119. {
  120. Su8ScanStep=1;
  121. }

  122. vGu8ScanTimerFlag=0;
  123. vGu16ScanTimerCnt=SCAN_TIME;  
  124. vGu8ScanTimerFlag=1;  
  125. }
  126. }

  127. void T0_time() interrupt 1     
  128. {
  129. DisplayScan();

  130. if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
  131. {
  132. vGu16ScanTimerCnt--;
  133. }


  134. TH0=0xfc;   
  135. TL0=0x66;   
  136. }


  137. void SystemInitial(void)
  138. {
  139. P0=0x00;
  140. P1_0=1;  
  141. P1_1=1;  
  142. P1_2=1;  
  143. P1_3=1;  

  144. TMOD=0x01;  
  145. TH0=0xfc;   
  146. TL0=0x66;   
  147. EA=1;      
  148. ET0=1;      
  149. TR0=1;      
  150. }

  151. void Delay(unsigned long u32DelayTime)
  152. {
  153.     for(;u32DelayTime>0;u32DelayTime--);
  154. }

  155. void PeripheralInitial(void)
  156. {

  157. }
复制代码




乐于分享,勇于质疑!
119#
 楼主| 发表于 2018-4-22 11:40:14 | 只看该作者
本帖最后由 jianhong_wu 于 2018-4-22 12:13 编辑

第一百一十五节: 按键控制数码管的秒表。
第一百一十五节_pdf文件.pdf (128.74 KB, 下载次数: 997)
【115.1   按键控制数码管的秒表。】
     
                上图115.1.1  数码管




                上图115.1.2  独立按键

       本节通过一个秒表的小项目,让大家学会以下4个知识点:
      (1)上层的界面显示框架几乎都要用到更新变量,更新变量包括整屏更新和局部更新,本节只用到整屏更新。更新变量是用全局变量在函数之间传递信息。作用是,当有某个需要显示的数据发生改变的时候,就要给更新变量置1,让显示函数重新更新一次显示,确保最新的数据能及时显示出来,平时没有数据更新改变的时候不用频繁更新显示避免占用CPU过多的时间。
      (2)凡是需要显示数字的地方,都必须涉及如何把一个数据按“个十百千万...”的位逐个提取出来的算法。这个算法比较简单,主要用“求余”和“求商”这两个运算语句就可以随心所欲的把数据位提取出来。除此之外,还要学会如何用if语句判断数据的范围,来把高位尚未用到的某个数码管屏蔽,让该位数码管只显示一个“不显示”的数据(避免直接显示一个0)。
      (3)我常把单片机程序简化成4个代表:按键(人机输入),数码管(人机界面),跑马灯(应用程序),串口(通信)。本节的“应用程序”不是跑马灯,而是秒表。不管是跑马灯,还是秒表,都要用到一个总启动Gu8RunStart和一个总运行步骤Gu8RunStep。建议大家,总启动Gu8RunStart和总运行步骤Gu8RunStep应该成双成对的出现(这样关断响应更及时,并且结构更紧凑,漏洞更少),比如,凡是总启动Gu8RunStart发生改变的时候,总运行步骤Gu8RunStep都复位归零一下。
      (4)一个硬件的定时器中断,可以衍生出N个软件定时器,之前跟大家介绍的是“递减式”的软件定时器,而且实际应用中,“递减式”的软件定时器也是用得最多。本节因为项目的需要,需要用到的是“累加式”的软件定时器。不管是哪种软件定时器,大家都要注意定时器变量在定义时所用到的数据类型,这个数据类型决定了定时时间的长度,比如在51单片机中,unsigned int的范围是0到65535,最大一次性定时65.535秒。而unsigned long的范围是0到4294967295,最大一次性定时4294967.295秒。本节秒表的时间超过65.535秒,因此需要用到unsigned long类型的定时器变量。
本节秒表程序的功能:K1按键是复位按键,每按一次,秒表都停止并且重新归零。K2按键是启动和暂停按键,当秒表处于复位后停止的状态时按一次则开始启动,当秒表处于正在工作的状态时按一次则处于暂停状态,当秒表处于暂停的状态时按一次则继续处于工作的状态。本节4位数码管,显示的时间是带2位小数点的,能显示的时间范围是:0.00秒到99.99秒。代码如下:
  1. #include "REG52.H"  

  2. #define KEY_FILTER_TIME  25

  3. #define SCAN_TIME  1   

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

  8. void KeyScan(void);  
  9. void KeyTask(void);  

  10. void DisplayScan(void);  //底层显示的驱动函数  
  11. void DisplayTask(void);  //上层显示的任务函数

  12. void RunTask(void);  //秒表的应用程序

  13. sbit KEY_INPUT1=P2^2;  
  14. sbit KEY_INPUT2=P2^1;  

  15. sbit P1_0=P1^0;  
  16. sbit P1_1=P1^1;  
  17. sbit P1_2=P1^2;  
  18. sbit P1_3=P1^3;

  19. //数码管转换表
  20. code unsigned char Cu8DigTable[]=
  21. {
  22. 0x3f,  //0       序号0
  23. 0x06,  //1       序号1
  24. 0x5b,  //2       序号2
  25. 0x4f,  //3       序号3
  26. 0x66,  //4       序号4
  27. 0x6d,  //5       序号5
  28. 0x7d,  //6       序号6
  29. 0x07,  //7       序号7
  30. 0x7f,  //8       序号8
  31. 0x6f,  //9       序号9
  32. 0x00,  //不显示  序号10
  33. };

  34. //数码管底层驱动扫描的软件定时器
  35. volatile unsigned char vGu8ScanTimerFlag=0;  
  36. volatile unsigned int vGu16ScanTimerCnt=0;  

  37. //秒表的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
  38. volatile unsigned char vGu8StopWatchTimerFlag=0;  
  39. volatile unsigned long vGu32StopWatchTimerCnt=0;  

  40. //数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
  41. volatile unsigned char vGu8UpdateTimerFlag=0;  
  42. volatile unsigned int vGu16UpdateTimerCnt=0;  


  43. unsigned char Gu8RunStart=0;  //应用程序的总启动
  44. unsigned char Gu8RunStep=0;  //应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
  45. unsigned char Gu8RunStatus=0; //当前秒表的状态。0代表停止,1代表正在工作中,2代表暂停

  46. unsigned char Gu8WdUpdate=1;  //开机默认整屏更新一次显示。此变量在显示框架中是非常重要的变量

  47. volatile unsigned char vGu8Display_Righ_4=10;  //开机默认最高位数码管显示一个“不显示”数据
  48. volatile unsigned char vGu8Display_Righ_3=0;  
  49. volatile unsigned char vGu8Display_Righ_2=0;  
  50. volatile unsigned char vGu8Display_Righ_1=0;  

  51. volatile unsigned char vGu8Display_Righ_Dot_4=0;  
  52. volatile unsigned char vGu8Display_Righ_Dot_3=1;    //开机默认保留显示2个小数点
  53. volatile unsigned char vGu8Display_Righ_Dot_2=0;  
  54. volatile unsigned char vGu8Display_Righ_Dot_1=0;  

  55. volatile unsigned char vGu8KeySec=0;  

  56. void main()
  57. {
  58. SystemInitial();            
  59. Delay(10000);               
  60. PeripheralInitial();      
  61.     while(1)  
  62. {  
  63. KeyTask();      //按键的任务函数
  64. DisplayTask();  //数码管显示的上层任务函数
  65. RunTask();      //秒表的应用程序
  66.     }
  67. }

  68. void KeyTask(void)    //按键的任务函数
  69. {
  70. if(0==vGu8KeySec)
  71. {
  72. return;
  73. }

  74. switch(vGu8KeySec)
  75. {
  76.    case 1:     //复位按键

  77. Gu8RunStatus=0; //秒表返回停止的状态

  78. Gu8RunStart=0;  //秒表停止
  79.             Gu8RunStep=0;  //总运行步骤归零。建议跟vGu8RunStart成双成对出现

  80. vGu8StopWatchTimerFlag=0;  
  81. vGu32StopWatchTimerCnt=0;   //秒表的软件定时器清零

  82.             Gu8WdUpdate=1;  //整屏更新一次显示

  83. vGu8KeySec=0;  
  84. break;

  85.    case 2:     //启动与暂停的按键
  86.         if(0==Gu8RunStatus) //在停止状态下
  87.         {
  88.             Gu8RunStatus=1;  //秒表处于工作状态

  89. vGu8StopWatchTimerFlag=0;
  90. vGu32StopWatchTimerCnt=0;  
  91. vGu8StopWatchTimerFlag=1;  //启动秒表的软件定时器


  92. Gu8RunStart=1;   //秒表总开关启动
  93.                 Gu8RunStep=0;    //总运行步骤归零。建议跟vGu8RunStart成双成对出现
  94. }
  95.         else if(1==Gu8RunStatus) //在工作状态下
  96.         {
  97.             Gu8RunStatus=2;  //秒表处于暂停状态
  98. }
  99.         else  //在暂停状态下
  100.         {
  101.             Gu8RunStatus=1;  //秒表处于工作状态
  102. }

  103.         Gu8WdUpdate=1;  //整屏更新一次显示,确保在暂停的时候能显示到最新的数据

  104. vGu8KeySec=0;
  105. break;

  106. }
  107. }

  108. void DisplayTask(void) //数码管显示的上层任务函数
  109. {
  110. //需要借用的中间变量,用来拆分数据位
  111. static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

  112. /* 注释一:
  113. *  此处为什么要多加4个中间过渡变量Su8Temp_X?是因为vGu32StopWatchTimerCnt分解数据的时候
  114. *  需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时了一会。我们
  115. *  的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,当vGu32StopWatchTimerCnt
  116. *  还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致显示的数据瞬间产生不完整,
  117. *  影响显示效果。因此,为了把需要显示的数据过渡最快,所以采取了先分解,再过渡显示的方法。
  118. */

  119. if(1==Gu8WdUpdate) //如果需要整屏更新
  120. {
  121. Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

  122.           //先分解数据

  123.           //Su8Temp_4提取“十秒”位。
  124. Su8Temp_4=vGu32StopWatchTimerCnt/10000; //实际精度是0.0001秒,但显示精度是0.01秒
  125. Su8Temp_4=Su8Temp_4%10;

  126.          //Su8Temp_3提取“个秒”位。
  127. Su8Temp_3=vGu32StopWatchTimerCnt/1000; //实际精度是0.0001秒,但显示精度是0.01秒
  128. Su8Temp_3=Su8Temp_3%10;

  129.          //Su8Temp_2提取“百毫秒”位。
  130. Su8Temp_2=vGu32StopWatchTimerCnt/100; //实际精度是0.0001秒,但显示精度是0.01秒
  131. Su8Temp_2=Su8Temp_2%10;

  132.           //Su8Temp_1提取“十毫秒”位。
  133. Su8Temp_1=vGu32StopWatchTimerCnt/10; //实际精度是0.0001秒,但显示精度是0.01秒
  134. Su8Temp_1=Su8Temp_1%10;

  135.           //判断数据范围,来决定最高位数码管是否需要显示。
  136.           if(vGu32StopWatchTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
  137.           {
  138.              Su8Temp_4=10;  //在数码管转换表里,10代表一个“不显示”的数据
  139. }

  140. //上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
  141. vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
  142. vGu8Display_Righ_3=Su8Temp_3;  
  143. vGu8Display_Righ_2=Su8Temp_2;  
  144. vGu8Display_Righ_1=Su8Temp_1;  

  145. vGu8Display_Righ_Dot_4=0;  
  146. vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
  147. vGu8Display_Righ_Dot_2=0;  
  148. vGu8Display_Righ_Dot_1=0;  

  149. }
  150. }

  151. void RunTask(void)  //秒表的应用程序
  152. {
  153. if(0==Gu8RunStart)
  154. {
  155.    return;  // 如果秒表处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
  156. }

  157. switch(Gu8RunStep)
  158. {
  159.     case 0:  //在这个步骤里,主要用来初始化一些参数

  160. vGu8UpdateTimerFlag=0;
  161. vGu16UpdateTimerCnt=10;  //每10ms更新显示一次当前秒表的时间
  162. vGu8UpdateTimerFlag=1;

  163. Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
  164. break;

  165.     case 1:  //每10ms更新一次显示,确保实时显示秒表当前的时间
  166.        if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前秒表的时间
  167.        {
  168.     vGu8UpdateTimerFlag=0;
  169. vGu16UpdateTimerCnt=10;  //重置定时器,为下一个10ms更新做准备
  170. vGu8UpdateTimerFlag=1;

  171.             Gu8WdUpdate=1;  //整屏更新一次显示当前秒表的时间
  172. }
  173. break;

  174. }

  175. }


  176. void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
  177. {
  178.    static unsigned char Su8KeyLock1;
  179.    static unsigned int  Su16KeyCnt1;
  180.    static unsigned char Su8KeyLock2;
  181.    static unsigned int  Su16KeyCnt2;

  182.    if(0!=KEY_INPUT1)
  183.    {
  184.       Su8KeyLock1=0;
  185.       Su16KeyCnt1=0;   
  186.    }
  187.    else if(0==Su8KeyLock1)
  188.    {
  189.       Su16KeyCnt1++;
  190.       if(Su16KeyCnt1>=KEY_FILTER_TIME)
  191.       {
  192.          Su8KeyLock1=1;  
  193.          vGu8KeySec=1;   
  194.       }
  195.    }

  196.    if(0!=KEY_INPUT2)
  197.    {
  198.       Su8KeyLock2=0;
  199.       Su16KeyCnt2=0;      
  200.    }
  201.    else if(0==Su8KeyLock2)
  202.    {
  203.       Su16KeyCnt2++;
  204.       if(Su16KeyCnt2>=KEY_FILTER_TIME)
  205.       {
  206.          Su8KeyLock2=1;  
  207.          vGu8KeySec=2;   
  208.       }
  209.    }
  210. }

  211. void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
  212. {
  213. static unsigned char Su8GetCode;  
  214. static unsigned char Su8ScanStep=1;  

  215. if(0==vGu16ScanTimerCnt)  
  216. {


  217.     P0=0x00;
  218. P1_0=1;  
  219. P1_1=1;  
  220. P1_2=1;  
  221. P1_3=1;  

  222.     switch(Su8ScanStep)
  223. {
  224.        case 1:
  225. Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

  226. if(1==vGu8Display_Righ_Dot_1)
  227. {
  228. Su8GetCode=Su8GetCode|0x80;  
  229. }
  230.     P0=Su8GetCode;
  231. P1_0=0;
  232. P1_1=1;  
  233. P1_2=1;  
  234. P1_3=1;            
  235. break;

  236.        case 2:
  237. Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
  238. if(1==vGu8Display_Righ_Dot_2)
  239. {
  240. Su8GetCode=Su8GetCode|0x80;  
  241. }
  242.     P0=Su8GetCode;
  243. P1_0=1;  
  244. P1_1=0;
  245. P1_2=1;
  246. P1_3=1;         
  247. break;

  248.        case 3:
  249. Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
  250. if(1==vGu8Display_Righ_Dot_3)
  251. {
  252. Su8GetCode=Su8GetCode|0x80;  
  253. }
  254.     P0=Su8GetCode;
  255. P1_0=1;  
  256. P1_1=1;  
  257. P1_2=0;  
  258. P1_3=1;         
  259. break;

  260.        case 4:
  261. Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
  262. if(1==vGu8Display_Righ_Dot_4)
  263. {
  264. Su8GetCode=Su8GetCode|0x80;  
  265. }
  266.     P0=Su8GetCode;
  267. P1_0=1;  
  268. P1_1=1;  
  269. P1_2=1;  
  270. P1_3=0;           
  271. break;

  272. }

  273. Su8ScanStep++;
  274. if(Su8ScanStep>4)
  275. {
  276. Su8ScanStep=1;
  277. }

  278. vGu8ScanTimerFlag=0;
  279. vGu16ScanTimerCnt=SCAN_TIME;  
  280. vGu8ScanTimerFlag=1;  
  281. }
  282. }

  283. void T0_time() interrupt 1     
  284. {
  285. KeyScan();      //按键底层的驱动扫描函数
  286. DisplayScan();  //数码管底层的驱动扫描函数

  287. if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
  288. {
  289. vGu16ScanTimerCnt--;  //递减式的软件定时器
  290. }

  291. //每10ms就定时更新一次显示的软件定时器
  292. if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)  
  293. {
  294. vGu16UpdateTimerCnt--;  //递减式的软件定时器
  295. }

  296. //秒表实际走的时间的软件定时器,注意,这里是“累加式”的软件定时器
  297. if(1==vGu8StopWatchTimerFlag&&1==Gu8RunStatus) //秒表处于工作的状态
  298. {
  299. vGu32StopWatchTimerCnt++;  //累加式的软件定时器
  300. }


  301. TH0=0xfc;   
  302. TL0=0x66;   
  303. }


  304. void SystemInitial(void)
  305. {
  306. P0=0x00;
  307. P1_0=1;  
  308. P1_1=1;  
  309. P1_2=1;  
  310. P1_3=1;  

  311. TMOD=0x01;  
  312. TH0=0xfc;   
  313. TL0=0x66;   
  314. EA=1;      
  315. ET0=1;      
  316. TR0=1;      
  317. }

  318. void Delay(unsigned long u32DelayTime)
  319. {
  320.     for(;u32DelayTime>0;u32DelayTime--);
  321. }

  322. void PeripheralInitial(void)
  323. {

  324. }
复制代码





乐于分享,勇于质疑!
120#
 楼主| 发表于 2018-4-30 11:42:08 | 只看该作者
本帖最后由 jianhong_wu 于 2018-4-30 11:59 编辑

第一百一十六节: 按键控制数码管的倒计时。
第一百一十六节_pdf文件.pdf (128.97 KB, 下载次数: 899)
【116.1   按键控制数码管的倒计时。】
   
                上图116.1.1  数码管




                上图116.1.2  独立按键

            
                上图116.1.3  有源蜂鸣器

       上一节讲“累加式”的秒表,这一节讲“递减式”的倒计时。通过此小项目,加深理解在显示框架中常用的更新变量(整屏更新和局部更新),以及应用程序与按键是如何关联的框架。同时,在拆分“个十百千万...”的时候,有一处地方必须注意,“先整除后求余”必须用一行代码一气呵成,不能拆分成两行代码“先整除;后求余”,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。上一节在此处残留了一个bug我发帖后还来不及修改过来,敬请大家注意。比如:

        以下这样是对的:
  1. static unsigned char Su8Temp_1;
  2. Su8Temp_1=vGu32CountdownTimerCnt/10%10; //一气呵成,没毛病。
复制代码


        以下这样是有隐患有bug的(除非把Su8Temp_1改成unsigned long类型):
  1. static unsigned char Su8Temp_1;
  2. Su8Temp_1=vGu32CountdownTimerCnt/10;
  3. Su8Temp_1=Su8Temp_1%10; //拆分成两行代码后,有隐患有bug。数据溢出的原因引起。
复制代码

        本节倒计时程序的功能:K1按键是复位按键,每按一次,倒计时停止并且重新恢复初始值10.00秒。K2按键是启动按键,当秒表处于复位后停止的状态时按一次则开始启动倒计时,当倒计时变为0.00秒的时候,蜂鸣器发出一次“滴”的提示声。此时,如果想启动新一轮的倒计时,只需按一次K1复位键,然后再按K2启动按键,就可以启动新一轮10.00秒的倒计时。代码如下:

  1. #include "REG52.H"  

  2. #define KEY_FILTER_TIME  25

  3. #define SCAN_TIME  1   

  4. #define VOICE_TIME   50     //蜂鸣器一次“滴”的声音长度 50ms  

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

  9. void KeyScan(void);  
  10. void KeyTask(void);  

  11. void VoiceScan(void);  //蜂鸣器的驱动函数
  12. void DisplayScan(void);  //底层显示的驱动函数  
  13. void DisplayTask(void);  //上层显示的任务函数

  14. void RunTask(void);  //倒计时的应用程序

  15. void BeepOpen(void);   
  16. void BeepClose(void);

  17. sbit KEY_INPUT1=P2^2;  
  18. sbit KEY_INPUT2=P2^1;  

  19. sbit P1_0=P1^0;  
  20. sbit P1_1=P1^1;  
  21. sbit P1_2=P1^2;  
  22. sbit P1_3=P1^3;

  23. sbit P3_4=P3^4;

  24. //数码管转换表
  25. code unsigned char Cu8DigTable[]=
  26. {
  27. 0x3f,  //0       序号0
  28. 0x06,  //1       序号1
  29. 0x5b,  //2       序号2
  30. 0x4f,  //3       序号3
  31. 0x66,  //4       序号4
  32. 0x6d,  //5       序号5
  33. 0x7d,  //6       序号6
  34. 0x07,  //7       序号7
  35. 0x7f,  //8       序号8
  36. 0x6f,  //9       序号9
  37. 0x00,  //不显示  序号10
  38. };

  39. //数码管底层驱动扫描的软件定时器
  40. volatile unsigned char vGu8ScanTimerFlag=0;  
  41. volatile unsigned int vGu16ScanTimerCnt=0;  

  42. //倒计时的软件定时器,注意,这里是unsigned long类型,范围是0到4294967295毫秒
  43. volatile unsigned char vGu8CountdownTimerFlag=0;  
  44. volatile unsigned long vGu32CountdownTimerCnt=10000;  //开机默认显示初始值10.000秒

  45. //数码管上层每10ms就定时刷新一次显示的软件定时器。用于及时更新显示秒表当前的实时数值
  46. volatile unsigned char vGu8UpdateTimerFlag=0;  
  47. volatile unsigned int vGu16UpdateTimerCnt=0;  

  48. //蜂鸣器的软件定时器
  49. volatile unsigned char vGu8BeepTimerFlag=0;  
  50. volatile unsigned int vGu16BeepTimerCnt=0;  

  51. unsigned char Gu8RunStart=0;  //应用程序的总启动
  52. unsigned char Gu8RunStep=0;  //应用程序的总运行步骤。建议跟vGu8RunStart成双成对出现
  53. unsigned char Gu8RunStatus=0; //当前倒计时的状态。0代表停止,1代表正在工作中

  54. unsigned char Gu8WdUpdate=1;  //开机默认整屏更新一次显示。此变量在显示框架中是非常重要的变量

  55. volatile unsigned char vGu8Display_Righ_4=1;  //显示“1”,跟vGu32CountdownTimerCnt高位一致
  56. volatile unsigned char vGu8Display_Righ_3=0;  
  57. volatile unsigned char vGu8Display_Righ_2=0;  
  58. volatile unsigned char vGu8Display_Righ_1=0;  

  59. volatile unsigned char vGu8Display_Righ_Dot_4=0;  
  60. volatile unsigned char vGu8Display_Righ_Dot_3=1;    //开机默认保留显示2个小数点
  61. volatile unsigned char vGu8Display_Righ_Dot_2=0;  
  62. volatile unsigned char vGu8Display_Righ_Dot_1=0;  

  63. volatile unsigned char vGu8KeySec=0;  

  64. void main()
  65. {
  66. SystemInitial();            
  67. Delay(10000);               
  68. PeripheralInitial();      
  69.     while(1)  
  70. {  
  71. KeyTask();      //按键的任务函数
  72. DisplayTask();  //数码管显示的上层任务函数
  73. RunTask();      //倒计时的应用程序
  74.     }
  75. }

  76. void KeyTask(void)    //按键的任务函数
  77. {
  78. if(0==vGu8KeySec)
  79. {
  80. return;
  81. }

  82. switch(vGu8KeySec)
  83. {
  84.    case 1:     //复位按键

  85. Gu8RunStatus=0; //倒计时返回停止的状态

  86. Gu8RunStart=0;  //倒计时的运行步骤的停止
  87.             Gu8RunStep=0;  //总运行步骤归零。建议跟vGu8RunStart成双成对出现

  88. vGu8CountdownTimerFlag=0;    //禁止倒计时开始(而Gu8RunStart是启动开关,不矛盾)
  89. vGu32CountdownTimerCnt=10000;   //倒计时的软件定时器恢复初始值10.000秒

  90.             Gu8WdUpdate=1;  //整屏更新一次显示

  91. vGu8KeySec=0;  
  92. break;

  93.    case 2:     //启动的按键
  94.         if(0==Gu8RunStatus) //在停止状态下
  95.         {

  96. vGu8CountdownTimerFlag=0;
  97. vGu32CountdownTimerCnt=10000;  //初始值是10.000秒
  98. vGu8CountdownTimerFlag=1;      //允许倒计时的软件定时器的启动

  99.             Gu8RunStatus=1;  //倒计时处于工作状态(并且,这一瞬间才正式启动倒计时)

  100. Gu8RunStart=1;   //倒计时的运行步骤的总开关开启
  101.                 Gu8RunStep=0;    //总运行步骤归零。建议跟vGu8RunStart成双成对出现

  102.             Gu8WdUpdate=1;  //整屏更新一次显示,确保在启动的时候能显示到最新的数据
  103. }


  104. vGu8KeySec=0;
  105. break;

  106. }
  107. }

  108. void DisplayTask(void) //数码管显示的上层任务函数
  109. {
  110. //需要借用的中间变量,用来拆分数据位。
  111. static unsigned char Su8Temp_4,Su8Temp_3,Su8Temp_2,Su8Temp_1; //需要借用的中间变量

  112. if(1==Gu8WdUpdate) //如果需要整屏更新
  113. {
  114. Gu8WdUpdate=0; //及时清零,只更新一次显示即可,避免一直进来更新显示

  115.           //先分解数据,注意,这里分解的时候,“先整除后求余”必须用一行代码一气呵成,不能拆
  116.           //分成两行代码,否则会有隐患会有bug。除非,把四个临时变都改成unsigned long类型。

  117.           //Su8Temp_4提取“十秒”位。
  118. Su8Temp_4=vGu32CountdownTimerCnt/10000%10; //实际精度是0.001秒,但显示精度是0.01秒

  119.          //Su8Temp_3提取“个秒”位。
  120. Su8Temp_3=vGu32CountdownTimerCnt/1000%10; //实际精度是0.001秒,但显示精度是0.01秒

  121.          //Su8Temp_2提取“百毫秒”位。
  122. Su8Temp_2=vGu32CountdownTimerCnt/100%10; //实际精度是0.001秒,但显示精度是0.01秒

  123.           //Su8Temp_1提取“十毫秒”位。
  124. Su8Temp_1=vGu32CountdownTimerCnt/10%10; //实际精度是0.001秒,但显示精度是0.01秒

  125.           //判断数据范围,来决定最高位数码管是否需要显示。
  126.           if(vGu32CountdownTimerCnt<10000) //10.000秒。实际4位数码管最大只能显示99.99秒
  127.           {
  128.              Su8Temp_4=10;  //在数码管转换表里,10代表一个“不显示”的数据
  129. }

  130. //上面先分解数据之后,再过渡需要显示的数据到底层驱动变量里,让过渡的时间越短越好
  131. vGu8Display_Righ_4=Su8Temp_4;  //过渡需要显示的数据到底层驱动变量
  132. vGu8Display_Righ_3=Su8Temp_3;  
  133. vGu8Display_Righ_2=Su8Temp_2;  
  134. vGu8Display_Righ_1=Su8Temp_1;  

  135. vGu8Display_Righ_Dot_4=0;  
  136. vGu8Display_Righ_Dot_3=1;   //保留显示2位小数点
  137. vGu8Display_Righ_Dot_2=0;  
  138. vGu8Display_Righ_Dot_1=0;  

  139. }
  140. }

  141. void RunTask(void)  //倒计时的应用程序
  142. {
  143. if(0==Gu8RunStart)
  144. {
  145.    return;  // 如果总开关处于停止状态,则直接退出当前函数,不执行该函数以下的其它代码
  146. }

  147. switch(Gu8RunStep)
  148. {
  149.     case 0:  //在这个步骤里,主要用来初始化一些参数

  150. vGu8UpdateTimerFlag=0;
  151. vGu16UpdateTimerCnt=10;  //每10ms更新显示一次当前倒计时的时间
  152. vGu8UpdateTimerFlag=1;

  153. Gu8RunStep=1; //跳转到每10ms更新显示一次的步骤里
  154. break;

  155.     case 1:  //每10ms更新一次显示,确保实时显示当前倒计时的时间
  156.        if(0==vGu16UpdateTimerCnt) //每10ms更新显示一次当前倒计时的时间
  157.        {


  158.     vGu8UpdateTimerFlag=0;
  159. vGu16UpdateTimerCnt=10;  //重置定时器,为下一个10ms更新做准备
  160. vGu8UpdateTimerFlag=1;

  161.             Gu8WdUpdate=1;  //整屏更新一次显示当前倒计时的时间

  162.             if(0==vGu32CountdownTimerCnt) //如果倒计时的时间到,则跳转到结束的步骤
  163.             {
  164. Gu8RunStep=2; //跳转到倒计时结束的步骤
  165. }

  166. }
  167. break;

  168. case 2:  //倒计时结束的步骤
  169. //Gu8RunStatus=0; //这行代码注释掉,让每次新启动之前都必须按一次K1复位按键才有效

  170. Gu8RunStart=0;  //倒计时的运行步骤的停止
  171.             Gu8RunStep=0;   //总运行步骤归零。建议跟vGu8RunStart成双成对出现

  172. vGu8BeepTimerFlag=0;  
  173. vGu16BeepTimerCnt=VOICE_TIME;  //蜂鸣器发出“滴”一声
  174. vGu8BeepTimerFlag=1;  

  175.         Gu8WdUpdate=1;  //整屏更新一次显示当前倒计时的时间

  176. break;

  177. }

  178. }


  179. void KeyScan(void)  //按键底层的驱动扫描函数,放在定时中断函数里
  180. {
  181.    static unsigned char Su8KeyLock1;
  182.    static unsigned int  Su16KeyCnt1;
  183.    static unsigned char Su8KeyLock2;
  184.    static unsigned int  Su16KeyCnt2;

  185.    if(0!=KEY_INPUT1)
  186.    {
  187.       Su8KeyLock1=0;
  188.       Su16KeyCnt1=0;   
  189.    }
  190.    else if(0==Su8KeyLock1)
  191.    {
  192.       Su16KeyCnt1++;
  193.       if(Su16KeyCnt1>=KEY_FILTER_TIME)
  194.       {
  195.          Su8KeyLock1=1;  
  196.          vGu8KeySec=1;   
  197.       }
  198.    }

  199.    if(0!=KEY_INPUT2)
  200.    {
  201.       Su8KeyLock2=0;
  202.       Su16KeyCnt2=0;      
  203.    }
  204.    else if(0==Su8KeyLock2)
  205.    {
  206.       Su16KeyCnt2++;
  207.       if(Su16KeyCnt2>=KEY_FILTER_TIME)
  208.       {
  209.          Su8KeyLock2=1;  
  210.          vGu8KeySec=2;   
  211.       }
  212.    }
  213. }

  214. void DisplayScan(void)    //数码管底层的驱动扫描函数,放在定时中断函数里
  215. {
  216. static unsigned char Su8GetCode;  
  217. static unsigned char Su8ScanStep=1;  

  218. if(0==vGu16ScanTimerCnt)  
  219. {


  220.     P0=0x00;
  221. P1_0=1;  
  222. P1_1=1;  
  223. P1_2=1;  
  224. P1_3=1;  

  225.     switch(Su8ScanStep)
  226. {
  227.        case 1:
  228. Su8GetCode=Cu8DigTable[vGu8Display_Righ_1];

  229. if(1==vGu8Display_Righ_Dot_1)
  230. {
  231. Su8GetCode=Su8GetCode|0x80;  
  232. }
  233.     P0=Su8GetCode;
  234. P1_0=0;
  235. P1_1=1;  
  236. P1_2=1;  
  237. P1_3=1;            
  238. break;

  239.        case 2:
  240. Su8GetCode=Cu8DigTable[vGu8Display_Righ_2];
  241. if(1==vGu8Display_Righ_Dot_2)
  242. {
  243. Su8GetCode=Su8GetCode|0x80;  
  244. }
  245.     P0=Su8GetCode;
  246. P1_0=1;  
  247. P1_1=0;
  248. P1_2=1;
  249. P1_3=1;         
  250. break;

  251.        case 3:
  252. Su8GetCode=Cu8DigTable[vGu8Display_Righ_3];
  253. if(1==vGu8Display_Righ_Dot_3)
  254. {
  255. Su8GetCode=Su8GetCode|0x80;  
  256. }
  257.     P0=Su8GetCode;
  258. P1_0=1;  
  259. P1_1=1;  
  260. P1_2=0;  
  261. P1_3=1;         
  262. break;

  263.        case 4:
  264. Su8GetCode=Cu8DigTable[vGu8Display_Righ_4];
  265. if(1==vGu8Display_Righ_Dot_4)
  266. {
  267. Su8GetCode=Su8GetCode|0x80;  
  268. }
  269.     P0=Su8GetCode;
  270. P1_0=1;  
  271. P1_1=1;  
  272. P1_2=1;  
  273. P1_3=0;           
  274. break;

  275. }

  276. Su8ScanStep++;
  277. if(Su8ScanStep>4)
  278. {
  279. Su8ScanStep=1;
  280. }

  281. vGu8ScanTimerFlag=0;
  282. vGu16ScanTimerCnt=SCAN_TIME;  
  283. vGu8ScanTimerFlag=1;  
  284. }
  285. }


  286. void VoiceScan(void) //蜂鸣器的驱动函数
  287. {

  288.           static unsigned char Su8Lock=0;  

  289. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  290.           {
  291.                   if(0==Su8Lock)
  292.                   {
  293.                    Su8Lock=1;  
  294. BeepOpen();
  295.      }
  296.     else  
  297. {     

  298.                        vGu16BeepTimerCnt--;         

  299.                    if(0==vGu16BeepTimerCnt)
  300.                    {
  301.                            Su8Lock=0;     
  302. BeepClose();  
  303.                    }

  304. }
  305.           }         
  306. }

  307. void BeepOpen(void)
  308. {
  309. P3_4=0;  
  310. }

  311. void BeepClose(void)
  312. {
  313. P3_4=1;  
  314. }

  315. void T0_time() interrupt 1     
  316. {
  317. VoiceScan();    //蜂鸣器的驱动函数
  318. KeyScan();      //按键底层的驱动扫描函数
  319. DisplayScan();  //数码管底层的驱动扫描函数

  320. if(1==vGu8ScanTimerFlag&&vGu16ScanTimerCnt>0)  
  321. {
  322. vGu16ScanTimerCnt--;  //递减式的软件定时器
  323. }

  324. //每10ms就定时更新一次显示的软件定时器
  325. if(1==vGu8UpdateTimerFlag&&vGu16UpdateTimerCnt>0)  
  326. {
  327. vGu16UpdateTimerCnt--;  //递减式的软件定时器
  328. }

  329. //倒计时实际走的时间的软件定时器,注意,这里还附加了启动状态的条件“&&1==Gu8RunStatus”
  330. if(1==vGu8CountdownTimerFlag&&vGu32CountdownTimerCnt>0&&1==Gu8RunStatus)
  331. {
  332. vGu32CountdownTimerCnt--;  //递减式的软件定时器
  333. }


  334. TH0=0xfc;   
  335. TL0=0x66;   
  336. }


  337. void SystemInitial(void)
  338. {
  339. P0=0x00;
  340. P1_0=1;  
  341. P1_1=1;  
  342. P1_2=1;  
  343. P1_3=1;  

  344. TMOD=0x01;  
  345. TH0=0xfc;   
  346. TL0=0x66;   
  347. EA=1;      
  348. ET0=1;      
  349. TR0=1;      
  350. }

  351. void Delay(unsigned long u32DelayTime)
  352. {
  353.     for(;u32DelayTime>0;u32DelayTime--);
  354. }

  355. void PeripheralInitial(void)
  356. {

  357. }

复制代码

乐于分享,勇于质疑!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|独闷闷网 ( 粤ICP备12007667号-2 )

GMT+8, 2024-4-25 20:36 , Processed in 0.289684 second(s), 15 queries .

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