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

[复制链接]
362928|721
 楼主| jianhong_wu 发表于 2018-1-22 15:23 | 显示全部楼层
第一百零四节: 矩阵按键“一键两用”的短按与长按。

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


本帖子中包含更多资源

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

×
pgdw 发表于 2018-1-23 16:43 | 显示全部楼层
谢谢吴老师的无私奉献,感恩感德。
 楼主| jianhong_wu 发表于 2018-1-28 12:58 | 显示全部楼层
第一百零五节: 矩阵按键按住不松手的连续均匀触发。

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


本帖子中包含更多资源

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

×
 楼主| jianhong_wu 发表于 2018-2-4 12:22 | 显示全部楼层
第一百零六节: 矩阵按键按住不松手的“先加速后匀速”触发。

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


本帖子中包含更多资源

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

×
冷画 发表于 2018-2-7 09:11 | 显示全部楼层
为楼主点赞  
yufei138 发表于 2018-2-7 10:22 | 显示全部楼层
佩服楼主的格局与境界。
 楼主| jianhong_wu 发表于 2018-2-11 11:10 | 显示全部楼层
第一百零七节: 开关感应器的识别与软件滤波。

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


本帖子中包含更多资源

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

×

评论

总是在教材上看见几个大字“软件滤波即可”。TMD究竟怎么弄的,没一本书具体操作的,都是原理。  发表于 2018-9-30 12:46
 楼主| jianhong_wu 发表于 2018-2-23 14:20 | 显示全部楼层
第一百零八节: 按键控制跑马灯的启动和暂停和停止。

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


本帖子中包含更多资源

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

×
 楼主| jianhong_wu 发表于 2018-2-23 14:26 | 显示全部楼层
本帖最后由 jianhong_wu 于 2018-2-23 14:28 编辑

对不起,重发了一次回复。
eatpeanut 发表于 2018-2-23 15:19 | 显示全部楼层
厉害   !!! 在哪儿点收藏啊???
 楼主| jianhong_wu 发表于 2018-3-1 09:14 | 显示全部楼层
第一百零九节: 按键控制跑马灯的方向。

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


本帖子中包含更多资源

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

×

评论

mark  发表于 2021-6-4 10:57
wlhuangcn 发表于 2018-3-2 11:33 | 显示全部楼层
感谢分享~
 楼主| jianhong_wu 发表于 2018-3-11 13:55 | 显示全部楼层
第一百一十一节: 工业自动化设备的开关信号的运动控制。

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


本帖子中包含更多资源

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

×
a51278207 发表于 2018-3-21 21:56 | 显示全部楼层
楼主写得非常好,收藏起来慢慢一点点看完,期待楼主一直更新下去,支持
arima 发表于 2018-3-26 19:31 | 显示全部楼层
经典啊!
期待楼主一直更新下去,支持!!!
EngineerLin 发表于 2018-3-26 22:31 | 显示全部楼层
支持666!

评论

en  发表于 2019-4-27 21:23
入坑的小学生 发表于 2018-3-28 14:39 | 显示全部楼层
楼主加油!回复一下暖贴!
 楼主| jianhong_wu 发表于 2018-3-30 10:18 | 显示全部楼层
第一百一十二节: 数码管显示的基础知识。

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


本帖子中包含更多资源

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

×
panpanpanpan 发表于 2018-4-6 20:26 | 显示全部楼层
。。。要等2020年是不是有点久了
yongxiang6091 发表于 2018-4-7 23:59 | 显示全部楼层
关注了好久,学习了,谢谢老师分享!!!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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