98

帖子

0

TA的资源

一粒金砂(高级)

21
 
第二十节:依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。

开场白:
上一节讲了先依次逐个亮再依次逐个灭的跑马灯程序。这一节在上一节的基础上,略作修改,继续讲跑马灯程序。我的跑马灯程序看似简单而且重复,其实蕴含着鸿哥的大智慧。它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,以后任何所谓复杂的工程项目,都不再复杂。要教会大家一个知识点:通过本跑马灯程序,加深理解鸿哥所有实战项目中switch状态机的思想精髓。
具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。

(2)实现功能:第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。第1至第8个LED灯一直灭。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

  3. void initial_myself();   
  4. void initial_peripheral();
  5. void delay_short(unsigned int uiDelayShort);
  6. void delay_long(unsigned int uiDelaylong);
  7. void led_flicker_09_16(); // 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  8. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  9. void led_update();  //LED更新函数
  10. void T0_time();  //定时中断函数


  11. sbit hc595_sh_dr=P2^3;   
  12. sbit hc595_st_dr=P2^4;  
  13. sbit hc595_ds_dr=P2^5;  

  14. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  15. unsigned char ucLed_dr2=0;
  16. unsigned char ucLed_dr3=0;
  17. unsigned char ucLed_dr4=0;
  18. unsigned char ucLed_dr5=0;
  19. unsigned char ucLed_dr6=0;
  20. unsigned char ucLed_dr7=0;
  21. unsigned char ucLed_dr8=0;
  22. unsigned char ucLed_dr9=0;
  23. unsigned char ucLed_dr10=0;
  24. unsigned char ucLed_dr11=0;
  25. unsigned char ucLed_dr12=0;
  26. unsigned char ucLed_dr13=0;
  27. unsigned char ucLed_dr14=0;
  28. unsigned char ucLed_dr15=0;
  29. unsigned char ucLed_dr16=0;

  30. unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。

  31. unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
  32. unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

  33. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  34. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

  35. void main()
  36.   {
  37.    initial_myself();  
  38.    delay_long(100);   
  39.    initial_peripheral();
  40.    while(1)   
  41.    {
  42.       led_flicker_09_16(); // 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  43.           led_update();  //LED更新函数
  44.    }

  45. }


  46. void led_update()  //LED更新函数
  47. {

  48.    if(ucLed_update==1)
  49.    {
  50.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  51.        if(ucLed_dr1==1)
  52.            {
  53.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  54.            }
  55.            else
  56.            {
  57.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  58.            }

  59.        if(ucLed_dr2==1)
  60.            {
  61.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  62.            }
  63.            else
  64.            {
  65.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  66.            }

  67.        if(ucLed_dr3==1)
  68.            {
  69.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  70.            }
  71.            else
  72.            {
  73.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  74.            }

  75.        if(ucLed_dr4==1)
  76.            {
  77.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  78.            }
  79.            else
  80.            {
  81.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  82.            }


  83.        if(ucLed_dr5==1)
  84.            {
  85.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  86.            }
  87.            else
  88.            {
  89.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  90.            }


  91.        if(ucLed_dr6==1)
  92.            {
  93.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  94.            }
  95.            else
  96.            {
  97.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  98.            }


  99.        if(ucLed_dr7==1)
  100.            {
  101.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  102.            }
  103.            else
  104.            {
  105.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  106.            }


  107.        if(ucLed_dr8==1)
  108.            {
  109.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  110.            }
  111.            else
  112.            {
  113.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  114.            }

  115.        if(ucLed_dr9==1)
  116.            {
  117.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  118.            }
  119.            else
  120.            {
  121.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  122.            }

  123.        if(ucLed_dr10==1)
  124.            {
  125.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  126.            }
  127.            else
  128.            {
  129.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  130.            }

  131.        if(ucLed_dr11==1)
  132.            {
  133.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  134.            }
  135.            else
  136.            {
  137.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  138.            }

  139.        if(ucLed_dr12==1)
  140.            {
  141.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  142.            }
  143.            else
  144.            {
  145.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  146.            }


  147.        if(ucLed_dr13==1)
  148.            {
  149.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  150.            }
  151.            else
  152.            {
  153.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  154.            }


  155.        if(ucLed_dr14==1)
  156.            {
  157.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  158.            }
  159.            else
  160.            {
  161.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  162.            }


  163.        if(ucLed_dr15==1)
  164.            {
  165.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  166.            }
  167.            else
  168.            {
  169.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  170.            }


  171.        if(ucLed_dr16==1)
  172.            {
  173.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  174.            }
  175.            else
  176.            {
  177.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  178.            }

  179.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  180.    }
  181. }

  182. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  183. {
  184.    unsigned char i;
  185.    unsigned char ucTempData;
  186.    hc595_sh_dr=0;
  187.    hc595_st_dr=0;

  188.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  189.    for(i=0;i<8;i++)
  190.    {
  191.          if(ucTempData>=0x80)hc595_ds_dr=1;
  192.          else hc595_ds_dr=0;

  193.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  194.          delay_short(15);
  195.          hc595_sh_dr=1;
  196.          delay_short(15);

  197.          ucTempData=ucTempData<<1;
  198.    }

  199.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  200.    for(i=0;i<8;i++)
  201.    {
  202.          if(ucTempData>=0x80)hc595_ds_dr=1;
  203.          else hc595_ds_dr=0;

  204.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  205.          delay_short(15);
  206.          hc595_sh_dr=1;
  207.          delay_short(15);

  208.          ucTempData=ucTempData<<1;
  209.    }

  210.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  211.    delay_short(15);
  212.    hc595_st_dr=1;
  213.    delay_short(15);

  214.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  215.    hc595_st_dr=0;
  216.    hc595_ds_dr=0;

  217. }

  218. /* 注释一:
  219. * 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
  220. * 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
  221. * 以后任何所谓复杂的工程项目,都不再复杂。
  222. */
  223. void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  224. {
  225.   switch(ucLedStep_09_16)
  226.   {
  227.      case 0:
  228.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  229.            {
  230.                uiTimeCnt_09_16=0; //时间计数器清零

  231.                ucLed_dr16=0;  //第16个灭
  232.                ucLed_dr9=1;  //第9个亮

  233.                ucLed_update=1;  //更新显示
  234.                ucLedStep_09_16=1; //切换到下一个步骤
  235.            }
  236.            break;
  237.      case 1:
  238.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  239.            {
  240.                uiTimeCnt_09_16=0; //时间计数器清零

  241.                ucLed_dr9=0;  //第9个灭
  242.                ucLed_dr10=1;  //第10个亮

  243.                ucLed_update=1;  //更新显示
  244.                ucLedStep_09_16=2; //切换到下一个步骤
  245.            }
  246.            break;
  247.      case 2:
  248.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  249.            {
  250.                uiTimeCnt_09_16=0; //时间计数器清零

  251.                ucLed_dr10=0;  //第10个灭
  252.                ucLed_dr11=1;  //第11个亮

  253.                ucLed_update=1;  //更新显示
  254.                ucLedStep_09_16=3; //切换到下一个步骤
  255.            }
  256.            break;
  257.      case 3:
  258.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  259.            {
  260.                uiTimeCnt_09_16=0; //时间计数器清零

  261.                ucLed_dr11=0;  //第11个灭
  262.                ucLed_dr12=1;  //第12个亮

  263.                ucLed_update=1;  //更新显示
  264.                ucLedStep_09_16=4; //切换到下一个步骤
  265.            }
  266.            break;
  267.      case 4:
  268.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  269.            {
  270.                uiTimeCnt_09_16=0; //时间计数器清零

  271.                ucLed_dr12=0;  //第12个灭
  272.                ucLed_dr13=1;  //第13个亮

  273.                ucLed_update=1;  //更新显示
  274.                ucLedStep_09_16=5; //切换到下一个步骤
  275.            }
  276.            break;
  277.      case 5:
  278.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  279.            {
  280.                uiTimeCnt_09_16=0; //时间计数器清零

  281.                ucLed_dr13=0;  //第13个灭
  282.                ucLed_dr14=1;  //第14个亮

  283.                ucLed_update=1;  //更新显示
  284.                ucLedStep_09_16=6; //切换到下一个步骤
  285.            }
  286.            break;
  287.      case 6:
  288.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  289.            {
  290.                uiTimeCnt_09_16=0; //时间计数器清零

  291.                ucLed_dr14=0;  //第14个灭
  292.                ucLed_dr15=1;  //第15个亮

  293.                ucLed_update=1;  //更新显示
  294.                ucLedStep_09_16=7; //切换到下一个步骤
  295.            }
  296.            break;
  297.      case 7:
  298.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  299.            {
  300.                uiTimeCnt_09_16=0; //时间计数器清零

  301.                ucLed_dr15=0;  //第15个灭
  302.                ucLed_dr16=1;  //第16个亮

  303.                ucLed_update=1;  //更新显示
  304.                ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
  305.            }
  306.            break;
  307.    
  308.    }

  309. }


  310. void T0_time() interrupt 1
  311. {
  312.   TF0=0;  //清除中断标志
  313.   TR0=0; //关中断

  314.   if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  315.   {
  316.       uiTimeCnt_09_16++;  //累加定时中断的次数,
  317.   }

  318.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  319.   TL0=0x2f;
  320.   TR0=1;  //开中断
  321. }

  322. void delay_short(unsigned int uiDelayShort)
  323. {
  324.    unsigned int i;  
  325.    for(i=0;i<uiDelayShort;i++)
  326.    {
  327.      ;   //一个分号相当于执行一条空语句
  328.    }
  329. }

  330. void delay_long(unsigned int uiDelayLong)
  331. {
  332.    unsigned int i;
  333.    unsigned int j;
  334.    for(i=0;i<uiDelayLong;i++)
  335.    {
  336.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  337.           {
  338.              ; //一个分号相当于执行一条空语句
  339.           }
  340.    }
  341. }


  342. void initial_myself()  //第一区 初始化单片机
  343. {

  344.   TMOD=0x01;  //设置定时器0为工作方式1


  345.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  346.   TL0=0x2f;


  347. }

  348. void initial_peripheral() //第二区 初始化外围
  349. {
  350.   EA=1;     //开总中断
  351.   ET0=1;    //允许定时中断
  352.   TR0=1;    //启动定时中断

  353. }
复制代码

总结陈词:
上一节和这一节讲了两种不同的跑马灯程序,如果要让这两种不同的跑马灯程序都能各自独立运行,就涉及到多任务并行处理的程序框架。没错,下一节就讲多任务并行处理这方面的知识,欲知详情,请听下回分解-----多任务并行处理两路跑马灯。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

22
 
第二十一节:多任务并行处理两路跑马灯。

开场白:
上一节讲了依次逐个亮灯并且每次只能亮一个灯的跑马灯程序。这一节要结合前面两节的内容,实现多任务并行处理两路跑马灯。要教会大家一个知识点:利用鸿哥的switch状态机思想,实现多任务并行处理的程序。
具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。

(2)实现功能:
第一路独立运行的任务是:第1个至第8个LED灯,先依次逐个亮,再依次逐个灭。
第二路独立运行的任务是:第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_time_level_01_08  200  //第1个至第8个LED跑马灯的速度延时时间
  3. #define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

  4. void initial_myself();   
  5. void initial_peripheral();
  6. void delay_short(unsigned int uiDelayShort);
  7. void delay_long(unsigned int uiDelaylong);
  8. void led_flicker_01_08(); //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
  9. void led_flicker_09_16(); //第二路独立运行的任务 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  10. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  11. void led_update();  //LED更新函数
  12. void T0_time();  //定时中断函数


  13. sbit hc595_sh_dr=P2^3;   
  14. sbit hc595_st_dr=P2^4;  
  15. sbit hc595_ds_dr=P2^5;  

  16. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  17. unsigned char ucLed_dr2=0;
  18. unsigned char ucLed_dr3=0;
  19. unsigned char ucLed_dr4=0;
  20. unsigned char ucLed_dr5=0;
  21. unsigned char ucLed_dr6=0;
  22. unsigned char ucLed_dr7=0;
  23. unsigned char ucLed_dr8=0;
  24. unsigned char ucLed_dr9=0;
  25. unsigned char ucLed_dr10=0;
  26. unsigned char ucLed_dr11=0;
  27. unsigned char ucLed_dr12=0;
  28. unsigned char ucLed_dr13=0;
  29. unsigned char ucLed_dr14=0;
  30. unsigned char ucLed_dr15=0;
  31. unsigned char ucLed_dr16=0;

  32. unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


  33. unsigned char ucLedStep_01_08=0; //第1个至第8个LED跑马灯的步骤变量
  34. unsigned int  uiTimeCnt_01_08=0; //第1个至第8个LED跑马灯的统计定时中断次数的延时计数器

  35. unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
  36. unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

  37. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  38. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

  39. void main()
  40.   {
  41.    initial_myself();  
  42.    delay_long(100);   
  43.    initial_peripheral();
  44.    while(1)   
  45.    {
  46.       led_flicker_01_08(); //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
  47.       led_flicker_09_16(); //第二路独立运行的任务 第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  48.           led_update();  //LED更新函数
  49.    }

  50. }


  51. void led_update()  //LED更新函数
  52. {

  53.    if(ucLed_update==1)
  54.    {
  55.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  56.        if(ucLed_dr1==1)
  57.            {
  58.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  59.            }
  60.            else
  61.            {
  62.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  63.            }

  64.        if(ucLed_dr2==1)
  65.            {
  66.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  67.            }
  68.            else
  69.            {
  70.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  71.            }

  72.        if(ucLed_dr3==1)
  73.            {
  74.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  75.            }
  76.            else
  77.            {
  78.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  79.            }

  80.        if(ucLed_dr4==1)
  81.            {
  82.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  83.            }
  84.            else
  85.            {
  86.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  87.            }


  88.        if(ucLed_dr5==1)
  89.            {
  90.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  91.            }
  92.            else
  93.            {
  94.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  95.            }


  96.        if(ucLed_dr6==1)
  97.            {
  98.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  99.            }
  100.            else
  101.            {
  102.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  103.            }


  104.        if(ucLed_dr7==1)
  105.            {
  106.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  107.            }
  108.            else
  109.            {
  110.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  111.            }


  112.        if(ucLed_dr8==1)
  113.            {
  114.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  115.            }
  116.            else
  117.            {
  118.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  119.            }

  120.        if(ucLed_dr9==1)
  121.            {
  122.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  123.            }
  124.            else
  125.            {
  126.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  127.            }

  128.        if(ucLed_dr10==1)
  129.            {
  130.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  131.            }
  132.            else
  133.            {
  134.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  135.            }

  136.        if(ucLed_dr11==1)
  137.            {
  138.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  139.            }
  140.            else
  141.            {
  142.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  143.            }

  144.        if(ucLed_dr12==1)
  145.            {
  146.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  147.            }
  148.            else
  149.            {
  150.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  151.            }


  152.        if(ucLed_dr13==1)
  153.            {
  154.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  155.            }
  156.            else
  157.            {
  158.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  159.            }


  160.        if(ucLed_dr14==1)
  161.            {
  162.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  163.            }
  164.            else
  165.            {
  166.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  167.            }


  168.        if(ucLed_dr15==1)
  169.            {
  170.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  171.            }
  172.            else
  173.            {
  174.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  175.            }


  176.        if(ucLed_dr16==1)
  177.            {
  178.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  179.            }
  180.            else
  181.            {
  182.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  183.            }

  184.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  185.    }
  186. }

  187. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  188. {
  189.    unsigned char i;
  190.    unsigned char ucTempData;
  191.    hc595_sh_dr=0;
  192.    hc595_st_dr=0;

  193.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  194.    for(i=0;i<8;i++)
  195.    {
  196.          if(ucTempData>=0x80)hc595_ds_dr=1;
  197.          else hc595_ds_dr=0;

  198.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  199.          delay_short(15);
  200.          hc595_sh_dr=1;
  201.          delay_short(15);

  202.          ucTempData=ucTempData<<1;
  203.    }

  204.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  205.    for(i=0;i<8;i++)
  206.    {
  207.          if(ucTempData>=0x80)hc595_ds_dr=1;
  208.          else hc595_ds_dr=0;

  209.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  210.          delay_short(15);
  211.          hc595_sh_dr=1;
  212.          delay_short(15);

  213.          ucTempData=ucTempData<<1;
  214.    }

  215.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  216.    delay_short(15);
  217.    hc595_st_dr=1;
  218.    delay_short(15);

  219.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  220.    hc595_st_dr=0;
  221.    hc595_ds_dr=0;

  222. }

  223. /* 注释一:
  224. * 以下程序,看似简单而且重复,其实蕴含着鸿哥的大智慧。
  225. * 它是基于鸿哥的switch状态机思想,领略到了它的简单和精髓,
  226. * 以后任何所谓复杂的工程项目,都不再复杂。
  227. */

  228. void led_flicker_01_08() //第一路独立运行的任务 第1个至第8个LED的跑马灯程序,逐个亮,逐个灭.
  229. {
  230.   switch(ucLedStep_01_08)
  231.   {
  232.      case 0:
  233.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  234.            {
  235.                uiTimeCnt_01_08=0; //时间计数器清零

  236.                ucLed_dr1=1;  //第1个亮

  237.                ucLed_update=1;  //更新显示
  238.                ucLedStep_01_08=1; //切换到下一个步骤
  239.            }
  240.            break;
  241.      case 1:
  242.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  243.            {
  244.                uiTimeCnt_01_08=0; //时间计数器清零

  245.                ucLed_dr2=1;  //第2个亮

  246.                ucLed_update=1;  //更新显示
  247.                ucLedStep_01_08=2; //切换到下一个步骤
  248.            }
  249.            break;
  250.      case 2:
  251.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  252.            {
  253.                uiTimeCnt_01_08=0; //时间计数器清零

  254.                ucLed_dr3=1;  //第3个亮

  255.                ucLed_update=1;  //更新显示
  256.                ucLedStep_01_08=3; //切换到下一个步骤
  257.            }
  258.            break;
  259.      case 3:
  260.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  261.            {
  262.                uiTimeCnt_01_08=0; //时间计数器清零

  263.                ucLed_dr4=1;  //第4个亮

  264.                ucLed_update=1;  //更新显示
  265.                ucLedStep_01_08=4; //切换到下一个步骤
  266.            }
  267.            break;
  268.      case 4:
  269.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  270.            {
  271.                uiTimeCnt_01_08=0; //时间计数器清零

  272.                ucLed_dr5=1;  //第5个亮

  273.                ucLed_update=1;  //更新显示
  274.                ucLedStep_01_08=5; //切换到下一个步骤
  275.            }
  276.            break;
  277.      case 5:
  278.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  279.            {
  280.                uiTimeCnt_01_08=0; //时间计数器清零

  281.                ucLed_dr6=1;  //第6个亮

  282.                ucLed_update=1;  //更新显示
  283.                ucLedStep_01_08=6; //切换到下一个步骤
  284.            }
  285.            break;
  286.      case 6:
  287.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  288.            {
  289.                uiTimeCnt_01_08=0; //时间计数器清零

  290.                ucLed_dr7=1;  //第7个亮

  291.                ucLed_update=1;  //更新显示
  292.                ucLedStep_01_08=7; //切换到下一个步骤
  293.            }
  294.            break;
  295.      case 7:
  296.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  297.            {
  298.                uiTimeCnt_01_08=0; //时间计数器清零

  299.                ucLed_dr8=1;  //第8个亮

  300.                ucLed_update=1;  //更新显示
  301.                ucLedStep_01_08=8; //切换到下一个步骤
  302.            }
  303.            break;
  304.      case 8:
  305.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  306.            {
  307.                uiTimeCnt_01_08=0; //时间计数器清零

  308.                ucLed_dr8=0;  //第8个灭

  309.                ucLed_update=1;  //更新显示
  310.                ucLedStep_01_08=9; //切换到下一个步骤
  311.            }
  312.            break;
  313.      case 9:
  314.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  315.            {
  316.                uiTimeCnt_01_08=0; //时间计数器清零

  317.                ucLed_dr7=0;  //第7个灭

  318.                ucLed_update=1;  //更新显示
  319.                ucLedStep_01_08=10; //切换到下一个步骤
  320.            }
  321.            break;
  322.      case 10:
  323.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  324.            {
  325.                uiTimeCnt_01_08=0; //时间计数器清零

  326.                ucLed_dr6=0;  //第6个灭

  327.                ucLed_update=1;  //更新显示
  328.                ucLedStep_01_08=11; //切换到下一个步骤
  329.            }
  330.            break;
  331.      case 11:
  332.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  333.            {
  334.                uiTimeCnt_01_08=0; //时间计数器清零

  335.                ucLed_dr5=0;  //第5个灭

  336.                ucLed_update=1;  //更新显示
  337.                ucLedStep_01_08=12; //切换到下一个步骤
  338.            }
  339.            break;
  340.      case 12:
  341.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  342.            {
  343.                uiTimeCnt_01_08=0; //时间计数器清零

  344.                ucLed_dr4=0;  //第4个灭

  345.                ucLed_update=1;  //更新显示
  346.                ucLedStep_01_08=13; //切换到下一个步骤
  347.            }
  348.            break;
  349.      case 13:
  350.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  351.            {
  352.                uiTimeCnt_01_08=0; //时间计数器清零

  353.                ucLed_dr3=0;  //第3个灭

  354.                ucLed_update=1;  //更新显示
  355.                ucLedStep_01_08=14; //切换到下一个步骤
  356.            }
  357.            break;
  358.      case 14:
  359.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  360.            {
  361.                uiTimeCnt_01_08=0; //时间计数器清零

  362.                ucLed_dr2=0;  //第2个灭

  363.                ucLed_update=1;  //更新显示
  364.                ucLedStep_01_08=15; //切换到下一个步骤
  365.            }
  366.            break;
  367.      case 15:
  368.            if(uiTimeCnt_01_08>=const_time_level_01_08) //时间到
  369.            {
  370.                uiTimeCnt_01_08=0; //时间计数器清零

  371.                ucLed_dr1=0;  //第1个灭

  372.                ucLed_update=1;  //更新显示
  373.                ucLedStep_01_08=0; //返回到最开始处,重新开始新的一次循环。
  374.            }
  375.            break;

  376.    }

  377. }


  378. void led_flicker_09_16() //第二路独立运行的任务  第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  379. {
  380.   switch(ucLedStep_09_16)
  381.   {
  382.      case 0:
  383.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  384.            {
  385.                uiTimeCnt_09_16=0; //时间计数器清零

  386.                ucLed_dr16=0;  //第16个灭
  387.                ucLed_dr9=1;  //第9个亮

  388.                ucLed_update=1;  //更新显示
  389.                ucLedStep_09_16=1; //切换到下一个步骤
  390.            }
  391.            break;
  392.      case 1:
  393.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  394.            {
  395.                uiTimeCnt_09_16=0; //时间计数器清零

  396.                ucLed_dr9=0;  //第9个灭
  397.                ucLed_dr10=1;  //第10个亮

  398.                ucLed_update=1;  //更新显示
  399.                ucLedStep_09_16=2; //切换到下一个步骤
  400.            }
  401.            break;
  402.      case 2:
  403.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  404.            {
  405.                uiTimeCnt_09_16=0; //时间计数器清零

  406.                ucLed_dr10=0;  //第10个灭
  407.                ucLed_dr11=1;  //第11个亮

  408.                ucLed_update=1;  //更新显示
  409.                ucLedStep_09_16=3; //切换到下一个步骤
  410.            }
  411.            break;
  412.      case 3:
  413.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  414.            {
  415.                uiTimeCnt_09_16=0; //时间计数器清零

  416.                ucLed_dr11=0;  //第11个灭
  417.                ucLed_dr12=1;  //第12个亮

  418.                ucLed_update=1;  //更新显示
  419.                ucLedStep_09_16=4; //切换到下一个步骤
  420.            }
  421.            break;
  422.      case 4:
  423.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  424.            {
  425.                uiTimeCnt_09_16=0; //时间计数器清零

  426.                ucLed_dr12=0;  //第12个灭
  427.                ucLed_dr13=1;  //第13个亮

  428.                ucLed_update=1;  //更新显示
  429.                ucLedStep_09_16=5; //切换到下一个步骤
  430.            }
  431.            break;
  432.      case 5:
  433.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  434.            {
  435.                uiTimeCnt_09_16=0; //时间计数器清零

  436.                ucLed_dr13=0;  //第13个灭
  437.                ucLed_dr14=1;  //第14个亮

  438.                ucLed_update=1;  //更新显示
  439.                ucLedStep_09_16=6; //切换到下一个步骤
  440.            }
  441.            break;
  442.      case 6:
  443.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  444.            {
  445.                uiTimeCnt_09_16=0; //时间计数器清零

  446.                ucLed_dr14=0;  //第14个灭
  447.                ucLed_dr15=1;  //第15个亮

  448.                ucLed_update=1;  //更新显示
  449.                ucLedStep_09_16=7; //切换到下一个步骤
  450.            }
  451.            break;
  452.      case 7:
  453.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  454.            {
  455.                uiTimeCnt_09_16=0; //时间计数器清零

  456.                ucLed_dr15=0;  //第15个灭
  457.                ucLed_dr16=1;  //第16个亮

  458.                ucLed_update=1;  //更新显示
  459.                ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
  460.            }
  461.            break;
  462.    
  463.    }

  464. }


  465. void T0_time() interrupt 1
  466. {
  467.   TF0=0;  //清除中断标志
  468.   TR0=0; //关中断

  469.   if(uiTimeCnt_01_08<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  470.   {
  471.       uiTimeCnt_01_08++;  //累加定时中断的次数,
  472.   }

  473.   if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  474.   {
  475.       uiTimeCnt_09_16++;  //累加定时中断的次数,
  476.   }

  477.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  478.   TL0=0x2f;
  479.   TR0=1;  //开中断
  480. }

  481. void delay_short(unsigned int uiDelayShort)
  482. {
  483.    unsigned int i;  
  484.    for(i=0;i<uiDelayShort;i++)
  485.    {
  486.      ;   //一个分号相当于执行一条空语句
  487.    }
  488. }

  489. void delay_long(unsigned int uiDelayLong)
  490. {
  491.    unsigned int i;
  492.    unsigned int j;
  493.    for(i=0;i<uiDelayLong;i++)
  494.    {
  495.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  496.           {
  497.              ; //一个分号相当于执行一条空语句
  498.           }
  499.    }
  500. }


  501. void initial_myself()  //第一区 初始化单片机
  502. {

  503.   TMOD=0x01;  //设置定时器0为工作方式1


  504.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  505.   TL0=0x2f;


  506. }

  507. void initial_peripheral() //第二区 初始化外围
  508. {
  509.   EA=1;     //开总中断
  510.   ET0=1;    //允许定时中断
  511.   TR0=1;    //启动定时中断

  512. }
复制代码

总结陈词:
这一节讲了多任务并行处理两路跑马灯的程序,从下一节开始,将会在跑马灯的基础上,新加入按键这个元素。如何把按键跟跑马灯的任务有效的关联起来,欲知详情,请听下回分解-----独立按键控制跑马灯的方向。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

23
 
第二十二节:独立按键控制跑马灯的方向。

开场白:
上一节讲了多任务并行处理两路跑马灯的程序。这一节要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的任务有效的关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。用矩阵键盘中的S1键作为改变方向的独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。按一次独立按键S1,将会更改跑马灯的运动方向。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_time_level_09_16  300  //第9个至第16个LED跑马灯的速度延时时间

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  4. #define const_key_time1  20    //按键去抖动延时的时间


  5. void initial_myself();   
  6. void initial_peripheral();
  7. void delay_short(unsigned int uiDelayShort);
  8. void delay_long(unsigned int uiDelaylong);

  9. void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  10. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  11. void led_update();  //LED更新函数
  12. void T0_time();  //定时中断函数

  13. void key_service(); //按键服务的应用程序
  14. void key_scan(); //按键扫描函数 放在定时中断里

  15. sbit hc595_sh_dr=P2^3;   
  16. sbit hc595_st_dr=P2^4;  
  17. sbit hc595_ds_dr=P2^5;  

  18. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  19. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  20. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  21. unsigned char ucKeySec=0;   //被触发的按键编号

  22. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  23. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  24. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  25. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  26. unsigned char ucLed_dr2=0;
  27. unsigned char ucLed_dr3=0;
  28. unsigned char ucLed_dr4=0;
  29. unsigned char ucLed_dr5=0;
  30. unsigned char ucLed_dr6=0;
  31. unsigned char ucLed_dr7=0;
  32. unsigned char ucLed_dr8=0;
  33. unsigned char ucLed_dr9=0;
  34. unsigned char ucLed_dr10=0;
  35. unsigned char ucLed_dr11=0;
  36. unsigned char ucLed_dr12=0;
  37. unsigned char ucLed_dr13=0;
  38. unsigned char ucLed_dr14=0;
  39. unsigned char ucLed_dr15=0;
  40. unsigned char ucLed_dr16=0;

  41. unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


  42. unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
  43. unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

  44. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  45. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

  46. unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向

  47. void main()
  48.   {
  49.    initial_myself();  
  50.    delay_long(100);   
  51.    initial_peripheral();
  52.    while(1)   
  53.    {
  54.       led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  55.           led_update();  //LED更新函数
  56.       key_service(); //按键服务的应用程序
  57.    }

  58. }


  59. void key_scan()//按键扫描函数 放在定时中断里
  60. {  

  61.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  62.   {
  63.      ucKeyLock1=0; //按键自锁标志清零
  64.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  65.   }
  66.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  67.   {
  68.      uiKeyTimeCnt1++; //累加定时中断次数
  69.      if(uiKeyTimeCnt1>const_key_time1)
  70.      {
  71.         uiKeyTimeCnt1=0;
  72.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  73.         ucKeySec=1;    //触发1号键
  74.      }
  75.   }



  76. }


  77. void key_service() //按键服务的应用程序
  78. {
  79.   switch(ucKeySec) //按键服务状态切换
  80.   {
  81.     case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

  82.           if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
  83.                   {
  84.                      ucLedDirFlag=1;
  85.                   }
  86.                   else
  87.                   {
  88.                            ucLedDirFlag=0;
  89.                   }

  90.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  91.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  92.           break;        
  93.                  
  94.   }               
  95. }




  96. void led_update()  //LED更新函数
  97. {

  98.    if(ucLed_update==1)
  99.    {
  100.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  101.        if(ucLed_dr1==1)
  102.            {
  103.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  104.            }
  105.            else
  106.            {
  107.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  108.            }

  109.        if(ucLed_dr2==1)
  110.            {
  111.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  112.            }
  113.            else
  114.            {
  115.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  116.            }

  117.        if(ucLed_dr3==1)
  118.            {
  119.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  120.            }
  121.            else
  122.            {
  123.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  124.            }

  125.        if(ucLed_dr4==1)
  126.            {
  127.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  128.            }
  129.            else
  130.            {
  131.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  132.            }


  133.        if(ucLed_dr5==1)
  134.            {
  135.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  136.            }
  137.            else
  138.            {
  139.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  140.            }


  141.        if(ucLed_dr6==1)
  142.            {
  143.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  144.            }
  145.            else
  146.            {
  147.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  148.            }


  149.        if(ucLed_dr7==1)
  150.            {
  151.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  152.            }
  153.            else
  154.            {
  155.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  156.            }


  157.        if(ucLed_dr8==1)
  158.            {
  159.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  160.            }
  161.            else
  162.            {
  163.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  164.            }

  165.        if(ucLed_dr9==1)
  166.            {
  167.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  168.            }
  169.            else
  170.            {
  171.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  172.            }

  173.        if(ucLed_dr10==1)
  174.            {
  175.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  176.            }
  177.            else
  178.            {
  179.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  180.            }

  181.        if(ucLed_dr11==1)
  182.            {
  183.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  184.            }
  185.            else
  186.            {
  187.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  188.            }

  189.        if(ucLed_dr12==1)
  190.            {
  191.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  192.            }
  193.            else
  194.            {
  195.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  196.            }


  197.        if(ucLed_dr13==1)
  198.            {
  199.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  200.            }
  201.            else
  202.            {
  203.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  204.            }


  205.        if(ucLed_dr14==1)
  206.            {
  207.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  208.            }
  209.            else
  210.            {
  211.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  212.            }


  213.        if(ucLed_dr15==1)
  214.            {
  215.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  216.            }
  217.            else
  218.            {
  219.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  220.            }


  221.        if(ucLed_dr16==1)
  222.            {
  223.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  224.            }
  225.            else
  226.            {
  227.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  228.            }

  229.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  230.    }
  231. }

  232. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  233. {
  234.    unsigned char i;
  235.    unsigned char ucTempData;
  236.    hc595_sh_dr=0;
  237.    hc595_st_dr=0;

  238.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  239.    for(i=0;i<8;i++)
  240.    {
  241.          if(ucTempData>=0x80)hc595_ds_dr=1;
  242.          else hc595_ds_dr=0;

  243.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  244.          delay_short(15);
  245.          hc595_sh_dr=1;
  246.          delay_short(15);

  247.          ucTempData=ucTempData<<1;
  248.    }

  249.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  250.    for(i=0;i<8;i++)
  251.    {
  252.          if(ucTempData>=0x80)hc595_ds_dr=1;
  253.          else hc595_ds_dr=0;

  254.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  255.          delay_short(15);
  256.          hc595_sh_dr=1;
  257.          delay_short(15);

  258.          ucTempData=ucTempData<<1;
  259.    }

  260.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  261.    delay_short(15);
  262.    hc595_st_dr=1;
  263.    delay_short(15);

  264.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  265.    hc595_st_dr=0;
  266.    hc595_ds_dr=0;

  267. }

  268. /* 注释一:
  269. * 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
  270. */

  271. void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  272. {
  273.   switch(ucLedStep_09_16)
  274.   {
  275.      case 0:
  276.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  277.            {
  278.                uiTimeCnt_09_16=0; //时间计数器清零

  279.                            if(ucLedDirFlag==0)  //正方向
  280.                            {
  281.                   ucLed_dr16=0;  //第16个灭
  282.                   ucLed_dr9=1;  //第9个亮

  283.                   ucLed_update=1;  //更新显示
  284.                   ucLedStep_09_16=1; //切换到下一个步骤
  285.                            }
  286.                            else  //反方向
  287.                            {
  288.                   ucLed_dr15=1;  //第15个亮
  289.                   ucLed_dr16=0;  //第16个灭

  290.                   ucLed_update=1;  //更新显示
  291.                   ucLedStep_09_16=7; //返回上一个步骤
  292.                            }
  293.            }
  294.            break;
  295.      case 1:
  296.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  297.            {
  298.                uiTimeCnt_09_16=0; //时间计数器清零

  299.                            if(ucLedDirFlag==0)  //正方向
  300.                            {
  301.                   ucLed_dr9=0;  //第9个灭
  302.                   ucLed_dr10=1;  //第10个亮

  303.                   ucLed_update=1;  //更新显示
  304.                   ucLedStep_09_16=2; //切换到下一个步骤
  305.                            }
  306.                            else  //反方向
  307.                            {
  308.                   ucLed_dr16=1;  //第16个亮
  309.                   ucLed_dr9=0;  //第9个灭

  310.                   ucLed_update=1;  //更新显示
  311.                   ucLedStep_09_16=0; //返回上一个步骤
  312.                            }
  313.            }
  314.            break;
  315.      case 2:
  316.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  317.            {
  318.                uiTimeCnt_09_16=0; //时间计数器清零

  319.                            if(ucLedDirFlag==0)  //正方向
  320.                            {
  321.                   ucLed_dr10=0;  //第10个灭
  322.                   ucLed_dr11=1;  //第11个亮

  323.                   ucLed_update=1;  //更新显示
  324.                   ucLedStep_09_16=3; //切换到下一个步骤
  325.                            }
  326.                            else  //反方向
  327.                            {
  328.                   ucLed_dr9=1;  //第9个亮
  329.                   ucLed_dr10=0;  //第10个灭

  330.                   ucLed_update=1;  //更新显示
  331.                   ucLedStep_09_16=1; //返回上一个步骤
  332.                            }
  333.            }
  334.            break;
  335.      case 3:
  336.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  337.            {
  338.                uiTimeCnt_09_16=0; //时间计数器清零

  339.                            if(ucLedDirFlag==0)  //正方向
  340.                            {
  341.                   ucLed_dr11=0;  //第11个灭
  342.                   ucLed_dr12=1;  //第12个亮

  343.                   ucLed_update=1;  //更新显示
  344.                   ucLedStep_09_16=4; //切换到下一个步骤
  345.                            }
  346.                            else  //反方向
  347.                            {
  348.                   ucLed_dr10=1;  //第10个亮
  349.                   ucLed_dr11=0;  //第11个灭

  350.                   ucLed_update=1;  //更新显示
  351.                   ucLedStep_09_16=2; //返回上一个步骤
  352.                            }
  353.            }
  354.            break;
  355.      case 4:
  356.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  357.            {
  358.                uiTimeCnt_09_16=0; //时间计数器清零

  359.                            if(ucLedDirFlag==0)  //正方向
  360.                            {
  361.                   ucLed_dr12=0;  //第12个灭
  362.                   ucLed_dr13=1;  //第13个亮

  363.                   ucLed_update=1;  //更新显示
  364.                   ucLedStep_09_16=5; //切换到下一个步骤
  365.                            }
  366.                            else  //反方向
  367.                            {
  368.                   ucLed_dr11=1;  //第11个亮
  369.                   ucLed_dr12=0;  //第12个灭

  370.                   ucLed_update=1;  //更新显示
  371.                   ucLedStep_09_16=3; //返回上一个步骤
  372.                            }
  373.            }
  374.            break;
  375.      case 5:
  376.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  377.            {
  378.                uiTimeCnt_09_16=0; //时间计数器清零

  379.                            if(ucLedDirFlag==0)  //正方向
  380.                            {
  381.                   ucLed_dr13=0;  //第13个灭
  382.                   ucLed_dr14=1;  //第14个亮

  383.                   ucLed_update=1;  //更新显示
  384.                   ucLedStep_09_16=6; //切换到下一个步骤
  385.                            }
  386.                            else  //反方向
  387.                            {
  388.                   ucLed_dr12=1;  //第12个亮
  389.                   ucLed_dr13=0;  //第13个灭

  390.                   ucLed_update=1;  //更新显示
  391.                   ucLedStep_09_16=4; //返回上一个步骤
  392.                            }
  393.            }
  394.            break;
  395.      case 6:
  396.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  397.            {
  398.                uiTimeCnt_09_16=0; //时间计数器清零

  399.                            if(ucLedDirFlag==0)  //正方向
  400.                            {
  401.                   ucLed_dr14=0;  //第14个灭
  402.                   ucLed_dr15=1;  //第15个亮

  403.                   ucLed_update=1;  //更新显示
  404.                   ucLedStep_09_16=7; //切换到下一个步骤
  405.                            }
  406.                            else  //反方向
  407.                            {
  408.                   ucLed_dr13=1;  //第13个亮
  409.                   ucLed_dr14=0;  //第14个灭

  410.                   ucLed_update=1;  //更新显示
  411.                   ucLedStep_09_16=5; //返回上一个步骤
  412.                            }
  413.            }
  414.            break;
  415.      case 7:
  416.            if(uiTimeCnt_09_16>=const_time_level_09_16) //时间到
  417.            {
  418.                uiTimeCnt_09_16=0; //时间计数器清零

  419.                            if(ucLedDirFlag==0)  //正方向
  420.                            {
  421.                   ucLed_dr15=0;  //第15个灭
  422.                   ucLed_dr16=1;  //第16个亮

  423.                   ucLed_update=1;  //更新显示
  424.                   ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
  425.                            }
  426.                            else  //反方向
  427.                            {
  428.                   ucLed_dr14=1;  //第14个亮
  429.                   ucLed_dr15=0;  //第15个灭

  430.                   ucLed_update=1;  //更新显示
  431.                   ucLedStep_09_16=6; //返回上一个步骤
  432.                            }
  433.            }
  434.            break;
  435.    
  436.    }

  437. }


  438. void T0_time() interrupt 1
  439. {
  440.   TF0=0;  //清除中断标志
  441.   TR0=0; //关中断


  442.   if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  443.   {
  444.       uiTimeCnt_09_16++;  //累加定时中断的次数,
  445.   }

  446.   key_scan(); //按键扫描函数

  447.   if(uiVoiceCnt!=0)
  448.   {
  449.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  450.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  451.   }
  452.   else
  453.   {
  454.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  455.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  456.   }

  457.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  458.   TL0=0x2f;
  459.   TR0=1;  //开中断
  460. }

  461. void delay_short(unsigned int uiDelayShort)
  462. {
  463.    unsigned int i;  
  464.    for(i=0;i<uiDelayShort;i++)
  465.    {
  466.      ;   //一个分号相当于执行一条空语句
  467.    }
  468. }

  469. void delay_long(unsigned int uiDelayLong)
  470. {
  471.    unsigned int i;
  472.    unsigned int j;
  473.    for(i=0;i<uiDelayLong;i++)
  474.    {
  475.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  476.           {
  477.              ; //一个分号相当于执行一条空语句
  478.           }
  479.    }
  480. }


  481. void initial_myself()  //第一区 初始化单片机
  482. {
  483. /* 注释二:
  484. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  485. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  486. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  487. */
  488.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  489.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  490.   TMOD=0x01;  //设置定时器0为工作方式1


  491.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  492.   TL0=0x2f;


  493. }

  494. void initial_peripheral() //第二区 初始化外围
  495. {
  496.   EA=1;     //开总中断
  497.   ET0=1;    //允许定时中断
  498.   TR0=1;    //启动定时中断

  499. }
复制代码

总结陈词:
这一节讲了独立按键控制跑马灯的方向。如果按键要控制跑马灯的速度,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的速度。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

24
 
第二十三节:独立按键控制跑马灯的速度。

开场白:
上一节讲了独立按键控制跑马灯的方向。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的速度有效关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。在上一节的基础上,增加一个加速按键和一个减速按键,用矩阵键盘中的S5键作为加速独立按键,用矩阵键盘中的S9键作为减速独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
在上一节的基础上,第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S5,速度都会加快。每按一次独立按键S9,速度都会减慢。跟上一节一样,用S1来改变方向。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);

  10. void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  11. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  12. void led_update();  //LED更新函数
  13. void T0_time();  //定时中断函数

  14. void key_service(); //按键服务的应用程序
  15. void key_scan(); //按键扫描函数 放在定时中断里

  16. sbit hc595_sh_dr=P2^3;   
  17. sbit hc595_st_dr=P2^4;  
  18. sbit hc595_ds_dr=P2^5;  

  19. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  20. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  21. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  22. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键

  23. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  24. unsigned char ucKeySec=0;   //被触发的按键编号

  25. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  26. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  27. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  28. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  29. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  30. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  31. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  32. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  33. unsigned char ucLed_dr2=0;
  34. unsigned char ucLed_dr3=0;
  35. unsigned char ucLed_dr4=0;
  36. unsigned char ucLed_dr5=0;
  37. unsigned char ucLed_dr6=0;
  38. unsigned char ucLed_dr7=0;
  39. unsigned char ucLed_dr8=0;
  40. unsigned char ucLed_dr9=0;
  41. unsigned char ucLed_dr10=0;
  42. unsigned char ucLed_dr11=0;
  43. unsigned char ucLed_dr12=0;
  44. unsigned char ucLed_dr13=0;
  45. unsigned char ucLed_dr14=0;
  46. unsigned char ucLed_dr15=0;
  47. unsigned char ucLed_dr16=0;

  48. unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


  49. unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
  50. unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

  51. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  52. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

  53. unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
  54. unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。

  55. void main()
  56.   {
  57.    initial_myself();  
  58.    delay_long(100);   
  59.    initial_peripheral();
  60.    while(1)   
  61.    {
  62.       led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  63.           led_update();  //LED更新函数
  64.       key_service(); //按键服务的应用程序
  65.    }

  66. }


  67. void key_scan()//按键扫描函数 放在定时中断里
  68. {  

  69.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  70.   {
  71.      ucKeyLock1=0; //按键自锁标志清零
  72.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  73.   }
  74.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  75.   {
  76.      uiKeyTimeCnt1++; //累加定时中断次数
  77.      if(uiKeyTimeCnt1>const_key_time1)
  78.      {
  79.         uiKeyTimeCnt1=0;
  80.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  81.         ucKeySec=1;    //触发1号键
  82.      }
  83.   }

  84.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  85.   {
  86.      ucKeyLock2=0; //按键自锁标志清零
  87.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  88.   }
  89.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  90.   {
  91.      uiKeyTimeCnt2++; //累加定时中断次数
  92.      if(uiKeyTimeCnt2>const_key_time2)
  93.      {
  94.         uiKeyTimeCnt2=0;
  95.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  96.         ucKeySec=2;    //触发2号键
  97.      }
  98.   }

  99.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  100.   {
  101.      ucKeyLock3=0; //按键自锁标志清零
  102.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  103.   }
  104.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  105.   {
  106.      uiKeyTimeCnt3++; //累加定时中断次数
  107.      if(uiKeyTimeCnt3>const_key_time3)
  108.      {
  109.         uiKeyTimeCnt3=0;
  110.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  111.         ucKeySec=3;    //触发3号键
  112.      }
  113.   }

  114. }


  115. void key_service() //按键服务的应用程序
  116. {
  117.   switch(ucKeySec) //按键服务状态切换
  118.   {
  119.     case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

  120.           if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
  121.                   {
  122.                      ucLedDirFlag=1;
  123.                   }
  124.                   else
  125.                   {
  126.                            ucLedDirFlag=0;
  127.                   }

  128.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  129.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  130.           break;   
  131.    
  132.     case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
  133.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
  134.                   if(uiSetTimeLevel_09_16<50)  //最快限定在50
  135.                   {
  136.                       uiSetTimeLevel_09_16=50;
  137.                   }

  138.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  139.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  140.           break;  

  141.     case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
  142.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
  143.                   if(uiSetTimeLevel_09_16>550)  //最慢限定在550
  144.                   {
  145.                       uiSetTimeLevel_09_16=550;
  146.                   }

  147.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  148.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  149.           break;                  
  150.   }               
  151. }




  152. void led_update()  //LED更新函数
  153. {

  154.    if(ucLed_update==1)
  155.    {
  156.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  157.        if(ucLed_dr1==1)
  158.            {
  159.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  160.            }
  161.            else
  162.            {
  163.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  164.            }

  165.        if(ucLed_dr2==1)
  166.            {
  167.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  168.            }
  169.            else
  170.            {
  171.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  172.            }

  173.        if(ucLed_dr3==1)
  174.            {
  175.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  176.            }
  177.            else
  178.            {
  179.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  180.            }

  181.        if(ucLed_dr4==1)
  182.            {
  183.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  184.            }
  185.            else
  186.            {
  187.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  188.            }


  189.        if(ucLed_dr5==1)
  190.            {
  191.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  192.            }
  193.            else
  194.            {
  195.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  196.            }


  197.        if(ucLed_dr6==1)
  198.            {
  199.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  200.            }
  201.            else
  202.            {
  203.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  204.            }


  205.        if(ucLed_dr7==1)
  206.            {
  207.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  208.            }
  209.            else
  210.            {
  211.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  212.            }


  213.        if(ucLed_dr8==1)
  214.            {
  215.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  216.            }
  217.            else
  218.            {
  219.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  220.            }

  221.        if(ucLed_dr9==1)
  222.            {
  223.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  224.            }
  225.            else
  226.            {
  227.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  228.            }

  229.        if(ucLed_dr10==1)
  230.            {
  231.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  232.            }
  233.            else
  234.            {
  235.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  236.            }

  237.        if(ucLed_dr11==1)
  238.            {
  239.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  240.            }
  241.            else
  242.            {
  243.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  244.            }

  245.        if(ucLed_dr12==1)
  246.            {
  247.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  248.            }
  249.            else
  250.            {
  251.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  252.            }


  253.        if(ucLed_dr13==1)
  254.            {
  255.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  256.            }
  257.            else
  258.            {
  259.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  260.            }


  261.        if(ucLed_dr14==1)
  262.            {
  263.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  264.            }
  265.            else
  266.            {
  267.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  268.            }


  269.        if(ucLed_dr15==1)
  270.            {
  271.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  272.            }
  273.            else
  274.            {
  275.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  276.            }


  277.        if(ucLed_dr16==1)
  278.            {
  279.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  280.            }
  281.            else
  282.            {
  283.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  284.            }

  285.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  286.    }
  287. }

  288. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  289. {
  290.    unsigned char i;
  291.    unsigned char ucTempData;
  292.    hc595_sh_dr=0;
  293.    hc595_st_dr=0;

  294.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  295.    for(i=0;i<8;i++)
  296.    {
  297.          if(ucTempData>=0x80)hc595_ds_dr=1;
  298.          else hc595_ds_dr=0;

  299.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  300.          delay_short(15);
  301.          hc595_sh_dr=1;
  302.          delay_short(15);

  303.          ucTempData=ucTempData<<1;
  304.    }

  305.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  306.    for(i=0;i<8;i++)
  307.    {
  308.          if(ucTempData>=0x80)hc595_ds_dr=1;
  309.          else hc595_ds_dr=0;

  310.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  311.          delay_short(15);
  312.          hc595_sh_dr=1;
  313.          delay_short(15);

  314.          ucTempData=ucTempData<<1;
  315.    }

  316.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  317.    delay_short(15);
  318.    hc595_st_dr=1;
  319.    delay_short(15);

  320.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  321.    hc595_st_dr=0;
  322.    hc595_ds_dr=0;

  323. }

  324. /* 注释一:
  325. * 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
  326. */

  327. void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  328. {
  329.   switch(ucLedStep_09_16)
  330.   {
  331.      case 0:
  332.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  333.            {
  334.                uiTimeCnt_09_16=0; //时间计数器清零

  335.                            if(ucLedDirFlag==0)  //正方向
  336.                            {
  337.                   ucLed_dr16=0;  //第16个灭
  338.                   ucLed_dr9=1;  //第9个亮

  339.                   ucLed_update=1;  //更新显示
  340.                   ucLedStep_09_16=1; //切换到下一个步骤
  341.                            }
  342.                            else  //反方向
  343.                            {
  344.                   ucLed_dr15=1;  //第15个亮
  345.                   ucLed_dr16=0;  //第16个灭

  346.                   ucLed_update=1;  //更新显示
  347.                   ucLedStep_09_16=7; //返回上一个步骤
  348.                            }
  349.            }
  350.            break;
  351.      case 1:
  352.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  353.            {
  354.                uiTimeCnt_09_16=0; //时间计数器清零

  355.                            if(ucLedDirFlag==0)  //正方向
  356.                            {
  357.                   ucLed_dr9=0;  //第9个灭
  358.                   ucLed_dr10=1;  //第10个亮

  359.                   ucLed_update=1;  //更新显示
  360.                   ucLedStep_09_16=2; //切换到下一个步骤
  361.                            }
  362.                            else  //反方向
  363.                            {
  364.                   ucLed_dr16=1;  //第16个亮
  365.                   ucLed_dr9=0;  //第9个灭

  366.                   ucLed_update=1;  //更新显示
  367.                   ucLedStep_09_16=0; //返回上一个步骤
  368.                            }
  369.            }
  370.            break;
  371.      case 2:
  372.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  373.            {
  374.                uiTimeCnt_09_16=0; //时间计数器清零

  375.                            if(ucLedDirFlag==0)  //正方向
  376.                            {
  377.                   ucLed_dr10=0;  //第10个灭
  378.                   ucLed_dr11=1;  //第11个亮

  379.                   ucLed_update=1;  //更新显示
  380.                   ucLedStep_09_16=3; //切换到下一个步骤
  381.                            }
  382.                            else  //反方向
  383.                            {
  384.                   ucLed_dr9=1;  //第9个亮
  385.                   ucLed_dr10=0;  //第10个灭

  386.                   ucLed_update=1;  //更新显示
  387.                   ucLedStep_09_16=1; //返回上一个步骤
  388.                            }
  389.            }
  390.            break;
  391.      case 3:
  392.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  393.            {
  394.                uiTimeCnt_09_16=0; //时间计数器清零

  395.                            if(ucLedDirFlag==0)  //正方向
  396.                            {
  397.                   ucLed_dr11=0;  //第11个灭
  398.                   ucLed_dr12=1;  //第12个亮

  399.                   ucLed_update=1;  //更新显示
  400.                   ucLedStep_09_16=4; //切换到下一个步骤
  401.                            }
  402.                            else  //反方向
  403.                            {
  404.                   ucLed_dr10=1;  //第10个亮
  405.                   ucLed_dr11=0;  //第11个灭

  406.                   ucLed_update=1;  //更新显示
  407.                   ucLedStep_09_16=2; //返回上一个步骤
  408.                            }
  409.            }
  410.            break;
  411.      case 4:
  412.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  413.            {
  414.                uiTimeCnt_09_16=0; //时间计数器清零

  415.                            if(ucLedDirFlag==0)  //正方向
  416.                            {
  417.                   ucLed_dr12=0;  //第12个灭
  418.                   ucLed_dr13=1;  //第13个亮

  419.                   ucLed_update=1;  //更新显示
  420.                   ucLedStep_09_16=5; //切换到下一个步骤
  421.                            }
  422.                            else  //反方向
  423.                            {
  424.                   ucLed_dr11=1;  //第11个亮
  425.                   ucLed_dr12=0;  //第12个灭

  426.                   ucLed_update=1;  //更新显示
  427.                   ucLedStep_09_16=3; //返回上一个步骤
  428.                            }
  429.            }
  430.            break;
  431.      case 5:
  432.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  433.            {
  434.                uiTimeCnt_09_16=0; //时间计数器清零

  435.                            if(ucLedDirFlag==0)  //正方向
  436.                            {
  437.                   ucLed_dr13=0;  //第13个灭
  438.                   ucLed_dr14=1;  //第14个亮

  439.                   ucLed_update=1;  //更新显示
  440.                   ucLedStep_09_16=6; //切换到下一个步骤
  441.                            }
  442.                            else  //反方向
  443.                            {
  444.                   ucLed_dr12=1;  //第12个亮
  445.                   ucLed_dr13=0;  //第13个灭

  446.                   ucLed_update=1;  //更新显示
  447.                   ucLedStep_09_16=4; //返回上一个步骤
  448.                            }
  449.            }
  450.            break;
  451.      case 6:
  452.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  453.            {
  454.                uiTimeCnt_09_16=0; //时间计数器清零

  455.                            if(ucLedDirFlag==0)  //正方向
  456.                            {
  457.                   ucLed_dr14=0;  //第14个灭
  458.                   ucLed_dr15=1;  //第15个亮

  459.                   ucLed_update=1;  //更新显示
  460.                   ucLedStep_09_16=7; //切换到下一个步骤
  461.                            }
  462.                            else  //反方向
  463.                            {
  464.                   ucLed_dr13=1;  //第13个亮
  465.                   ucLed_dr14=0;  //第14个灭

  466.                   ucLed_update=1;  //更新显示
  467.                   ucLedStep_09_16=5; //返回上一个步骤
  468.                            }
  469.            }
  470.            break;
  471.      case 7:
  472.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  473.            {
  474.                uiTimeCnt_09_16=0; //时间计数器清零

  475.                            if(ucLedDirFlag==0)  //正方向
  476.                            {
  477.                   ucLed_dr15=0;  //第15个灭
  478.                   ucLed_dr16=1;  //第16个亮

  479.                   ucLed_update=1;  //更新显示
  480.                   ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
  481.                            }
  482.                            else  //反方向
  483.                            {
  484.                   ucLed_dr14=1;  //第14个亮
  485.                   ucLed_dr15=0;  //第15个灭

  486.                   ucLed_update=1;  //更新显示
  487.                   ucLedStep_09_16=6; //返回上一个步骤
  488.                            }
  489.            }
  490.            break;
  491.    
  492.    }

  493. }


  494. void T0_time() interrupt 1
  495. {
  496.   TF0=0;  //清除中断标志
  497.   TR0=0; //关中断


  498.   if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  499.   {
  500.       uiTimeCnt_09_16++;  //累加定时中断的次数,
  501.   }

  502.   key_scan(); //按键扫描函数

  503.   if(uiVoiceCnt!=0)
  504.   {
  505.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  506.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  507.   }
  508.   else
  509.   {
  510.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  511.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  512.   }

  513.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  514.   TL0=0x2f;
  515.   TR0=1;  //开中断
  516. }

  517. void delay_short(unsigned int uiDelayShort)
  518. {
  519.    unsigned int i;  
  520.    for(i=0;i<uiDelayShort;i++)
  521.    {
  522.      ;   //一个分号相当于执行一条空语句
  523.    }
  524. }

  525. void delay_long(unsigned int uiDelayLong)
  526. {
  527.    unsigned int i;
  528.    unsigned int j;
  529.    for(i=0;i<uiDelayLong;i++)
  530.    {
  531.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  532.           {
  533.              ; //一个分号相当于执行一条空语句
  534.           }
  535.    }
  536. }


  537. void initial_myself()  //第一区 初始化单片机
  538. {
  539. /* 注释二:
  540. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  541. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  542. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  543. */
  544.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  545.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  546.   TMOD=0x01;  //设置定时器0为工作方式1


  547.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  548.   TL0=0x2f;


  549. }

  550. void initial_peripheral() //第二区 初始化外围
  551. {
  552.   EA=1;     //开总中断
  553.   ET0=1;    //允许定时中断
  554.   TR0=1;    //启动定时中断

  555. }
复制代码

总结陈词:
这一节讲了独立按键控制跑马灯的速度。如果按键要控制跑马灯的启动和暂停,我们该怎么编写程序呢?欲知详情,请听下回分解-----独立按键控制跑马灯的启动和暂停。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

25
 
第二十四节:独立按键控制跑马灯的启动和暂停。

开场白:
上一节讲了独立按键控制跑马灯的速度。这一节继续要教会大家一个知识点:如何通过一个中间变量把按键跟跑马灯的启动和暂停有效关联起来。
具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。在上一节的基础上,增加一个启动和暂停按键,用矩阵键盘中的S13键作为启动和暂停独立按键,记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
在上一节的基础上,第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S13键,原来运行的跑马灯会暂停,原来暂停的跑马灯会运行。其它跟上一节一样,用S1来改变方向,用S5和S9来改变速度。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. #define const_key_time4  20    //按键去抖动延时的时间

  7. void initial_myself();   
  8. void initial_peripheral();
  9. void delay_short(unsigned int uiDelayShort);
  10. void delay_long(unsigned int uiDelaylong);

  11. void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  12. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  13. void led_update();  //LED更新函数
  14. void T0_time();  //定时中断函数

  15. void key_service(); //按键服务的应用程序
  16. void key_scan(); //按键扫描函数 放在定时中断里

  17. sbit hc595_sh_dr=P2^3;   
  18. sbit hc595_st_dr=P2^4;  
  19. sbit hc595_ds_dr=P2^5;  

  20. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  21. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  22. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  23. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  24. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

  25. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  26. unsigned char ucKeySec=0;   //被触发的按键编号

  27. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  28. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  29. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  30. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  31. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  32. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志


  33. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  34. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

  35. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  36. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  37. unsigned char ucLed_dr2=0;
  38. unsigned char ucLed_dr3=0;
  39. unsigned char ucLed_dr4=0;
  40. unsigned char ucLed_dr5=0;
  41. unsigned char ucLed_dr6=0;
  42. unsigned char ucLed_dr7=0;
  43. unsigned char ucLed_dr8=0;
  44. unsigned char ucLed_dr9=0;
  45. unsigned char ucLed_dr10=0;
  46. unsigned char ucLed_dr11=0;
  47. unsigned char ucLed_dr12=0;
  48. unsigned char ucLed_dr13=0;
  49. unsigned char ucLed_dr14=0;
  50. unsigned char ucLed_dr15=0;
  51. unsigned char ucLed_dr16=0;

  52. unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


  53. unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
  54. unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

  55. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  56. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

  57. unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
  58. unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。
  59. unsigned char ucLedStartFlag=1;   //启动和暂停的变量,0代表暂停,1代表启动

  60. void main()
  61.   {
  62.    initial_myself();  
  63.    delay_long(100);   
  64.    initial_peripheral();
  65.    while(1)   
  66.    {
  67.       led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  68.           led_update();  //LED更新函数
  69.       key_service(); //按键服务的应用程序
  70.    }

  71. }


  72. void key_scan()//按键扫描函数 放在定时中断里
  73. {  

  74.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  75.   {
  76.      ucKeyLock1=0; //按键自锁标志清零
  77.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  78.   }
  79.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  80.   {
  81.      uiKeyTimeCnt1++; //累加定时中断次数
  82.      if(uiKeyTimeCnt1>const_key_time1)
  83.      {
  84.         uiKeyTimeCnt1=0;
  85.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  86.         ucKeySec=1;    //触发1号键
  87.      }
  88.   }

  89.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  90.   {
  91.      ucKeyLock2=0; //按键自锁标志清零
  92.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  93.   }
  94.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  95.   {
  96.      uiKeyTimeCnt2++; //累加定时中断次数
  97.      if(uiKeyTimeCnt2>const_key_time2)
  98.      {
  99.         uiKeyTimeCnt2=0;
  100.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  101.         ucKeySec=2;    //触发2号键
  102.      }
  103.   }

  104.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  105.   {
  106.      ucKeyLock3=0; //按键自锁标志清零
  107.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  108.   }
  109.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  110.   {
  111.      uiKeyTimeCnt3++; //累加定时中断次数
  112.      if(uiKeyTimeCnt3>const_key_time3)
  113.      {
  114.         uiKeyTimeCnt3=0;
  115.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  116.         ucKeySec=3;    //触发3号键
  117.      }
  118.   }

  119.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  120.   {
  121.      ucKeyLock4=0; //按键自锁标志清零
  122.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  123.   }
  124.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  125.   {
  126.      uiKeyTimeCnt4++; //累加定时中断次数
  127.      if(uiKeyTimeCnt4>const_key_time4)
  128.      {
  129.         uiKeyTimeCnt4=0;
  130.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  131.         ucKeySec=4;    //触发4号键
  132.      }
  133.   }

  134. }


  135. void key_service() //按键服务的应用程序
  136. {
  137.   switch(ucKeySec) //按键服务状态切换
  138.   {
  139.     case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

  140.           if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
  141.                   {
  142.                      ucLedDirFlag=1;
  143.                   }
  144.                   else
  145.                   {
  146.                            ucLedDirFlag=0;
  147.                   }

  148.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  149.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  150.           break;   
  151.    
  152.     case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
  153.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
  154.                   if(uiSetTimeLevel_09_16<50)  //最快限定在50
  155.                   {
  156.                       uiSetTimeLevel_09_16=50;
  157.                   }

  158.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  159.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  160.           break;  

  161.     case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
  162.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
  163.                   if(uiSetTimeLevel_09_16>550)  //最慢限定在550
  164.                   {
  165.                       uiSetTimeLevel_09_16=550;
  166.                   }

  167.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  168.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  169.           break;         
  170.          
  171.     case 4:// 启动和暂停按键 对应朱兆祺学习板的S13键  ucLedStartFlag为0时代表暂停,为1时代表启动

  172.               if(ucLedStartFlag==1)  //启动和暂停两种状态循环切换
  173.                   {
  174.                      ucLedStartFlag=0;
  175.                   }
  176.                   else                   //启动和暂停两种状态循环切换
  177.                   {
  178.                            ucLedStartFlag=1;
  179.                   }

  180.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  181.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  182.           break;   
  183.   }               
  184. }




  185. void led_update()  //LED更新函数
  186. {

  187.    if(ucLed_update==1)
  188.    {
  189.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  190.        if(ucLed_dr1==1)
  191.            {
  192.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  193.            }
  194.            else
  195.            {
  196.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  197.            }

  198.        if(ucLed_dr2==1)
  199.            {
  200.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  201.            }
  202.            else
  203.            {
  204.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  205.            }

  206.        if(ucLed_dr3==1)
  207.            {
  208.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  209.            }
  210.            else
  211.            {
  212.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  213.            }

  214.        if(ucLed_dr4==1)
  215.            {
  216.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  217.            }
  218.            else
  219.            {
  220.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  221.            }


  222.        if(ucLed_dr5==1)
  223.            {
  224.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  225.            }
  226.            else
  227.            {
  228.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  229.            }


  230.        if(ucLed_dr6==1)
  231.            {
  232.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  233.            }
  234.            else
  235.            {
  236.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  237.            }


  238.        if(ucLed_dr7==1)
  239.            {
  240.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  241.            }
  242.            else
  243.            {
  244.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  245.            }


  246.        if(ucLed_dr8==1)
  247.            {
  248.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  249.            }
  250.            else
  251.            {
  252.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  253.            }

  254.        if(ucLed_dr9==1)
  255.            {
  256.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  257.            }
  258.            else
  259.            {
  260.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  261.            }

  262.        if(ucLed_dr10==1)
  263.            {
  264.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  265.            }
  266.            else
  267.            {
  268.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  269.            }

  270.        if(ucLed_dr11==1)
  271.            {
  272.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  273.            }
  274.            else
  275.            {
  276.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  277.            }

  278.        if(ucLed_dr12==1)
  279.            {
  280.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  281.            }
  282.            else
  283.            {
  284.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  285.            }


  286.        if(ucLed_dr13==1)
  287.            {
  288.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  289.            }
  290.            else
  291.            {
  292.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  293.            }


  294.        if(ucLed_dr14==1)
  295.            {
  296.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  297.            }
  298.            else
  299.            {
  300.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  301.            }


  302.        if(ucLed_dr15==1)
  303.            {
  304.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  305.            }
  306.            else
  307.            {
  308.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  309.            }


  310.        if(ucLed_dr16==1)
  311.            {
  312.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  313.            }
  314.            else
  315.            {
  316.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  317.            }

  318.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  319.    }
  320. }

  321. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  322. {
  323.    unsigned char i;
  324.    unsigned char ucTempData;
  325.    hc595_sh_dr=0;
  326.    hc595_st_dr=0;

  327.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  328.    for(i=0;i<8;i++)
  329.    {
  330.          if(ucTempData>=0x80)hc595_ds_dr=1;
  331.          else hc595_ds_dr=0;

  332.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  333.          delay_short(15);
  334.          hc595_sh_dr=1;
  335.          delay_short(15);

  336.          ucTempData=ucTempData<<1;
  337.    }

  338.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  339.    for(i=0;i<8;i++)
  340.    {
  341.          if(ucTempData>=0x80)hc595_ds_dr=1;
  342.          else hc595_ds_dr=0;

  343.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  344.          delay_short(15);
  345.          hc595_sh_dr=1;
  346.          delay_short(15);

  347.          ucTempData=ucTempData<<1;
  348.    }

  349.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  350.    delay_short(15);
  351.    hc595_st_dr=1;
  352.    delay_short(15);

  353.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  354.    hc595_st_dr=0;
  355.    hc595_ds_dr=0;

  356. }

  357. /* 注释一:
  358. * 以下程序,要学会如何通过中间变量,把按键和跑马灯的任务关联起来
  359. */

  360. void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  361. {
  362.   if(ucLedStartFlag==1)  //此变量为1时代表启动
  363.   {
  364.      switch(ucLedStep_09_16)
  365.      {
  366.      case 0:
  367.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  368.            {
  369.                uiTimeCnt_09_16=0; //时间计数器清零

  370.                            if(ucLedDirFlag==0)  //正方向
  371.                            {
  372.                   ucLed_dr16=0;  //第16个灭
  373.                   ucLed_dr9=1;  //第9个亮

  374.                   ucLed_update=1;  //更新显示
  375.                   ucLedStep_09_16=1; //切换到下一个步骤
  376.                            }
  377.                            else  //反方向
  378.                            {
  379.                   ucLed_dr15=1;  //第15个亮
  380.                   ucLed_dr16=0;  //第16个灭

  381.                   ucLed_update=1;  //更新显示
  382.                   ucLedStep_09_16=7; //返回上一个步骤
  383.                            }
  384.            }
  385.            break;
  386.      case 1:
  387.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  388.            {
  389.                uiTimeCnt_09_16=0; //时间计数器清零

  390.                            if(ucLedDirFlag==0)  //正方向
  391.                            {
  392.                   ucLed_dr9=0;  //第9个灭
  393.                   ucLed_dr10=1;  //第10个亮

  394.                   ucLed_update=1;  //更新显示
  395.                   ucLedStep_09_16=2; //切换到下一个步骤
  396.                            }
  397.                            else  //反方向
  398.                            {
  399.                   ucLed_dr16=1;  //第16个亮
  400.                   ucLed_dr9=0;  //第9个灭

  401.                   ucLed_update=1;  //更新显示
  402.                   ucLedStep_09_16=0; //返回上一个步骤
  403.                            }
  404.            }
  405.            break;
  406.      case 2:
  407.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  408.            {
  409.                uiTimeCnt_09_16=0; //时间计数器清零

  410.                            if(ucLedDirFlag==0)  //正方向
  411.                            {
  412.                   ucLed_dr10=0;  //第10个灭
  413.                   ucLed_dr11=1;  //第11个亮

  414.                   ucLed_update=1;  //更新显示
  415.                   ucLedStep_09_16=3; //切换到下一个步骤
  416.                            }
  417.                            else  //反方向
  418.                            {
  419.                   ucLed_dr9=1;  //第9个亮
  420.                   ucLed_dr10=0;  //第10个灭

  421.                   ucLed_update=1;  //更新显示
  422.                   ucLedStep_09_16=1; //返回上一个步骤
  423.                            }
  424.            }
  425.            break;
  426.      case 3:
  427.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  428.            {
  429.                uiTimeCnt_09_16=0; //时间计数器清零

  430.                            if(ucLedDirFlag==0)  //正方向
  431.                            {
  432.                   ucLed_dr11=0;  //第11个灭
  433.                   ucLed_dr12=1;  //第12个亮

  434.                   ucLed_update=1;  //更新显示
  435.                   ucLedStep_09_16=4; //切换到下一个步骤
  436.                            }
  437.                            else  //反方向
  438.                            {
  439.                   ucLed_dr10=1;  //第10个亮
  440.                   ucLed_dr11=0;  //第11个灭

  441.                   ucLed_update=1;  //更新显示
  442.                   ucLedStep_09_16=2; //返回上一个步骤
  443.                            }
  444.            }
  445.            break;
  446.      case 4:
  447.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  448.            {
  449.                uiTimeCnt_09_16=0; //时间计数器清零

  450.                            if(ucLedDirFlag==0)  //正方向
  451.                            {
  452.                   ucLed_dr12=0;  //第12个灭
  453.                   ucLed_dr13=1;  //第13个亮

  454.                   ucLed_update=1;  //更新显示
  455.                   ucLedStep_09_16=5; //切换到下一个步骤
  456.                            }
  457.                            else  //反方向
  458.                            {
  459.                   ucLed_dr11=1;  //第11个亮
  460.                   ucLed_dr12=0;  //第12个灭

  461.                   ucLed_update=1;  //更新显示
  462.                   ucLedStep_09_16=3; //返回上一个步骤
  463.                            }
  464.            }
  465.            break;
  466.      case 5:
  467.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  468.            {
  469.                uiTimeCnt_09_16=0; //时间计数器清零

  470.                            if(ucLedDirFlag==0)  //正方向
  471.                            {
  472.                   ucLed_dr13=0;  //第13个灭
  473.                   ucLed_dr14=1;  //第14个亮

  474.                   ucLed_update=1;  //更新显示
  475.                   ucLedStep_09_16=6; //切换到下一个步骤
  476.                            }
  477.                            else  //反方向
  478.                            {
  479.                   ucLed_dr12=1;  //第12个亮
  480.                   ucLed_dr13=0;  //第13个灭

  481.                   ucLed_update=1;  //更新显示
  482.                   ucLedStep_09_16=4; //返回上一个步骤
  483.                            }
  484.            }
  485.            break;
  486.      case 6:
  487.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  488.            {
  489.                uiTimeCnt_09_16=0; //时间计数器清零

  490.                            if(ucLedDirFlag==0)  //正方向
  491.                            {
  492.                   ucLed_dr14=0;  //第14个灭
  493.                   ucLed_dr15=1;  //第15个亮

  494.                   ucLed_update=1;  //更新显示
  495.                   ucLedStep_09_16=7; //切换到下一个步骤
  496.                            }
  497.                            else  //反方向
  498.                            {
  499.                   ucLed_dr13=1;  //第13个亮
  500.                   ucLed_dr14=0;  //第14个灭

  501.                   ucLed_update=1;  //更新显示
  502.                   ucLedStep_09_16=5; //返回上一个步骤
  503.                            }
  504.            }
  505.            break;
  506.      case 7:
  507.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  508.            {
  509.                uiTimeCnt_09_16=0; //时间计数器清零

  510.                            if(ucLedDirFlag==0)  //正方向
  511.                            {
  512.                   ucLed_dr15=0;  //第15个灭
  513.                   ucLed_dr16=1;  //第16个亮

  514.                   ucLed_update=1;  //更新显示
  515.                   ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
  516.                            }
  517.                            else  //反方向
  518.                            {
  519.                   ucLed_dr14=1;  //第14个亮
  520.                   ucLed_dr15=0;  //第15个灭

  521.                   ucLed_update=1;  //更新显示
  522.                   ucLedStep_09_16=6; //返回上一个步骤
  523.                            }
  524.            }
  525.            break;
  526.    
  527.       }
  528.    }

  529. }


  530. void T0_time() interrupt 1
  531. {
  532.   TF0=0;  //清除中断标志
  533.   TR0=0; //关中断


  534.   if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  535.   {
  536.       if(ucLedStartFlag==1)  //此变量为1时代表启动
  537.           {
  538.          uiTimeCnt_09_16++;  //累加定时中断的次数,
  539.           }
  540.   }

  541.   key_scan(); //按键扫描函数

  542.   if(uiVoiceCnt!=0)
  543.   {
  544.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  545.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  546.   }
  547.   else
  548.   {
  549.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  550.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  551.   }

  552.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  553.   TL0=0x2f;
  554.   TR0=1;  //开中断
  555. }

  556. void delay_short(unsigned int uiDelayShort)
  557. {
  558.    unsigned int i;  
  559.    for(i=0;i<uiDelayShort;i++)
  560.    {
  561.      ;   //一个分号相当于执行一条空语句
  562.    }
  563. }

  564. void delay_long(unsigned int uiDelayLong)
  565. {
  566.    unsigned int i;
  567.    unsigned int j;
  568.    for(i=0;i<uiDelayLong;i++)
  569.    {
  570.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  571.           {
  572.              ; //一个分号相当于执行一条空语句
  573.           }
  574.    }
  575. }


  576. void initial_myself()  //第一区 初始化单片机
  577. {
  578. /* 注释二:
  579. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  580. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  581. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  582. */
  583.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  584.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  585.   TMOD=0x01;  //设置定时器0为工作方式1


  586.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  587.   TL0=0x2f;


  588. }

  589. void initial_peripheral() //第二区 初始化外围
  590. {
  591.   EA=1;     //开总中断
  592.   ET0=1;    //允许定时中断
  593.   TR0=1;    //启动定时中断

  594. }
复制代码

总结陈词:
这几节循序渐进地讲了独立按键控制跑马灯各种状态的程序。在很多实际工控项目中,经常会涉及到运动的自动控制,运动的自动控制就必然会涉及到感应器。下一节我将会讲感应器和运动控制的程序框架,欲知详情,请听下回分解-----用LED灯和按键来模拟工业自动化设备的运动控制。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

26
 
第二十五节:用LED灯和按键来模拟工业自动化设备的运动控制。

开场白:
前面三节讲了独立按键控制跑马灯的各种状态,这一节我们要做一个机械手控制程序,这个机械手可以左右移动,最左边有一个开关感应器,最右边也有一个开关感应器。它也可以上下移动,最下面有一个开关感应器。左右移动是通过一个气缸控制,上下移动也是通过一个气缸控制。而单片机控制气缸,本质上是通过三极管把信号放大,然后控制气缸上的电磁阀。这个系统机械手驱动部分的输出和输入信号如下:
    2个输出IO口,分别控制2个气缸。对于左右移动的气缸,当IO口为0时往左边跑,当IO口为1时往右边跑。对于上下移动的气缸,当IO口为0时往上边跑,当IO口为1时往下边跑。
      3个输入IO口,分别检测3个开关感应器。感应器没有被触发时,IO口检测为高电平1。被触发时,IO口检测为低电平0。
这一节继续要教会大家两个知识点:
第一点:如何用软件进行开关感应器的抗干扰处理。
第二点:如何用Switch语句搭建工业自动控制的程序框架。还是那句话,我们只要以Switch语句为支点,再复杂再繁琐的程序都可以轻松地编写出来。

具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。用矩阵键盘中的S1键作为启动独立按键,用S5按键模拟左边的开关感应器,用S9按键模拟右边的开关感应器,用S13按键模拟下边的开关感应器。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
      开机默认机械手在左上方的原点位置。按下启动按键后,机械手从左边开始往右边移动,当机械手移动到最右边时,机械手马上开始往下移动,最后机械手移动到最右下角的位置时,延时1秒,然后原路返回,一直返回到左上角的原点位置。注意:启动按键必须等机械手处于左上角原点位置时,启动按键的触发才有效。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  3. #define const_key_time1  20    //按键去抖动延时的时间


  4. #define const_sensor  20   //开关感应器去抖动延时的时间

  5. #define const_1s  500  //1秒钟大概的定时中断次数

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);

  10. void left_to_right();  //从左边移动到右边
  11. void right_to_left(); //从右边返回到左边
  12. void up_to_dowm();   //从上边移动到下边
  13. void down_to_up();    //从下边返回到上边


  14. void run(); //设备自动控制程序
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  16. void led_update();  //LED更新函数
  17. void T0_time();  //定时中断函数

  18. void key_service(); //按键服务的应用程序
  19. void key_scan(); //按键扫描函数 放在定时中断里
  20. void sensor_scan(); //开关感应器软件抗干扰处理函数,放在定时中断里。

  21. sbit hc595_sh_dr=P2^3;   
  22. sbit hc595_st_dr=P2^4;  
  23. sbit hc595_ds_dr=P2^5;  

  24. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  25. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键

  26. sbit left_sr=P0^1; //左边的开关感应器    对应朱兆祺学习板的S5键   
  27. sbit right_sr=P0^2; //右边的开关感应器   有对应朱兆祺学习板的S9键
  28. sbit down_sr=P0^3; //下边的开关感应器    对应朱兆祺学习板的S13键

  29. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  30. unsigned char ucKeySec=0;   //被触发的按键编号

  31. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  32. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志


  33. unsigned char ucLeftSr=0;  //左边感应器经过软件抗干扰处理后的状态标志
  34. unsigned char ucRightSr=0;  //右边感应器经过软件抗干扰处理后的状态标志
  35. unsigned char ucDownSr=0;  //下边感应器经过软件抗干扰处理后的状态标志

  36. unsigned int  uiLeftCnt1=0;  //左边感应器软件抗干扰所需的计数器变量
  37. unsigned int  uiLeftCnt2=0;

  38. unsigned int  uiRightCnt1=0;  //右边感应器软件抗干扰所需的计数器变量
  39. unsigned int  uiRightCnt2=0;

  40. unsigned int  uiDownCnt1=0;   //下边软件抗干扰所需的计数器变量
  41. unsigned int  uiDownCnt2=0;

  42. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  43. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  44. unsigned char ucLed_dr2=0;
  45. unsigned char ucLed_dr3=0;
  46. unsigned char ucLed_dr4=0;
  47. unsigned char ucLed_dr5=0;
  48. unsigned char ucLed_dr6=0;
  49. unsigned char ucLed_dr7=0;
  50. unsigned char ucLed_dr8=0;
  51. unsigned char ucLed_dr9=0;
  52. unsigned char ucLed_dr10=0;
  53. unsigned char ucLed_dr11=0;
  54. unsigned char ucLed_dr12=0;
  55. unsigned char ucLed_dr13=0;
  56. unsigned char ucLed_dr14=0;
  57. unsigned char ucLed_dr15=0;
  58. unsigned char ucLed_dr16=0;

  59. unsigned char ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。



  60. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  61. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量



  62. unsigned int  uiRunTimeCnt=0;  //运动中的时间延时计数器变量
  63. unsigned char ucRunStep=0;  //运动控制的步骤变量

  64. void main()
  65.   {
  66.    initial_myself();  
  67.    delay_long(100);   
  68.    initial_peripheral();
  69.    while(1)   
  70.    {
  71.       run(); //设备自动控制程序
  72.       led_update();  //LED更新函数
  73.       key_service(); //按键服务的应用程序
  74.    }

  75. }


  76. /* 注释一:
  77. * 开关感应器的抗干扰处理,本质上类似按键的去抖动处理。唯一的区别是:
  78. * 按键去抖动关注的是IO口的一种状态,而开关感应器关注的是IO口的两种状态。
  79. * 当开关感应器从原来的1状态切换到0状态之前,要进行软件滤波处理过程,一旦成功地
  80. * 切换到0状态了,再想从0状态切换到1状态的时候,又要经过软件滤波处理过程,符合
  81. * 条件后才能切换到1的状态。通俗的话来说,按键的去抖动从1变成0难,从0变成1容易。
  82. * 开关感应器从1变成0难,从0变成1也难。这里所说的"难"是指要经过去抖处理。
  83. */

  84. void sensor_scan() //开关感应器软件抗干扰处理函数,放在定时中断里。
  85. {
  86.    if(left_sr==1)  //左边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S5键  
  87.    {
  88.        uiLeftCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  89.        uiLeftCnt2++; //类似独立按键去抖动的软件抗干扰处理
  90.            if(uiLeftCnt2>const_sensor)
  91.            {
  92.               uiLeftCnt2=0;
  93.                   ucLeftSr=1;   //说明感应器确实没有被接触
  94.            }
  95.    }
  96.    else    //左边感应器是低电平,说明有可能被接触到了
  97.    {
  98.        uiLeftCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  99.        uiLeftCnt1++;
  100.            if(uiLeftCnt1>const_sensor)
  101.            {
  102.               uiLeftCnt1=0;
  103.                   ucLeftSr=0;   //说明感应器确实被接触到了
  104.            }
  105.    }

  106.    if(right_sr==1)  //右边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S9键  
  107.    {
  108.        uiRightCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  109.        uiRightCnt2++; //类似独立按键去抖动的软件抗干扰处理
  110.            if(uiRightCnt2>const_sensor)
  111.            {
  112.               uiRightCnt2=0;
  113.                   ucRightSr=1;   //说明感应器确实没有被接触
  114.            }
  115.    }
  116.    else    //右边感应器是低电平,说明有可能被接触到了   
  117.    {
  118.        uiRightCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  119.        uiRightCnt1++;
  120.            if(uiRightCnt1>const_sensor)
  121.            {
  122.               uiRightCnt1=0;
  123.                   ucRightSr=0;   //说明感应器确实被接触到了
  124.            }
  125.    }

  126.    if(down_sr==1)  //下边感应器是高电平,说明有可能没有被接触    对应朱兆祺学习板的S13键  
  127.    {
  128.        uiDownCnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  129.        uiDownCnt2++; //类似独立按键去抖动的软件抗干扰处理
  130.            if(uiDownCnt2>const_sensor)
  131.            {
  132.               uiDownCnt2=0;
  133.                   ucDownSr=1;   //说明感应器确实没有被接触
  134.            }
  135.    }
  136.    else    //下边感应器是低电平,说明有可能被接触到了
  137.    {
  138.        uiDownCnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  139.        uiDownCnt1++;
  140.            if(uiDownCnt1>const_sensor)
  141.            {
  142.               uiDownCnt1=0;
  143.                   ucDownSr=0;   //说明感应器确实被接触到了
  144.            }
  145.    }
  146. }


  147. void key_scan()//按键扫描函数 放在定时中断里
  148. {  

  149.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  150.   {
  151.      ucKeyLock1=0; //按键自锁标志清零
  152.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  153.   }
  154.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  155.   {
  156.      uiKeyTimeCnt1++; //累加定时中断次数
  157.      if(uiKeyTimeCnt1>const_key_time1)
  158.      {
  159.         uiKeyTimeCnt1=0;
  160.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  161.         ucKeySec=1;    //触发1号键
  162.      }
  163.   }



  164. }


  165. void key_service() //按键服务的应用程序
  166. {
  167.   switch(ucKeySec) //按键服务状态切换
  168.   {
  169.     case 1:// 启动按键   对应朱兆祺学习板的S1键
  170.          if(ucLeftSr==0)  //处于左上角原点位置
  171.          {
  172.              ucRunStep=1; //启动
  173.              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。  
  174.          }           
  175.    
  176.          ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  177.          break;   
  178.      
  179.   }               
  180. }



  181. void led_update()  //LED更新函数
  182. {

  183.    if(ucLed_update==1)
  184.    {
  185.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  186.        if(ucLed_dr1==1)
  187.            {
  188.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  189.            }
  190.            else
  191.            {
  192.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  193.            }

  194.        if(ucLed_dr2==1)
  195.            {
  196.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  197.            }
  198.            else
  199.            {
  200.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  201.            }

  202.        if(ucLed_dr3==1)
  203.            {
  204.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  205.            }
  206.            else
  207.            {
  208.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  209.            }

  210.        if(ucLed_dr4==1)
  211.            {
  212.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  213.            }
  214.            else
  215.            {
  216.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  217.            }


  218.        if(ucLed_dr5==1)
  219.            {
  220.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  221.            }
  222.            else
  223.            {
  224.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  225.            }


  226.        if(ucLed_dr6==1)
  227.            {
  228.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  229.            }
  230.            else
  231.            {
  232.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  233.            }


  234.        if(ucLed_dr7==1)
  235.            {
  236.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  237.            }
  238.            else
  239.            {
  240.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  241.            }


  242.        if(ucLed_dr8==1)
  243.            {
  244.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  245.            }
  246.            else
  247.            {
  248.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  249.            }

  250.        if(ucLed_dr9==1)
  251.            {
  252.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  253.            }
  254.            else
  255.            {
  256.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  257.            }

  258.        if(ucLed_dr10==1)
  259.            {
  260.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  261.            }
  262.            else
  263.            {
  264.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  265.            }

  266.        if(ucLed_dr11==1)
  267.            {
  268.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  269.            }
  270.            else
  271.            {
  272.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  273.            }

  274.        if(ucLed_dr12==1)
  275.            {
  276.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  277.            }
  278.            else
  279.            {
  280.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  281.            }


  282.        if(ucLed_dr13==1)
  283.            {
  284.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  285.            }
  286.            else
  287.            {
  288.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  289.            }


  290.        if(ucLed_dr14==1)
  291.            {
  292.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  293.            }
  294.            else
  295.            {
  296.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  297.            }


  298.        if(ucLed_dr15==1)
  299.            {
  300.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  301.            }
  302.            else
  303.            {
  304.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  305.            }


  306.        if(ucLed_dr16==1)
  307.            {
  308.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  309.            }
  310.            else
  311.            {
  312.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  313.            }

  314.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  315.    }
  316. }

  317. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  318. {
  319.    unsigned char i;
  320.    unsigned char ucTempData;
  321.    hc595_sh_dr=0;
  322.    hc595_st_dr=0;

  323.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  324.    for(i=0;i<8;i++)
  325.    {
  326.          if(ucTempData>=0x80)hc595_ds_dr=1;
  327.          else hc595_ds_dr=0;

  328.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  329.          delay_short(15);
  330.          hc595_sh_dr=1;
  331.          delay_short(15);

  332.          ucTempData=ucTempData<<1;
  333.    }

  334.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  335.    for(i=0;i<8;i++)
  336.    {
  337.          if(ucTempData>=0x80)hc595_ds_dr=1;
  338.          else hc595_ds_dr=0;

  339.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  340.          delay_short(15);
  341.          hc595_sh_dr=1;
  342.          delay_short(15);

  343.          ucTempData=ucTempData<<1;
  344.    }

  345.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  346.    delay_short(15);
  347.    hc595_st_dr=1;
  348.    delay_short(15);

  349.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  350.    hc595_st_dr=0;
  351.    hc595_ds_dr=0;

  352. }


  353. void left_to_right()  //从左边移动到右边
  354. {
  355.    ucLed_dr1=1;   // 1代表左右气缸从左边移动到右边

  356.    ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
  357. }
  358. void right_to_left() //从右边返回到左边
  359. {
  360.    ucLed_dr1=0;   // 0代表左右气缸从右边返回到左边

  361.    ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
  362. }
  363. void up_to_down()   //从上边移动到下边
  364. {
  365.    ucLed_dr2=1;   // 1代表上下气缸从上边移动到下边

  366.    ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
  367. }
  368. void down_to_up()    //从下边返回到上边
  369. {
  370.    ucLed_dr2=0;   // 0代表上下气缸从下边返回到上边

  371.    ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
  372. }


  373. void run() //设备自动控制程序
  374. {

  375. switch(ucRunStep)
  376. {
  377.        case 0:    //机械手处于左上角原点的位置,待命状态。此时触发启动按键ucRunStep=1,就触发后续一些列的连续动作。

  378.             break;

  379.        case 1:    //机械手从左边往右边移动
  380.             left_to_right();
  381.             ucRunStep=2;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  382.             break;

  383.        case 2:    //等待机械手移动到最右边,直到触发了最右边的开关感应器。
  384.             if(ucRightSr==0)  //右边感应器被触发
  385.             {
  386.                ucRunStep=3;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  387.             }
  388.             break;

  389.        case 3:    //机械手从右上边往右下边移动,从上往下。
  390.             up_to_down();
  391.             ucRunStep=4;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  392.             break;

  393.        case 4:    //等待机械手从右上边移动到右下边,直到触发了右下边的开关感应器。
  394.             if(ucDownSr==0)  //右下边感应器被触发
  395.             {
  396.                uiRunTimeCnt=0;  //时间计数器清零,为接下来延时1秒钟做准备
  397.                ucRunStep=5;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  398.             }
  399.             break;

  400.        case 5:    //机械手在右下边延时1秒
  401.             if(uiRunTimeCnt>const_1s)  //延时1秒
  402.             {
  403.                ucRunStep=6;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  404.             }
  405.             break;
  406.        case 6:    //原路返回,机械手从右下边往右上边移动。
  407.             down_to_up();
  408.             ucRunStep=7;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  409.             break;

  410.        case 7:    //原路返回,等待机械手移动到最右边的感应开关
  411.             if(ucRightSr==0)
  412.             {
  413.                ucRunStep=8;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  414.             }
  415.             break;

  416.        case 8:    //原路返回,等待机械手从右边往左边移动
  417.             right_to_left();
  418.             ucRunStep=9;  //这就是鸿哥传说中的怎样灵活控制步骤变量

  419.             break;

  420.        case 9:    //原路返回,等待机械手移动到最左边的感应开关,表示返回到了原点
  421.             if(ucLeftSr==0) //返回到左上角的原点位置
  422.             {
  423.                ucRunStep=0;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  424.             }
  425.             break;
  426.    }
  427. }


  428. void T0_time() interrupt 1
  429. {
  430.   TF0=0;  //清除中断标志
  431.   TR0=0; //关中断


  432.   sensor_scan(); //开关感应器软件抗干扰处理函数
  433.   key_scan(); //按键扫描函数

  434.   if(uiRunTimeCnt<0xffff) //不要超过最大int类型范围
  435.   {
  436.      uiRunTimeCnt++; //延时计数器
  437.   }
  438.   if(uiVoiceCnt!=0)
  439.   {
  440.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  441.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  442.   }
  443.   else
  444.   {
  445.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  446.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  447.   }

  448.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  449.   TL0=0x2f;
  450.   TR0=1;  //开中断
  451. }

  452. void delay_short(unsigned int uiDelayShort)
  453. {
  454.    unsigned int i;  
  455.    for(i=0;i<uiDelayShort;i++)
  456.    {
  457.      ;   //一个分号相当于执行一条空语句
  458.    }
  459. }

  460. void delay_long(unsigned int uiDelayLong)
  461. {
  462.    unsigned int i;
  463.    unsigned int j;
  464.    for(i=0;i<uiDelayLong;i++)
  465.    {
  466.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  467.           {
  468.              ; //一个分号相当于执行一条空语句
  469.           }
  470.    }
  471. }


  472. void initial_myself()  //第一区 初始化单片机
  473. {
  474. /* 注释二:
  475. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  476. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  477. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  478. */
  479.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  480.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  481.   TMOD=0x01;  //设置定时器0为工作方式1


  482.   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  483.   TL0=0x2f;


  484. }

  485. void initial_peripheral() //第二区 初始化外围
  486. {
  487.   EA=1;     //开总中断
  488.   ET0=1;    //允许定时中断
  489.   TR0=1;    //启动定时中断

  490. }
复制代码

总结陈词:
前面花了很多节内容在讲按键和跑马灯的关系,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的。人机界面的程序框架该怎么样写?欲知详情,请听下回分解-----在主函数while循环中驱动数码管的动态扫描程序。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

27
 
第二十六节:在主函数while循环中驱动数码管的动态扫描程序。

开场白:
上一节通过一个机械手自动控制程序展示了我在工控常用的编程框架,但是一直没涉及到人机界面,在大多数的实际项目中,人机界面是必不可少的,这一节开始讲最常用的人机界面------动态数码管的驱动。

这一节要教会大家两个知识点:
第一点:数码管的动态驱动原理。
第二点:如何通过编程,让数码管显示的内容转移到几个变量接口上,方便以后编写更上一层的窗口程序。

具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。用两片74HC595动态驱动八位共阴数码管。

(2)实现功能:
      开机后显示  8765.4321  的内容,注意,其中有一个小数点。
(3)源代码讲解如下:
  1. #include "REG52.H"


  2. void initial_myself();   
  3. void initial_peripheral();
  4. void delay_short(unsigned int uiDelayShort);
  5. void delay_long(unsigned int uiDelaylong);

  6. //驱动数码管的74HC595
  7. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  8. void display_drive(); //显示数码管字模的驱动函数

  9. //驱动LED的74HC595
  10. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);


  11. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  12. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  13. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  14. sbit dig_hc595_st_dr=P2^1;  
  15. sbit dig_hc595_ds_dr=P2^2;  

  16. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  17. sbit hc595_st_dr=P2^4;  
  18. sbit hc595_ds_dr=P2^5;  


  19. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  20. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  21. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  22. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  23. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  24. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  25. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  26. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  27. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  28. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  29. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  30. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  31. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  32. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  33. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  34. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  35. unsigned char ucDigShowTemp=0; //临时中间变量
  36. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  37. unsigned char ucDisplayUpdate=1; //更新显示标志

  38. //根据原理图得出的共阴数码管字模表
  39. code unsigned char dig_table[]=
  40. {
  41. 0x3f,  //0       序号0
  42. 0x06,  //1       序号1
  43. 0x5b,  //2       序号2
  44. 0x4f,  //3       序号3
  45. 0x66,  //4       序号4
  46. 0x6d,  //5       序号5
  47. 0x7d,  //6       序号6
  48. 0x07,  //7       序号7
  49. 0x7f,  //8       序号8
  50. 0x6f,  //9       序号9
  51. 0x00,  //不显示  序号10
  52. };

  53. void main()
  54.   {
  55.    initial_myself();  
  56.    delay_long(100);   
  57.    initial_peripheral();
  58.    while(1)  
  59.    {
  60.       display_drive();  //显示数码管字模的驱动函数
  61.    }

  62. }

  63. /* 注释一:
  64. * 动态驱动数码管的原理是,在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
  65. * 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
  66. * 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
  67. * 数码管公共位com的引脚。
  68. */

  69. void display_drive()  
  70. {
  71.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  72.    switch(ucDisplayDriveStep)
  73.    {
  74.       case 1:  //显示第1位
  75.            ucDigShowTemp=dig_table[ucDigShow1];
  76.                    if(ucDigDot1==1)
  77.                    {
  78.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  79.                    }
  80.            dig_hc595_drive(ucDigShowTemp,0xfe);
  81.                break;
  82.       case 2:  //显示第2位
  83.            ucDigShowTemp=dig_table[ucDigShow2];
  84.                    if(ucDigDot2==1)
  85.                    {
  86.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  87.                    }
  88.            dig_hc595_drive(ucDigShowTemp,0xfd);
  89.                break;
  90.       case 3:  //显示第3位
  91.            ucDigShowTemp=dig_table[ucDigShow3];
  92.                    if(ucDigDot3==1)
  93.                    {
  94.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  95.                    }
  96.            dig_hc595_drive(ucDigShowTemp,0xfb);
  97.                break;
  98.       case 4:  //显示第4位
  99.            ucDigShowTemp=dig_table[ucDigShow4];
  100.                    if(ucDigDot4==1)
  101.                    {
  102.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  103.                    }
  104.            dig_hc595_drive(ucDigShowTemp,0xf7);
  105.                break;
  106.       case 5:  //显示第5位
  107.            ucDigShowTemp=dig_table[ucDigShow5];
  108.                    if(ucDigDot5==1)
  109.                    {
  110.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  111.                    }
  112.            dig_hc595_drive(ucDigShowTemp,0xef);
  113.                break;
  114.       case 6:  //显示第6位
  115.            ucDigShowTemp=dig_table[ucDigShow6];
  116.                    if(ucDigDot6==1)
  117.                    {
  118.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  119.                    }
  120.            dig_hc595_drive(ucDigShowTemp,0xdf);
  121.                break;
  122.       case 7:  //显示第7位
  123.            ucDigShowTemp=dig_table[ucDigShow7];
  124.                    if(ucDigDot7==1)
  125.                    {
  126.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  127.            }
  128.            dig_hc595_drive(ucDigShowTemp,0xbf);
  129.                break;
  130.       case 8:  //显示第8位
  131.            ucDigShowTemp=dig_table[ucDigShow8];
  132.                    if(ucDigDot8==1)
  133.                    {
  134.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  135.                    }
  136.            dig_hc595_drive(ucDigShowTemp,0x7f);
  137.                break;
  138.    }

  139.    ucDisplayDriveStep++;
  140.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  141.    {
  142.      ucDisplayDriveStep=1;
  143.    }

  144. /* 注释二:
  145. * 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
  146. * 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
  147. * 位的数码管界面,这样可以增加显示的效果。但是,由于朱兆祺51学习板是间接经过74HC595驱动数码管的,
  148. * 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
  149. * 因此这里不再需要加delay延时函数或者计数延时。
  150. */


  151. }


  152. //数码管的74HC595驱动函数
  153. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  154. {
  155.    unsigned char i;
  156.    unsigned char ucTempData;
  157.    dig_hc595_sh_dr=0;
  158.    dig_hc595_st_dr=0;

  159.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  160.    for(i=0;i<8;i++)
  161.    {
  162.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  163.          else dig_hc595_ds_dr=0;

  164. /* 注释三:
  165. *  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
  166. */
  167.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  168.          delay_short(1);
  169.          dig_hc595_sh_dr=1;
  170.          delay_short(1);

  171.          ucTempData=ucTempData<<1;
  172.    }

  173.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  174.    for(i=0;i<8;i++)
  175.    {
  176.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  177.          else dig_hc595_ds_dr=0;

  178.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  179.          delay_short(1);
  180.          dig_hc595_sh_dr=1;
  181.          delay_short(1);

  182.          ucTempData=ucTempData<<1;
  183.    }

  184.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  185.    delay_short(1);
  186.    dig_hc595_st_dr=1;
  187.    delay_short(1);

  188.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  189.    dig_hc595_st_dr=0;
  190.    dig_hc595_ds_dr=0;

  191. }


  192. //LED灯的74HC595驱动函数
  193. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  194. {
  195.    unsigned char i;
  196.    unsigned char ucTempData;
  197.    hc595_sh_dr=0;
  198.    hc595_st_dr=0;

  199.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  200.    for(i=0;i<8;i++)
  201.    {
  202.          if(ucTempData>=0x80)hc595_ds_dr=1;
  203.          else hc595_ds_dr=0;

  204.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  205.          delay_short(1);
  206.          hc595_sh_dr=1;
  207.          delay_short(1);

  208.          ucTempData=ucTempData<<1;
  209.    }

  210.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  211.    for(i=0;i<8;i++)
  212.    {
  213.          if(ucTempData>=0x80)hc595_ds_dr=1;
  214.          else hc595_ds_dr=0;

  215.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  216.          delay_short(1);
  217.          hc595_sh_dr=1;
  218.          delay_short(1);

  219.          ucTempData=ucTempData<<1;
  220.    }

  221.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  222.    delay_short(1);
  223.    hc595_st_dr=1;
  224.    delay_short(1);

  225.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  226.    hc595_st_dr=0;
  227.    hc595_ds_dr=0;

  228. }





  229. void delay_short(unsigned int uiDelayShort)
  230. {
  231.    unsigned int i;  
  232.    for(i=0;i<uiDelayShort;i++)
  233.    {
  234.      ;   //一个分号相当于执行一条空语句
  235.    }
  236. }


  237. void delay_long(unsigned int uiDelayLong)
  238. {
  239.    unsigned int i;
  240.    unsigned int j;
  241.    for(i=0;i<uiDelayLong;i++)
  242.    {
  243.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  244.           {
  245.              ; //一个分号相当于执行一条空语句
  246.           }
  247.    }
  248. }


  249. void initial_myself()  //第一区 初始化单片机
  250. {

  251.   led_dr=0;  //关闭独立LED灯
  252.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  253.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  254. }
  255. void initial_peripheral() //第二区 初始化外围
  256. {
  257. /* 注释四:
  258. * 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
  259. * 只要更改以下对应变量的内容,就可以显示你想显示的数字。初学者应该仔细看看display_drive等函数,
  260. * 了解来龙去脉,就可以知道本驱动程序的框架原理了。
  261. */
  262.    ucDigShow8=8;  //第8位数码管要显示的内容
  263.    ucDigShow7=7;  //第7位数码管要显示的内容
  264.    ucDigShow6=6;  //第6位数码管要显示的内容
  265.    ucDigShow5=5;  //第5位数码管要显示的内容
  266.    ucDigShow4=4;  //第4位数码管要显示的内容
  267.    ucDigShow3=3;  //第3位数码管要显示的内容
  268.    ucDigShow2=2;  //第2位数码管要显示的内容
  269.    ucDigShow1=1;  //第1位数码管要显示的内容


  270.    ucDigDot8=0;  
  271.    ucDigDot7=0;  
  272.    ucDigDot6=0;
  273.    ucDigDot5=1;  //显示第5位的小数点
  274.    ucDigDot4=0;
  275.    ucDigDot3=0;  
  276.    ucDigDot2=0;
  277.    ucDigDot1=0;
  278. }
复制代码

总结陈词:
把本程序下载到朱兆祺51学习板上,发现显示的效果还是挺不错的。但是,本程序也有一个弱点,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果,有没有办法改善它?当然有。欲知详情,请听下回分解-----在定时中断里动态扫描数码管的程序。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

28
 
第二十七节:在定时中断里动态扫描数码管的程序。

开场白:
上一节讲了在主函数循环中动态扫描数码管的程序,但是该程序有一个隐患,在一些项目中 ,主函数循环中的任务越多,就意味着在某一瞬间,每显示一位数码管停留的时间就会越久,一旦超过某个值,会严重影响显示的效果。这一节要教会大家两个知识点:
第一个:如何把动态扫描数码管的程序放在定时中断里,彻底解决上节的显示隐患。
第二个:在定时中断里的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的初始值2000改成了500。

具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。用两片74HC595动态驱动八位共阴数码管。

(2)实现功能:
      开机后显示  8765.4321  的内容,注意,其中有一个小数点。
(3)源代码讲解如下:
  1. #include "REG52.H"


  2. void initial_myself();   
  3. void initial_peripheral();
  4. void delay_short(unsigned int uiDelayShort);
  5. void delay_long(unsigned int uiDelaylong);

  6. //驱动数码管的74HC595
  7. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  8. void display_drive(); //显示数码管字模的驱动函数

  9. //驱动LED的74HC595
  10. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  11. void T0_time();  //定时中断函数

  12. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  13. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  14. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  15. sbit dig_hc595_st_dr=P2^1;  
  16. sbit dig_hc595_ds_dr=P2^2;  

  17. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  18. sbit hc595_st_dr=P2^4;  
  19. sbit hc595_ds_dr=P2^5;  


  20. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  21. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  22. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  23. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  24. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  25. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  26. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  27. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  28. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  29. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  30. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  31. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  32. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  33. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  34. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  35. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  36. unsigned char ucDigShowTemp=0; //临时中间变量
  37. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  38. unsigned char ucDisplayUpdate=1; //更新显示标志

  39. //根据原理图得出的共阴数码管字模表
  40. code unsigned char dig_table[]=
  41. {
  42. 0x3f,  //0       序号0
  43. 0x06,  //1       序号1
  44. 0x5b,  //2       序号2
  45. 0x4f,  //3       序号3
  46. 0x66,  //4       序号4
  47. 0x6d,  //5       序号5
  48. 0x7d,  //6       序号6
  49. 0x07,  //7       序号7
  50. 0x7f,  //8       序号8
  51. 0x6f,  //9       序号9
  52. 0x00,  //不显示  序号10
  53. };

  54. void main()
  55.   {
  56.    initial_myself();  
  57.    delay_long(100);   
  58.    initial_peripheral();
  59.    while(1)  
  60.    {
  61.       ;
  62.    }

  63. }

  64. /* 注释一:
  65. * 动态驱动数码管的原理是,在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
  66. * 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
  67. * 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
  68. * 数码管公共位com的引脚。
  69. */

  70. void display_drive()  
  71. {
  72.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  73.    switch(ucDisplayDriveStep)
  74.    {
  75.       case 1:  //显示第1位
  76.            ucDigShowTemp=dig_table[ucDigShow1];
  77.                    if(ucDigDot1==1)
  78.                    {
  79.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  80.                    }
  81.            dig_hc595_drive(ucDigShowTemp,0xfe);
  82.                break;
  83.       case 2:  //显示第2位
  84.            ucDigShowTemp=dig_table[ucDigShow2];
  85.                    if(ucDigDot2==1)
  86.                    {
  87.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  88.                    }
  89.            dig_hc595_drive(ucDigShowTemp,0xfd);
  90.                break;
  91.       case 3:  //显示第3位
  92.            ucDigShowTemp=dig_table[ucDigShow3];
  93.                    if(ucDigDot3==1)
  94.                    {
  95.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  96.                    }
  97.            dig_hc595_drive(ucDigShowTemp,0xfb);
  98.                break;
  99.       case 4:  //显示第4位
  100.            ucDigShowTemp=dig_table[ucDigShow4];
  101.                    if(ucDigDot4==1)
  102.                    {
  103.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  104.                    }
  105.            dig_hc595_drive(ucDigShowTemp,0xf7);
  106.                break;
  107.       case 5:  //显示第5位
  108.            ucDigShowTemp=dig_table[ucDigShow5];
  109.                    if(ucDigDot5==1)
  110.                    {
  111.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  112.                    }
  113.            dig_hc595_drive(ucDigShowTemp,0xef);
  114.                break;
  115.       case 6:  //显示第6位
  116.            ucDigShowTemp=dig_table[ucDigShow6];
  117.                    if(ucDigDot6==1)
  118.                    {
  119.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  120.                    }
  121.            dig_hc595_drive(ucDigShowTemp,0xdf);
  122.                break;
  123.       case 7:  //显示第7位
  124.            ucDigShowTemp=dig_table[ucDigShow7];
  125.                    if(ucDigDot7==1)
  126.                    {
  127.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  128.            }
  129.            dig_hc595_drive(ucDigShowTemp,0xbf);
  130.                break;
  131.       case 8:  //显示第8位
  132.            ucDigShowTemp=dig_table[ucDigShow8];
  133.                    if(ucDigDot8==1)
  134.                    {
  135.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  136.                    }
  137.            dig_hc595_drive(ucDigShowTemp,0x7f);
  138.                break;
  139.    }

  140.    ucDisplayDriveStep++;
  141.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  142.    {
  143.      ucDisplayDriveStep=1;
  144.    }

  145. /* 注释二:
  146. * 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
  147. * 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
  148. * 位的数码管界面,这样可以增加显示的效果。但是,由于朱兆祺51学习板是间接经过74HC595驱动数码管的,
  149. * 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
  150. * 因此这里不再需要加delay延时函数或者计数延时。
  151. */


  152. }


  153. //数码管的74HC595驱动函数
  154. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  155. {
  156.    unsigned char i;
  157.    unsigned char ucTempData;
  158.    dig_hc595_sh_dr=0;
  159.    dig_hc595_st_dr=0;

  160.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  161.    for(i=0;i<8;i++)
  162.    {
  163.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  164.          else dig_hc595_ds_dr=0;

  165. /* 注释三:
  166. *  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
  167. */
  168.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  169.          delay_short(1);
  170.          dig_hc595_sh_dr=1;
  171.          delay_short(1);

  172.          ucTempData=ucTempData<<1;
  173.    }

  174.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  175.    for(i=0;i<8;i++)
  176.    {
  177.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  178.          else dig_hc595_ds_dr=0;

  179.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  180.          delay_short(1);
  181.          dig_hc595_sh_dr=1;
  182.          delay_short(1);

  183.          ucTempData=ucTempData<<1;
  184.    }

  185.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  186.    delay_short(1);
  187.    dig_hc595_st_dr=1;
  188.    delay_short(1);

  189.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  190.    dig_hc595_st_dr=0;
  191.    dig_hc595_ds_dr=0;

  192. }


  193. //LED灯的74HC595驱动函数
  194. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  195. {
  196.    unsigned char i;
  197.    unsigned char ucTempData;
  198.    hc595_sh_dr=0;
  199.    hc595_st_dr=0;

  200.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  201.    for(i=0;i<8;i++)
  202.    {
  203.          if(ucTempData>=0x80)hc595_ds_dr=1;
  204.          else hc595_ds_dr=0;

  205.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  206.          delay_short(1);
  207.          hc595_sh_dr=1;
  208.          delay_short(1);

  209.          ucTempData=ucTempData<<1;
  210.    }

  211.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  212.    for(i=0;i<8;i++)
  213.    {
  214.          if(ucTempData>=0x80)hc595_ds_dr=1;
  215.          else hc595_ds_dr=0;

  216.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  217.          delay_short(1);
  218.          hc595_sh_dr=1;
  219.          delay_short(1);

  220.          ucTempData=ucTempData<<1;
  221.    }

  222.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  223.    delay_short(1);
  224.    hc595_st_dr=1;
  225.    delay_short(1);

  226.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  227.    hc595_st_dr=0;
  228.    hc595_ds_dr=0;

  229. }


  230. void T0_time() interrupt 1
  231. {
  232.   TF0=0;  //清除中断标志
  233.   TR0=0; //关中断

  234.   display_drive();  //数码管字模的驱动函数

  235. /* 注释四:
  236. *  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
  237. */
  238.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  239.   TL0=0x0b;
  240.   TR0=1;  //开中断
  241. }


  242. void delay_short(unsigned int uiDelayShort)
  243. {
  244.    unsigned int i;  
  245.    for(i=0;i<uiDelayShort;i++)
  246.    {
  247.      ;   //一个分号相当于执行一条空语句
  248.    }
  249. }


  250. void delay_long(unsigned int uiDelayLong)
  251. {
  252.    unsigned int i;
  253.    unsigned int j;
  254.    for(i=0;i<uiDelayLong;i++)
  255.    {
  256.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  257.           {
  258.              ; //一个分号相当于执行一条空语句
  259.           }
  260.    }
  261. }


  262. void initial_myself()  //第一区 初始化单片机
  263. {

  264.   led_dr=0;  //关闭独立LED灯
  265.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  266.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  267.   TMOD=0x01;  //设置定时器0为工作方式1

  268.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  269.   TL0=0x0b;

  270. }
  271. void initial_peripheral() //第二区 初始化外围
  272. {
  273. /* 注释五:
  274. * 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
  275. * 只要更改以下对应变量的内容,就可以显示你想显示的数字。初学者应该仔细看看display_drive等函数,
  276. * 了解来龙去脉,就可以知道本驱动程序的框架原理了。
  277. */
  278.    ucDigShow8=8;  //第8位数码管要显示的内容
  279.    ucDigShow7=7;  //第7位数码管要显示的内容
  280.    ucDigShow6=6;  //第6位数码管要显示的内容
  281.    ucDigShow5=5;  //第5位数码管要显示的内容
  282.    ucDigShow4=4;  //第4位数码管要显示的内容
  283.    ucDigShow3=3;  //第3位数码管要显示的内容
  284.    ucDigShow2=2;  //第2位数码管要显示的内容
  285.    ucDigShow1=1;  //第1位数码管要显示的内容


  286.    ucDigDot8=0;  
  287.    ucDigDot7=0;  
  288.    ucDigDot6=0;
  289.    ucDigDot5=1;  //显示第5位的小数点
  290.    ucDigDot4=0;
  291.    ucDigDot3=0;  
  292.    ucDigDot2=0;
  293.    ucDigDot1=0;

  294.    EA=1;     //开总中断
  295.    ET0=1;    //允许定时中断
  296.    TR0=1;    //启动定时中断

  297. }
复制代码

总结陈词:
     有的朋友会质疑,很多教科书上说,定时中断函数里面的内容应该越少越好,你把动态驱动数码管的函数放在中断里面,难道不会影响其它任务的执行吗?我的回答是,大部分的小项目都不会影响,只有少数实时性要求非常高的项目会影响,而对于这类项目,我的做法是从一开始设计硬件电路板的时候,就应该放弃用动态扫描数码管的方案,而是应该选数码管专用驱动芯片来实现静态驱动。因为动态扫描数码管本来就不适合应用在实时性非常高的项目。
      前面这两节都讲了数码管的驱动程序,要在此基础上,做一些项目中经常遇到的界面应用,我们该怎么写程序?欲知详情,请听下回分解-----数码管通过切换窗口来设置参数。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

29
 
第二十八节:数码管通过切换窗口来设置参数。

开场白:
上一节讲了数码管的驱动程序,这节在上节的基础上,通过按键切换不同的窗口来设置不同的参数。
这一节要教会大家三个知识点:
第一个:鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
第二个:如何通过一个窗口变量来把按键,数码管,被设置的参数关联起来。
第三个:需要特别注意,在显示被设置参数时,应该先分解出每一位,然后再把分解出来的数据过渡到显示缓冲变量里。

具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换窗口按键对应S9键

(2)实现功能:
     通过按键设置4个不同的参数。
      一共有4个窗口。每个窗口显示一个参数。
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. //驱动数码管的74HC595
  11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  12. void display_drive(); //显示数码管字模的驱动函数
  13. void display_service(); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  16. void T0_time();  //定时中断函数
  17. void key_service(); //按键服务的应用程序
  18. void key_scan();//按键扫描函数 放在定时中断里

  19. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  20. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  21. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  22. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  23. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  24. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

  25. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  26. sbit dig_hc595_st_dr=P2^1;  
  27. sbit dig_hc595_ds_dr=P2^2;  
  28. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  29. sbit hc595_st_dr=P2^4;  
  30. sbit hc595_ds_dr=P2^5;  
  31. unsigned char ucKeySec=0;   //被触发的按键编号
  32. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  33. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  34. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  35. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  36. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  37. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  38. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  39. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  40. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  41. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  42. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  43. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  44. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  45. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  46. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  47. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  48. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  49. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  50. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  51. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  52. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  53. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  54. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  55. unsigned char ucDigShowTemp=0; //临时中间变量
  56. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  57. unsigned char ucWd1Update=1; //窗口1更新显示标志
  58. unsigned char ucWd2Update=0; //窗口2更新显示标志
  59. unsigned char ucWd3Update=0; //窗口3更新显示标志
  60. unsigned char ucWd4Update=0; //窗口4更新显示标志
  61. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  62. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  63. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  64. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  65. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  66. unsigned char ucTemp1=0;  //中间过渡变量
  67. unsigned char ucTemp2=0;  //中间过渡变量
  68. unsigned char ucTemp3=0;  //中间过渡变量
  69. unsigned char ucTemp4=0;  //中间过渡变量

  70. //根据原理图得出的共阴数码管字模表
  71. code unsigned char dig_table[]=
  72. {
  73. 0x3f,  //0       序号0
  74. 0x06,  //1       序号1
  75. 0x5b,  //2       序号2
  76. 0x4f,  //3       序号3
  77. 0x66,  //4       序号4
  78. 0x6d,  //5       序号5
  79. 0x7d,  //6       序号6
  80. 0x07,  //7       序号7
  81. 0x7f,  //8       序号8
  82. 0x6f,  //9       序号9
  83. 0x00,  //无      序号10
  84. 0x40,  //-       序号11
  85. 0x73,  //P       序号12
  86. };
  87. void main()
  88.   {
  89.    initial_myself();  
  90.    delay_long(100);   
  91.    initial_peripheral();
  92.    while(1)  
  93.    {
  94.      key_service(); //按键服务的应用程序
  95.          display_service(); //显示的窗口菜单服务程序
  96.    }
  97. }
  98. /* 注释一:
  99. *鸿哥首次提出的"一二级菜单显示理论":
  100. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  101. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  102. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  103. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  104. */

  105. void display_service() //显示的窗口菜单服务程序
  106. {

  107.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  108.    {
  109.        case 1:   //显示P--1窗口的数据
  110.             if(ucWd1Update==1)  //窗口1要全部更新显示
  111.    {
  112.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  113.                ucDigShow8=12;  //第8位数码管显示P
  114.                ucDigShow7=11;  //第7位数码管显示-
  115.                ucDigShow6=1;   //第6位数码管显示1
  116.                ucDigShow5=10;  //第5位数码管显示无
  117. /* 注释二:
  118. * 此处为什么要多加4个中间过渡变量ucTemp?是因为uiSetData1分解数据的时候
  119. * 需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时
  120. * 了一会。我们的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,
  121. * 当uiSetData1还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致
  122. * 显示的数据瞬间产生不完整,影响显示效果。因此,为了把需要显示的数据过渡最快,
  123. * 所以采取了先分解,再过渡显示的方法。
  124. */
  125.               //先分解数据
  126.                        ucTemp4=uiSetData1/1000;     
  127.                        ucTemp3=uiSetData1%1000/100;
  128.                        ucTemp2=uiSetData1%100/10;
  129.                        ucTemp1=uiSetData1%10;
  130.   
  131.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
  132.                ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  133.                ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  134.                ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  135.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  136.             }
  137.             break;
  138.         case 2:  //显示P--2窗口的数据
  139.             if(ucWd2Update==1)  //窗口2要全部更新显示
  140.    {
  141.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  142.                ucDigShow8=12;  //第8位数码管显示P
  143.                ucDigShow7=11;  //第7位数码管显示-
  144.                ucDigShow6=2;  //第6位数码管显示2
  145.                ucDigShow5=10;   //第5位数码管显示无
  146.                        ucTemp4=uiSetData2/1000;     //分解数据
  147.                        ucTemp3=uiSetData2%1000/100;
  148.                        ucTemp2=uiSetData2%100/10;
  149.                        ucTemp1=uiSetData2%10;
  150.                ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  151.                ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  152.                ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  153.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  154.     }
  155.              break;
  156.         case 3:  //显示P--3窗口的数据
  157.             if(ucWd3Update==1)  //窗口3要全部更新显示
  158.    {
  159.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  160.                ucDigShow8=12;  //第8位数码管显示P
  161.                ucDigShow7=11;  //第7位数码管显示-
  162.                ucDigShow6=3;  //第6位数码管显示3
  163.                ucDigShow5=10;   //第5位数码管显示无
  164.                        ucTemp4=uiSetData3/1000;     //分解数据
  165.                        ucTemp3=uiSetData3%1000/100;
  166.                        ucTemp2=uiSetData3%100/10;
  167.                        ucTemp1=uiSetData3%10;
  168.                ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  169.                ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  170.                ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  171.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  172.    }
  173.             break;
  174.         case 4:  //显示P--4窗口的数据
  175.             if(ucWd4Update==1)  //窗口4要全部更新显示
  176.    {
  177.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  178.                ucDigShow8=12;  //第8位数码管显示P
  179.                ucDigShow7=11;  //第7位数码管显示-
  180.                ucDigShow6=4;  //第6位数码管显示4
  181.                ucDigShow5=10;   //第5位数码管显示无
  182.                        ucTemp4=uiSetData4/1000;     //分解数据
  183.                        ucTemp3=uiSetData4%1000/100;
  184.                        ucTemp2=uiSetData4%100/10;
  185.                        ucTemp1=uiSetData4%10;
  186.                ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  187.                ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  188.                ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  189.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  190.     }
  191.              break;
  192.            }
  193.    

  194. }

  195. void key_scan()//按键扫描函数 放在定时中断里
  196. {  
  197.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  198.   {
  199.      ucKeyLock1=0; //按键自锁标志清零
  200.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  201.   }
  202.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  203.   {
  204.      uiKeyTimeCnt1++; //累加定时中断次数
  205.      if(uiKeyTimeCnt1>const_key_time1)
  206.      {
  207.         uiKeyTimeCnt1=0;
  208.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  209.         ucKeySec=1;    //触发1号键
  210.      }
  211.   }
  212.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  213.   {
  214.      ucKeyLock2=0; //按键自锁标志清零
  215.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  216.   }
  217.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  218.   {
  219.      uiKeyTimeCnt2++; //累加定时中断次数
  220.      if(uiKeyTimeCnt2>const_key_time2)
  221.      {
  222.         uiKeyTimeCnt2=0;
  223.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  224.         ucKeySec=2;    //触发2号键
  225.      }
  226.   }
  227.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  228.   {
  229.      ucKeyLock3=0; //按键自锁标志清零
  230.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  231.   }
  232.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  233.   {
  234.      uiKeyTimeCnt3++; //累加定时中断次数
  235.      if(uiKeyTimeCnt3>const_key_time3)
  236.      {
  237.         uiKeyTimeCnt3=0;
  238.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  239.         ucKeySec=3;    //触发3号键
  240.      }
  241.   }


  242. }

  243. void key_service() //按键服务的应用程序
  244. {
  245.   switch(ucKeySec) //按键服务状态切换
  246.   {
  247.     case 1:// 加按键 对应朱兆祺学习板的S1键
  248.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  249.                   {
  250.                      case 1:
  251.                   uiSetData1++;   
  252.                                   if(uiSetData1>9999) //最大值是9999
  253.                                   {
  254.                                      uiSetData1=9999;
  255.                                   }
  256.                            ucWd1Update=1;  //窗口1更新显示
  257.                               break;
  258.                      case 2:
  259.                   uiSetData2++;
  260.                                   if(uiSetData2>9999) //最大值是9999
  261.                                   {
  262.                                      uiSetData2=9999;
  263.                                   }
  264.                            ucWd2Update=1;  //窗口2更新显示
  265.                               break;
  266.                      case 3:
  267.                   uiSetData3++;
  268.                                   if(uiSetData3>9999) //最大值是9999
  269.                                   {
  270.                                      uiSetData3=9999;
  271.                                   }
  272.                            ucWd3Update=1;  //窗口3更新显示
  273.                               break;
  274.                      case 4:
  275.                   uiSetData4++;
  276.                                   if(uiSetData4>9999) //最大值是9999
  277.                                   {
  278.                                      uiSetData4=9999;
  279.                                   }
  280.                            ucWd4Update=1;  //窗口4更新显示
  281.                               break;
  282.                   }
  283.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  284.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  285.           break;   
  286.    
  287.     case 2:// 减按键 对应朱兆祺学习板的S5键
  288.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  289.                   {
  290.                      case 1:
  291.                   uiSetData1--;   
  292. /* 注释三:
  293. * 单片机C编译有一个特点,当一个无符号类型的数据0减去1时,就会溢出反而变成这个类型数据的最大值
  294. * 对于int类型的数据,最大值肯定比9999大,因此下面的临界点用if(uiSetData1>9999)来判断。
  295. */
  296.                                   if(uiSetData1>9999)  
  297.                                   {
  298.                                      uiSetData1=0;  //最小值是0
  299.                                   }
  300.                            ucWd1Update=1;  //窗口1更新显示
  301.                               break;
  302.                      case 2:
  303.                   uiSetData2--;
  304.                                   if(uiSetData2>9999)
  305.                                   {
  306.                                      uiSetData2=0;  //最小值是0
  307.                                   }
  308.                            ucWd2Update=1;  //窗口2更新显示
  309.                               break;
  310.                      case 3:
  311.                   uiSetData3--;
  312.                                   if(uiSetData3>9999)
  313.                                   {
  314.                                      uiSetData3=0;  //最小值是0
  315.                                   }
  316.                            ucWd3Update=1;  //窗口3更新显示
  317.                               break;
  318.                      case 4:
  319.                   uiSetData4--;
  320.                                   if(uiSetData4>9999)
  321.                                   {
  322.                                      uiSetData4=0;  //最小值是0
  323.                                   }
  324.                            ucWd4Update=1;  //窗口4更新显示
  325.                               break;
  326.                   }

  327.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  328.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  329.           break;  
  330.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  331.           ucWd++;  //切换窗口
  332.                   if(ucWd>4)
  333.                   {
  334.                     ucWd=1;
  335.                   }
  336.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  337.                   {
  338.                      case 1:
  339.                            ucWd1Update=1;  //窗口1更新显示
  340.                               break;
  341.                      case 2:
  342.                            ucWd2Update=1;  //窗口2更新显示
  343.                               break;
  344.                      case 3:
  345.                            ucWd3Update=1;  //窗口3更新显示
  346.                               break;
  347.                      case 4:
  348.                            ucWd4Update=1;  //窗口4更新显示
  349.                               break;
  350.                   }
  351.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  352.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  353.           break;         
  354.          
  355.   }               
  356. }

  357. void display_drive()  
  358. {
  359.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  360.    switch(ucDisplayDriveStep)
  361.    {
  362.       case 1:  //显示第1位
  363.            ucDigShowTemp=dig_table[ucDigShow1];
  364.                    if(ucDigDot1==1)
  365.                    {
  366.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  367.                    }
  368.            dig_hc595_drive(ucDigShowTemp,0xfe);
  369.                break;
  370.       case 2:  //显示第2位
  371.            ucDigShowTemp=dig_table[ucDigShow2];
  372.                    if(ucDigDot2==1)
  373.                    {
  374.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  375.                    }
  376.            dig_hc595_drive(ucDigShowTemp,0xfd);
  377.                break;
  378.       case 3:  //显示第3位
  379.            ucDigShowTemp=dig_table[ucDigShow3];
  380.                    if(ucDigDot3==1)
  381.                    {
  382.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  383.                    }
  384.            dig_hc595_drive(ucDigShowTemp,0xfb);
  385.                break;
  386.       case 4:  //显示第4位
  387.            ucDigShowTemp=dig_table[ucDigShow4];
  388.                    if(ucDigDot4==1)
  389.                    {
  390.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  391.                    }
  392.            dig_hc595_drive(ucDigShowTemp,0xf7);
  393.                break;
  394.       case 5:  //显示第5位
  395.            ucDigShowTemp=dig_table[ucDigShow5];
  396.                    if(ucDigDot5==1)
  397.                    {
  398.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  399.                    }
  400.            dig_hc595_drive(ucDigShowTemp,0xef);
  401.                break;
  402.       case 6:  //显示第6位
  403.            ucDigShowTemp=dig_table[ucDigShow6];
  404.                    if(ucDigDot6==1)
  405.                    {
  406.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  407.                    }
  408.            dig_hc595_drive(ucDigShowTemp,0xdf);
  409.                break;
  410.       case 7:  //显示第7位
  411.            ucDigShowTemp=dig_table[ucDigShow7];
  412.                    if(ucDigDot7==1)
  413.                    {
  414.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  415.            }
  416.            dig_hc595_drive(ucDigShowTemp,0xbf);
  417.                break;
  418.       case 8:  //显示第8位
  419.            ucDigShowTemp=dig_table[ucDigShow8];
  420.                    if(ucDigDot8==1)
  421.                    {
  422.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  423.                    }
  424.            dig_hc595_drive(ucDigShowTemp,0x7f);
  425.                break;
  426.    }
  427.    ucDisplayDriveStep++;
  428.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  429.    {
  430.      ucDisplayDriveStep=1;
  431.    }


  432. }

  433. //数码管的74HC595驱动函数
  434. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  435. {
  436.    unsigned char i;
  437.    unsigned char ucTempData;
  438.    dig_hc595_sh_dr=0;
  439.    dig_hc595_st_dr=0;
  440.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  441.    for(i=0;i<8;i++)
  442.    {
  443.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  444.          else dig_hc595_ds_dr=0;
  445.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  446.          delay_short(1);
  447.          dig_hc595_sh_dr=1;
  448.          delay_short(1);
  449.          ucTempData=ucTempData<<1;
  450.    }
  451.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  452.    for(i=0;i<8;i++)
  453.    {
  454.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  455.          else dig_hc595_ds_dr=0;
  456.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  457.          delay_short(1);
  458.          dig_hc595_sh_dr=1;
  459.          delay_short(1);
  460.          ucTempData=ucTempData<<1;
  461.    }
  462.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  463.    delay_short(1);
  464.    dig_hc595_st_dr=1;
  465.    delay_short(1);
  466.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  467.    dig_hc595_st_dr=0;
  468.    dig_hc595_ds_dr=0;
  469. }

  470. //LED灯的74HC595驱动函数
  471. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  472. {
  473.    unsigned char i;
  474.    unsigned char ucTempData;
  475.    hc595_sh_dr=0;
  476.    hc595_st_dr=0;
  477.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  478.    for(i=0;i<8;i++)
  479.    {
  480.          if(ucTempData>=0x80)hc595_ds_dr=1;
  481.          else hc595_ds_dr=0;
  482.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  483.          delay_short(1);
  484.          hc595_sh_dr=1;
  485.          delay_short(1);
  486.          ucTempData=ucTempData<<1;
  487.    }
  488.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  489.    for(i=0;i<8;i++)
  490.    {
  491.          if(ucTempData>=0x80)hc595_ds_dr=1;
  492.          else hc595_ds_dr=0;
  493.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  494.          delay_short(1);
  495.          hc595_sh_dr=1;
  496.          delay_short(1);
  497.          ucTempData=ucTempData<<1;
  498.    }
  499.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  500.    delay_short(1);
  501.    hc595_st_dr=1;
  502.    delay_short(1);
  503.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  504.    hc595_st_dr=0;
  505.    hc595_ds_dr=0;
  506. }

  507. void T0_time() interrupt 1
  508. {
  509.   TF0=0;  //清除中断标志
  510.   TR0=0; //关中断
  511.   key_scan(); //按键扫描函数
  512.   if(uiVoiceCnt!=0)
  513.   {
  514.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  515.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  516. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  517.   }
  518.   else
  519.   {
  520.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  521.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  522. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  523.   }
  524.   display_drive();  //数码管字模的驱动函数

  525.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  526.   TL0=0x0b;
  527.   TR0=1;  //开中断
  528. }

  529. void delay_short(unsigned int uiDelayShort)
  530. {
  531.    unsigned int i;  
  532.    for(i=0;i<uiDelayShort;i++)
  533.    {
  534.      ;   //一个分号相当于执行一条空语句
  535.    }
  536. }

  537. void delay_long(unsigned int uiDelayLong)
  538. {
  539.    unsigned int i;
  540.    unsigned int j;
  541.    for(i=0;i<uiDelayLong;i++)
  542.    {
  543.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  544.           {
  545.              ; //一个分号相当于执行一条空语句
  546.           }
  547.    }
  548. }

  549. void initial_myself()  //第一区 初始化单片机
  550. {
  551. /* 注释四:
  552. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  553. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  554. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  555. */
  556.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  557.   led_dr=0;  //关闭独立LED灯
  558.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  559.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  560.   TMOD=0x01;  //设置定时器0为工作方式1
  561.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  562.   TL0=0x0b;
  563. }
  564. void initial_peripheral() //第二区 初始化外围
  565. {

  566.    ucDigDot8=0;   //小数点全部不显示
  567.    ucDigDot7=0;  
  568.    ucDigDot6=0;
  569.    ucDigDot5=0;  
  570.    ucDigDot4=0;
  571.    ucDigDot3=0;  
  572.    ucDigDot2=0;
  573.    ucDigDot1=0;
  574.    EA=1;     //开总中断
  575.    ET0=1;    //允许定时中断
  576.    TR0=1;    //启动定时中断
  577. }

复制代码

总结陈词:
    这节在第4,3,2,1位显示设置的参数时,还有一点小瑕疵。比如设置参数等于56时,实际显示的是“0056”,也就是高位为0的如果不显示,效果才会更好。我们要把高位为0的去掉不显示,该怎么改程序呢?欲知详情,请听下回分解-----数码管通过切换窗口来设置参数,并且不显示为0的高位。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

30
 
第二十九节:数码管通过切换窗口来设置参数,并且不显示为0的高位。

开场白:
上一节在第4,3,2,1位显示设置的参数时,还有一点小瑕疵。比如设置参数等于56时,实际显示的是“0056”,也就是高位为0的如果不显示,效果才会更好。
这一节要教会大家两个知识点:
第一个:在上一节display_service()函数里略作修改,把高位为0的去掉不显示。
第二个:加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换窗口按键对应S9键

(2)实现功能:
     通过按键设置4个不同的参数。
      一共有4个窗口。每个窗口显示一个参数。
第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。
有三个按键。一个是加按键,按下此按键会依次增加当前窗口的参数。一个是减按键,按下此按键会依次减少当前窗口的参数。一个是切换窗口按键,按下此按键会依次循环切换不同的窗口。
并且要求被设置的数据不显示为0的高位。比如参数是12时,不能显示“0012”,只能第4,3位不显示,第2,1位显示“12”。

(3)源代码讲解如下:

  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. //驱动数码管的74HC595
  11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  12. void display_drive(); //显示数码管字模的驱动函数
  13. void display_service(); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  16. void T0_time();  //定时中断函数
  17. void key_service(); //按键服务的应用程序
  18. void key_scan();//按键扫描函数 放在定时中断里

  19. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  20. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  21. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  22. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  23. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  24. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

  25. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  26. sbit dig_hc595_st_dr=P2^1;  
  27. sbit dig_hc595_ds_dr=P2^2;  
  28. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  29. sbit hc595_st_dr=P2^4;  
  30. sbit hc595_ds_dr=P2^5;  
  31. unsigned char ucKeySec=0;   //被触发的按键编号
  32. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  33. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  34. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  35. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  36. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  37. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  38. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  39. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  40. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  41. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  42. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  43. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  44. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  45. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  46. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  47. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  48. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  49. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  50. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  51. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  52. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  53. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  54. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  55. unsigned char ucDigShowTemp=0; //临时中间变量
  56. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  57. unsigned char ucWd1Update=1; //窗口1更新显示标志
  58. unsigned char ucWd2Update=0; //窗口2更新显示标志
  59. unsigned char ucWd3Update=0; //窗口3更新显示标志
  60. unsigned char ucWd4Update=0; //窗口4更新显示标志
  61. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  62. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  63. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  64. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  65. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  66. unsigned char ucTemp1=0;  //中间过渡变量
  67. unsigned char ucTemp2=0;  //中间过渡变量
  68. unsigned char ucTemp3=0;  //中间过渡变量
  69. unsigned char ucTemp4=0;  //中间过渡变量

  70. //根据原理图得出的共阴数码管字模表
  71. code unsigned char dig_table[]=
  72. {
  73. 0x3f,  //0       序号0
  74. 0x06,  //1       序号1
  75. 0x5b,  //2       序号2
  76. 0x4f,  //3       序号3
  77. 0x66,  //4       序号4
  78. 0x6d,  //5       序号5
  79. 0x7d,  //6       序号6
  80. 0x07,  //7       序号7
  81. 0x7f,  //8       序号8
  82. 0x6f,  //9       序号9
  83. 0x00,  //无      序号10
  84. 0x40,  //-       序号11
  85. 0x73,  //P       序号12
  86. };
  87. void main()
  88.   {
  89.    initial_myself();  
  90.    delay_long(100);   
  91.    initial_peripheral();
  92.    while(1)  
  93.    {
  94.      key_service(); //按键服务的应用程序
  95.          display_service(); //显示的窗口菜单服务程序
  96.    }
  97. }
  98. /* 注释一:
  99. *鸿哥首次提出的"一二级菜单显示理论":
  100. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  101. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  102. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  103. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  104. */

  105. void display_service() //显示的窗口菜单服务程序
  106. {

  107.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  108.    {
  109.        case 1:   //显示P--1窗口的数据
  110.             if(ucWd1Update==1)  //窗口1要全部更新显示
  111.    {
  112.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  113.                ucDigShow8=12;  //第8位数码管显示P
  114.                ucDigShow7=11;  //第7位数码管显示-
  115.                ucDigShow6=1;   //第6位数码管显示1
  116.                ucDigShow5=10;  //第5位数码管显示无

  117.               //先分解数据
  118.                        ucTemp4=uiSetData1/1000;     
  119.                        ucTemp3=uiSetData1%1000/100;
  120.                        ucTemp2=uiSetData1%100/10;
  121.                        ucTemp1=uiSetData1%10;
  122.   
  123.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好
  124. /* 注释二:
  125. * 就是在这里略作修改,把高位为0的去掉不显示。
  126. */
  127.                if(uiSetData1<1000)   
  128.                            {
  129.                               ucDigShow4=10;  //如果小于1000,千位显示无
  130.                            }
  131.                else
  132.                            {
  133.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  134.                            }
  135.                if(uiSetData1<100)
  136.                            {
  137.                   ucDigShow3=10;  //如果小于100,百位显示无
  138.                            }
  139.                            else
  140.                            {
  141.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  142.                            }
  143.                if(uiSetData1<10)
  144.                            {
  145.                   ucDigShow2=10;  //如果小于10,十位显示无
  146.                            }
  147.                            else
  148.                            {
  149.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  150.                }
  151.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  152.             }
  153.             break;
  154.         case 2:  //显示P--2窗口的数据
  155.             if(ucWd2Update==1)  //窗口2要全部更新显示
  156.    {
  157.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  158.                ucDigShow8=12;  //第8位数码管显示P
  159.                ucDigShow7=11;  //第7位数码管显示-
  160.                ucDigShow6=2;  //第6位数码管显示2
  161.                ucDigShow5=10;   //第5位数码管显示无
  162.                        ucTemp4=uiSetData2/1000;     //分解数据
  163.                        ucTemp3=uiSetData2%1000/100;
  164.                        ucTemp2=uiSetData2%100/10;
  165.                        ucTemp1=uiSetData2%10;

  166.                if(uiSetData2<1000)   
  167.                            {
  168.                               ucDigShow4=10;  //如果小于1000,千位显示无
  169.                            }
  170.                else
  171.                            {
  172.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  173.                            }
  174.                if(uiSetData2<100)
  175.                            {
  176.                   ucDigShow3=10;  //如果小于100,百位显示无
  177.                            }
  178.                            else
  179.                            {
  180.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  181.                            }
  182.                if(uiSetData2<10)
  183.                            {
  184.                   ucDigShow2=10;  //如果小于10,十位显示无
  185.                            }
  186.                            else
  187.                            {
  188.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  189.                }
  190.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  191.     }
  192.              break;
  193.         case 3:  //显示P--3窗口的数据
  194.             if(ucWd3Update==1)  //窗口3要全部更新显示
  195.    {
  196.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  197.                ucDigShow8=12;  //第8位数码管显示P
  198.                ucDigShow7=11;  //第7位数码管显示-
  199.                ucDigShow6=3;  //第6位数码管显示3
  200.                ucDigShow5=10;   //第5位数码管显示无
  201.                        ucTemp4=uiSetData3/1000;     //分解数据
  202.                        ucTemp3=uiSetData3%1000/100;
  203.                        ucTemp2=uiSetData3%100/10;
  204.                        ucTemp1=uiSetData3%10;
  205.                if(uiSetData3<1000)   
  206.                            {
  207.                               ucDigShow4=10;  //如果小于1000,千位显示无
  208.                            }
  209.                else
  210.                            {
  211.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  212.                            }
  213.                if(uiSetData3<100)
  214.                            {
  215.                   ucDigShow3=10;  //如果小于100,百位显示无
  216.                            }
  217.                            else
  218.                            {
  219.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  220.                            }
  221.                if(uiSetData3<10)
  222.                            {
  223.                   ucDigShow2=10;  //如果小于10,十位显示无
  224.                            }
  225.                            else
  226.                            {
  227.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  228.                }
  229.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  230.    }
  231.             break;
  232.         case 4:  //显示P--4窗口的数据
  233.             if(ucWd4Update==1)  //窗口4要全部更新显示
  234.    {
  235.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  236.                ucDigShow8=12;  //第8位数码管显示P
  237.                ucDigShow7=11;  //第7位数码管显示-
  238.                ucDigShow6=4;  //第6位数码管显示4
  239.                ucDigShow5=10;   //第5位数码管显示无
  240.                        ucTemp4=uiSetData4/1000;     //分解数据
  241.                        ucTemp3=uiSetData4%1000/100;
  242.                        ucTemp2=uiSetData4%100/10;
  243.                        ucTemp1=uiSetData4%10;

  244.                if(uiSetData4<1000)   
  245.                            {
  246.                               ucDigShow4=10;  //如果小于1000,千位显示无
  247.                            }
  248.                else
  249.                            {
  250.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  251.                            }
  252.                if(uiSetData4<100)
  253.                            {
  254.                   ucDigShow3=10;  //如果小于100,百位显示无
  255.                            }
  256.                            else
  257.                            {
  258.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  259.                            }
  260.                if(uiSetData4<10)
  261.                            {
  262.                   ucDigShow2=10;  //如果小于10,十位显示无
  263.                            }
  264.                            else
  265.                            {
  266.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  267.                }
  268.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  269.     }
  270.              break;
  271.            }
  272.    

  273. }

  274. void key_scan()//按键扫描函数 放在定时中断里
  275. {  
  276.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  277.   {
  278.      ucKeyLock1=0; //按键自锁标志清零
  279.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  280.   }
  281.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  282.   {
  283.      uiKeyTimeCnt1++; //累加定时中断次数
  284.      if(uiKeyTimeCnt1>const_key_time1)
  285.      {
  286.         uiKeyTimeCnt1=0;
  287.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  288.         ucKeySec=1;    //触发1号键
  289.      }
  290.   }
  291.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  292.   {
  293.      ucKeyLock2=0; //按键自锁标志清零
  294.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  295.   }
  296.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  297.   {
  298.      uiKeyTimeCnt2++; //累加定时中断次数
  299.      if(uiKeyTimeCnt2>const_key_time2)
  300.      {
  301.         uiKeyTimeCnt2=0;
  302.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  303.         ucKeySec=2;    //触发2号键
  304.      }
  305.   }
  306.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  307.   {
  308.      ucKeyLock3=0; //按键自锁标志清零
  309.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  310.   }
  311.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  312.   {
  313.      uiKeyTimeCnt3++; //累加定时中断次数
  314.      if(uiKeyTimeCnt3>const_key_time3)
  315.      {
  316.         uiKeyTimeCnt3=0;
  317.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  318.         ucKeySec=3;    //触发3号键
  319.      }
  320.   }


  321. }

  322. void key_service() //按键服务的应用程序
  323. {
  324.   switch(ucKeySec) //按键服务状态切换
  325.   {
  326.     case 1:// 加按键 对应朱兆祺学习板的S1键
  327.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  328.                   {
  329.                      case 1:
  330.                   uiSetData1++;   
  331.                                   if(uiSetData1>9999) //最大值是9999
  332.                                   {
  333.                                      uiSetData1=9999;
  334.                                   }
  335.                            ucWd1Update=1;  //窗口1更新显示
  336.                               break;
  337.                      case 2:
  338.                   uiSetData2++;
  339.                                   if(uiSetData2>9999) //最大值是9999
  340.                                   {
  341.                                      uiSetData2=9999;
  342.                                   }
  343.                            ucWd2Update=1;  //窗口2更新显示
  344.                               break;
  345.                      case 3:
  346.                   uiSetData3++;
  347.                                   if(uiSetData3>9999) //最大值是9999
  348.                                   {
  349.                                      uiSetData3=9999;
  350.                                   }
  351.                            ucWd3Update=1;  //窗口3更新显示
  352.                               break;
  353.                      case 4:
  354.                   uiSetData4++;
  355.                                   if(uiSetData4>9999) //最大值是9999
  356.                                   {
  357.                                      uiSetData4=9999;
  358.                                   }
  359.                            ucWd4Update=1;  //窗口4更新显示
  360.                               break;
  361.                   }
  362.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  363.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  364.           break;   
  365.    
  366.     case 2:// 减按键 对应朱兆祺学习板的S5键
  367.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  368.                   {
  369.                      case 1:
  370.                   uiSetData1--;   

  371.                                   if(uiSetData1>9999)  
  372.                                   {
  373.                                      uiSetData1=0;  //最小值是0
  374.                                   }
  375.                            ucWd1Update=1;  //窗口1更新显示
  376.                               break;
  377.                      case 2:
  378.                   uiSetData2--;
  379.                                   if(uiSetData2>9999)
  380.                                   {
  381.                                      uiSetData2=0;  //最小值是0
  382.                                   }
  383.                            ucWd2Update=1;  //窗口2更新显示
  384.                               break;
  385.                      case 3:
  386.                   uiSetData3--;
  387.                                   if(uiSetData3>9999)
  388.                                   {
  389.                                      uiSetData3=0;  //最小值是0
  390.                                   }
  391.                            ucWd3Update=1;  //窗口3更新显示
  392.                               break;
  393.                      case 4:
  394.                   uiSetData4--;
  395.                                   if(uiSetData4>9999)
  396.                                   {
  397.                                      uiSetData4=0;  //最小值是0
  398.                                   }
  399.                            ucWd4Update=1;  //窗口4更新显示
  400.                               break;
  401.                   }

  402.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  403.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  404.           break;  
  405.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  406.           ucWd++;  //切换窗口
  407.                   if(ucWd>4)
  408.                   {
  409.                     ucWd=1;
  410.                   }
  411.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  412.                   {
  413.                      case 1:
  414.                            ucWd1Update=1;  //窗口1更新显示
  415.                               break;
  416.                      case 2:
  417.                            ucWd2Update=1;  //窗口2更新显示
  418.                               break;
  419.                      case 3:
  420.                            ucWd3Update=1;  //窗口3更新显示
  421.                               break;
  422.                      case 4:
  423.                            ucWd4Update=1;  //窗口4更新显示
  424.                               break;
  425.                   }
  426.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  427.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  428.           break;         
  429.          
  430.   }               
  431. }

  432. void display_drive()  
  433. {
  434.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  435.    switch(ucDisplayDriveStep)
  436.    {
  437.       case 1:  //显示第1位
  438.            ucDigShowTemp=dig_table[ucDigShow1];
  439.                    if(ucDigDot1==1)
  440.                    {
  441.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  442.                    }
  443.            dig_hc595_drive(ucDigShowTemp,0xfe);
  444.                break;
  445.       case 2:  //显示第2位
  446.            ucDigShowTemp=dig_table[ucDigShow2];
  447.                    if(ucDigDot2==1)
  448.                    {
  449.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  450.                    }
  451.            dig_hc595_drive(ucDigShowTemp,0xfd);
  452.                break;
  453.       case 3:  //显示第3位
  454.            ucDigShowTemp=dig_table[ucDigShow3];
  455.                    if(ucDigDot3==1)
  456.                    {
  457.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  458.                    }
  459.            dig_hc595_drive(ucDigShowTemp,0xfb);
  460.                break;
  461.       case 4:  //显示第4位
  462.            ucDigShowTemp=dig_table[ucDigShow4];
  463.                    if(ucDigDot4==1)
  464.                    {
  465.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  466.                    }
  467.            dig_hc595_drive(ucDigShowTemp,0xf7);
  468.                break;
  469.       case 5:  //显示第5位
  470.            ucDigShowTemp=dig_table[ucDigShow5];
  471.                    if(ucDigDot5==1)
  472.                    {
  473.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  474.                    }
  475.            dig_hc595_drive(ucDigShowTemp,0xef);
  476.                break;
  477.       case 6:  //显示第6位
  478.            ucDigShowTemp=dig_table[ucDigShow6];
  479.                    if(ucDigDot6==1)
  480.                    {
  481.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  482.                    }
  483.            dig_hc595_drive(ucDigShowTemp,0xdf);
  484.                break;
  485.       case 7:  //显示第7位
  486.            ucDigShowTemp=dig_table[ucDigShow7];
  487.                    if(ucDigDot7==1)
  488.                    {
  489.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  490.            }
  491.            dig_hc595_drive(ucDigShowTemp,0xbf);
  492.                break;
  493.       case 8:  //显示第8位
  494.            ucDigShowTemp=dig_table[ucDigShow8];
  495.                    if(ucDigDot8==1)
  496.                    {
  497.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  498.                    }
  499.            dig_hc595_drive(ucDigShowTemp,0x7f);
  500.                break;
  501.    }
  502.    ucDisplayDriveStep++;
  503.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  504.    {
  505.      ucDisplayDriveStep=1;
  506.    }


  507. }

  508. //数码管的74HC595驱动函数
  509. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  510. {
  511.    unsigned char i;
  512.    unsigned char ucTempData;
  513.    dig_hc595_sh_dr=0;
  514.    dig_hc595_st_dr=0;
  515.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  516.    for(i=0;i<8;i++)
  517.    {
  518.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  519.          else dig_hc595_ds_dr=0;
  520.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  521.          delay_short(1);
  522.          dig_hc595_sh_dr=1;
  523.          delay_short(1);
  524.          ucTempData=ucTempData<<1;
  525.    }
  526.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  527.    for(i=0;i<8;i++)
  528.    {
  529.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  530.          else dig_hc595_ds_dr=0;
  531.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  532.          delay_short(1);
  533.          dig_hc595_sh_dr=1;
  534.          delay_short(1);
  535.          ucTempData=ucTempData<<1;
  536.    }
  537.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  538.    delay_short(1);
  539.    dig_hc595_st_dr=1;
  540.    delay_short(1);
  541.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  542.    dig_hc595_st_dr=0;
  543.    dig_hc595_ds_dr=0;
  544. }

  545. //LED灯的74HC595驱动函数
  546. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  547. {
  548.    unsigned char i;
  549.    unsigned char ucTempData;
  550.    hc595_sh_dr=0;
  551.    hc595_st_dr=0;
  552.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  553.    for(i=0;i<8;i++)
  554.    {
  555.          if(ucTempData>=0x80)hc595_ds_dr=1;
  556.          else hc595_ds_dr=0;
  557.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  558.          delay_short(1);
  559.          hc595_sh_dr=1;
  560.          delay_short(1);
  561.          ucTempData=ucTempData<<1;
  562.    }
  563.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  564.    for(i=0;i<8;i++)
  565.    {
  566.          if(ucTempData>=0x80)hc595_ds_dr=1;
  567.          else hc595_ds_dr=0;
  568.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  569.          delay_short(1);
  570.          hc595_sh_dr=1;
  571.          delay_short(1);
  572.          ucTempData=ucTempData<<1;
  573.    }
  574.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  575.    delay_short(1);
  576.    hc595_st_dr=1;
  577.    delay_short(1);
  578.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  579.    hc595_st_dr=0;
  580.    hc595_ds_dr=0;
  581. }

  582. void T0_time() interrupt 1
  583. {
  584.   TF0=0;  //清除中断标志
  585.   TR0=0; //关中断
  586.   key_scan(); //按键扫描函数
  587.   if(uiVoiceCnt!=0)
  588.   {
  589.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  590.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  591. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  592.   }
  593.   else
  594.   {
  595.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  596.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  597. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  598.   }
  599.   display_drive();  //数码管字模的驱动函数

  600.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  601.   TL0=0x0b;
  602.   TR0=1;  //开中断
  603. }

  604. void delay_short(unsigned int uiDelayShort)
  605. {
  606.    unsigned int i;  
  607.    for(i=0;i<uiDelayShort;i++)
  608.    {
  609.      ;   //一个分号相当于执行一条空语句
  610.    }
  611. }

  612. void delay_long(unsigned int uiDelayLong)
  613. {
  614.    unsigned int i;
  615.    unsigned int j;
  616.    for(i=0;i<uiDelayLong;i++)
  617.    {
  618.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  619.           {
  620.              ; //一个分号相当于执行一条空语句
  621.           }
  622.    }
  623. }

  624. void initial_myself()  //第一区 初始化单片机
  625. {
  626. /* 注释三:
  627. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  628. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  629. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  630. */
  631.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  632.   led_dr=0;  //关闭独立LED灯
  633.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  634.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  635.   TMOD=0x01;  //设置定时器0为工作方式1
  636.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  637.   TL0=0x0b;
  638. }
  639. void initial_peripheral() //第二区 初始化外围
  640. {

  641.    ucDigDot8=0;   //小数点全部不显示
  642.    ucDigDot7=0;  
  643.    ucDigDot6=0;
  644.    ucDigDot5=0;  
  645.    ucDigDot4=0;
  646.    ucDigDot3=0;  
  647.    ucDigDot2=0;
  648.    ucDigDot1=0;
  649.    EA=1;     //开总中断
  650.    ET0=1;    //允许定时中断
  651.    TR0=1;    //启动定时中断
  652. }

复制代码

总结陈词:
数码管通过切换窗口来设置参数,这里的窗口类似于一级菜单,在一级菜单下,还可以分解出二级菜单。一级菜单的特点是整屏数码管的显示内容全部都改变,而二级菜单的特点是只改变其中一部分数码管的内容。二级菜单的程序怎么编写?欲知详情,请听下回分解-----数码管通过闪烁来设置数据。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

31
 
第三十节:数码管通过闪烁来设置数据。

开场白:
   上一节讲了一级菜单,这一节要教会大家两个知识点:
第一个:二级菜单的程序的程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标闪烁”按键对应S9键

(2)实现功能:
     通过按键设置4个不同的参数。
    只有1个窗口。这个窗口显示4个参数。
第8,7位数码管显示第1个参数。第6,5位数码管显示第2个参数。第4,3位数码管显示第3个参数。第2,1位数码管显示第4个参数。每个参数的范围是从0到99。
有三个按键。一个是“光标闪烁”按键,依次按下此按键,每两位数码管会依次处于闪烁的状态,哪两位数码管处于闪烁状态时,此时按加键或者减键就可以设置当前选中的参数。依次按下“光标闪烁”按键,数码管会在以下5种状态中循环:只有第8,7位数码管闪烁---只有第6,5位数码管闪烁---只有第4,3位数码管闪烁---只有第2,1位数码管闪烁---所有的数码管都不闪烁。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间

  6. #define const_dpy_time_half  200  //数码管闪烁时间的半值
  7. #define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

  8. void initial_myself();   
  9. void initial_peripheral();
  10. void delay_short(unsigned int uiDelayShort);
  11. void delay_long(unsigned int uiDelaylong);

  12. //驱动数码管的74HC595
  13. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  14. void display_drive(); //显示数码管字模的驱动函数

  15. void display_service(); //显示的窗口菜单服务程序

  16. //驱动LED的74HC595
  17. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  18. void T0_time();  //定时中断函数

  19. void key_service(); //按键服务的应用程序
  20. void key_scan();//按键扫描函数 放在定时中断里


  21. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  22. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  23. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键

  24. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  25. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  26. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  27. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  28. sbit dig_hc595_st_dr=P2^1;  
  29. sbit dig_hc595_ds_dr=P2^2;  

  30. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  31. sbit hc595_st_dr=P2^4;  
  32. sbit hc595_ds_dr=P2^5;  

  33. unsigned char ucKeySec=0;   //被触发的按键编号

  34. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  35. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  36. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  37. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  38. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  39. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  40. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


  41. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  42. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  43. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  44. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  45. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  46. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  47. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  48. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  49. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  50. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  51. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  52. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  53. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  54. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  55. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  56. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  57. unsigned char ucDigShowTemp=0; //临时中间变量
  58. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  59. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  60. unsigned char ucWd1Update=1; //窗口1更新显示标志

  61. unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

  62. unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
  63. unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
  64. unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志
  65. unsigned char ucWd1Part4Update=0; //在窗口1中,局部4的更新显示标志

  66. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  67. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  68. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  69. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4


  70. unsigned char ucTemp1=0;  //中间过渡变量
  71. unsigned char ucTemp2=0;  //中间过渡变量
  72. unsigned char ucTemp3=0;  //中间过渡变量
  73. unsigned char ucTemp4=0;  //中间过渡变量
  74. unsigned char ucTemp5=0;  //中间过渡变量
  75. unsigned char ucTemp6=0;  //中间过渡变量
  76. unsigned char ucTemp7=0;  //中间过渡变量
  77. unsigned char ucTemp8=0;  //中间过渡变量

  78. unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

  79. //根据原理图得出的共阴数码管字模表
  80. code unsigned char dig_table[]=
  81. {
  82. 0x3f,  //0       序号0
  83. 0x06,  //1       序号1
  84. 0x5b,  //2       序号2
  85. 0x4f,  //3       序号3
  86. 0x66,  //4       序号4
  87. 0x6d,  //5       序号5
  88. 0x7d,  //6       序号6
  89. 0x07,  //7       序号7
  90. 0x7f,  //8       序号8
  91. 0x6f,  //9       序号9
  92. 0x00,  //无      序号10
  93. 0x40,  //-       序号11
  94. 0x73,  //P       序号12
  95. };

  96. void main()
  97.   {
  98.    initial_myself();  
  99.    delay_long(100);   
  100.    initial_peripheral();
  101.    while(1)  
  102.    {
  103.      key_service(); //按键服务的应用程序
  104.          display_service(); //显示的窗口菜单服务程序
  105.    }

  106. }

  107. /* 注释一:
  108. *鸿哥首次提出的"一二级菜单显示理论":
  109. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  110. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  111. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  112. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  113. */


  114. void display_service() //显示的窗口菜单服务程序
  115. {


  116.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  117.    {
  118.        case 1:   //显示窗口1的数据

  119.                         if(ucWd1Part1Update==1)  //仅仅参数1局部更新
  120.                         {
  121.                            ucWd1Part1Update=0;   //及时清零标志,避免一直进来扫描

  122.                ucTemp8=uiSetData1/10;  //第1个参数
  123.                ucTemp7=uiSetData1%10;
  124.                            if(uiSetData1<10)
  125.                            {
  126.                               ucDigShow8=10;
  127.                            }
  128.                            else
  129.                            {
  130.                               ucDigShow8=ucTemp8;
  131.                            }
  132.                            ucDigShow7=ucTemp7;
  133.                         }

  134.                         if(ucWd1Part2Update==1)  //仅仅参数2局部更新
  135.                         {
  136.                            ucWd1Part2Update=0;  //及时清零标志,避免一直进来扫描

  137.                ucTemp6=uiSetData2/10;  //第2个参数
  138.                ucTemp5=uiSetData2%10;
  139.                            if(uiSetData2<10)
  140.                            {
  141.                               ucDigShow6=10;
  142.                            }
  143.                            else
  144.                            {
  145.                               ucDigShow6=ucTemp6;
  146.                            }
  147.                            ucDigShow5=ucTemp5;

  148.                         }

  149.                         if(ucWd1Part3Update==1)  //仅仅参数3局部更新
  150.                         {
  151.                            ucWd1Part3Update=0;  //及时清零标志,避免一直进来扫描

  152.                ucTemp4=uiSetData3/10;  //第3个参数
  153.                ucTemp3=uiSetData3%10;
  154.                            if(uiSetData3<10)
  155.                            {
  156.                               ucDigShow4=10;
  157.                            }
  158.                            else
  159.                            {
  160.                               ucDigShow4=ucTemp4;
  161.                            }
  162.                            ucDigShow3=ucTemp3;
  163.                         }

  164.                         if(ucWd1Part4Update==1)  //仅仅参数4局部更新
  165.                         {
  166.                            ucWd1Part4Update=0;   //及时清零标志,避免一直进来扫描

  167.                ucTemp2=uiSetData4/10;  //第4个参数
  168.                ucTemp1=uiSetData4%10;

  169.                            if(uiSetData4<10)
  170.                            {
  171.                               ucDigShow2=10;
  172.                            }
  173.                            else
  174.                            {
  175.                               ucDigShow2=ucTemp2;
  176.                            }
  177.                            ucDigShow1=ucTemp1;
  178.                         }

  179. /* 注释二:
  180. * 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
  181. * 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
  182. */

  183.             if(ucWd1Update==1)  //窗口1要全部更新显示
  184.                         {
  185.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  186.                ucTemp8=uiSetData1/10;  //第1个参数
  187.                ucTemp7=uiSetData1%10;

  188.                ucTemp6=uiSetData2/10;  //第2个参数
  189.                ucTemp5=uiSetData2%10;


  190.                ucTemp4=uiSetData3/10;  //第3个参数
  191.                ucTemp3=uiSetData3%10;

  192.                ucTemp2=uiSetData4/10;  //第4个参数
  193.                ucTemp1=uiSetData4%10;


  194.                            if(uiSetData1<10)
  195.                            {
  196.                               ucDigShow8=10;
  197.                            }
  198.                            else
  199.                            {
  200.                               ucDigShow8=ucTemp8;
  201.                            }
  202.                            ucDigShow7=ucTemp7;


  203.                            if(uiSetData2<10)
  204.                            {
  205.                               ucDigShow6=10;
  206.                            }
  207.                            else
  208.                            {
  209.                               ucDigShow6=ucTemp6;
  210.                            }
  211.                            ucDigShow5=ucTemp5;

  212.                            if(uiSetData3<10)
  213.                            {
  214.                               ucDigShow4=10;
  215.                            }
  216.                            else
  217.                            {
  218.                               ucDigShow4=ucTemp4;
  219.                            }
  220.                            ucDigShow3=ucTemp3;

  221.                            if(uiSetData4<10)
  222.                            {
  223.                               ucDigShow2=10;
  224.                            }
  225.                            else
  226.                            {
  227.                               ucDigShow2=ucTemp2;
  228.                            }
  229.                            ucDigShow1=ucTemp1;

  230.             }


  231.                         //数码管闪烁
  232.             switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
  233.                         {
  234.                            case 0:  //4个参数都不闪烁

  235.                                 break;
  236.                            case 1:  //第1个参数闪烁
  237.                                 if(uiDpyTimeCnt==const_dpy_time_half)
  238.                                         {
  239.                                    if(uiSetData1<10)        //数码管显示内容
  240.                                    {
  241.                                       ucDigShow8=10;
  242.                                    }
  243.                                    else
  244.                                    {
  245.                                       ucDigShow8=ucTemp8;
  246.                                    }
  247.                                    ucDigShow7=ucTemp7;
  248.                                         }
  249.                                 else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  250.                                         {
  251.                                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零

  252.                                    ucDigShow8=10;   //数码管显示空,什么都不显示
  253.                                    ucDigShow7=10;

  254.                                         }
  255.                                 break;
  256.                            case 2:   //第2个参数闪烁
  257.                                 if(uiDpyTimeCnt==const_dpy_time_half)
  258.                                         {
  259.                                    if(uiSetData2<10)        //数码管显示内容
  260.                                    {
  261.                                       ucDigShow6=10;
  262.                                    }
  263.                                    else
  264.                                    {
  265.                                       ucDigShow6=ucTemp6;
  266.                                    }
  267.                                    ucDigShow5=ucTemp5;
  268.                                         }
  269.                                 else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
  270.                                         {
  271.                                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零

  272.                                    ucDigShow6=10;   //数码管显示空,什么都不显示
  273.                                    ucDigShow5=10;

  274.                                         }
  275.                                 break;
  276.                            case 3:  //第3个参数闪烁
  277.                                 if(uiDpyTimeCnt==const_dpy_time_half)
  278.                                         {
  279.                                    if(uiSetData3<10)        //数码管显示内容
  280.                                    {
  281.                                       ucDigShow4=10;
  282.                                    }
  283.                                    else
  284.                                    {
  285.                                       ucDigShow4=ucTemp4;
  286.                                    }
  287.                                    ucDigShow3=ucTemp3;
  288.                                         }
  289.                                 else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  290.                                         {
  291.                                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零

  292.                                    ucDigShow4=10;   //数码管显示空,什么都不显示
  293.                                    ucDigShow3=10;

  294.                                         }
  295.                                 break;
  296.                            case 4:  //第4个参数闪烁
  297.                                 if(uiDpyTimeCnt==const_dpy_time_half)
  298.                                         {
  299.                                    if(uiSetData4<10)        //数码管显示内容
  300.                                    {
  301.                                       ucDigShow2=10;
  302.                                    }
  303.                                    else
  304.                                    {
  305.                                       ucDigShow2=ucTemp2;
  306.                                    }
  307.                                    ucDigShow1=ucTemp1;
  308.                                         }
  309.                                 else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  310.                                         {
  311.                                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零

  312.                                    ucDigShow2=10;   //数码管显示空,什么都不显示
  313.                                    ucDigShow1=10;

  314.                                         }
  315.                                 break;
  316.                         }

  317.             break;
  318.    
  319.      }
  320.    


  321. }


  322. void key_scan()//按键扫描函数 放在定时中断里
  323. {  

  324.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  325.   {
  326.      ucKeyLock1=0; //按键自锁标志清零
  327.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  328.   }
  329.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  330.   {
  331.      uiKeyTimeCnt1++; //累加定时中断次数
  332.      if(uiKeyTimeCnt1>const_key_time1)
  333.      {
  334.         uiKeyTimeCnt1=0;
  335.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  336.         ucKeySec=1;    //触发1号键
  337.      }
  338.   }

  339.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  340.   {
  341.      ucKeyLock2=0; //按键自锁标志清零
  342.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  343.   }
  344.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  345.   {
  346.      uiKeyTimeCnt2++; //累加定时中断次数
  347.      if(uiKeyTimeCnt2>const_key_time2)
  348.      {
  349.         uiKeyTimeCnt2=0;
  350.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  351.         ucKeySec=2;    //触发2号键
  352.      }
  353.   }

  354.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  355.   {
  356.      ucKeyLock3=0; //按键自锁标志清零
  357.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  358.   }
  359.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  360.   {
  361.      uiKeyTimeCnt3++; //累加定时中断次数
  362.      if(uiKeyTimeCnt3>const_key_time3)
  363.      {
  364.         uiKeyTimeCnt3=0;
  365.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  366.         ucKeySec=3;    //触发3号键
  367.      }
  368.   }



  369. }


  370. void key_service() //按键服务的应用程序
  371. {
  372.   switch(ucKeySec) //按键服务状态切换
  373.   {
  374.     case 1:// 加按键 对应朱兆祺学习板的S1键
  375.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  376.           {
  377.               case 1:
  378.                    switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
  379.                                    {
  380.                                       case 0:
  381.                                                break;
  382.                                       case 1:
  383.                            uiSetData1++;   
  384.                            if(uiSetData1>99) //最大值是99
  385.                            {
  386.                                uiSetData1=99;
  387.                            }
  388.                                                    ucWd1Part1Update=1; //局部更新显示参数1
  389.                                                break;
  390.                                       case 2:
  391.                            uiSetData2++;   
  392.                            if(uiSetData2>99) //最大值是99
  393.                            {
  394.                                uiSetData2=99;
  395.                            }
  396.                                                    ucWd1Part2Update=1; //局部更新显示参数2
  397.                                                break;
  398.                                       case 3:
  399.                            uiSetData3++;   
  400.                            if(uiSetData3>99) //最大值是99
  401.                            {
  402.                                uiSetData3=99;
  403.                            }
  404.                                                    ucWd1Part3Update=1; //局部更新显示参数3
  405.                                                break;
  406.                                       case 4:
  407.                            uiSetData4++;   
  408.                            if(uiSetData4>99) //最大值是99
  409.                            {
  410.                                uiSetData4=99;
  411.                            }
  412.                                                    ucWd1Part4Update=1; //局部更新显示参数4
  413.                                                break;
  414.                                    }
  415.                    break;
  416.           }     
  417.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  418.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  419.           break;   
  420.    
  421.     case 2:// 减按键 对应朱兆祺学习板的S5键
  422.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  423.           {
  424.               case 1:
  425.                    switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
  426.                                    {
  427.                                       case 0:
  428.                                                break;
  429.                                       case 1:
  430.                            uiSetData1--;   
  431.                            if(uiSetData1>99) //0减去1溢出肯定大于99
  432.                            {
  433.                                uiSetData1=0;
  434.                            }
  435.                                                    ucWd1Part1Update=1; //局部更新显示参数1
  436.                                                break;
  437.                                       case 2:
  438.                            uiSetData2--;   
  439.                            if(uiSetData2>99) //0减去1溢出肯定大于99
  440.                            {
  441.                                uiSetData2=0;
  442.                            }
  443.                                                    ucWd1Part2Update=1; //局部更新显示参数2
  444.                                                break;
  445.                                       case 3:
  446.                            uiSetData3--;   
  447.                            if(uiSetData3>99) //0减去1溢出肯定大于99
  448.                            {
  449.                                uiSetData3=0;
  450.                            }
  451.                                                    ucWd1Part3Update=1; //局部更新显示参数3
  452.                                                break;
  453.                                       case 4:
  454.                            uiSetData4--;  
  455.                            if(uiSetData4>99) //0减去1溢出肯定大于99
  456.                            {
  457.                                uiSetData4=0;
  458.                            }
  459.                                                    ucWd1Part4Update=1; //局部更新显示参数4
  460.                                                break;
  461.                                    }
  462.                    break;
  463.           }  
  464.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  465.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  466.           break;  

  467.     case 3:// 切换"光标闪烁"按键 对应朱兆祺学习板的S9键
  468.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  469.           {
  470.               case 1:  //在窗口1下,切换"光标闪烁"
  471.                    ucPart++;
  472.                                    if(ucPart>4)
  473.                                    {
  474.                                      ucPart=0;
  475.                                    }
  476.                                    ucWd1Update=1;  //窗口1全部更新显示
  477.                    break;
  478.           }  
  479.         
  480.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  481.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  482.           break;         
  483.          

  484.   }               
  485. }


  486. void display_drive()  
  487. {
  488.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  489.    switch(ucDisplayDriveStep)
  490.    {
  491.       case 1:  //显示第1位
  492.            ucDigShowTemp=dig_table[ucDigShow1];
  493.                    if(ucDigDot1==1)
  494.                    {
  495.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  496.                    }
  497.            dig_hc595_drive(ucDigShowTemp,0xfe);
  498.                break;
  499.       case 2:  //显示第2位
  500.            ucDigShowTemp=dig_table[ucDigShow2];
  501.                    if(ucDigDot2==1)
  502.                    {
  503.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  504.                    }
  505.            dig_hc595_drive(ucDigShowTemp,0xfd);
  506.                break;
  507.       case 3:  //显示第3位
  508.            ucDigShowTemp=dig_table[ucDigShow3];
  509.                    if(ucDigDot3==1)
  510.                    {
  511.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  512.                    }
  513.            dig_hc595_drive(ucDigShowTemp,0xfb);
  514.                break;
  515.       case 4:  //显示第4位
  516.            ucDigShowTemp=dig_table[ucDigShow4];
  517.                    if(ucDigDot4==1)
  518.                    {
  519.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  520.                    }
  521.            dig_hc595_drive(ucDigShowTemp,0xf7);
  522.                break;
  523.       case 5:  //显示第5位
  524.            ucDigShowTemp=dig_table[ucDigShow5];
  525.                    if(ucDigDot5==1)
  526.                    {
  527.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  528.                    }
  529.            dig_hc595_drive(ucDigShowTemp,0xef);
  530.                break;
  531.       case 6:  //显示第6位
  532.            ucDigShowTemp=dig_table[ucDigShow6];
  533.                    if(ucDigDot6==1)
  534.                    {
  535.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  536.                    }
  537.            dig_hc595_drive(ucDigShowTemp,0xdf);
  538.                break;
  539.       case 7:  //显示第7位
  540.            ucDigShowTemp=dig_table[ucDigShow7];
  541.                    if(ucDigDot7==1)
  542.                    {
  543.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  544.            }
  545.            dig_hc595_drive(ucDigShowTemp,0xbf);
  546.                break;
  547.       case 8:  //显示第8位
  548.            ucDigShowTemp=dig_table[ucDigShow8];
  549.                    if(ucDigDot8==1)
  550.                    {
  551.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  552.                    }
  553.            dig_hc595_drive(ucDigShowTemp,0x7f);
  554.                break;
  555.    }

  556.    ucDisplayDriveStep++;
  557.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  558.    {
  559.      ucDisplayDriveStep=1;
  560.    }



  561. }


  562. //数码管的74HC595驱动函数
  563. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  564. {
  565.    unsigned char i;
  566.    unsigned char ucTempData;
  567.    dig_hc595_sh_dr=0;
  568.    dig_hc595_st_dr=0;

  569.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  570.    for(i=0;i<8;i++)
  571.    {
  572.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  573.          else dig_hc595_ds_dr=0;

  574.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  575.          delay_short(1);
  576.          dig_hc595_sh_dr=1;
  577.          delay_short(1);

  578.          ucTempData=ucTempData<<1;
  579.    }

  580.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  581.    for(i=0;i<8;i++)
  582.    {
  583.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  584.          else dig_hc595_ds_dr=0;

  585.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  586.          delay_short(1);
  587.          dig_hc595_sh_dr=1;
  588.          delay_short(1);

  589.          ucTempData=ucTempData<<1;
  590.    }

  591.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  592.    delay_short(1);
  593.    dig_hc595_st_dr=1;
  594.    delay_short(1);

  595.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  596.    dig_hc595_st_dr=0;
  597.    dig_hc595_ds_dr=0;

  598. }


  599. //LED灯的74HC595驱动函数
  600. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  601. {
  602.    unsigned char i;
  603.    unsigned char ucTempData;
  604.    hc595_sh_dr=0;
  605.    hc595_st_dr=0;

  606.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  607.    for(i=0;i<8;i++)
  608.    {
  609.          if(ucTempData>=0x80)hc595_ds_dr=1;
  610.          else hc595_ds_dr=0;

  611.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  612.          delay_short(1);
  613.          hc595_sh_dr=1;
  614.          delay_short(1);

  615.          ucTempData=ucTempData<<1;
  616.    }

  617.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  618.    for(i=0;i<8;i++)
  619.    {
  620.          if(ucTempData>=0x80)hc595_ds_dr=1;
  621.          else hc595_ds_dr=0;

  622.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  623.          delay_short(1);
  624.          hc595_sh_dr=1;
  625.          delay_short(1);

  626.          ucTempData=ucTempData<<1;
  627.    }

  628.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  629.    delay_short(1);
  630.    hc595_st_dr=1;
  631.    delay_short(1);

  632.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  633.    hc595_st_dr=0;
  634.    hc595_ds_dr=0;

  635. }


  636. void T0_time() interrupt 1
  637. {
  638.   TF0=0;  //清除中断标志
  639.   TR0=0; //关中断

  640.   key_scan(); //按键扫描函数

  641.   uiDpyTimeCnt++;  //数码管的闪烁计时器

  642.   if(uiVoiceCnt!=0)
  643.   {
  644.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  645.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  646. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  647.   }
  648.   else
  649.   {
  650.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  651.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  652. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  653.   }

  654.   display_drive();  //数码管字模的驱动函数


  655.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  656.   TL0=0x0b;
  657.   TR0=1;  //开中断
  658. }


  659. void delay_short(unsigned int uiDelayShort)
  660. {
  661.    unsigned int i;  
  662.    for(i=0;i<uiDelayShort;i++)
  663.    {
  664.      ;   //一个分号相当于执行一条空语句
  665.    }
  666. }


  667. void delay_long(unsigned int uiDelayLong)
  668. {
  669.    unsigned int i;
  670.    unsigned int j;
  671.    for(i=0;i<uiDelayLong;i++)
  672.    {
  673.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  674.           {
  675.              ; //一个分号相当于执行一条空语句
  676.           }
  677.    }
  678. }


  679. void initial_myself()  //第一区 初始化单片机
  680. {

  681. /* 注释三:
  682. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  683. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  684. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  685. */
  686.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  687.   led_dr=0;  //关闭独立LED灯
  688.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  689.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  690.   TMOD=0x01;  //设置定时器0为工作方式1

  691.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  692.   TL0=0x0b;

  693. }

  694. void initial_peripheral() //第二区 初始化外围
  695. {


  696.    ucDigDot8=0;   //小数点全部不显示
  697.    ucDigDot7=0;  
  698.    ucDigDot6=0;
  699.    ucDigDot5=0;  
  700.    ucDigDot4=0;
  701.    ucDigDot3=0;  
  702.    ucDigDot2=0;
  703.    ucDigDot1=0;

  704.    EA=1;     //开总中断
  705.    ET0=1;    //允许定时中断
  706.    TR0=1;    //启动定时中断

  707. }

复制代码

总结陈词:
这节讲了数码管通过闪烁来设置数据的基本程序,但是该程序只有一个窗口。实际应用中,有些项目会有几个窗口,而且每个窗口都要设置几个参数,这样的程序该怎么写?欲知详情,请听下回分解-----数码管通过一二级菜单来设置数据的综合程序。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

32
 
第三十一节:数码管通过一二级菜单来设置数据的综合程序。

开场白:
   上一节讲了二级菜单,这一节要教会大家两个知识点:
第一个:数码管通过一二级菜单来设置数据的综合程序框架。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标闪烁”按键对应S9键,切换窗口按键对应S13键。

(2)实现功能:
     通过按键设置4个不同的参数。
    有2个窗口。每个窗口显示2个参数。
   第8,7,6,5位数码管显示”P-1 ”代表第1个窗口,显示”P-2 ”代表第2个窗口。第4,3位数码管显示该窗口下其中一个参数,第2,1位数码管显示该窗口下其中另外一个参数。每个参数的范围是从0到99。
有四个按键。
一个是切换窗口按键,依次按下此按键,会依次切换窗口显示。一个是“光标闪烁”按键,依次按下此按键,每两位数码管会依次处于闪烁的状态,哪两位数码管处于闪烁状态时,此时按加键或者减键就可以设置当前选中的参数。依次按下“光标闪烁”按键,数码管会在以下3种状态中循环:只有第4,3位数码管闪烁---只有第2,1位数码管闪烁---所有的数码管都不闪烁。

(3)源代码讲解如下:

  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. #define const_key_time4  20    //按键去抖动延时的时间

  7. #define const_dpy_time_half  200  //数码管闪烁时间的半值
  8. #define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

  9. void initial_myself();   
  10. void initial_peripheral();
  11. void delay_short(unsigned int uiDelayShort);
  12. void delay_long(unsigned int uiDelaylong);

  13. //驱动数码管的74HC595
  14. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  15. void display_drive(); //显示数码管字模的驱动函数

  16. void display_service(); //显示的窗口菜单服务程序

  17. //驱动LED的74HC595
  18. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  19. void T0_time();  //定时中断函数

  20. void key_service(); //按键服务的应用程序
  21. void key_scan();//按键扫描函数 放在定时中断里


  22. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  23. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  24. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  25. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

  26. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  27. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  28. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  29. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  30. sbit dig_hc595_st_dr=P2^1;  
  31. sbit dig_hc595_ds_dr=P2^2;  

  32. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  33. sbit hc595_st_dr=P2^4;  
  34. sbit hc595_ds_dr=P2^5;  

  35. unsigned char ucKeySec=0;   //被触发的按键编号

  36. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  37. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  38. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  39. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  40. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  41. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  42. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  43. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

  44. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


  45. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  46. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  47. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  48. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  49. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  50. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  51. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  52. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  53. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  54. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  55. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  56. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  57. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  58. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  59. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  60. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  61. unsigned char ucDigShowTemp=0; //临时中间变量
  62. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  63. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  64. unsigned char ucWd1Update=1; //窗口1更新显示标志
  65. unsigned char ucWd2Update=0; //窗口2更新显示标志
  66. unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

  67. unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
  68. unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
  69. unsigned char ucWd2Part1Update=0; //在窗口2中,局部1的更新显示标志
  70. unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志

  71. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  72. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  73. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  74. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4


  75. unsigned char ucTemp1=0;  //中间过渡变量
  76. unsigned char ucTemp2=0;  //中间过渡变量
  77. unsigned char ucTemp3=0;  //中间过渡变量
  78. unsigned char ucTemp4=0;  //中间过渡变量
  79. unsigned char ucTemp5=0;  //中间过渡变量
  80. unsigned char ucTemp6=0;  //中间过渡变量
  81. unsigned char ucTemp7=0;  //中间过渡变量
  82. unsigned char ucTemp8=0;  //中间过渡变量

  83. unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

  84. //根据原理图得出的共阴数码管字模表
  85. code unsigned char dig_table[]=
  86. {
  87. 0x3f,  //0       序号0
  88. 0x06,  //1       序号1
  89. 0x5b,  //2       序号2
  90. 0x4f,  //3       序号3
  91. 0x66,  //4       序号4
  92. 0x6d,  //5       序号5
  93. 0x7d,  //6       序号6
  94. 0x07,  //7       序号7
  95. 0x7f,  //8       序号8
  96. 0x6f,  //9       序号9
  97. 0x00,  //无      序号10
  98. 0x40,  //-       序号11
  99. 0x73,  //P       序号12
  100. };

  101. void main()
  102.   {
  103.    initial_myself();  
  104.    delay_long(100);   
  105.    initial_peripheral();
  106.    while(1)  
  107.    {
  108.      key_service(); //按键服务的应用程序
  109.          display_service(); //显示的窗口菜单服务程序
  110.    }

  111. }

  112. /* 注释一:
  113. *鸿哥首次提出的"一二级菜单显示理论":
  114. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  115. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  116. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  117. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  118. */


  119. void display_service() //显示的窗口菜单服务程序
  120. {


  121.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  122.    {
  123.        case 1:   //显示窗口1的数据

  124.                         if(ucWd1Part1Update==1)  //仅仅参数1局部更新
  125.                         {
  126.                            ucWd1Part1Update=0;   //及时清零标志,避免一直进来扫描

  127.                ucTemp4=uiSetData1/10;  //第1个参数
  128.                ucTemp3=uiSetData1%10;
  129.                            if(uiSetData1<10)
  130.                            {
  131.                               ucDigShow4=10;
  132.                            }
  133.                            else
  134.                            {
  135.                               ucDigShow4=ucTemp4;
  136.                            }
  137.                            ucDigShow3=ucTemp3;
  138.                         }

  139.                         if(ucWd1Part2Update==1)  //仅仅参数2局部更新
  140.                         {
  141.                            ucWd1Part2Update=0;  //及时清零标志,避免一直进来扫描

  142.                ucTemp2=uiSetData2/10;  //第2个参数
  143.                ucTemp1=uiSetData2%10;
  144.                            if(uiSetData2<10)
  145.                            {
  146.                               ucDigShow2=10;
  147.                            }
  148.                            else
  149.                            {
  150.                               ucDigShow2=ucTemp2;
  151.                            }
  152.                            ucDigShow1=ucTemp1;

  153.                         }

  154.         

  155. /* 注释二:
  156. * 必须注意局部更新和全部更新的编写顺序,局部更新应该写在全部更新之前,
  157. * 当局部更新和全部更新同时发生时,这样就能保证到全部更新的优先响应。
  158. */

  159.             if(ucWd1Update==1)  //窗口1要全部更新显示
  160.                         {
  161.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  162.                ucTemp8=12;  //显示P
  163.                ucTemp7=11;  //显示-
  164.                ucTemp6=1;  //显示1
  165.                ucTemp5=10;  //显示空

  166.                ucTemp4=uiSetData1/10;  //第1个参数
  167.                ucTemp3=uiSetData1%10;

  168.                ucTemp2=uiSetData2/10;  //第2个参数
  169.                ucTemp1=uiSetData2%10;


  170.                ucDigShow8=ucTemp8;  
  171.                ucDigShow7=ucTemp7;  
  172.                ucDigShow6=ucTemp6;  
  173.                ucDigShow5=ucTemp5;

  174.                            if(uiSetData1<10)
  175.                            {
  176.                               ucDigShow4=10;
  177.                            }
  178.                            else
  179.                            {
  180.                               ucDigShow4=ucTemp4;
  181.                            }
  182.                            ucDigShow3=ucTemp3;


  183.                            if(uiSetData2<10)
  184.                            {
  185.                               ucDigShow2=10;
  186.                            }
  187.                            else
  188.                            {
  189.                               ucDigShow2=ucTemp2;
  190.                            }
  191.                            ucDigShow1=ucTemp1;

  192.                         

  193.             }


  194.                         //数码管闪烁
  195.             switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
  196.                         {
  197.                            case 0:  //2个参数都不闪烁

  198.                                 break;
  199.                            case 1:  //第1个参数闪烁
  200.                                 if(uiDpyTimeCnt==const_dpy_time_half)
  201.                                         {
  202.                                    if(uiSetData1<10)        //数码管显示内容
  203.                                    {
  204.                                       ucDigShow4=10;
  205.                                    }
  206.                                    else
  207.                                    {
  208.                                       ucDigShow4=ucTemp4;
  209.                                    }
  210.                                    ucDigShow3=ucTemp3;
  211.                                         }
  212.                                 else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  213.                                         {
  214.                                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零

  215.                                    ucDigShow4=10;   //数码管显示空,什么都不显示
  216.                                    ucDigShow3=10;

  217.                                         }
  218.                                 break;
  219.                            case 2:   //第2个参数闪烁
  220.                                 if(uiDpyTimeCnt==const_dpy_time_half)
  221.                                         {
  222.                                    if(uiSetData2<10)        //数码管显示内容
  223.                                    {
  224.                                       ucDigShow2=10;
  225.                                    }
  226.                                    else
  227.                                    {
  228.                                       ucDigShow2=ucTemp2;
  229.                                    }
  230.                                    ucDigShow1=ucTemp1;
  231.                                         }
  232.                                 else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
  233.                                         {
  234.                                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零

  235.                                    ucDigShow2=10;   //数码管显示空,什么都不显示
  236.                                    ucDigShow1=10;

  237.                                         }
  238.                                 break;
  239.         
  240.                         }

  241.             break;
  242.        case 2:   //显示窗口2的数据

  243.                         if(ucWd2Part1Update==1)  //在窗口2中,仅仅参数1局部更新
  244.                         {
  245.                            ucWd2Part1Update=0;   //及时清零标志,避免一直进来扫描

  246.                ucTemp4=uiSetData3/10;  //第3个参数
  247.                ucTemp3=uiSetData3%10;
  248.                            if(uiSetData3<10)
  249.                            {
  250.                               ucDigShow4=10;
  251.                            }
  252.                            else
  253.                            {
  254.                               ucDigShow4=ucTemp4;
  255.                            }
  256.                            ucDigShow3=ucTemp3;
  257.                         }

  258.                         if(ucWd2Part2Update==1)  //在窗口2中,仅仅参数2局部更新
  259.                         {
  260.                            ucWd2Part2Update=0;  //及时清零标志,避免一直进来扫描

  261.                ucTemp2=uiSetData4/10;  //第4个参数
  262.                ucTemp1=uiSetData4%10;
  263.                            if(uiSetData4<10)
  264.                            {
  265.                               ucDigShow2=10;
  266.                            }
  267.                            else
  268.                            {
  269.                               ucDigShow2=ucTemp2;
  270.                            }
  271.                            ucDigShow1=ucTemp1;

  272.                         }

  273.             if(ucWd2Update==1)  //窗口2要全部更新显示
  274.                         {
  275.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描

  276.                ucTemp8=12;  //显示P
  277.                ucTemp7=11;  //显示-
  278.                ucTemp6=2;  //显示2
  279.                ucTemp5=10;  //显示空

  280.                ucTemp4=uiSetData3/10;  //第3个参数
  281.                ucTemp3=uiSetData3%10;

  282.                ucTemp2=uiSetData4/10;  //第4个参数
  283.                ucTemp1=uiSetData4%10;


  284.                ucDigShow8=ucTemp8;  
  285.                ucDigShow7=ucTemp7;  
  286.                ucDigShow6=ucTemp6;  
  287.                ucDigShow5=ucTemp5;

  288.                            if(uiSetData3<10)
  289.                            {
  290.                               ucDigShow4=10;
  291.                            }
  292.                            else
  293.                            {
  294.                               ucDigShow4=ucTemp4;
  295.                            }
  296.                            ucDigShow3=ucTemp3;


  297.                            if(uiSetData4<10)
  298.                            {
  299.                               ucDigShow2=10;
  300.                            }
  301.                            else
  302.                            {
  303.                               ucDigShow2=ucTemp2;
  304.                            }
  305.                            ucDigShow1=ucTemp1;

  306.                         

  307.             }


  308.                         //数码管闪烁
  309.             switch(ucPart)  //根据局部变量的值,使对应的参数产生闪烁的动态效果。
  310.                         {
  311.                            case 0:  //2个参数都不闪烁

  312.                                 break;
  313.                            case 1:  //第3个参数闪烁
  314.                                 if(uiDpyTimeCnt==const_dpy_time_half)
  315.                                         {
  316.                                    if(uiSetData3<10)        //数码管显示内容
  317.                                    {
  318.                                       ucDigShow4=10;
  319.                                    }
  320.                                    else
  321.                                    {
  322.                                       ucDigShow4=ucTemp4;
  323.                                    }
  324.                                    ucDigShow3=ucTemp3;
  325.                                         }
  326.                                 else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  327.                                         {
  328.                                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零

  329.                                    ucDigShow4=10;   //数码管显示空,什么都不显示
  330.                                    ucDigShow3=10;

  331.                                         }
  332.                                 break;
  333.                            case 2:   //第4个参数闪烁
  334.                                 if(uiDpyTimeCnt==const_dpy_time_half)
  335.                                         {
  336.                                    if(uiSetData4<10)        //数码管显示内容
  337.                                    {
  338.                                       ucDigShow2=10;
  339.                                    }
  340.                                    else
  341.                                    {
  342.                                       ucDigShow2=ucTemp2;
  343.                                    }
  344.                                    ucDigShow1=ucTemp1;
  345.                                         }
  346.                                 else if(uiDpyTimeCnt>const_dpy_time_all)  //const_dpy_time_all一定要比const_dpy_time_half 大
  347.                                         {
  348.                                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零

  349.                                    ucDigShow2=10;   //数码管显示空,什么都不显示
  350.                                    ucDigShow1=10;

  351.                                         }
  352.                                 break;
  353.         
  354.                         }

  355.             break;   
  356.      }
  357.    


  358. }


  359. void key_scan()//按键扫描函数 放在定时中断里
  360. {  

  361.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  362.   {
  363.      ucKeyLock1=0; //按键自锁标志清零
  364.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  365.   }
  366.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  367.   {
  368.      uiKeyTimeCnt1++; //累加定时中断次数
  369.      if(uiKeyTimeCnt1>const_key_time1)
  370.      {
  371.         uiKeyTimeCnt1=0;
  372.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  373.         ucKeySec=1;    //触发1号键
  374.      }
  375.   }

  376.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  377.   {
  378.      ucKeyLock2=0; //按键自锁标志清零
  379.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  380.   }
  381.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  382.   {
  383.      uiKeyTimeCnt2++; //累加定时中断次数
  384.      if(uiKeyTimeCnt2>const_key_time2)
  385.      {
  386.         uiKeyTimeCnt2=0;
  387.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  388.         ucKeySec=2;    //触发2号键
  389.      }
  390.   }

  391.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  392.   {
  393.      ucKeyLock3=0; //按键自锁标志清零
  394.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  395.   }
  396.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  397.   {
  398.      uiKeyTimeCnt3++; //累加定时中断次数
  399.      if(uiKeyTimeCnt3>const_key_time3)
  400.      {
  401.         uiKeyTimeCnt3=0;
  402.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  403.         ucKeySec=3;    //触发3号键
  404.      }
  405.   }

  406.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  407.   {
  408.      ucKeyLock4=0; //按键自锁标志清零
  409.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  410.   }
  411.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  412.   {
  413.      uiKeyTimeCnt4++; //累加定时中断次数
  414.      if(uiKeyTimeCnt4>const_key_time4)
  415.      {
  416.         uiKeyTimeCnt4=0;
  417.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  418.         ucKeySec=4;    //触发4号键
  419.      }
  420.   }

  421. }


  422. void key_service() //按键服务的应用程序
  423. {
  424.   switch(ucKeySec) //按键服务状态切换
  425.   {
  426.     case 1:// 加按键 对应朱兆祺学习板的S1键
  427.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  428.           {
  429.               case 1:
  430.                    switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
  431.                                    {
  432.                                       case 0:
  433.                                                break;
  434.                                       case 1:
  435.                            uiSetData1++;   
  436.                            if(uiSetData1>99) //最大值是99
  437.                            {
  438.                                uiSetData1=99;
  439.                            }
  440.                                                    ucWd1Part1Update=1; //局部更新显示参数1
  441.                                                break;
  442.                                       case 2:
  443.                            uiSetData2++;   
  444.                            if(uiSetData2>99) //最大值是99
  445.                            {
  446.                                uiSetData2=99;
  447.                            }
  448.                                                    ucWd1Part2Update=1; //局部更新显示参数2
  449.                                                break;
  450.                                    }
  451.                    break;
  452.               case 2:
  453.                    switch(ucPart)  //在窗口2下,根据不同的局部闪烁位置来设置不同的参数
  454.                                    {
  455.                                       case 0:
  456.                                                break;
  457.                                       case 1:
  458.                            uiSetData3++;   
  459.                            if(uiSetData3>99) //最大值是99
  460.                            {
  461.                                uiSetData3=99;
  462.                            }
  463.                                                    ucWd2Part1Update=1; //局部更新显示参数1
  464.                                                break;
  465.                                       case 2:
  466.                            uiSetData4++;   
  467.                            if(uiSetData4>99) //最大值是99
  468.                            {
  469.                                uiSetData4=99;
  470.                            }
  471.                                                    ucWd2Part2Update=1; //局部更新显示参数2
  472.                                                break;
  473.                                    }
  474.                    break;
  475.           }     
  476.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  477.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  478.           break;   
  479.    
  480.     case 2:// 减按键 对应朱兆祺学习板的S5键
  481.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  482.           {
  483.               case 1:
  484.                    switch(ucPart)  //在窗口1下,根据不同的局部闪烁位置来设置不同的参数
  485.                                    {
  486.                                       case 0:
  487.                                                break;
  488.                                       case 1:
  489.                            uiSetData1--;   
  490.                            if(uiSetData1>99) //0减去1溢出肯定大于99
  491.                            {
  492.                                uiSetData1=0;
  493.                            }
  494.                                                    ucWd1Part1Update=1; //局部更新显示参数1
  495.                                                break;
  496.                                       case 2:
  497.                            uiSetData2--;   
  498.                            if(uiSetData2>99) //0减去1溢出肯定大于99
  499.                            {
  500.                                uiSetData2=0;
  501.                            }
  502.                                                    ucWd1Part2Update=1; //局部更新显示参数2
  503.                                                break;
  504.                                    }
  505.                    break;
  506.               case 2:
  507.                    switch(ucPart)  //在窗口2下,根据不同的局部闪烁位置来设置不同的参数
  508.                                    {
  509.                                       case 0:
  510.                                                break;
  511.                                       case 1:
  512.                            uiSetData3--;   
  513.                            if(uiSetData3>99) //0减去1溢出肯定大于99
  514.                            {
  515.                                uiSetData3=0;
  516.                            }
  517.                                                    ucWd2Part1Update=1; //局部更新显示参数1
  518.                                                break;
  519.                                       case 2:
  520.                            uiSetData4--;   
  521.                            if(uiSetData4>99) //0减去1溢出肯定大于99
  522.                            {
  523.                                uiSetData4=0;
  524.                            }
  525.                                                    ucWd2Part2Update=1; //局部更新显示参数2
  526.                                                break;
  527.                                    }
  528.                    break;
  529.           }  
  530.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  531.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  532.           break;  

  533.     case 3:// 切换"光标闪烁"按键 对应朱兆祺学习板的S9键
  534.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  535.           {
  536.               case 1:  //在窗口1下,切换"光标闪烁"
  537.                    ucPart++;
  538.                                    if(ucPart>2)
  539.                                    {
  540.                                      ucPart=0;
  541.                                    }
  542.                                    ucWd1Update=1;  //窗口1全部更新显示
  543.                    break;
  544.               case 2:  //在窗口2下,切换"光标闪烁"
  545.                    ucPart++;
  546.                                    if(ucPart>2)
  547.                                    {
  548.                                      ucPart=0;
  549.                                    }
  550.                                    ucWd2Update=1;  //窗口2全部更新显示
  551.                    break;
  552.           }  
  553.         
  554.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  555.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  556.           break;      
  557.    
  558.     case 4:// 切换窗口按键 对应朱兆祺学习板的S13键
  559.               ucWd++;
  560.                   if(ucWd>2)
  561.                   {
  562.                      ucWd=1;
  563.                   }

  564.                   ucPart=0; //强行把局部变量复位,让新切换的窗口不闪烁

  565.           switch(ucWd)  //在不同的窗口下,更新显示不同的窗口
  566.           {
  567.               case 1:  //在窗口1下
  568.                                    ucWd1Update=1;  //窗口1全部更新显示
  569.                    break;
  570.               case 2:  //在窗口2下
  571.                                    ucWd2Update=1;  //窗口2全部更新显示
  572.                    break;
  573.           }  
  574.         
  575.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  576.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  577.           break;         

  578.   }               
  579. }


  580. void display_drive()  
  581. {
  582.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  583.    switch(ucDisplayDriveStep)
  584.    {
  585.       case 1:  //显示第1位
  586.            ucDigShowTemp=dig_table[ucDigShow1];
  587.                    if(ucDigDot1==1)
  588.                    {
  589.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  590.                    }
  591.            dig_hc595_drive(ucDigShowTemp,0xfe);
  592.                break;
  593.       case 2:  //显示第2位
  594.            ucDigShowTemp=dig_table[ucDigShow2];
  595.                    if(ucDigDot2==1)
  596.                    {
  597.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  598.                    }
  599.            dig_hc595_drive(ucDigShowTemp,0xfd);
  600.                break;
  601.       case 3:  //显示第3位
  602.            ucDigShowTemp=dig_table[ucDigShow3];
  603.                    if(ucDigDot3==1)
  604.                    {
  605.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  606.                    }
  607.            dig_hc595_drive(ucDigShowTemp,0xfb);
  608.                break;
  609.       case 4:  //显示第4位
  610.            ucDigShowTemp=dig_table[ucDigShow4];
  611.                    if(ucDigDot4==1)
  612.                    {
  613.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  614.                    }
  615.            dig_hc595_drive(ucDigShowTemp,0xf7);
  616.                break;
  617.       case 5:  //显示第5位
  618.            ucDigShowTemp=dig_table[ucDigShow5];
  619.                    if(ucDigDot5==1)
  620.                    {
  621.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  622.                    }
  623.            dig_hc595_drive(ucDigShowTemp,0xef);
  624.                break;
  625.       case 6:  //显示第6位
  626.            ucDigShowTemp=dig_table[ucDigShow6];
  627.                    if(ucDigDot6==1)
  628.                    {
  629.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  630.                    }
  631.            dig_hc595_drive(ucDigShowTemp,0xdf);
  632.                break;
  633.       case 7:  //显示第7位
  634.            ucDigShowTemp=dig_table[ucDigShow7];
  635.                    if(ucDigDot7==1)
  636.                    {
  637.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  638.            }
  639.            dig_hc595_drive(ucDigShowTemp,0xbf);
  640.                break;
  641.       case 8:  //显示第8位
  642.            ucDigShowTemp=dig_table[ucDigShow8];
  643.                    if(ucDigDot8==1)
  644.                    {
  645.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  646.                    }
  647.            dig_hc595_drive(ucDigShowTemp,0x7f);
  648.                break;
  649.    }

  650.    ucDisplayDriveStep++;
  651.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  652.    {
  653.      ucDisplayDriveStep=1;
  654.    }



  655. }


  656. //数码管的74HC595驱动函数
  657. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  658. {
  659.    unsigned char i;
  660.    unsigned char ucTempData;
  661.    dig_hc595_sh_dr=0;
  662.    dig_hc595_st_dr=0;

  663.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  664.    for(i=0;i<8;i++)
  665.    {
  666.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  667.          else dig_hc595_ds_dr=0;

  668.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  669.          delay_short(1);
  670.          dig_hc595_sh_dr=1;
  671.          delay_short(1);

  672.          ucTempData=ucTempData<<1;
  673.    }

  674.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  675.    for(i=0;i<8;i++)
  676.    {
  677.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  678.          else dig_hc595_ds_dr=0;

  679.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  680.          delay_short(1);
  681.          dig_hc595_sh_dr=1;
  682.          delay_short(1);

  683.          ucTempData=ucTempData<<1;
  684.    }

  685.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  686.    delay_short(1);
  687.    dig_hc595_st_dr=1;
  688.    delay_short(1);

  689.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  690.    dig_hc595_st_dr=0;
  691.    dig_hc595_ds_dr=0;

  692. }


  693. //LED灯的74HC595驱动函数
  694. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  695. {
  696.    unsigned char i;
  697.    unsigned char ucTempData;
  698.    hc595_sh_dr=0;
  699.    hc595_st_dr=0;

  700.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  701.    for(i=0;i<8;i++)
  702.    {
  703.          if(ucTempData>=0x80)hc595_ds_dr=1;
  704.          else hc595_ds_dr=0;

  705.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  706.          delay_short(1);
  707.          hc595_sh_dr=1;
  708.          delay_short(1);

  709.          ucTempData=ucTempData<<1;
  710.    }

  711.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  712.    for(i=0;i<8;i++)
  713.    {
  714.          if(ucTempData>=0x80)hc595_ds_dr=1;
  715.          else hc595_ds_dr=0;

  716.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  717.          delay_short(1);
  718.          hc595_sh_dr=1;
  719.          delay_short(1);

  720.          ucTempData=ucTempData<<1;
  721.    }

  722.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  723.    delay_short(1);
  724.    hc595_st_dr=1;
  725.    delay_short(1);

  726.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  727.    hc595_st_dr=0;
  728.    hc595_ds_dr=0;

  729. }


  730. void T0_time() interrupt 1
  731. {
  732.   TF0=0;  //清除中断标志
  733.   TR0=0; //关中断

  734.   key_scan(); //按键扫描函数

  735.   uiDpyTimeCnt++;  //数码管的闪烁计时器

  736.   if(uiVoiceCnt!=0)
  737.   {
  738.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  739.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  740. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  741.   }
  742.   else
  743.   {
  744.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  745.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  746. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  747.   }

  748.   display_drive();  //数码管字模的驱动函数


  749.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  750.   TL0=0x0b;
  751.   TR0=1;  //开中断
  752. }


  753. void delay_short(unsigned int uiDelayShort)
  754. {
  755.    unsigned int i;  
  756.    for(i=0;i<uiDelayShort;i++)
  757.    {
  758.      ;   //一个分号相当于执行一条空语句
  759.    }
  760. }


  761. void delay_long(unsigned int uiDelayLong)
  762. {
  763.    unsigned int i;
  764.    unsigned int j;
  765.    for(i=0;i<uiDelayLong;i++)
  766.    {
  767.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  768.           {
  769.              ; //一个分号相当于执行一条空语句
  770.           }
  771.    }
  772. }


  773. void initial_myself()  //第一区 初始化单片机
  774. {

  775. /* 注释三:
  776. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  777. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  778. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  779. */
  780.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  781.   led_dr=0;  //关闭独立LED灯
  782.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  783.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  784.   TMOD=0x01;  //设置定时器0为工作方式1

  785.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  786.   TL0=0x0b;

  787. }

  788. void initial_peripheral() //第二区 初始化外围
  789. {


  790.    ucDigDot8=0;   //小数点全部不显示
  791.    ucDigDot7=0;  
  792.    ucDigDot6=0;
  793.    ucDigDot5=0;  
  794.    ucDigDot4=0;
  795.    ucDigDot3=0;  
  796.    ucDigDot2=0;
  797.    ucDigDot1=0;

  798.    EA=1;     //开总中断
  799.    ET0=1;    //允许定时中断
  800.    TR0=1;    //启动定时中断

  801. }

复制代码

总结陈词:
这节讲了数码管通过一二级菜单来设置数据的综合程序,鸿哥的人机界面程序框架基本上都涉及到了,为了继续加深熟悉鸿哥的“一二级菜单显示理论”,下一节会继续讲一个常用的数码管项目小程序,这个项目小程序鸿哥是怎么写的?欲知详情,请听下回分解-----数码管中的倒计时程序。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

33
 
第三十二节:数码管中的倒计时程序。

开场白:
   上一节讲了一二级菜单的综合程序,这一节要教会大家三个知识点:
第一个:通过本程序,继续加深理解按键与数码管的关联方法。
第二个:复习一下我在第五节教给大家的时间校正法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。


具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。启动和暂停键对应S1键,复位键对应S5键。

(2)实现功能:按下启动暂停按键时,倒计时开始工作,再按一次启动暂停按键时,则暂停倒计时。在任何时候,按下复位按键,倒计时将暂停工作,并且恢复倒计时当前默认值99。
     
(3)源代码讲解如下:

  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_voice_long   200    //蜂鸣器长叫的持续时间

  4. #define const_key_time1  20    //按键去抖动延时的时间
  5. #define const_key_time2  20    //按键去抖动延时的时间


  6. #define const_dpy_time_half  200  //数码管闪烁时间的半值
  7. #define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

  8. /* 注释一:
  9. * 如何知道1秒钟需要多少个定时中断?
  10. * 这个需要编写一段小程序测试,得到测试的结果后再按比例修正。
  11. * 步骤:
  12. * 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
  13. * 第二步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表。
  14. *         如果单片机倒计时跑完了99秒,而手机上的秒表才走了45秒。
  15. * 第三步:那么最终得出1秒钟需要的定时中断次数是:const_1s=(200*99)/45=440
  16. */


  17. #define const_1s  440   //大概一秒钟所需要的定时中断次数

  18. void initial_myself();   
  19. void initial_peripheral();
  20. void delay_short(unsigned int uiDelayShort);
  21. void delay_long(unsigned int uiDelaylong);

  22. //驱动数码管的74HC595
  23. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  24. void display_drive(); //显示数码管字模的驱动函数
  25. void display_service(); //显示的窗口菜单服务程序

  26. //驱动LED的74HC595
  27. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  28. void T0_time();  //定时中断函数
  29. void key_service(); //按键服务的应用程序
  30. void key_scan();//按键扫描函数 放在定时中断里


  31. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  32. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键

  33. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  34. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  35. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  36. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  37. sbit dig_hc595_st_dr=P2^1;  
  38. sbit dig_hc595_ds_dr=P2^2;  

  39. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  40. sbit hc595_st_dr=P2^4;  
  41. sbit hc595_ds_dr=P2^5;  

  42. unsigned char ucKeySec=0;   //被触发的按键编号

  43. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  44. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  45. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  46. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志


  47. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


  48. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  49. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  50. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  51. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  52. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  53. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  54. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  55. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  56. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  57. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  58. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  59. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  60. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  61. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  62. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  63. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  64. unsigned char ucDigShowTemp=0; //临时中间变量
  65. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  66. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  67. unsigned char ucWd1Update=1; //窗口1更新显示标志


  68. unsigned char ucCountDown=99;  //倒计时的当前值
  69. unsigned char ucStartFlag=0;  //暂停与启动的标志位
  70. unsigned int  uiTimeCnt=0;  //倒计时的时间计时器

  71. unsigned char ucTemp1=0;  //中间过渡变量
  72. unsigned char ucTemp2=0;  //中间过渡变量
  73. unsigned char ucTemp3=0;  //中间过渡变量
  74. unsigned char ucTemp4=0;  //中间过渡变量
  75. unsigned char ucTemp5=0;  //中间过渡变量
  76. unsigned char ucTemp6=0;  //中间过渡变量
  77. unsigned char ucTemp7=0;  //中间过渡变量
  78. unsigned char ucTemp8=0;  //中间过渡变量


  79. //根据原理图得出的共阴数码管字模表
  80. code unsigned char dig_table[]=
  81. {
  82. 0x3f,  //0       序号0
  83. 0x06,  //1       序号1
  84. 0x5b,  //2       序号2
  85. 0x4f,  //3       序号3
  86. 0x66,  //4       序号4
  87. 0x6d,  //5       序号5
  88. 0x7d,  //6       序号6
  89. 0x07,  //7       序号7
  90. 0x7f,  //8       序号8
  91. 0x6f,  //9       序号9
  92. 0x00,  //无      序号10
  93. 0x40,  //-       序号11
  94. 0x73,  //P       序号12
  95. };

  96. void main()
  97.   {
  98.    initial_myself();  
  99.    delay_long(100);   
  100.    initial_peripheral();
  101.    while(1)  
  102.    {
  103.        key_service(); //按键服务的应用程序
  104.        display_service(); //显示的窗口菜单服务程序
  105.    }

  106. }


  107. /* 注释二:
  108. *鸿哥首次提出的"一二级菜单显示理论":
  109. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  110. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  111. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  112. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  113. */


  114. void display_service() //显示的窗口菜单服务程序
  115. {



  116.   //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
  117.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  118.    {
  119.        case 1:   //显示窗口1的数据
  120.             if(ucWd1Update==1)  //窗口1要全部更新显示
  121.                         {
  122.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  123.                ucTemp8=10;  //显示空
  124.                ucTemp7=10;  //显示空
  125.                ucTemp6=10;  //显示空
  126.                ucTemp5=10;  //显示空
  127.                ucTemp4=10;  //显示空
  128.                ucTemp3=10;  //显示空

  129.                ucTemp2=ucCountDown/10;  //倒计时的当前值
  130.                ucTemp1=ucCountDown%10;


  131.                ucDigShow8=ucTemp8;  
  132.                ucDigShow7=ucTemp7;  
  133.                ucDigShow6=ucTemp6;  
  134.                ucDigShow5=ucTemp5;
  135.                ucDigShow4=ucTemp4;  
  136.                ucDigShow3=ucTemp3;


  137.                            if(ucCountDown<10)
  138.                            {
  139.                               ucDigShow2=10;
  140.                            }
  141.                            else
  142.                            {
  143.                               ucDigShow2=ucTemp2;
  144.                            }
  145.                            ucDigShow1=ucTemp1;

  146.                        

  147.             }
  148.             break;
  149.    
  150.      }
  151.    


  152. }


  153. void key_scan()//按键扫描函数 放在定时中断里
  154. {  

  155.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  156.   {
  157.      ucKeyLock1=0; //按键自锁标志清零
  158.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  159.   }
  160.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  161.   {
  162.      uiKeyTimeCnt1++; //累加定时中断次数
  163.      if(uiKeyTimeCnt1>const_key_time1)
  164.      {
  165.         uiKeyTimeCnt1=0;
  166.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  167.         ucKeySec=1;    //触发1号键
  168.      }
  169.   }

  170.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  171.   {
  172.      ucKeyLock2=0; //按键自锁标志清零
  173.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  174.   }
  175.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  176.   {
  177.      uiKeyTimeCnt2++; //累加定时中断次数
  178.      if(uiKeyTimeCnt2>const_key_time2)
  179.      {
  180.         uiKeyTimeCnt2=0;
  181.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  182.         ucKeySec=2;    //触发2号键
  183.      }
  184.   }

  185. }


  186. void key_service() //按键服务的应用程序
  187. {
  188.   switch(ucKeySec) //按键服务状态切换
  189.   {
  190.     case 1:// 启动和暂停按键 对应朱兆祺学习板的S1键

  191.        
  192.          //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
  193.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  194.           {
  195.               case 1:
  196.                    if(ucStartFlag==0)  //如果原来处于暂停的状态,则启动
  197.                                    {
  198.                       ucStartFlag=1; //启动
  199.                                    }
  200.                                    else     //如果原来处于启动的状态,则暂停
  201.                                    {
  202.                                       ucStartFlag=0;  //暂停
  203.                                    }
  204.                    break;
  205.            
  206.           }     
  207.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  208.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  209.           break;   
  210.    
  211.     case 2:// 复位按键 对应朱兆祺学习板的S5键

  212.          //由于本程序只有一个窗口,读者在做实际项目的时候,可以省略switch(ucWd)
  213.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  214.           {
  215.               case 1:
  216.                                    ucStartFlag=0;  //暂停
  217.                    ucCountDown=99;  //恢复倒计时的默认值99
  218.                    uiTimeCnt=0;  //倒计时的时间计时器清零
  219.                                    ucWd1Update=1; //窗口1更新显示标志  只要ucCountDown变化了,就要更新显示一次
  220.                    break;
  221.          
  222.           }  
  223.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  224.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  225.           break;  

  226.   }               
  227. }


  228. void display_drive()  
  229. {
  230.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  231.    switch(ucDisplayDriveStep)
  232.    {
  233.       case 1:  //显示第1位
  234.            ucDigShowTemp=dig_table[ucDigShow1];
  235.                    if(ucDigDot1==1)
  236.                    {
  237.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  238.                    }
  239.            dig_hc595_drive(ucDigShowTemp,0xfe);
  240.                break;
  241.       case 2:  //显示第2位
  242.            ucDigShowTemp=dig_table[ucDigShow2];
  243.                    if(ucDigDot2==1)
  244.                    {
  245.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  246.                    }
  247.            dig_hc595_drive(ucDigShowTemp,0xfd);
  248.                break;
  249.       case 3:  //显示第3位
  250.            ucDigShowTemp=dig_table[ucDigShow3];
  251.                    if(ucDigDot3==1)
  252.                    {
  253.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  254.                    }
  255.            dig_hc595_drive(ucDigShowTemp,0xfb);
  256.                break;
  257.       case 4:  //显示第4位
  258.            ucDigShowTemp=dig_table[ucDigShow4];
  259.                    if(ucDigDot4==1)
  260.                    {
  261.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  262.                    }
  263.            dig_hc595_drive(ucDigShowTemp,0xf7);
  264.                break;
  265.       case 5:  //显示第5位
  266.            ucDigShowTemp=dig_table[ucDigShow5];
  267.                    if(ucDigDot5==1)
  268.                    {
  269.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  270.                    }
  271.            dig_hc595_drive(ucDigShowTemp,0xef);
  272.                break;
  273.       case 6:  //显示第6位
  274.            ucDigShowTemp=dig_table[ucDigShow6];
  275.                    if(ucDigDot6==1)
  276.                    {
  277.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  278.                    }
  279.            dig_hc595_drive(ucDigShowTemp,0xdf);
  280.                break;
  281.       case 7:  //显示第7位
  282.            ucDigShowTemp=dig_table[ucDigShow7];
  283.                    if(ucDigDot7==1)
  284.                    {
  285.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  286.            }
  287.            dig_hc595_drive(ucDigShowTemp,0xbf);
  288.                break;
  289.       case 8:  //显示第8位
  290.            ucDigShowTemp=dig_table[ucDigShow8];
  291.                    if(ucDigDot8==1)
  292.                    {
  293.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  294.                    }
  295.            dig_hc595_drive(ucDigShowTemp,0x7f);
  296.                break;
  297.    }

  298.    ucDisplayDriveStep++;
  299.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  300.    {
  301.      ucDisplayDriveStep=1;
  302.    }



  303. }


  304. //数码管的74HC595驱动函数
  305. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  306. {
  307.    unsigned char i;
  308.    unsigned char ucTempData;
  309.    dig_hc595_sh_dr=0;
  310.    dig_hc595_st_dr=0;

  311.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  312.    for(i=0;i<8;i++)
  313.    {
  314.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  315.          else dig_hc595_ds_dr=0;

  316.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  317.          delay_short(1);
  318.          dig_hc595_sh_dr=1;
  319.          delay_short(1);

  320.          ucTempData=ucTempData<<1;
  321.    }

  322.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  323.    for(i=0;i<8;i++)
  324.    {
  325.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  326.          else dig_hc595_ds_dr=0;

  327.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  328.          delay_short(1);
  329.          dig_hc595_sh_dr=1;
  330.          delay_short(1);

  331.          ucTempData=ucTempData<<1;
  332.    }

  333.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  334.    delay_short(1);
  335.    dig_hc595_st_dr=1;
  336.    delay_short(1);

  337.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  338.    dig_hc595_st_dr=0;
  339.    dig_hc595_ds_dr=0;

  340. }


  341. //LED灯的74HC595驱动函数
  342. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  343. {
  344.    unsigned char i;
  345.    unsigned char ucTempData;
  346.    hc595_sh_dr=0;
  347.    hc595_st_dr=0;

  348.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  349.    for(i=0;i<8;i++)
  350.    {
  351.          if(ucTempData>=0x80)hc595_ds_dr=1;
  352.          else hc595_ds_dr=0;

  353.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  354.          delay_short(1);
  355.          hc595_sh_dr=1;
  356.          delay_short(1);

  357.          ucTempData=ucTempData<<1;
  358.    }

  359.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  360.    for(i=0;i<8;i++)
  361.    {
  362.          if(ucTempData>=0x80)hc595_ds_dr=1;
  363.          else hc595_ds_dr=0;

  364.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  365.          delay_short(1);
  366.          hc595_sh_dr=1;
  367.          delay_short(1);

  368.          ucTempData=ucTempData<<1;
  369.    }

  370.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  371.    delay_short(1);
  372.    hc595_st_dr=1;
  373.    delay_short(1);

  374.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  375.    hc595_st_dr=0;
  376.    hc595_ds_dr=0;

  377. }


  378. void T0_time() interrupt 1
  379. {
  380.   TF0=0;  //清除中断标志
  381.   TR0=0; //关中断

  382.   key_scan(); //按键扫描函数


  383.   if(ucStartFlag==1)  //启动倒计时的计时器
  384.   {
  385.      uiTimeCnt++;
  386.      if(uiTimeCnt>=const_1s)    //1秒钟的时间到
  387.      {
  388.             if(ucCountDown!=0) //加这个判断,就是避免在0的情况下减1
  389.             {
  390.                ucCountDown--;  //倒计时当前显示值减1
  391.             }

  392.         if(ucCountDown==0)  //倒计时结束
  393.             {
  394.                ucStartFlag=0;  //暂停
  395.            uiVoiceCnt=const_voice_long; //蜂鸣器触发提醒,滴一声就停。
  396.             }

  397.         ucWd1Update=1; //窗口1更新显示标志
  398.         uiTimeCnt=0;   //计时器清零,准备从新开始计时
  399.      }
  400.   }



  401.   if(uiVoiceCnt!=0)
  402.   {
  403.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  404.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  405. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  406.   }
  407.   else
  408.   {
  409.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  410.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  411. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  412.   }

  413.   display_drive();  //数码管字模的驱动函数


  414.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  415.   TL0=0x0b;
  416.   TR0=1;  //开中断
  417. }


  418. void delay_short(unsigned int uiDelayShort)
  419. {
  420.    unsigned int i;  
  421.    for(i=0;i<uiDelayShort;i++)
  422.    {
  423.      ;   //一个分号相当于执行一条空语句
  424.    }
  425. }


  426. void delay_long(unsigned int uiDelayLong)
  427. {
  428.    unsigned int i;
  429.    unsigned int j;
  430.    for(i=0;i<uiDelayLong;i++)
  431.    {
  432.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  433.           {
  434.              ; //一个分号相当于执行一条空语句
  435.           }
  436.    }
  437. }


  438. void initial_myself()  //第一区 初始化单片机
  439. {

  440. /* 注释三:
  441. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  442. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  443. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  444. */
  445.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  446.   led_dr=0;  //关闭独立LED灯
  447.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  448.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  449.   TMOD=0x01;  //设置定时器0为工作方式1

  450.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  451.   TL0=0x0b;

  452. }

  453. void initial_peripheral() //第二区 初始化外围
  454. {


  455.    ucDigDot8=0;   //小数点全部不显示
  456.    ucDigDot7=0;  
  457.    ucDigDot6=0;
  458.    ucDigDot5=0;  
  459.    ucDigDot4=0;
  460.    ucDigDot3=0;  
  461.    ucDigDot2=0;
  462.    ucDigDot1=0;

  463.    EA=1;     //开总中断
  464.    ET0=1;    //允许定时中断
  465.    TR0=1;    //启动定时中断

  466. }
复制代码

总结陈词:
这节讲了数码管中的倒计时程序。如果要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位,该怎么编写这个程序?欲知详情,请听下回分解-----能设置速度档位的数码管倒计时程序。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

34
 
第三十三节:能设置速度档位的数码管倒计时程序。

开场白:
   上一节讲了数码管中的倒计时程序。这节要在此程序上多增加两个按键,用来控制数码管倒计时的速度档位,并且需要在数码管中闪烁显示被设置的速度档位。这一节要教会大家三个知识点:
第一个:把一个按键的短按与长按复合应用在项目中的程序结构。
第二个:通过本程序,继续加深理解按键与数码管的关联方法。
第三个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。启动和暂停键对应S1键,复位键对应S5键。加键对应S9键,减键对应S13键。

(2)实现功能:按下启动暂停按键时,倒计时开始工作,再按一次启动暂停按键时,则暂停倒计时。在任何时候,按下复位按键,倒计时将暂停工作,并且恢复倒计时当前默认值99。如果长按复位按键,在数码管会切换到第2个闪烁窗口,用来设置速度档位,修改完速度档位后,再一次按下复位按键,或者直接按启动暂停按键,会切换回窗口1显示倒计时的当前数据。
     
(3)源代码讲解如下:

  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_voice_long   200    //蜂鸣器长叫的持续时间

  4. #define const_key_time1  20    //按键去抖动延时的时间
  5. #define const_key_time2  20    //按键去抖动延时的时间
  6. #define const_key_time3  20    //按键去抖动延时的时间
  7. #define const_key_time4  20    //按键去抖动延时的时间

  8. #define const_key_long_time 200  //长按复位按键的时间


  9. #define const_dpy_time_half  200  //数码管闪烁时间的半值
  10. #define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大


  11. void initial_myself();   
  12. void initial_peripheral();
  13. void delay_short(unsigned int uiDelayShort);
  14. void delay_long(unsigned int uiDelaylong);

  15. //驱动数码管的74HC595
  16. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  17. void display_drive(); //显示数码管字模的驱动函数

  18. void display_service(); //显示的窗口菜单服务程序

  19. //驱动LED的74HC595
  20. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  21. void T0_time();  //定时中断函数
  22. void key_service(); //按键服务的应用程序
  23. void key_scan();//按键扫描函数 放在定时中断里


  24. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  25. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  26. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  27. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键


  28. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  29. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  30. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  31. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  32. sbit dig_hc595_st_dr=P2^1;  
  33. sbit dig_hc595_ds_dr=P2^2;  

  34. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  35. sbit hc595_st_dr=P2^4;  
  36. sbit hc595_ds_dr=P2^5;  

  37. unsigned char ucKeySec=0;   //被触发的按键编号

  38. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  39. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  40. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  41. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  42. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  43. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  44. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  45. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志




  46. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


  47. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  48. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  49. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  50. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  51. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  52. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  53. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  54. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  55. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  56. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  57. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  58. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  59. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  60. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  61. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  62. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  63. unsigned char ucDigShowTemp=0; //临时中间变量
  64. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  65. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  66. unsigned char ucWd1Update=1; //窗口1更新显示标志
  67. unsigned char ucWd2Update=1; //窗口2更新显示标志

  68. unsigned char ucCountDown=99;  //倒计时的当前值
  69. unsigned char ucStartFlag=0;  //暂停与启动的标志位
  70. unsigned int  uiTimeCnt=0;  //倒计时的时间计时器

  71. unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

  72. unsigned int  uiSetData1=50;  //速度档位
  73. unsigned int  uiSpeedCnt=400;  //影响速度变量,它跟速度档位uiSetData1有关联

  74. unsigned char ucTemp1=0;  //中间过渡变量
  75. unsigned char ucTemp2=0;  //中间过渡变量
  76. unsigned char ucTemp3=0;  //中间过渡变量
  77. unsigned char ucTemp4=0;  //中间过渡变量
  78. unsigned char ucTemp5=0;  //中间过渡变量
  79. unsigned char ucTemp6=0;  //中间过渡变量
  80. unsigned char ucTemp7=0;  //中间过渡变量
  81. unsigned char ucTemp8=0;  //中间过渡变量


  82. //根据原理图得出的共阴数码管字模表
  83. code unsigned char dig_table[]=
  84. {
  85. 0x3f,  //0       序号0
  86. 0x06,  //1       序号1
  87. 0x5b,  //2       序号2
  88. 0x4f,  //3       序号3
  89. 0x66,  //4       序号4
  90. 0x6d,  //5       序号5
  91. 0x7d,  //6       序号6
  92. 0x07,  //7       序号7
  93. 0x7f,  //8       序号8
  94. 0x6f,  //9       序号9
  95. 0x00,  //无      序号10
  96. 0x40,  //-       序号11
  97. 0x73,  //P       序号12
  98. };

  99. void main()
  100.   {
  101.    initial_myself();  
  102.    delay_long(100);   
  103.    initial_peripheral();
  104.    while(1)  
  105.    {
  106.        key_service(); //按键服务的应用程序
  107.        display_service(); //显示的窗口菜单服务程序
  108.    }

  109. }



  110. /* 注释一:
  111. *鸿哥首次提出的"一二级菜单显示理论":
  112. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  113. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  114. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  115. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  116. */


  117. void display_service() //显示的窗口菜单服务程序
  118. {


  119.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  120.    {
  121.        case 1:   //显示窗口1的数据
  122.             if(ucWd1Update==1)  //窗口1要全部更新显示
  123.                         {
  124.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  125.                ucTemp8=10;  //显示空
  126.                ucTemp7=10;  //显示空
  127.                ucTemp6=10;  //显示空
  128.                ucTemp5=10;  //显示空
  129.                ucTemp4=10;  //显示空
  130.                ucTemp3=10;  //显示空

  131.                ucTemp2=ucCountDown/10;  //倒计时的当前值
  132.                ucTemp1=ucCountDown%10;


  133.                ucDigShow8=ucTemp8;  
  134.                ucDigShow7=ucTemp7;  
  135.                ucDigShow6=ucTemp6;  
  136.                ucDigShow5=ucTemp5;
  137.                ucDigShow4=ucTemp4;  
  138.                ucDigShow3=ucTemp3;


  139.                            if(ucCountDown<10)
  140.                            {
  141.                               ucDigShow2=10;
  142.                            }
  143.                            else
  144.                            {
  145.                               ucDigShow2=ucTemp2;
  146.                            }
  147.                            ucDigShow1=ucTemp1;

  148.                        

  149.             }
  150.             break;
  151.        case 2:   //显示窗口2的数据
  152.             if(ucWd2Update==1)  //窗口2要全部更新显示
  153.             {
  154.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描

  155.                ucTemp8=10;  //显示空
  156.                ucTemp7=10;  //显示空
  157.                ucTemp6=10;  //显示空
  158.                ucTemp5=10;  //显示空
  159.                ucTemp4=10;  //显示空
  160.                ucTemp3=10;  //显示空

  161.                ucTemp2=uiSetData1/10;  //倒计时的速度档位
  162.                ucTemp1=uiSetData1%10;


  163.                ucDigShow8=ucTemp8;  
  164.                ucDigShow7=ucTemp7;  
  165.                ucDigShow6=ucTemp6;  
  166.                ucDigShow5=ucTemp5;
  167.                ucDigShow4=ucTemp4;  
  168.                ucDigShow3=ucTemp3;


  169.                            if(uiSetData1<10)
  170.                            {
  171.                               ucDigShow2=10;
  172.                            }
  173.                            else
  174.                            {
  175.                               ucDigShow2=ucTemp2;
  176.                            }
  177.                            ucDigShow1=ucTemp1;

  178.                         

  179.             }

  180.             //数码管闪烁
  181.             if(uiDpyTimeCnt==const_dpy_time_half)
  182.             {
  183.                if(uiSetData1<10)        //数码管显示内容
  184.                {
  185.                     ucDigShow2=10;
  186.                }
  187.                else
  188.                {
  189.                     ucDigShow2=ucTemp2;
  190.                }
  191.                ucDigShow1=ucTemp1;
  192.             }
  193.             else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  194.             {                       
  195.                uiDpyTimeCnt=0;   //及时把闪烁记时器清零                          

  196.                ucDigShow2=10;   //数码管显示空,什么都不显示
  197.                ucDigShow1=10;

  198.             }

  199.             break;

  200.    
  201.      }
  202.    


  203. }


  204. void key_scan()//按键扫描函数 放在定时中断里
  205. {  

  206.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  207.   {
  208.      ucKeyLock1=0; //按键自锁标志清零
  209.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  210.   }
  211.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  212.   {
  213.      uiKeyTimeCnt1++; //累加定时中断次数
  214.      if(uiKeyTimeCnt1>const_key_time1)
  215.      {
  216.         uiKeyTimeCnt1=0;
  217.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  218.         ucKeySec=1;    //触发1号键
  219.      }
  220.   }

  221. /* 注释二:
  222. * 请注意以下长按复位按键与短按复位按键的写法。在本程序中,每次长按复位按键必然
  223. * 触发一次短按复位按键。
  224. */

  225.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  226.   {
  227.      ucKeyLock2=0; //按键自锁标志清零
  228.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  229.   }
  230.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  231.   {
  232.      uiKeyTimeCnt2++; //累加定时中断次数
  233.      if(uiKeyTimeCnt2>const_key_time2)
  234.      {
  235.         uiKeyTimeCnt2=0;
  236.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  237.         ucKeySec=2;    //触发2号键
  238.      }
  239.   }
  240.   else if(uiKeyTimeCnt2<const_key_long_time)   //长按复位按键
  241.   {
  242.       uiKeyTimeCnt2++;
  243.           if(uiKeyTimeCnt2==const_key_long_time)
  244.           {
  245.              ucKeySec=17;    //触发17号长按复位键
  246.           }
  247.   }

  248. if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  249.   {
  250.      ucKeyLock3=0; //按键自锁标志清零
  251.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  252.   }
  253.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  254.   {
  255.      uiKeyTimeCnt3++; //累加定时中断次数
  256.      if(uiKeyTimeCnt3>const_key_time3)
  257.      {
  258.         uiKeyTimeCnt3=0;
  259.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  260.         ucKeySec=3;    //触发3号键
  261.      }
  262.   }

  263.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  264.   {
  265.      ucKeyLock4=0; //按键自锁标志清零
  266.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  267.   }
  268.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  269.   {
  270.      uiKeyTimeCnt4++; //累加定时中断次数
  271.      if(uiKeyTimeCnt4>const_key_time4)
  272.      {
  273.         uiKeyTimeCnt4=0;
  274.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  275.         ucKeySec=4;    //触发4号键
  276.      }
  277.   }




  278. }


  279. void key_service() //按键服务的应用程序
  280. {
  281.   switch(ucKeySec) //按键服务状态切换
  282.   {
  283.     case 1:// 启动和暂停按键 对应朱兆祺学习板的S1键

  284.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  285.           {
  286.               case 1:
  287.                    if(ucStartFlag==0)  //如果原来处于暂停的状态,则启动
  288.                                    {
  289.                       ucStartFlag=1; //启动
  290.                                    }
  291.                                    else     //如果原来处于启动的状态,则暂停
  292.                                    {
  293.                                       ucStartFlag=0;  //暂停
  294.                                    }
  295.                    break;


  296.            
  297.           }   

  298.           ucWd=1;  //不管在哪个窗口,强行切换回窗口1
  299.                   ucWd1Update=1; //窗口1更新显示标志

  300.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  301.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  302.           break;   
  303.    
  304.     case 2:// 复位按键 对应朱兆祺学习板的S5键

  305.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  306.           {
  307.               case 1:    //在窗口1中
  308.                                    ucStartFlag=0;  //暂停
  309.                    ucCountDown=99;  //恢复倒计时的默认值99
  310.                    uiTimeCnt=0;  //倒计时的时间计时器清零
  311.                                    ucWd1Update=1; //窗口1更新显示标志  只要ucCountDown变化了,就要更新显示一次
  312.                    break;
  313.               case 2:    //在窗口2中
  314.                    ucWd=1;  //切换回窗口1
  315.                                    ucWd1Update=1; //窗口1更新显示标志
  316.                    break;
  317.           }  
  318.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  319.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  320.           break;  

  321.     case 3:// 加按键 对应朱兆祺学习板的S9键


  322.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  323.           {
  324.               case 2:   //在窗口2中
  325.                    uiSetData1++;       //速度档位累加,档位越大,速度越快.
  326.                                    if(uiSetData1>99)
  327.                                    {
  328.                                       uiSetData1=99;
  329.                                    }
  330.                    uiSpeedCnt=440-(uiSetData1*2);  //速度档位越大,累计中断数uiSpeedCnt越小,从而倒计时的时间越快

  331.                                    ucWd2Update=1; //窗口2更新显示
  332.                    break;
  333.           }     
  334.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  335.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  336.           break;   
  337.    
  338.     case 4:// 减按键 对应朱兆祺学习板的S13键
  339.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  340.           {
  341.               case 2:   //在窗口2中
  342.                                if(uiSetData1>0)  //加此条件判断,避免0减1
  343.                                    {
  344.                       uiSetData1--;       //速度档位累减,档位越小,速度越慢.
  345.                                    }

  346.                    uiSpeedCnt=440-(uiSetData1*2);  //速度档位越小,累计中断数uiSpeedCnt越大,从而倒计时的时间越慢

  347.                                    ucWd2Update=1; //窗口2更新显示
  348.                    break;
  349.           }
  350.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  351.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  352.           break;

  353.     case 17:// 长按复位按键 对应朱兆祺学习板的S5键

  354.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  355.           {
  356.               case 1:  //窗口1下
  357.                    ucWd=2;  //切换到闪烁窗口2  进行设置速度档位显示
  358.                                    ucWd2Update=1; //窗口2更新显示标志
  359.                    break;
  360.          
  361.           }  
  362.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  363.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  364.           break;

  365.   }               
  366. }


  367. void display_drive()  
  368. {
  369.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  370.    switch(ucDisplayDriveStep)
  371.    {
  372.       case 1:  //显示第1位
  373.            ucDigShowTemp=dig_table[ucDigShow1];
  374.                    if(ucDigDot1==1)
  375.                    {
  376.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  377.                    }
  378.            dig_hc595_drive(ucDigShowTemp,0xfe);
  379.                break;
  380.       case 2:  //显示第2位
  381.            ucDigShowTemp=dig_table[ucDigShow2];
  382.                    if(ucDigDot2==1)
  383.                    {
  384.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  385.                    }
  386.            dig_hc595_drive(ucDigShowTemp,0xfd);
  387.                break;
  388.       case 3:  //显示第3位
  389.            ucDigShowTemp=dig_table[ucDigShow3];
  390.                    if(ucDigDot3==1)
  391.                    {
  392.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  393.                    }
  394.            dig_hc595_drive(ucDigShowTemp,0xfb);
  395.                break;
  396.       case 4:  //显示第4位
  397.            ucDigShowTemp=dig_table[ucDigShow4];
  398.                    if(ucDigDot4==1)
  399.                    {
  400.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  401.                    }
  402.            dig_hc595_drive(ucDigShowTemp,0xf7);
  403.                break;
  404.       case 5:  //显示第5位
  405.            ucDigShowTemp=dig_table[ucDigShow5];
  406.                    if(ucDigDot5==1)
  407.                    {
  408.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  409.                    }
  410.            dig_hc595_drive(ucDigShowTemp,0xef);
  411.                break;
  412.       case 6:  //显示第6位
  413.            ucDigShowTemp=dig_table[ucDigShow6];
  414.                    if(ucDigDot6==1)
  415.                    {
  416.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  417.                    }
  418.            dig_hc595_drive(ucDigShowTemp,0xdf);
  419.                break;
  420.       case 7:  //显示第7位
  421.            ucDigShowTemp=dig_table[ucDigShow7];
  422.                    if(ucDigDot7==1)
  423.                    {
  424.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  425.            }
  426.            dig_hc595_drive(ucDigShowTemp,0xbf);
  427.                break;
  428.       case 8:  //显示第8位
  429.            ucDigShowTemp=dig_table[ucDigShow8];
  430.                    if(ucDigDot8==1)
  431.                    {
  432.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  433.                    }
  434.            dig_hc595_drive(ucDigShowTemp,0x7f);
  435.                break;
  436.    }

  437.    ucDisplayDriveStep++;
  438.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  439.    {
  440.      ucDisplayDriveStep=1;
  441.    }



  442. }


  443. //数码管的74HC595驱动函数
  444. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  445. {
  446.    unsigned char i;
  447.    unsigned char ucTempData;
  448.    dig_hc595_sh_dr=0;
  449.    dig_hc595_st_dr=0;

  450.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  451.    for(i=0;i<8;i++)
  452.    {
  453.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  454.          else dig_hc595_ds_dr=0;

  455.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  456.          delay_short(1);
  457.          dig_hc595_sh_dr=1;
  458.          delay_short(1);

  459.          ucTempData=ucTempData<<1;
  460.    }

  461.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  462.    for(i=0;i<8;i++)
  463.    {
  464.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  465.          else dig_hc595_ds_dr=0;

  466.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  467.          delay_short(1);
  468.          dig_hc595_sh_dr=1;
  469.          delay_short(1);

  470.          ucTempData=ucTempData<<1;
  471.    }

  472.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  473.    delay_short(1);
  474.    dig_hc595_st_dr=1;
  475.    delay_short(1);

  476.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  477.    dig_hc595_st_dr=0;
  478.    dig_hc595_ds_dr=0;

  479. }


  480. //LED灯的74HC595驱动函数
  481. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  482. {
  483.    unsigned char i;
  484.    unsigned char ucTempData;
  485.    hc595_sh_dr=0;
  486.    hc595_st_dr=0;

  487.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  488.    for(i=0;i<8;i++)
  489.    {
  490.          if(ucTempData>=0x80)hc595_ds_dr=1;
  491.          else hc595_ds_dr=0;

  492.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  493.          delay_short(1);
  494.          hc595_sh_dr=1;
  495.          delay_short(1);

  496.          ucTempData=ucTempData<<1;
  497.    }

  498.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  499.    for(i=0;i<8;i++)
  500.    {
  501.          if(ucTempData>=0x80)hc595_ds_dr=1;
  502.          else hc595_ds_dr=0;

  503.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  504.          delay_short(1);
  505.          hc595_sh_dr=1;
  506.          delay_short(1);

  507.          ucTempData=ucTempData<<1;
  508.    }

  509.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  510.    delay_short(1);
  511.    hc595_st_dr=1;
  512.    delay_short(1);

  513.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  514.    hc595_st_dr=0;
  515.    hc595_ds_dr=0;

  516. }


  517. void T0_time() interrupt 1
  518. {
  519.   TF0=0;  //清除中断标志
  520.   TR0=0; //关中断

  521.   key_scan(); //按键扫描函数


  522.   if(ucStartFlag==1)  //启动倒计时的计时器
  523.   {
  524.      uiTimeCnt++;
  525.      if(uiTimeCnt>=uiSpeedCnt)    //时间到
  526.      {
  527.             if(ucCountDown!=0) //加这个判断,就是避免在0的情况下减1
  528.             {
  529.                ucCountDown--;  //倒计时当前显示值减1
  530.             }

  531.         if(ucCountDown==0)  //倒计时结束
  532.             {
  533.                ucStartFlag=0;  //暂停
  534.            uiVoiceCnt=const_voice_long; //蜂鸣器触发提醒,滴一声就停。
  535.             }

  536.         ucWd1Update=1; //窗口1更新显示标志
  537.         uiTimeCnt=0;   //计时器清零,准备从新开始计时
  538.      }
  539.   }


  540.   uiDpyTimeCnt++;  //数码管的闪烁计时器


  541.   if(uiVoiceCnt!=0)
  542.   {
  543.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  544.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  545. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  546.   }
  547.   else
  548.   {
  549.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  550.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  551. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  552.   }

  553.   display_drive();  //数码管字模的驱动函数


  554.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  555.   TL0=0x0b;
  556.   TR0=1;  //开中断
  557. }


  558. void delay_short(unsigned int uiDelayShort)
  559. {
  560.    unsigned int i;  
  561.    for(i=0;i<uiDelayShort;i++)
  562.    {
  563.      ;   //一个分号相当于执行一条空语句
  564.    }
  565. }


  566. void delay_long(unsigned int uiDelayLong)
  567. {
  568.    unsigned int i;
  569.    unsigned int j;
  570.    for(i=0;i<uiDelayLong;i++)
  571.    {
  572.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  573.           {
  574.              ; //一个分号相当于执行一条空语句
  575.           }
  576.    }
  577. }


  578. void initial_myself()  //第一区 初始化单片机
  579. {

  580. /* 注释三:
  581. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  582. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  583. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  584. */
  585.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  586.   led_dr=0;  //关闭独立LED灯
  587.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  588.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

  589.   TMOD=0x01;  //设置定时器0为工作方式1

  590.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  591.   TL0=0x0b;

  592. }

  593. void initial_peripheral() //第二区 初始化外围
  594. {


  595.    ucDigDot8=0;   //小数点全部不显示
  596.    ucDigDot7=0;  
  597.    ucDigDot6=0;
  598.    ucDigDot5=0;  
  599.    ucDigDot4=0;
  600.    ucDigDot3=0;  
  601.    ucDigDot2=0;
  602.    ucDigDot1=0;

  603.    uiSpeedCnt=440-(uiSetData1*2);  //速度档位越大,累计中断数uiSpeedCnt越小,从而倒计时的时间越快

  604.    EA=1;     //开总中断
  605.    ET0=1;    //允许定时中断
  606.    TR0=1;    //启动定时中断

  607. }
复制代码

总结陈词:
这节讲了能设置速度档位的数码管倒计时程序。现在很多人用iphone4S的手机,这个手机每次开机显示的时候,都要通过4个密码开锁,如果我们要用4位数码管来实现这个密码锁功能,该怎么编写这个程序?欲知详情,请听下回分解-----在数码管中实现iphone4S开机密码锁的程序。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

35
 
第三十四节:在数码管中实现iphone4S开机密码锁的程序。

开场白:
    这一节要教会大家四个知识点:
第一个:类似手机上10秒钟内无按键操作将自动进入锁屏的程序。
第二个:如何用一个数组来接收按键的一串数字输入。
第三个:矩阵键盘中,数字按键的输入,由于这部分按键的代码相似度非常高,因此把它封装在一个函数里可以非常简洁方便。
第四个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。其他的按键不用。

(2)实现功能:
本程序有3个窗口。
开机显示第1个密码登录框窗口“----”,在这个窗口下输入密码,如果密码等于”9922”表示密码正确,将会切换到第2个显示按键值的窗口。在窗口2下,按不同的按键会显示不同的按键值,如果10秒内没有按键操作,将会自动切换到第1个密码登录窗口,类似手机上的自动锁屏操作。在密码登录窗口1下,如果密码不正确,会自动清除密码的数字,继续在窗口1下显示”----”。  
窗口3是用来停留0.5秒显示全部密码的信息,然后根据密码的正确与否自动切换到对应的窗口。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_no_key_push 4400   //大概10秒内无按键按下的时间
  3. #define const_0_1s  220            //大概0.5秒的时间

  4. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  5. #define const_key_time  20    //按键去抖动延时的时间


  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. //驱动数码管的74HC595
  11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  12. void display_drive(); //显示数码管字模的驱动函数
  13. void display_service(); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  16. void T0_time();  //定时中断函数

  17. void number_key_input(unsigned char ucWhichKey);  //由于数字按键的代码相似度高,因此封装在这个函数里
  18. void key_service(); //按键服务的应用程序
  19. void key_scan();//按键扫描函数 放在定时中断里

  20. sbit key_sr1=P0^0; //第一行输入
  21. sbit key_sr2=P0^1; //第二行输入
  22. sbit key_sr3=P0^2; //第三行输入
  23. sbit key_sr4=P0^3; //第四行输入

  24. sbit key_dr1=P0^4; //第一列输出
  25. sbit key_dr2=P0^5; //第二列输出
  26. sbit key_dr3=P0^6; //第三列输出
  27. sbit key_dr4=P0^7; //第四列输出


  28. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  29. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

  30. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  31. sbit dig_hc595_st_dr=P2^1;  
  32. sbit dig_hc595_ds_dr=P2^2;  
  33. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  34. sbit hc595_st_dr=P2^4;  
  35. sbit hc595_ds_dr=P2^5;  

  36. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  37. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  38. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  39. unsigned char ucRowRecord=1; //记录当前扫描到第几列了


  40. unsigned char ucKeySec=0;   //被触发的按键编号
  41. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  42. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  43. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  44. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  45. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  46. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  47. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  48. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  49. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  50. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  51. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  52. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  53. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  54. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  55. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  56. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  57. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  58. unsigned char ucDigShowTemp=0; //临时中间变量
  59. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  60. unsigned char ucWd1Update=1; //窗口1更新显示标志
  61. unsigned char ucWd2Update=0; //窗口2更新显示标志
  62. unsigned char ucWd3Update=0; //窗口3更新显示标志
  63. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。


  64. unsigned char ucInputPassword[4];  //在第1个窗口下,显示输入的4个密码
  65. unsigned char ucPasswordCnt=0; //记录当前已经输入到哪一位密码了
  66. unsigned char ucKeyNumber=1;  //在第2个窗口下,显示当前被按下的按键

  67. unsigned int  uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器
  68. unsigned int  uiPasswordTimer=const_0_1s;  //显示0.5秒钟全部密码的计时器,让窗口3停留显示0.5秒钟之后自动消失

  69. unsigned char ucTemp1=0;  //中间过渡变量
  70. unsigned char ucTemp2=0;  //中间过渡变量
  71. unsigned char ucTemp3=0;  //中间过渡变量
  72. unsigned char ucTemp4=0;  //中间过渡变量

  73. //根据原理图得出的共阴数码管字模表
  74. code unsigned char dig_table[]=
  75. {
  76. 0x3f,  //0       序号0
  77. 0x06,  //1       序号1
  78. 0x5b,  //2       序号2
  79. 0x4f,  //3       序号3
  80. 0x66,  //4       序号4
  81. 0x6d,  //5       序号5
  82. 0x7d,  //6       序号6
  83. 0x07,  //7       序号7
  84. 0x7f,  //8       序号8
  85. 0x6f,  //9       序号9
  86. 0x00,  //无      序号10
  87. 0x40,  //-       序号11
  88. 0x73,  //P       序号12
  89. };
  90. void main()
  91.   {
  92.    initial_myself();  
  93.    delay_long(100);   
  94.    initial_peripheral();
  95.    while(1)  
  96.    {
  97.      key_service(); //按键服务的应用程序
  98.          display_service(); //显示的窗口菜单服务程序
  99.    }
  100. }
  101. /* 注释一:
  102. *鸿哥首次提出的"一二级菜单显示理论":
  103. *凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
  104. *每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
  105. *局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
  106. *表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
  107. */

  108. void display_service() //显示的窗口菜单服务程序
  109. {

  110.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  111.    {
  112.        case 1:   //显示输入密码的登录框
  113.             if(ucWd1Update==1)  //窗口1要全部更新显示
  114.             {
  115.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  116.                ucDigShow8=10;  //第8位数码管显示无
  117.                ucDigShow7=10;  //第7位数码管显示无
  118.                ucDigShow6=10;  //第6位数码管显示无
  119.                ucDigShow5=10;  //第5位数码管显示无

  120.                ucDigShow4=ucInputPassword[0];  //第4位数码管显示输入的密码
  121.                ucDigShow3=ucInputPassword[1];  //第3位数码管显示输入的密码
  122.                ucDigShow2=ucInputPassword[2];  //第2位数码管显示输入的密码
  123.                ucDigShow1=ucInputPassword[3];  //第1位数码管显示输入的密码

  124.             }
  125.             break;
  126.         case 2:  //显示被按下的键值
  127.             if(ucWd2Update==1)  //窗口2要全部更新显示
  128.             {
  129.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描

  130.                ucDigShow8=10;  //第8位数码管显示无
  131.                ucDigShow7=10;  //第7位数码管显示无
  132.                ucDigShow6=10;  //第6位数码管显示无
  133.                ucDigShow5=10;  //第5位数码管显示无

  134.                ucDigShow4=10;  //第4位数码管显示无
  135.                ucDigShow3=10;  //第3位数码管显示无
  136.                ucDigShow2=10;  //第2位数码管显示无
  137.                ucDigShow1=ucKeyNumber; //第1位数码管显示被按下的键值
  138.             }
  139.             break;
  140.        case 3:   //当输入完4个密码后,显示1秒钟的密码登录框,
  141.             if(ucWd3Update==1)  //窗口3要全部更新显示
  142.             {
  143.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描

  144.                ucDigShow8=10;  //第8位数码管显示无
  145.                ucDigShow7=10;  //第7位数码管显示无
  146.                ucDigShow6=10;  //第6位数码管显示无
  147.                ucDigShow5=10;  //第5位数码管显示无

  148.                ucDigShow4=ucInputPassword[0];  //第4位数码管显示输入的密码
  149.                ucDigShow3=ucInputPassword[1];  //第3位数码管显示输入的密码
  150.                ucDigShow2=ucInputPassword[2];  //第2位数码管显示输入的密码
  151.                ucDigShow1=ucInputPassword[3];  //第1位数码管显示输入的密码

  152.             }
  153.             break;     
  154.     }
  155.    

  156. }
  157. void key_scan()//按键扫描函数 放在定时中断里
  158. {  


  159.   switch(ucKeyStep)
  160.   {
  161.      case 1:   //按键扫描输出第ucRowRecord列低电平
  162.               if(ucRowRecord==1)  //第一列输出低电平
  163.                   {
  164.              key_dr1=0;      
  165.              key_dr2=1;
  166.              key_dr3=1;   
  167.              key_dr4=1;
  168.                   }
  169.               else if(ucRowRecord==2)  //第二列输出低电平
  170.                   {
  171.              key_dr1=1;      
  172.              key_dr2=0;
  173.              key_dr3=1;   
  174.              key_dr4=1;
  175.                   }
  176.               else if(ucRowRecord==3)  //第三列输出低电平
  177.                   {
  178.              key_dr1=1;      
  179.              key_dr2=1;
  180.              key_dr3=0;   
  181.              key_dr4=1;
  182.                   }
  183.               else   //第四列输出低电平
  184.                   {
  185.              key_dr1=1;      
  186.              key_dr2=1;
  187.              key_dr3=1;   
  188.              key_dr4=0;
  189.                   }

  190.           uiKeyTimeCnt=0;  //延时计数器清零
  191.           ucKeyStep++;     //切换到下一个运行步骤
  192.               break;

  193.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  194.           uiKeyTimeCnt++;
  195.                   if(uiKeyTimeCnt>1)
  196.                   {
  197.                      uiKeyTimeCnt=0;
  198.              ucKeyStep++;     //切换到下一个运行步骤
  199.                   }
  200.               break;

  201.      case 3:
  202.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  203.           {  
  204.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  205.              ucKeyLock=0;  //按键自锁标志清零
  206.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  207.    
  208.                          ucRowRecord++;  //输出下一列
  209.                          if(ucRowRecord>4)  
  210.                          {
  211.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  212.                          }

  213.           }
  214.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  215.                   {
  216.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  217.                          {
  218.                             uiKeyTimeCnt++;  //去抖动延时计数器
  219.                                 if(uiKeyTimeCnt>const_key_time)
  220.                                 {
  221.                                    uiKeyTimeCnt=0;
  222.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  223.                        if(ucRowRecord==1)  //第一列输出低电平
  224.                            {
  225.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  226.                            }
  227.                        else if(ucRowRecord==2)  //第二列输出低电平
  228.                            {
  229.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  230.                            }
  231.                        else if(ucRowRecord==3)  //第三列输出低电平
  232.                            {
  233.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  234.                            }
  235.                        else   //第四列输出低电平
  236.                            {
  237.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  238.                            }

  239.                                 }
  240.                         
  241.                          }
  242.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  243.                          {
  244.                             uiKeyTimeCnt++;  //去抖动延时计数器
  245.                                 if(uiKeyTimeCnt>const_key_time)
  246.                                 {
  247.                                    uiKeyTimeCnt=0;
  248.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  249.                        if(ucRowRecord==1)  //第一列输出低电平
  250.                            {
  251.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  252.                            }
  253.                        else if(ucRowRecord==2)  //第二列输出低电平
  254.                            {
  255.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  256.                            }
  257.                        else if(ucRowRecord==3)  //第三列输出低电平
  258.                            {
  259.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  260.                            }
  261.                        else   //第四列输出低电平
  262.                            {
  263.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  264.                            }
  265.                                 }
  266.                         
  267.                          }
  268.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  269.                          {
  270.                             uiKeyTimeCnt++;  //去抖动延时计数器
  271.                                 if(uiKeyTimeCnt>const_key_time)
  272.                                 {
  273.                                    uiKeyTimeCnt=0;
  274.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  275.                        if(ucRowRecord==1)  //第一列输出低电平
  276.                            {
  277.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  278.                            }
  279.                        else if(ucRowRecord==2)  //第二列输出低电平
  280.                            {
  281.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  282.                            }
  283.                        else if(ucRowRecord==3)  //第三列输出低电平
  284.                            {
  285.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  286.                            }
  287.                        else   //第四列输出低电平
  288.                            {
  289.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  290.                            }
  291.                                 }
  292.                         
  293.                          }
  294.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  295.                          {
  296.                             uiKeyTimeCnt++;  //去抖动延时计数器
  297.                                 if(uiKeyTimeCnt>const_key_time)
  298.                                 {
  299.                                    uiKeyTimeCnt=0;
  300.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  301.                        if(ucRowRecord==1)  //第一列输出低电平
  302.                            {
  303.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  304.                            }
  305.                        else if(ucRowRecord==2)  //第二列输出低电平
  306.                            {
  307.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  308.                            }
  309.                        else if(ucRowRecord==3)  //第三列输出低电平
  310.                            {
  311.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  312.                            }
  313.                        else   //第四列输出低电平
  314.                            {
  315.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  316.                            }
  317.                                 }
  318.                         
  319.                          }
  320.                   
  321.                   }
  322.               break;

  323.   }


  324. }


  325. void key_service() //第三区 按键服务的应用程序
  326. {
  327.   switch(ucKeySec) //按键服务状态切换
  328.   {
  329.     case 1:// 1号键 对应朱兆祺学习板的S1键
  330.           number_key_input(1);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  331.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  332.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  333.           break;        
  334.     case 2:// 2号键 对应朱兆祺学习板的S2键
  335.           number_key_input(2);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  336.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  337.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  338.           break;     
  339.     case 3:// 3号键 对应朱兆祺学习板的S3键
  340.           number_key_input(3);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  341.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  342.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  343.           break;         
  344.     case 4:// 4号键 对应朱兆祺学习板的S4键
  345.           number_key_input(4);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  346.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  347.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  348.           break;   
  349.     case 5:// 5号键 对应朱兆祺学习板的S5键
  350.           number_key_input(5);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  351.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  352.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  353.           break;   
  354.     case 6:// 6号键 对应朱兆祺学习板的S6键
  355.           number_key_input(6);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  356.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  357.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  358.           break;   
  359.     case 7:// 7号键 对应朱兆祺学习板的S7键
  360.           number_key_input(7);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  361.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  362.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  363.           break;   
  364.     case 8:// 8号键 对应朱兆祺学习板的S8键
  365.           number_key_input(8);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  366.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  367.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  368.           break;   
  369.     case 9:// 9号键 对应朱兆祺学习板的S9键
  370.           number_key_input(9);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  371.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  372.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  373.           break;   
  374.     case 10:// 把这个按键专门用来输入数字0    对应朱兆祺学习板的S10键
  375.           number_key_input(0);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  376.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  377.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  378.           break;   
  379.     case 11:// 11号键 对应朱兆祺学习板的S11键

  380.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  381.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  382.           break;   
  383.     case 12:// 12号键 对应朱兆祺学习板的S12键

  384.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  385.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  386.           break;   
  387.     case 13:// 13号键 对应朱兆祺学习板的S13键

  388.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  389.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  390.           break;   
  391.     case 14:// 14号键 对应朱兆祺学习板的S14键

  392.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  393.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  394.           break;   
  395.     case 15:// 15号键 对应朱兆祺学习板的S15键

  396.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  397.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  398.           break;   
  399.     case 16:// 16号键 对应朱兆祺学习板的S16键

  400.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  401.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  402.           break;   
  403.   }               
  404. }

  405. void number_key_input(unsigned char ucWhichKey)  //由于数字按键的代码相似度高,因此封装在这个函数里
  406. {


  407.     switch(ucWd)
  408.            {
  409.            case 1:   //在显示密码登录框的窗口下
  410.             ucInputPassword[ucPasswordCnt]=ucWhichKey;   //输入的密码值显示
  411.             ucPasswordCnt++;
  412.                         if(ucPasswordCnt>=4)
  413.                         {
  414.                             ucPasswordCnt=0;
  415.                                 ucWd=3;//切换到第3个的窗口,停留显示1秒钟全部密码
  416.                             ucWd3Update=1;  //更新显示窗口3
  417.                 uiPasswordTimer=const_0_1s;  //显示0.5秒钟全部密码的计时器,让窗口3停留显示0.5秒钟之后自动消失
  418.                         }

  419.                         ucWd1Update=1; //更新显示窗口1

  420.                         uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器赋新值
  421.                         break;
  422.            case 2:   //在显示按键值的窗口下
  423.                 ucKeyNumber=ucWhichKey; //输入的按键数值显示
  424.                     ucWd2Update=1;  //更新显示窗口2

  425.                         uiNoKeyPushTimer=const_no_key_push;  //10秒内无按键按下的计时器赋新值
  426.                     break;
  427.     }

  428. }


  429. void display_drive()  
  430. {
  431.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  432.    switch(ucDisplayDriveStep)
  433.    {
  434.       case 1:  //显示第1位
  435.            ucDigShowTemp=dig_table[ucDigShow1];
  436.                    if(ucDigDot1==1)
  437.                    {
  438.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  439.                    }
  440.            dig_hc595_drive(ucDigShowTemp,0xfe);
  441.                break;
  442.       case 2:  //显示第2位
  443.            ucDigShowTemp=dig_table[ucDigShow2];
  444.                    if(ucDigDot2==1)
  445.                    {
  446.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  447.                    }
  448.            dig_hc595_drive(ucDigShowTemp,0xfd);
  449.                break;
  450.       case 3:  //显示第3位
  451.            ucDigShowTemp=dig_table[ucDigShow3];
  452.                    if(ucDigDot3==1)
  453.                    {
  454.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  455.                    }
  456.            dig_hc595_drive(ucDigShowTemp,0xfb);
  457.                break;
  458.       case 4:  //显示第4位
  459.            ucDigShowTemp=dig_table[ucDigShow4];
  460.                    if(ucDigDot4==1)
  461.                    {
  462.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  463.                    }
  464.            dig_hc595_drive(ucDigShowTemp,0xf7);
  465.                break;
  466.       case 5:  //显示第5位
  467.            ucDigShowTemp=dig_table[ucDigShow5];
  468.                    if(ucDigDot5==1)
  469.                    {
  470.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  471.                    }
  472.            dig_hc595_drive(ucDigShowTemp,0xef);
  473.                break;
  474.       case 6:  //显示第6位
  475.            ucDigShowTemp=dig_table[ucDigShow6];
  476.                    if(ucDigDot6==1)
  477.                    {
  478.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  479.                    }
  480.            dig_hc595_drive(ucDigShowTemp,0xdf);
  481.                break;
  482.       case 7:  //显示第7位
  483.            ucDigShowTemp=dig_table[ucDigShow7];
  484.                    if(ucDigDot7==1)
  485.                    {
  486.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  487.            }
  488.            dig_hc595_drive(ucDigShowTemp,0xbf);
  489.                break;
  490.       case 8:  //显示第8位
  491.            ucDigShowTemp=dig_table[ucDigShow8];
  492.                    if(ucDigDot8==1)
  493.                    {
  494.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  495.                    }
  496.            dig_hc595_drive(ucDigShowTemp,0x7f);
  497.                break;
  498.    }
  499.    ucDisplayDriveStep++;
  500.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  501.    {
  502.      ucDisplayDriveStep=1;
  503.    }

  504. }

  505. //数码管的74HC595驱动函数
  506. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  507. {
  508.    unsigned char i;
  509.    unsigned char ucTempData;
  510.    dig_hc595_sh_dr=0;
  511.    dig_hc595_st_dr=0;
  512.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  513.    for(i=0;i<8;i++)
  514.    {
  515.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  516.          else dig_hc595_ds_dr=0;
  517.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  518.          delay_short(1);
  519.          dig_hc595_sh_dr=1;
  520.          delay_short(1);
  521.          ucTempData=ucTempData<<1;
  522.    }
  523.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  524.    for(i=0;i<8;i++)
  525.    {
  526.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  527.          else dig_hc595_ds_dr=0;
  528.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  529.          delay_short(1);
  530.          dig_hc595_sh_dr=1;
  531.          delay_short(1);
  532.          ucTempData=ucTempData<<1;
  533.    }
  534.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  535.    delay_short(1);
  536.    dig_hc595_st_dr=1;
  537.    delay_short(1);
  538.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  539.    dig_hc595_st_dr=0;
  540.    dig_hc595_ds_dr=0;
  541. }

  542. //LED灯的74HC595驱动函数
  543. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  544. {
  545.    unsigned char i;
  546.    unsigned char ucTempData;
  547.    hc595_sh_dr=0;
  548.    hc595_st_dr=0;
  549.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  550.    for(i=0;i<8;i++)
  551.    {
  552.          if(ucTempData>=0x80)hc595_ds_dr=1;
  553.          else hc595_ds_dr=0;
  554.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  555.          delay_short(1);
  556.          hc595_sh_dr=1;
  557.          delay_short(1);
  558.          ucTempData=ucTempData<<1;
  559.    }
  560.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  561.    for(i=0;i<8;i++)
  562.    {
  563.          if(ucTempData>=0x80)hc595_ds_dr=1;
  564.          else hc595_ds_dr=0;
  565.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  566.          delay_short(1);
  567.          hc595_sh_dr=1;
  568.          delay_short(1);
  569.          ucTempData=ucTempData<<1;
  570.    }
  571.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  572.    delay_short(1);
  573.    hc595_st_dr=1;
  574.    delay_short(1);
  575.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  576.    hc595_st_dr=0;
  577.    hc595_ds_dr=0;
  578. }

  579. void T0_time() interrupt 1
  580. {
  581.   unsigned int i;
  582.   TF0=0;  //清除中断标志
  583.   TR0=0; //关中断

  584.   if(ucWd==3)  //在窗口3下
  585.   {
  586.      if(uiPasswordTimer>0)   
  587.          {
  588.             uiPasswordTimer--;  
  589.          }
  590.      if(uiPasswordTimer==0)
  591.          {
  592.                 if(ucInputPassword[0]==9&&ucInputPassword[1]==9&&ucInputPassword[2]==2&&ucInputPassword[3]==2)  
  593.             {     //如果密码等于9922,则正确
  594.                       ucWd=2;//切换到第2个显示按键的窗口
  595.                       ucWd2Update=1;  //更新显示窗口2
  596.             }
  597.                 else //如果密码不正确,则继续显示----
  598.             {
  599.               for(i=0;i<4;i++)
  600.               {
  601.                  ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  602.               }
  603.                           ucWd=1;
  604.                   ucWd1Update=1; //更新显示窗口1
  605.                 }

  606.          }
  607.   }


  608.   if(ucWd==2)  //在窗口2下
  609.   {
  610.      if(uiNoKeyPushTimer>0)   
  611.          {
  612.             uiNoKeyPushTimer--;  
  613.          }
  614.      if(uiNoKeyPushTimer==0)//如果10秒内无按键按下,则自动切换到显示密码登录框的界面
  615.          {
  616.        for(i=0;i<4;i++)
  617.        {
  618.           ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  619.        }
  620.            ucWd=1;
  621.            ucWd1Update=1; //更新显示窗口1
  622.          }
  623.   }


  624.   key_scan(); //按键扫描函数
  625.   if(uiVoiceCnt!=0)
  626.   {
  627.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  628.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  629. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  630.   }
  631.   else
  632.   {
  633.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  634.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  635. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  636.   }
  637.   display_drive();  //数码管字模的驱动函数

  638.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  639.   TL0=0x0b;
  640.   TR0=1;  //开中断
  641. }

  642. void delay_short(unsigned int uiDelayShort)
  643. {
  644.    unsigned int i;  
  645.    for(i=0;i<uiDelayShort;i++)
  646.    {
  647.      ;   //一个分号相当于执行一条空语句
  648.    }
  649. }

  650. void delay_long(unsigned int uiDelayLong)
  651. {
  652.    unsigned int i;
  653.    unsigned int j;
  654.    for(i=0;i<uiDelayLong;i++)
  655.    {
  656.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  657.           {
  658.              ; //一个分号相当于执行一条空语句
  659.           }
  660.    }
  661. }

  662. void initial_myself()  //第一区 初始化单片机
  663. {

  664.   led_dr=0;  //关闭独立LED灯
  665.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  666.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  667.   TMOD=0x01;  //设置定时器0为工作方式1
  668.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  669.   TL0=0x0b;
  670. }
  671. void initial_peripheral() //第二区 初始化外围
  672. {
  673.    unsigned int i; //个人的变量命名习惯,i,j,k等单个字母的变量名只用在for循环里
  674.    for(i=0;i<4;i++)
  675.    {
  676.      ucInputPassword[i]=11;  //开机默认密码全部显示"----"
  677.    }

  678.    ucDigDot8=0;   //小数点全部不显示
  679.    ucDigDot7=0;  
  680.    ucDigDot6=0;
  681.    ucDigDot5=0;  
  682.    ucDigDot4=0;
  683.    ucDigDot3=0;  
  684.    ucDigDot2=0;
  685.    ucDigDot1=0;
  686.    EA=1;     //开总中断
  687.    ET0=1;    //允许定时中断
  688.    TR0=1;    //启动定时中断
  689. }
复制代码

总结陈词:
这节讲了iphone4S开机密码锁的程序。2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用,这个程序该怎么编写?欲知详情,请听下回分解-----带数码管显示的象棋比赛专用计时器。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

36
 
第三十五节:带数码管显示的象棋比赛专用计时器。

开场白:
2014年春节的时候,一帮朋友举行小规模的象棋比赛,有一些朋友下棋的速度实在是太慢了,为了限制比赛时间,我专门用朱兆祺的51学习板做了一个棋类比赛专用计时器给他们用。这一节要教会大家两个知识点:
第一个:按键服务程序操作的精髓在于根据当前系统处于什么窗口状态下就执行什么操作。紧紧围绕着不同的窗口ucWd来执行不同的操作。
第二个:继续加深熟悉鸿哥首次提出的“一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。

具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。
刚上电开机时,红棋加时键对应S1键,红棋减时键对应S2键.。
刚上电开机时,黑棋加时键对应S3键,黑棋减时键对应S4键.。
比赛中途暂停双方计时的暂停按键对应S6键。刚上电时,复位双方默认20分时间的复位按键对应S7按键。
红棋的抢时按键对应S13键,黑棋的抢时按键对应S16按键。

(2)实现功能:
棋类计时器有点像抢答器,本质上有两个计时器。比赛的时候对弈的两个棋友各用一个不同的按键抢时间,红棋走一步棋后,就按一下自己的抢时按键,这个时候红棋的计时器停止计时,而黑棋的计时器开始计时,黑棋走了一步棋后,按一下自己的计时器,黑棋停止计时,红棋继续计时,依次循环,谁的时间最先用完谁就输,蜂鸣器也会发出长鸣的声音提示时间到。
上电开机默认双方各有20分钟的时间,左边显示的是红棋的时间,右边显示的是黑棋的时间。此时可以通过S1,S2.,S3,S4的加减按键来设置各自的最大倒计时时间。此时如果按下复位按键S7,会自动把双方的时间设置为默认的20分钟。
设置好最大倒计时的时间后,此时任意一方按下各自的抢时按键(S13或者S16),则自己的计时器停止计时,而对方开始倒计时。此时数码管显示的是对方的时间,而自己的时间屏蔽不显示。
在开始倒计时的时候,如果中途有棋友要接听电话或者忙别的事情,需要暂时暂停一下双方的时间,这个时候可以按S6暂停按键来暂停双方的计时,忙完后再次按下暂停按键会继续倒计时。任何一方的时间走完,都会蜂鸣器长鸣提示。
(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_voice_long   900   //蜂鸣器长叫的持续时间

  4. #define const_key_time  10    //按键去抖动延时的时间

  5. #define const_1s     422   //产生一秒钟的时间基准

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. void T0_time();  //定时中断函数
  11. void key_service();
  12. void key_scan(); //按键扫描函数 放在定时中断里

  13. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  14. void display_drive();  //放在定时中断里的数码管驱动函数
  15. void time_service();  //放在定时中断里的时间应用程序
  16. void display_service();  


  17. sbit key_sr1=P0^0; //第一行输入
  18. sbit key_sr2=P0^1; //第二行输入
  19. sbit key_sr3=P0^2; //第三行输入
  20. sbit key_sr4=P0^3; //第四行输入

  21. sbit key_dr1=P0^4; //第一列输出
  22. sbit key_dr2=P0^5; //第二列输出
  23. sbit key_dr3=P0^6; //第三列输出
  24. sbit key_dr4=P0^7; //第四列输出

  25. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口


  26. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停


  27. sbit dig_hc595_sh_dr=P2^0;     //数码管 的74HC595程序
  28. sbit dig_hc595_st_dr=P2^1;  
  29. sbit dig_hc595_ds_dr=P2^2;  

  30. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  31. sbit hc595_st_dr=P2^4;  
  32. sbit hc595_ds_dr=P2^5;  


  33. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  34. unsigned char ucKeySec=0;   //被触发的按键编号
  35. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  36. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  37. unsigned char ucRowRecord=1; //记录当前扫描到第几列了

  38. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  39. unsigned char ucDigShow8=0;  //第8位数码管要显示的内容
  40. unsigned char ucDigShow7=0;  //第7位数码管要显示的内容
  41. unsigned char ucDigShow6=0;  //第6位数码管要显示的内容
  42. unsigned char ucDigShow5=0;  //第5位数码管要显示的内容
  43. unsigned char ucDigShow4=0;  //第4位数码管要显示的内容
  44. unsigned char ucDigShow3=0;  //第3位数码管要显示的内容
  45. unsigned char ucDigShow2=0;  //第2位数码管要显示的内容
  46. unsigned char ucDigShow1=0;  //第1位数码管要显示的内容
  47. unsigned char ucDigDot3=1;  //数码管3的小数点是否显示的标志
  48. unsigned char ucDigDot7=1;  //数码管7的小数点是否显示的标志

  49. unsigned char ucDigShowTemp=0; //临时中间变量

  50. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  51. unsigned int uiRedTimeCnt=0;    //红棋产生秒基准的时间计时器
  52. unsigned int uiBlackTimeCnt=0;  //黑棋产生秒基准的时间计时器

  53. unsigned int uiRedTotal=1200;    //红棋的总时间
  54. unsigned int uiBlackTotal=1200;  //黑棋的总时间

  55. unsigned char ucRedFlag=0;  //红棋是否开始计时的标志
  56. unsigned char ucBlackFlag=0;  //黑棋是否开始计时的标志

  57. unsigned char ucDisplayUpdate=1; //更新显示标志

  58. /* 注释一:
  59. *  ucWd变量是本程序最核心的变量,代表显示哪一个窗口和系统处于当前哪种状态
  60. */
  61. unsigned char ucWd=1;

  62. code unsigned char dig_table[]=
  63. {
  64. 0x3f,  //0       序号0
  65. 0x06,  //1       序号1
  66. 0x5b,  //2       序号2
  67. 0x4f,  //3       序号3
  68. 0x66,  //4       序号4
  69. 0x6d,  //5       序号5
  70. 0x7d,  //6       序号6
  71. 0x07,  //7       序号7
  72. 0x7f,  //8       序号8
  73. 0x6f,  //9       序号9
  74. 0x00,  //不显示  序号10
  75. };

  76. void main()
  77.   {
  78.    initial_myself();  
  79.    delay_long(100);   
  80.    initial_peripheral();
  81.    while(1)  
  82.    {
  83.        key_service();
  84.        display_service();  
  85.    }

  86. }


  87. void time_service()  //放在定时中断里的时间应用程序
  88. {
  89.   if(ucRedFlag==1)  //1代表红棋在运行中
  90.   {
  91.      uiRedTimeCnt++;
  92.          if(uiRedTimeCnt>const_1s)
  93.          {
  94.         uiRedTimeCnt=0;
  95.         if(uiRedTotal>0)
  96.                 {
  97.                    uiRedTotal--;
  98.                 }
  99.                 else  //时间到
  100.                 {
  101.                     ucRedFlag=0;    //红棋和黑棋同时停止计时
  102.                    ucBlackFlag=0;
  103.                    ucWd=1;  //切换到第一个窗口的状态
  104.                    uiVoiceCnt=const_voice_long; //报警声音触发
  105.                 }
  106.                

  107.         ucDisplayUpdate=1;  //更新显示
  108.          }
  109.   }


  110.   if(ucBlackFlag==1)  //1代表黑棋在运行中
  111.   {
  112.      uiBlackTimeCnt++;
  113.          if(uiBlackTimeCnt>const_1s)
  114.          {
  115.         uiBlackTimeCnt=0;
  116.         if(uiBlackTotal>0)
  117.                 {
  118.                    uiBlackTotal--;
  119.                 }
  120.                 else  //时间到
  121.                 {
  122.                     ucRedFlag=0;  //红棋和黑棋同时停止计时
  123.                    ucBlackFlag=0;
  124.                    ucWd=1;  //切换到第一个窗口的状态
  125.                    uiVoiceCnt=const_voice_long; //报警声音触发
  126.                 }
  127.                

  128.         ucDisplayUpdate=1;  //更新显示
  129.          }
  130.   }
  131. }

  132. void display_service()  //放在定时中断里的显示应用程序
  133. {
  134.   if(ucDisplayUpdate==1)  //有数据更新显示
  135.   {
  136.      ucDisplayUpdate=0;
  137.          switch(ucWd)     //本程序最核心的变量ucWd
  138.          {
  139.            case 1:  //窗口1,代表刚上电或者复位后的状态
  140.                       //红棋分解出分
  141.                        ucDigShowTemp=uiRedTotal/60;
  142.             ucDigShow8=ucDigShowTemp/10;
  143.             ucDigShow7=ucDigShowTemp%10;

  144.                        //红棋分解出秒
  145.                     ucDigShowTemp=uiRedTotal%60;
  146.             ucDigShow6=ucDigShowTemp/10;
  147.             ucDigShow5=ucDigShowTemp%10;
  148.                         ucDigDot7=1;  //数码管7的小数点显示

  149.                       //黑棋分解出分
  150.                        ucDigShowTemp=uiBlackTotal/60;
  151.             ucDigShow4=ucDigShowTemp/10;
  152.             ucDigShow3=ucDigShowTemp%10;

  153.                        //黑棋分解出秒
  154.                     ucDigShowTemp=uiBlackTotal%60;
  155.             ucDigShow2=ucDigShowTemp/10;
  156.             ucDigShow1=ucDigShowTemp%10;
  157.                         ucDigDot3=1;  //数码管3的小数点显示

  158.             led_dr=1;  //计时器处于停止状态,LED亮

  159.                 break;
  160.            case 2:  //窗口2,代表黑棋正在运行中的状态

  161.                       //红棋全部不显示
  162.             ucDigShow8=10;
  163.             ucDigShow7=10;
  164.             ucDigShow6=10;
  165.             ucDigShow5=10;
  166.                         ucDigDot7=0;  //数码管7的小数点不显示

  167.                       //黑棋分解出分
  168.                        ucDigShowTemp=uiBlackTotal/60;
  169.             ucDigShow4=ucDigShowTemp/10;
  170.             ucDigShow3=ucDigShowTemp%10;

  171.                        //黑棋分解出秒
  172.                     ucDigShowTemp=uiBlackTotal%60;
  173.             ucDigShow2=ucDigShowTemp/10;
  174.             ucDigShow1=ucDigShowTemp%10;
  175.                         ucDigDot3=1;  //数码管3的小数点显示

  176.             led_dr=0;  //计时器处于计时状态,LED灭

  177.                 break;

  178.            case 3:  //窗口3,代表黑棋在中途暂停的状态

  179.                       //红棋全部不显示
  180.             ucDigShow8=10;
  181.             ucDigShow7=10;
  182.             ucDigShow6=10;
  183.             ucDigShow5=10;
  184.                         ucDigDot7=0;  //数码管7的小数点不显示

  185.                       //黑棋分解出分
  186.                        ucDigShowTemp=uiBlackTotal/60;
  187.             ucDigShow4=ucDigShowTemp/10;
  188.             ucDigShow3=ucDigShowTemp%10;

  189.                        //黑棋分解出秒
  190.                     ucDigShowTemp=uiBlackTotal%60;
  191.             ucDigShow2=ucDigShowTemp/10;
  192.             ucDigShow1=ucDigShowTemp%10;
  193.                         ucDigDot3=1;  //数码管3的小数点显示


  194.             led_dr=1;  //计时器处于暂停状态,LED亮

  195.                 break;
  196.            case 4:  //窗口4,代表红棋正在运行中的状态
  197.                       //红棋分解出分
  198.                        ucDigShowTemp=uiRedTotal/60;
  199.             ucDigShow8=ucDigShowTemp/10;
  200.             ucDigShow7=ucDigShowTemp%10;

  201.                        //红棋分解出秒
  202.                     ucDigShowTemp=uiRedTotal%60;
  203.             ucDigShow6=ucDigShowTemp/10;
  204.             ucDigShow5=ucDigShowTemp%10;
  205.                         ucDigDot7=1;  //数码管7的小数点显示


  206.                       //黑棋全部不显示
  207.             ucDigShow4=10;
  208.             ucDigShow3=10;
  209.             ucDigShow2=10;
  210.             ucDigShow1=10;
  211.                         ucDigDot3=0;  //数码管3的小数点不显示

  212.             led_dr=0;  //计时器处于倒计时状态,LED灭

  213.                 break;

  214.            case 5:  //窗口5,代表红棋在中途暂停的状态
  215.                       //红棋分解出分
  216.                        ucDigShowTemp=uiRedTotal/60;
  217.             ucDigShow8=ucDigShowTemp/10;
  218.             ucDigShow7=ucDigShowTemp%10;

  219.                        //红棋分解出秒
  220.                     ucDigShowTemp=uiRedTotal%60;
  221.             ucDigShow6=ucDigShowTemp/10;
  222.             ucDigShow5=ucDigShowTemp%10;
  223.                         ucDigDot7=1;  //数码管7的小数点显示


  224.                       //黑棋全部不显示
  225.             ucDigShow4=10;
  226.             ucDigShow3=10;
  227.             ucDigShow2=10;
  228.             ucDigShow1=10;
  229.                         ucDigDot3=0;  //数码管3的小数点不显示

  230.             led_dr=1;  //计时器处于暂停状态,LED亮

  231.                 break;
  232.          }
  233.   }
  234. }

  235. void display_drive()  //放在定时中断里的数码管驱动函数
  236. {
  237.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  238.    switch(ucDisplayDriveStep)
  239.    {
  240.       case 1:  //显示第1位
  241.            ucDigShowTemp=dig_table[ucDigShow1];
  242.            dig_hc595_drive(ucDigShowTemp,0xfe);
  243.                break;
  244.       case 2:  //显示第2位
  245.            ucDigShowTemp=dig_table[ucDigShow2];
  246.            dig_hc595_drive(ucDigShowTemp,0xfd);
  247.                break;
  248.       case 3:  //显示第3位
  249.            ucDigShowTemp=dig_table[ucDigShow3];
  250.                    if(ucDigDot3==1)
  251.                    {
  252.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  253.                    }
  254.            dig_hc595_drive(ucDigShowTemp,0xfb);
  255.                break;
  256.       case 4:  //显示第4位
  257.            ucDigShowTemp=dig_table[ucDigShow4];
  258.            dig_hc595_drive(ucDigShowTemp,0xf7);
  259.                break;
  260.       case 5:  //显示第5位
  261.            ucDigShowTemp=dig_table[ucDigShow5];
  262.            dig_hc595_drive(ucDigShowTemp,0xef);
  263.                break;
  264.       case 6:  //显示第6位
  265.            ucDigShowTemp=dig_table[ucDigShow6];
  266.            dig_hc595_drive(ucDigShowTemp,0xdf);
  267.                break;
  268.       case 7:  //显示第7位
  269.            ucDigShowTemp=dig_table[ucDigShow7];
  270.                    if(ucDigDot7==1)
  271.                    {
  272.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  273.            }
  274.            dig_hc595_drive(ucDigShowTemp,0xbf);
  275.                break;
  276.       case 8:  //显示第8位
  277.            ucDigShowTemp=dig_table[ucDigShow8];
  278.            dig_hc595_drive(ucDigShowTemp,0x7f);
  279.                break;
  280.    }

  281.    ucDisplayDriveStep++;
  282.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  283.    {
  284.      ucDisplayDriveStep=1;
  285.    }
  286. }


  287. //数码管的74HC595驱动函数
  288. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  289. {
  290.    unsigned char i;
  291.    unsigned char ucTempData;
  292.    dig_hc595_sh_dr=0;
  293.    dig_hc595_st_dr=0;

  294.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  295.    for(i=0;i<8;i++)
  296.    {
  297.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  298.          else dig_hc595_ds_dr=0;

  299. /* 注释二:
  300. *  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
  301. */
  302.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  303.          delay_short(1);
  304.          dig_hc595_sh_dr=1;
  305.          delay_short(1);

  306.          ucTempData=ucTempData<<1;
  307.    }

  308.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  309.    for(i=0;i<8;i++)
  310.    {
  311.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  312.          else dig_hc595_ds_dr=0;

  313.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  314.          delay_short(1);
  315.          dig_hc595_sh_dr=1;
  316.          delay_short(1);

  317.          ucTempData=ucTempData<<1;
  318.    }

  319.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  320.    delay_short(1);
  321.    dig_hc595_st_dr=1;
  322.    delay_short(1);

  323.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  324.    dig_hc595_st_dr=0;
  325.    dig_hc595_ds_dr=0;

  326. }


  327. //LED灯的74HC595驱动函数
  328. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  329. {
  330.    unsigned char i;
  331.    unsigned char ucTempData;
  332.    hc595_sh_dr=0;
  333.    hc595_st_dr=0;

  334.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  335.    for(i=0;i<8;i++)
  336.    {
  337.          if(ucTempData>=0x80)hc595_ds_dr=1;
  338.          else hc595_ds_dr=0;

  339.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  340.          delay_short(1);
  341.          hc595_sh_dr=1;
  342.          delay_short(1);

  343.          ucTempData=ucTempData<<1;
  344.    }

  345.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  346.    for(i=0;i<8;i++)
  347.    {
  348.          if(ucTempData>=0x80)hc595_ds_dr=1;
  349.          else hc595_ds_dr=0;

  350.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  351.          delay_short(1);
  352.          hc595_sh_dr=1;
  353.          delay_short(1);

  354.          ucTempData=ucTempData<<1;
  355.    }

  356.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  357.    delay_short(1);
  358.    hc595_st_dr=1;
  359.    delay_short(1);

  360.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  361.    hc595_st_dr=0;
  362.    hc595_ds_dr=0;

  363. }


  364. void key_scan()//按键扫描函数 放在定时中断里
  365. {  

  366.   switch(ucKeyStep)
  367.   {
  368.      case 1:   //按键扫描输出第ucRowRecord列低电平
  369.               if(ucRowRecord==1)  //第一列输出低电平
  370.                   {
  371.              key_dr1=0;      
  372.              key_dr2=1;
  373.              key_dr3=1;   
  374.              key_dr4=1;
  375.                   }
  376.               else if(ucRowRecord==2)  //第二列输出低电平
  377.                   {
  378.              key_dr1=1;      
  379.              key_dr2=0;
  380.              key_dr3=1;   
  381.              key_dr4=1;
  382.                   }
  383.               else if(ucRowRecord==3)  //第三列输出低电平
  384.                   {
  385.              key_dr1=1;      
  386.              key_dr2=1;
  387.              key_dr3=0;   
  388.              key_dr4=1;
  389.                   }
  390.               else   //第四列输出低电平
  391.                   {
  392.              key_dr1=1;      
  393.              key_dr2=1;
  394.              key_dr3=1;   
  395.              key_dr4=0;
  396.                   }

  397.           uiKeyTimeCnt=0;  //延时计数器清零
  398.           ucKeyStep++;     //切换到下一个运行步骤
  399.               break;

  400.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  401.           uiKeyTimeCnt++;
  402.                   if(uiKeyTimeCnt>1)
  403.                   {
  404.                      uiKeyTimeCnt=0;
  405.              ucKeyStep++;     //切换到下一个运行步骤
  406.                   }
  407.               break;

  408.      case 3:
  409.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  410.           {  
  411.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  412.              ucKeyLock=0;  //按键自锁标志清零
  413.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  414.    
  415.                          ucRowRecord++;  //输出下一列
  416.                          if(ucRowRecord>4)  
  417.                          {
  418.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  419.                          }

  420.           }
  421.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  422.                   {
  423.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  424.                          {
  425.                             uiKeyTimeCnt++;  //去抖动延时计数器
  426.                                 if(uiKeyTimeCnt>const_key_time)
  427.                                 {
  428.                                    uiKeyTimeCnt=0;
  429.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  430.                        if(ucRowRecord==1)  //第一列输出低电平
  431.                            {
  432.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  433.                            }
  434.                        else if(ucRowRecord==2)  //第二列输出低电平
  435.                            {
  436.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  437.                            }
  438.                        else if(ucRowRecord==3)  //第三列输出低电平
  439.                            {
  440.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  441.                            }
  442.                        else   //第四列输出低电平
  443.                            {
  444.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  445.                            }

  446.                                 }
  447.                         
  448.                          }
  449.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  450.                          {
  451.                             uiKeyTimeCnt++;  //去抖动延时计数器
  452.                                 if(uiKeyTimeCnt>const_key_time)
  453.                                 {
  454.                                    uiKeyTimeCnt=0;
  455.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  456.                        if(ucRowRecord==1)  //第一列输出低电平
  457.                            {
  458.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  459.                            }
  460.                        else if(ucRowRecord==2)  //第二列输出低电平
  461.                            {
  462.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  463.                            }
  464.                        else if(ucRowRecord==3)  //第三列输出低电平
  465.                            {
  466.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  467.                            }
  468.                        else   //第四列输出低电平
  469.                            {
  470.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  471.                            }
  472.                                 }
  473.                         
  474.                          }
  475.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  476.                          {
  477.                             uiKeyTimeCnt++;  //去抖动延时计数器
  478.                                 if(uiKeyTimeCnt>const_key_time)
  479.                                 {
  480.                                    uiKeyTimeCnt=0;
  481.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  482.                        if(ucRowRecord==1)  //第一列输出低电平
  483.                            {
  484.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  485.                            }
  486.                        else if(ucRowRecord==2)  //第二列输出低电平
  487.                            {
  488.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  489.                            }
  490.                        else if(ucRowRecord==3)  //第三列输出低电平
  491.                            {
  492.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  493.                            }
  494.                        else   //第四列输出低电平
  495.                            {
  496.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  497.                            }
  498.                                 }
  499.                         
  500.                          }
  501.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  502.                          {
  503.                             uiKeyTimeCnt++;  //去抖动延时计数器
  504.                                 if(uiKeyTimeCnt>const_key_time)
  505.                                 {
  506.                                    uiKeyTimeCnt=0;
  507.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  508.                        if(ucRowRecord==1)  //第一列输出低电平
  509.                            {
  510.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  511.                            }
  512.                        else if(ucRowRecord==2)  //第二列输出低电平
  513.                            {
  514.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  515.                            }
  516.                        else if(ucRowRecord==3)  //第三列输出低电平
  517.                            {
  518.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  519.                            }
  520.                        else   //第四列输出低电平
  521.                            {
  522.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  523.                            }
  524.                                 }
  525.                         
  526.                          }
  527.                   
  528.                   }
  529.               break;

  530.   }


  531. }

  532. /* 注释三:
  533. *  按键服务程序操作的精髓在于根据当前系统处于什么窗口下就执行什么操作。
  534. *  紧紧围绕着不同的窗口ucWd来执行不同的操作。
  535. */
  536. void key_service() //第三区 放在定时中断里的按键服务应用程序
  537. {
  538.   switch(ucKeySec) //按键服务状态切换
  539.   {
  540.     case 1:// 1号键 对应朱兆祺学习板的S1键  红棋加分 按键
  541.               switch(ucWd)  //本程序最核心的变量ucWd
  542.               {
  543.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  544.                               uiRedTotal=uiRedTotal+60;  //加红棋分的时间,此处60秒代表一分
  545.                                   if(uiRedTotal>5940)
  546.                                   {
  547.                                      uiRedTotal=5940;
  548.                                   }
  549.                   uiRedTotal=uiRedTotal-(uiRedTotal%60);  //去秒取整分

  550.                                   ucDisplayUpdate=1;  //更新显示
  551.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  552.                       break;

  553.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  554.                       break;

  555.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  556.                       break;

  557.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  558.                       break;

  559.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  560.                       break;

  561.           }
  562.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  563.           break;        

  564.     case 2:// 2号键 对应朱兆祺学习板的S2键  红棋减分 按键
  565.               switch(ucWd)  //本程序最核心的变量ucWd
  566.               {
  567.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  568.                               if(uiRedTotal>=60)
  569.                                   {
  570.                                  uiRedTotal=uiRedTotal-60;  //减红棋分的时间,此处60秒代表一分
  571.                                   }
  572.                   uiRedTotal=uiRedTotal-(uiRedTotal%60);  //去秒取整分

  573.                                   ucDisplayUpdate=1;  //更新显示
  574.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  575.                       break;

  576.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  577.                       break;

  578.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  579.                       break;

  580.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  581.                       break;

  582.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  583.                       break;

  584.           }
  585.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  586.           break;     

  587.     case 3:// 3号键 对应朱兆祺学习板的S3键  黑棋加分 按键
  588.               switch(ucWd)  //本程序最核心的变量ucWd
  589.               {
  590.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  591.                               uiBlackTotal=uiBlackTotal+60;  //加黑棋分的时间,此处60秒代表一分
  592.                                   if(uiBlackTotal>5940)
  593.                                   {
  594.                                      uiBlackTotal=5940;
  595.                                   }
  596.                   uiBlackTotal=uiBlackTotal-(uiBlackTotal%60);  //去秒取整分

  597.                                   ucDisplayUpdate=1;  //更新显示
  598.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  599.                       break;

  600.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  601.                       break;

  602.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  603.                       break;

  604.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  605.                       break;

  606.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  607.                       break;

  608.           }
  609.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  610.           break;         

  611.     case 4:// 4号键 对应朱兆祺学习板的S4键  黑棋减分 按键
  612.               switch(ucWd)  //本程序最核心的变量ucWd
  613.               {
  614.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  615.                               if(uiBlackTotal>=60)
  616.                                   {
  617.                                  uiBlackTotal=uiBlackTotal-60;  //减黑棋分的时间,此处60秒代表一分
  618.                                   }
  619.                   uiBlackTotal=uiBlackTotal-(uiBlackTotal%60);  //去秒取整分

  620.                                   ucDisplayUpdate=1;  //更新显示
  621.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  622.                       break;

  623.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  624.                       break;

  625.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  626.                       break;

  627.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  628.                       break;

  629.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  630.                       break;

  631.           }
  632.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  633.           break;   

  634.     case 5:// 5号键 对应朱兆祺学习板的S5键


  635.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  636.           break;   

  637.     case 6:// 6号键 对应朱兆祺学习板的S6键  中途暂停和启动按键
  638.               switch(ucWd)  //本程序最核心的变量ucWd
  639.               {
  640.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          

  641.                       break;

  642.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  643.                   ucRedFlag=0;    //暂停计时
  644.                   ucBlackFlag=0;//暂停计时
  645.                                   ucWd=3; //切换到黑棋中途暂停的状态

  646.                                   ucDisplayUpdate=1;  //更新显示
  647.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  648.                       break;

  649.                  case 3:  //窗口3,代表黑棋在中途暂停的状态
  650.                   ucRedFlag=0;   //红棋暂停计时
  651.                   ucBlackFlag=1; //黑棋继续计时
  652.                                   ucWd=2;       //切换到黑棋正在运行中的状态

  653.                                   ucDisplayUpdate=1;  //更新显示
  654.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  655.                       break;

  656.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  657.                   ucRedFlag=0;    //暂停计时
  658.                   ucBlackFlag=0;//暂停计时
  659.                                   ucWd=5;       //切换到红棋中途暂停的状态

  660.                                   ucDisplayUpdate=1;  //更新显示
  661.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  662.                       break;

  663.                  case 5:  //窗口5,代表红棋在中途暂停的状态
  664.                   ucRedFlag=1;   //红棋继续计时
  665.                   ucBlackFlag=0; //黑棋暂停计时
  666.                                   ucWd=4;       //切换到红棋正在运行中的状态

  667.                                   ucDisplayUpdate=1;  //更新显示
  668.                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。

  669.                       break;

  670.           }
  671.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  672.           break;   

  673.     case 7:// 7号键 对应朱兆祺学习板的S7键  在第一个窗口下,把计时器的值恢复为开机时的默认值20分钟
  674.               switch(ucWd)  //本程序最核心的变量ucWd
  675.               {
  676.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  677.                   uiRedTotal=1200;    //红棋的总时间
  678.                   uiBlackTotal=1200;  //黑棋的总时间

  679.                                   ucDisplayUpdate=1;  //更新显示
  680.                                   uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  681.                       break;

  682.                  case 2:  //窗口2,代表黑棋正在运行中的状态       

  683.                       break;

  684.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  685.                       break;

  686.                  case 4:  //窗口4,代表红棋正在运行中的状态          

  687.                       break;

  688.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  689.                       break;

  690.           }
  691.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  692.           break;   
  693.     case 8:// 8号键 对应朱兆祺学习板的S8键

  694.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  695.           break;   
  696.     case 9:// 9号键 对应朱兆祺学习板的S9键

  697.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  698.           break;   
  699.     case 10:// 10号键 对应朱兆祺学习板的S10键

  700.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  701.           break;   
  702.     case 11:// 11号键 对应朱兆祺学习板的S11键

  703.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  704.           break;   
  705.     case 12:// 12号键 对应朱兆祺学习板的S12键

  706.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  707.           break;   
  708.     case 13:// 13号键 对应朱兆祺学习板的S13键  红棋按下
  709.               switch(ucWd)  //本程序最核心的变量ucWd
  710.               {
  711.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  712.                   ucRedFlag=0;    //红棋暂停计时
  713.                   ucBlackFlag=1;  //黑棋继续计时
  714.                                   ucWd=2; //切换到黑棋正在运行中的状态

  715.                                   ucDisplayUpdate=1;  //更新显示
  716.                       break;

  717.                  case 2:  //窗口2,代表黑棋正在运行中的状态       

  718.                       break;

  719.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  720.                       break;

  721.                  case 4:  //窗口4,代表红棋正在运行中的状态          
  722.                   ucRedFlag=0;    //红棋暂停计时
  723.                   ucBlackFlag=1;  //黑棋继续计时
  724.                                   ucWd=2; //切换到黑棋正在运行中的状态

  725.                                   ucDisplayUpdate=1;  //更新显示
  726.                       break;

  727.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  728.                       break;

  729.           }

  730.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  731.           break;   
  732.     case 14:// 14号键 对应朱兆祺学习板的S14键

  733.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  734.           break;   
  735.     case 15:// 15号键 对应朱兆祺学习板的S15键

  736.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  737.           break;   

  738.     case 16:// 16号键 对应朱兆祺学习板的S16键    黑棋按下
  739.               switch(ucWd)  //本程序最核心的变量ucWd
  740.               {
  741.                  case 1:  //窗口1,代表刚上电,完成或者复位后的状态          
  742.                   ucRedFlag=1;    //红棋继续计时
  743.                   ucBlackFlag=0;  //黑棋暂停计时
  744.                                   ucWd=4; //切换到红棋正在运行中的状态

  745.                                   ucDisplayUpdate=1;  //更新显示
  746.                       break;

  747.                  case 2:  //窗口2,代表黑棋正在运行中的状态       
  748.                   ucRedFlag=1;    //红棋继续计时
  749.                   ucBlackFlag=0;  //黑棋暂停计时
  750.                                   ucWd=4; //切换到红棋正在运行中的状态

  751.                                   ucDisplayUpdate=1;  //更新显示
  752.                       break;

  753.                  case 3:  //窗口3,代表黑棋在中途暂停的状态

  754.                       break;

  755.                  case 4:  //窗口4,代表红棋正在运行中的状态          

  756.                       break;

  757.                  case 5:  //窗口5,代表红棋在中途暂停的状态

  758.                       break;

  759.           }
  760.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  761.           break;   
  762.   }               
  763. }



  764. void T0_time() interrupt 1
  765. {
  766.   TF0=0;  //清除中断标志
  767.   TR0=0; //关中断
  768.   key_scan(); //放在定时中断里的按键扫描函数
  769.   time_service();  //放在定时中断里的时间应用程序

  770.   if(uiVoiceCnt!=0)
  771.   {
  772.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  773.          beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  774.   }
  775.   else
  776.   {
  777.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  778.            beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  779.   }

  780.   display_drive();  //放在定时中断里的数码管驱动函数

  781. /* 注释四:
  782. *  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
  783. */
  784.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  785.   TL0=0x0b;
  786.   TR0=1;  //开中断
  787. }

  788. void delay_short(unsigned int uiDelayShort)
  789. {
  790.    unsigned int i;  
  791.    for(i=0;i<uiDelayShort;i++)
  792.    {
  793.      ;   //一个分号相当于执行一条空语句
  794.    }
  795. }


  796. void delay_long(unsigned int uiDelayLong)
  797. {
  798.    unsigned int i;
  799.    unsigned int j;
  800.    for(i=0;i<uiDelayLong;i++)
  801.    {
  802.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  803.           {
  804.              ; //一个分号相当于执行一条空语句
  805.           }
  806.    }
  807. }


  808. void initial_myself()  //第一区 初始化单片机
  809. {

  810.   led_dr=1;
  811.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  812.   hc595_drive(0x00,0x00);
  813.   TMOD=0x01;  //设置定时器0为工作方式1

  814.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  815.   TL0=0x0b;
  816. }
  817. void initial_peripheral() //第二区 初始化外围
  818. {


  819.   EA=1;     //开总中断
  820.   ET0=1;    //允许定时中断
  821.   TR0=1;    //启动定时中断



  822. }
复制代码

总结陈词:
这节讲了象棋比赛专用计时器的项目程序。为了继续加深读者理解按键和显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----带数码管显示的加法简易计算器。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

37
 
第三十六节:带数码管显示的加法简易计算器。

开场白:
   这一节要做一个简单的计算器。这个计算器不带小数点,只能进行不超过8位数据的加法运算,它麻雀虽小但是五脏俱全,它能清晰地勾勒出商业计算器的程序框架和思路。读者只要看懂本节程序框架的规律,以后自己想做一个复杂一点的计算器应该是没问题的。复杂的计算器在算法上要用数组进行特殊处理,不能简单地直接用C语言的+,-,*,/运算符,这方面的内容我会在以后的章节中跟大家分享。
这一节要教会大家两个知识点:
第一个:数字按键的输入和十进制数值的移位方法。
第二个:继续加深理解按键与数码管的关联程序框架。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。加号键对应S13,等于号键对应S14,清除复位按键对应S16。其它按键不用。

(2)实现功能:
常用的加法计算器功能。有连加功能。
本程序有2个窗口。
第1个窗口:原始数据和运算结果窗口。  比如加法运算中的被加数
第2个窗口:第二个参与运行的数据窗口。比如加法运算中的加数

(3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_voice_long   900   //蜂鸣器长叫的持续时间

  4. #define const_key_time  10    //按键去抖动延时的时间

  5. #define const_1s     422   //产生一秒钟的时间基准

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);
  10. void T0_time();  //定时中断函数
  11. void key_service();
  12. void key_scan(); //按键扫描函数 放在定时中断里

  13. void number_key_input(unsigned long ucWhichKey);  //由于数字按键的代码相似度高,因此封装在这个函数里

  14. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);
  15. void display_drive();  //放在定时中断里的数码管驱动函数
  16. void display_service();  


  17. sbit key_sr1=P0^0; //第一行输入
  18. sbit key_sr2=P0^1; //第二行输入
  19. sbit key_sr3=P0^2; //第三行输入
  20. sbit key_sr4=P0^3; //第四行输入

  21. sbit key_dr1=P0^4; //第一列输出
  22. sbit key_dr2=P0^5; //第二列输出
  23. sbit key_dr3=P0^6; //第三列输出
  24. sbit key_dr4=P0^7; //第四列输出

  25. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口


  26. sbit led_dr=P3^5; //LED指示灯


  27. sbit dig_hc595_sh_dr=P2^0;     //数码管 的74HC595程序
  28. sbit dig_hc595_st_dr=P2^1;  
  29. sbit dig_hc595_ds_dr=P2^2;  

  30. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  31. sbit hc595_st_dr=P2^4;  
  32. sbit hc595_ds_dr=P2^5;  


  33. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  34. unsigned char ucKeySec=0;   //被触发的按键编号
  35. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  36. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  37. unsigned char ucRowRecord=1; //记录当前扫描到第几列了

  38. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  39. unsigned char ucDigShow8=0;  //第8位数码管要显示的内容
  40. unsigned char ucDigShow7=0;  //第7位数码管要显示的内容
  41. unsigned char ucDigShow6=0;  //第6位数码管要显示的内容
  42. unsigned char ucDigShow5=0;  //第5位数码管要显示的内容
  43. unsigned char ucDigShow4=0;  //第4位数码管要显示的内容
  44. unsigned char ucDigShow3=0;  //第3位数码管要显示的内容
  45. unsigned char ucDigShow2=0;  //第2位数码管要显示的内容
  46. unsigned char ucDigShow1=0;  //第1位数码管要显示的内容


  47. unsigned char ucDigShowTemp=0; //临时中间变量

  48. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  49. unsigned char ucDisplayUpdate=1; //更新显示标志

  50. unsigned long ulSource=0;  //原始数据    比如在加法运算中的被加数
  51. unsigned long ulOther=0; //另外一个参与运算的数据  比如在加法运算中的加数
  52. unsigned long ulResult=0; //运算结果
  53. unsigned char ucOperator=0; //运行符号。0代表当前没有选择运行符号。1代表当前的运算符是加法。

  54. /* 注释一:
  55. *  ucWd变量是本程序最核心的变量,代表数码管显示哪一个窗口
  56. *  本程序只有两个窗口,他们分别是:
  57. *  第一个窗口:原始数据和运算结果窗口。  比如加法运算中的被加数
  58. *  第二个窗口:第二个参与运行的数据窗口。比如加法运算中的加数
  59. */
  60. unsigned char ucWd=1;

  61. code unsigned char dig_table[]=
  62. {
  63. 0x3f,  //0       序号0
  64. 0x06,  //1       序号1
  65. 0x5b,  //2       序号2
  66. 0x4f,  //3       序号3
  67. 0x66,  //4       序号4
  68. 0x6d,  //5       序号5
  69. 0x7d,  //6       序号6
  70. 0x07,  //7       序号7
  71. 0x7f,  //8       序号8
  72. 0x6f,  //9       序号9
  73. 0x00,  //不显示  序号10
  74. };

  75. void main()
  76.   {
  77.    initial_myself();  
  78.    delay_long(100);   
  79.    initial_peripheral();
  80.    while(1)  
  81.    {
  82.        key_service();
  83.        display_service();  
  84.    }

  85. }



  86. void display_service()  //放在定时中断里的显示应用程序
  87. {
  88.   if(ucDisplayUpdate==1)  //有数据更新显示
  89.   {
  90.      ucDisplayUpdate=0;
  91.          switch(ucWd)     //本程序最核心的变量ucWd
  92.          {
  93.            case 1:  //窗口1  原始数据和运算结果窗口
  94.                 if(ulSource>=10000000)
  95.                                 {
  96.                                    ucDigShow8=ulSource/10000000;
  97.                                 }
  98.                                 else
  99.                                 {
  100.                                ucDigShow8=10;//数据显示空
  101.                                 }


  102.                 if(ulSource>=1000000)
  103.                                 {
  104.                                    ucDigShow7=ulSource%10000000/1000000;
  105.                                 }
  106.                                 else
  107.                                 {
  108.                                ucDigShow7=10;//数据显示空
  109.                                 }


  110.                 if(ulSource>=100000)
  111.                                 {
  112.                                    ucDigShow6=ulSource%1000000/100000;
  113.                                 }
  114.                                 else
  115.                                 {
  116.                                ucDigShow6=10;//数据显示空
  117.                                 }

  118.                 if(ulSource>=10000)
  119.                                 {
  120.                                    ucDigShow5=ulSource%100000/10000;
  121.                                 }
  122.                                 else
  123.                                 {
  124.                                ucDigShow5=10;//数据显示空
  125.                                 }

  126.                 if(ulSource>=1000)
  127.                                 {
  128.                                    ucDigShow4=ulSource%10000/1000;
  129.                                 }
  130.                                 else
  131.                                 {
  132.                                ucDigShow4=10;//数据显示空
  133.                                 }

  134.                 if(ulSource>=100)
  135.                                 {
  136.                                    ucDigShow3=ulSource%1000/100;
  137.                                 }
  138.                                 else
  139.                                 {
  140.                                ucDigShow3=10;//数据显示空
  141.                                 }

  142.                 if(ulSource>=10)
  143.                                 {
  144.                                    ucDigShow2=ulSource%100/10;
  145.                                 }
  146.                                 else
  147.                                 {
  148.                                ucDigShow2=10;//数据显示空
  149.                                 }

  150.                                 ucDigShow1=ulSource%10;

  151.                 break;
  152.            case 2:  //窗口2  第二个参与运算数据的窗口  比如加法运算中的加数
  153.                 if(ulOther>=10000000)
  154.                                 {
  155.                                    ucDigShow8=ulOther/10000000;
  156.                                 }
  157.                                 else
  158.                                 {
  159.                                ucDigShow8=10;//数据显示空
  160.                                 }


  161.                 if(ulOther>=1000000)
  162.                                 {
  163.                                    ucDigShow7=ulOther%10000000/1000000;
  164.                                 }
  165.                                 else
  166.                                 {
  167.                                ucDigShow7=10;//数据显示空
  168.                                 }


  169.                 if(ulOther>=100000)
  170.                                 {
  171.                                    ucDigShow6=ulOther%1000000/100000;
  172.                                 }
  173.                                 else
  174.                                 {
  175.                                ucDigShow6=10;//数据显示空
  176.                                 }

  177.                 if(ulOther>=10000)
  178.                                 {
  179.                                    ucDigShow5=ulOther%100000/10000;
  180.                                 }
  181.                                 else
  182.                                 {
  183.                                ucDigShow5=10;//数据显示空
  184.                                 }

  185.                 if(ulOther>=1000)
  186.                                 {
  187.                                    ucDigShow4=ulOther%10000/1000;
  188.                                 }
  189.                                 else
  190.                                 {
  191.                                ucDigShow4=10;//数据显示空
  192.                                 }

  193.                 if(ulOther>=100)
  194.                                 {
  195.                                    ucDigShow3=ulOther%1000/100;
  196.                                 }
  197.                                 else
  198.                                 {
  199.                                ucDigShow3=10;//数据显示空
  200.                                 }

  201.                 if(ulOther>=10)
  202.                                 {
  203.                                    ucDigShow2=ulOther%100/10;
  204.                                 }
  205.                                 else
  206.                                 {
  207.                                ucDigShow2=10;//数据显示空
  208.                                 }

  209.                                 ucDigShow1=ulOther%10;

  210.                 break;
  211.          }
  212.   }
  213. }

  214. void display_drive()  //放在定时中断里的数码管驱动函数
  215. {
  216.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  217.    switch(ucDisplayDriveStep)
  218.    {
  219.       case 1:  //显示第1位
  220.            ucDigShowTemp=dig_table[ucDigShow1];
  221.            dig_hc595_drive(ucDigShowTemp,0xfe);
  222.                break;
  223.       case 2:  //显示第2位
  224.            ucDigShowTemp=dig_table[ucDigShow2];
  225.            dig_hc595_drive(ucDigShowTemp,0xfd);
  226.                break;
  227.       case 3:  //显示第3位
  228.            ucDigShowTemp=dig_table[ucDigShow3];
  229.            dig_hc595_drive(ucDigShowTemp,0xfb);
  230.                break;
  231.       case 4:  //显示第4位
  232.            ucDigShowTemp=dig_table[ucDigShow4];
  233.            dig_hc595_drive(ucDigShowTemp,0xf7);
  234.                break;
  235.       case 5:  //显示第5位
  236.            ucDigShowTemp=dig_table[ucDigShow5];
  237.            dig_hc595_drive(ucDigShowTemp,0xef);
  238.                break;
  239.       case 6:  //显示第6位
  240.            ucDigShowTemp=dig_table[ucDigShow6];
  241.            dig_hc595_drive(ucDigShowTemp,0xdf);
  242.                break;
  243.       case 7:  //显示第7位
  244.            ucDigShowTemp=dig_table[ucDigShow7];
  245.            dig_hc595_drive(ucDigShowTemp,0xbf);
  246.                break;
  247.       case 8:  //显示第8位
  248.            ucDigShowTemp=dig_table[ucDigShow8];
  249.            dig_hc595_drive(ucDigShowTemp,0x7f);
  250.                break;
  251.    }

  252.    ucDisplayDriveStep++;
  253.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  254.    {
  255.      ucDisplayDriveStep=1;
  256.    }
  257. }


  258. //数码管的74HC595驱动函数
  259. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  260. {
  261.    unsigned char i;
  262.    unsigned char ucTempData;
  263.    dig_hc595_sh_dr=0;
  264.    dig_hc595_st_dr=0;

  265.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  266.    for(i=0;i<8;i++)
  267.    {
  268.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  269.          else dig_hc595_ds_dr=0;

  270. /* 注释二:
  271. *  注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。
  272. */
  273.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  274.          delay_short(1);
  275.          dig_hc595_sh_dr=1;
  276.          delay_short(1);

  277.          ucTempData=ucTempData<<1;
  278.    }

  279.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  280.    for(i=0;i<8;i++)
  281.    {
  282.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  283.          else dig_hc595_ds_dr=0;

  284.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  285.          delay_short(1);
  286.          dig_hc595_sh_dr=1;
  287.          delay_short(1);

  288.          ucTempData=ucTempData<<1;
  289.    }

  290.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  291.    delay_short(1);
  292.    dig_hc595_st_dr=1;
  293.    delay_short(1);

  294.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  295.    dig_hc595_st_dr=0;
  296.    dig_hc595_ds_dr=0;

  297. }


  298. //LED灯的74HC595驱动函数
  299. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  300. {
  301.    unsigned char i;
  302.    unsigned char ucTempData;
  303.    hc595_sh_dr=0;
  304.    hc595_st_dr=0;

  305.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  306.    for(i=0;i<8;i++)
  307.    {
  308.          if(ucTempData>=0x80)hc595_ds_dr=1;
  309.          else hc595_ds_dr=0;

  310.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  311.          delay_short(1);
  312.          hc595_sh_dr=1;
  313.          delay_short(1);

  314.          ucTempData=ucTempData<<1;
  315.    }

  316.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  317.    for(i=0;i<8;i++)
  318.    {
  319.          if(ucTempData>=0x80)hc595_ds_dr=1;
  320.          else hc595_ds_dr=0;

  321.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  322.          delay_short(1);
  323.          hc595_sh_dr=1;
  324.          delay_short(1);

  325.          ucTempData=ucTempData<<1;
  326.    }

  327.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  328.    delay_short(1);
  329.    hc595_st_dr=1;
  330.    delay_short(1);

  331.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  332.    hc595_st_dr=0;
  333.    hc595_ds_dr=0;

  334. }


  335. void key_scan()//按键扫描函数 放在定时中断里
  336. {  

  337.   switch(ucKeyStep)
  338.   {
  339.      case 1:   //按键扫描输出第ucRowRecord列低电平
  340.               if(ucRowRecord==1)  //第一列输出低电平
  341.                   {
  342.              key_dr1=0;      
  343.              key_dr2=1;
  344.              key_dr3=1;   
  345.              key_dr4=1;
  346.                   }
  347.               else if(ucRowRecord==2)  //第二列输出低电平
  348.                   {
  349.              key_dr1=1;      
  350.              key_dr2=0;
  351.              key_dr3=1;   
  352.              key_dr4=1;
  353.                   }
  354.               else if(ucRowRecord==3)  //第三列输出低电平
  355.                   {
  356.              key_dr1=1;      
  357.              key_dr2=1;
  358.              key_dr3=0;   
  359.              key_dr4=1;
  360.                   }
  361.               else   //第四列输出低电平
  362.                   {
  363.              key_dr1=1;      
  364.              key_dr2=1;
  365.              key_dr3=1;   
  366.              key_dr4=0;
  367.                   }

  368.           uiKeyTimeCnt=0;  //延时计数器清零
  369.           ucKeyStep++;     //切换到下一个运行步骤
  370.               break;

  371.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  372.           uiKeyTimeCnt++;
  373.                   if(uiKeyTimeCnt>1)
  374.                   {
  375.                      uiKeyTimeCnt=0;
  376.              ucKeyStep++;     //切换到下一个运行步骤
  377.                   }
  378.               break;

  379.      case 3:
  380.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  381.           {  
  382.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  383.              ucKeyLock=0;  //按键自锁标志清零
  384.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  385.    
  386.                          ucRowRecord++;  //输出下一列
  387.                          if(ucRowRecord>4)  
  388.                          {
  389.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  390.                          }

  391.           }
  392.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  393.                   {
  394.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  395.                          {
  396.                             uiKeyTimeCnt++;  //去抖动延时计数器
  397.                                 if(uiKeyTimeCnt>const_key_time)
  398.                                 {
  399.                                    uiKeyTimeCnt=0;
  400.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  401.                        if(ucRowRecord==1)  //第一列输出低电平
  402.                            {
  403.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  404.                            }
  405.                        else if(ucRowRecord==2)  //第二列输出低电平
  406.                            {
  407.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  408.                            }
  409.                        else if(ucRowRecord==3)  //第三列输出低电平
  410.                            {
  411.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  412.                            }
  413.                        else   //第四列输出低电平
  414.                            {
  415.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  416.                            }

  417.                                 }
  418.                         
  419.                          }
  420.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  421.                          {
  422.                             uiKeyTimeCnt++;  //去抖动延时计数器
  423.                                 if(uiKeyTimeCnt>const_key_time)
  424.                                 {
  425.                                    uiKeyTimeCnt=0;
  426.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  427.                        if(ucRowRecord==1)  //第一列输出低电平
  428.                            {
  429.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  430.                            }
  431.                        else if(ucRowRecord==2)  //第二列输出低电平
  432.                            {
  433.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  434.                            }
  435.                        else if(ucRowRecord==3)  //第三列输出低电平
  436.                            {
  437.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  438.                            }
  439.                        else   //第四列输出低电平
  440.                            {
  441.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  442.                            }
  443.                                 }
  444.                         
  445.                          }
  446.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  447.                          {
  448.                             uiKeyTimeCnt++;  //去抖动延时计数器
  449.                                 if(uiKeyTimeCnt>const_key_time)
  450.                                 {
  451.                                    uiKeyTimeCnt=0;
  452.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  453.                        if(ucRowRecord==1)  //第一列输出低电平
  454.                            {
  455.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  456.                            }
  457.                        else if(ucRowRecord==2)  //第二列输出低电平
  458.                            {
  459.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  460.                            }
  461.                        else if(ucRowRecord==3)  //第三列输出低电平
  462.                            {
  463.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  464.                            }
  465.                        else   //第四列输出低电平
  466.                            {
  467.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  468.                            }
  469.                                 }
  470.                         
  471.                          }
  472.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  473.                          {
  474.                             uiKeyTimeCnt++;  //去抖动延时计数器
  475.                                 if(uiKeyTimeCnt>const_key_time)
  476.                                 {
  477.                                    uiKeyTimeCnt=0;
  478.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  479.                        if(ucRowRecord==1)  //第一列输出低电平
  480.                            {
  481.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  482.                            }
  483.                        else if(ucRowRecord==2)  //第二列输出低电平
  484.                            {
  485.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  486.                            }
  487.                        else if(ucRowRecord==3)  //第三列输出低电平
  488.                            {
  489.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  490.                            }
  491.                        else   //第四列输出低电平
  492.                            {
  493.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  494.                            }
  495.                                 }
  496.                         
  497.                          }
  498.                   
  499.                   }
  500.               break;

  501.   }


  502. }

  503. /* 注释三:
  504. *  按键服务程序操作的精髓在于根据当前系统处于什么窗口下,在此窗口下的运算符处于
  505. *  什么状态,然后紧紧围绕着不同的窗口ucWd,不同的ucOperator来执行不同的操作。
  506. */
  507. void key_service() //第三区 按键服务的应用程序
  508. {
  509.   switch(ucKeySec) //按键服务状态切换
  510.   {
  511.     case 1:// 1号键 对应朱兆祺学习板的S1键
  512.           number_key_input(1);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  513.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  514.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  515.           break;        
  516.     case 2:// 2号键 对应朱兆祺学习板的S2键
  517.           number_key_input(2);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  518.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  519.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  520.           break;     
  521.     case 3:// 3号键 对应朱兆祺学习板的S3键
  522.           number_key_input(3);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  523.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  524.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  525.           break;         
  526.     case 4:// 4号键 对应朱兆祺学习板的S4键
  527.           number_key_input(4);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  528.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  529.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  530.           break;   
  531.     case 5:// 5号键 对应朱兆祺学习板的S5键
  532.           number_key_input(5);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  533.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  534.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  535.           break;   
  536.     case 6:// 6号键 对应朱兆祺学习板的S6键
  537.           number_key_input(6);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  538.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  539.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  540.           break;   
  541.     case 7:// 7号键 对应朱兆祺学习板的S7键
  542.           number_key_input(7);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  543.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  544.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  545.           break;   
  546.     case 8:// 8号键 对应朱兆祺学习板的S8键
  547.           number_key_input(8);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  548.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  549.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  550.           break;   
  551.     case 9:// 9号键 对应朱兆祺学习板的S9键
  552.           number_key_input(9);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  553.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  554.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  555.           break;   
  556.     case 10:// 把这个按键专门用来输入数字0    对应朱兆祺学习板的S10键
  557.           number_key_input(0);  //由于数字按键的代码相似度高,因此把具体代码封装在这个函数里
  558.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  559.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  560.           break;   
  561.     case 11:// 11号键 对应朱兆祺学习板的S11键

  562.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  563.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  564.           break;   
  565.     case 12:// 12号键 对应朱兆祺学习板的S12键

  566.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  567.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  568.           break;   
  569.     case 13:// 13号键 加号按键  对应朱兆祺学习板的S13键
  570.           switch(ucWd)
  571.                  {
  572.                   case 1:   //在原始数据和运算结果的窗口下
  573.                    ucOperator=1; //加法
  574.                    ulOther=ulSource;  //第二个运算数默认等于原始数
  575.                    ucDisplayUpdate=1;  //刷新显示窗口
  576.                                  break;
  577.                   case 2:   //在第二个参与运算数据的窗口下
  578.                    ulResult=ulSource+ulOther;//连加
  579.                    ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  580.                    ucWd=1;        //切换到第一个窗口
  581.                    ucDisplayUpdate=1;  //刷新显示窗口
  582.                            break;
  583.           }

  584.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  585.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  586.           break;   
  587.     case 14:// 14号键 等于号按键  对应朱兆祺学习板的S14键  
  588.           switch(ucWd)
  589.                  {
  590.                   case 1:   //在原始数据和运算结果的窗口下
  591.                            switch(ucOperator)  //根据不同的运算符号进行不同的操作
  592.                            {
  593.                                    case 0:  //无运算符号

  594.                                         break;
  595.                                    case 1:  //加法
  596.                             ulResult=ulSource+ulOther;//连加
  597.                             ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  598.                             ucDisplayUpdate=1;  //刷新显示窗口
  599.                                         break;
  600.                                    case 2:  //减法  本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去

  601.                                         break;
  602.                        
  603.                            }
  604.                                  break;
  605.                   case 2:   //在第二个参与运算数据的窗口下
  606.                            switch(ucOperator)  //根据不同的运算符号进行不同的操作
  607.                            {
  608.                                    case 1:  //加法
  609.                             ulResult=ulSource+ulOther;//连加
  610.                             ulSource=ulResult; //下一次运算的原始数据默认为当前运算结果,方便连加功能
  611.                             ucWd=1;        //切换到第一个窗口
  612.                             ucDisplayUpdate=1;  //刷新显示窗口
  613.                                         break;
  614.                                    case 2:  //减法  本程序没有减法功能,如果读者想增加减法程序,可以按键这个框架添加下去

  615.                                         break;
  616.                        
  617.                            }
  618.                            break;
  619.           }

  620.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  621.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  622.           break;   
  623.     case 15:// 15号键 对应朱兆祺学习板的S15键

  624.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  625.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  626.           break;   
  627.     case 16:// 16号键 清除按键 相当于复位的功能。重新输入数据  对应朱兆祺学习板的S16键
  628.               ulSource=0;
  629.                   ulOther=0;
  630.           ulResult=0;
  631.                   ucOperator=0;
  632.           ucWd=1;        //切换到第一个窗口
  633.           ucDisplayUpdate=1;  //刷新显示窗口
  634.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  635.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  636.           break;   
  637.   }               
  638. }


  639. /* 注释四:
  640. * 此处参与运算的输入数字ucWhichKey记得用最大变量类型unsigned long,可以避免数据溢出等错误
  641. */
  642. void number_key_input(unsigned long ucWhichKey)  //由于数字按键的代码相似度高,因此封装在这个函数里
  643. {


  644.     switch(ucWd)
  645.            {
  646.            case 1:   //在原始数据和运算结果的窗口下
  647.             switch(ucOperator)  //根据不同的运算符号进行不同的操作
  648.                         {
  649.                            case 0:  //无运算符号  按键输入原始数据,比如被加输
  650.                                 if(ulSource<=9999999) //最大只能输入8位数
  651.                                         {
  652.                        ulSource=ulSource*10+ucWhichKey;  //十进制的数值移位方法。
  653.                                         }
  654.                                 break;
  655.                            default:  //在已经按下了运算符号的情况下
  656.                     ulOther=0;  //第二个运算数先清零,再输入新的数据,然后马上切换到第2个窗口下
  657.                     ulOther=ucWhichKey;
  658.                     ucWd=2; //马上切换到第二个窗口下
  659.                                 break;
  660.                        
  661.                         }

  662.             ucDisplayUpdate=1;  //刷新显示窗口
  663.                         break;
  664.            case 2:   //在第二个参与运算数据的窗口下   按键输入第二个参与运算的数据
  665.                         if(ulOther<=9999999) //最大只能输入8位数
  666.                         {
  667.                ulOther=ulOther*10+ucWhichKey;  //十进制的数值移位方法。
  668.                     }

  669.             ucDisplayUpdate=1;  //刷新显示窗口
  670.                     break;
  671.     }

  672. }


  673. void T0_time() interrupt 1
  674. {
  675.   TF0=0;  //清除中断标志
  676.   TR0=0; //关中断
  677.   key_scan(); //放在定时中断里的按键扫描函数
  678.   if(uiVoiceCnt!=0)
  679.   {
  680.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  681.          beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  682.   }
  683.   else
  684.   {
  685.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  686.            beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  687.   }

  688.   display_drive();  //放在定时中断里的数码管驱动函数

  689. /* 注释五:
  690. *  注意,此处的重装初始值不能太大,否则动态扫描数码管的速度就不够。我把原来常用的2000改成了500。
  691. */
  692.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  693.   TL0=0x0b;
  694.   TR0=1;  //开中断
  695. }

  696. void delay_short(unsigned int uiDelayShort)
  697. {
  698.    unsigned int i;  
  699.    for(i=0;i<uiDelayShort;i++)
  700.    {
  701.      ;   //一个分号相当于执行一条空语句
  702.    }
  703. }


  704. void delay_long(unsigned int uiDelayLong)
  705. {
  706.    unsigned int i;
  707.    unsigned int j;
  708.    for(i=0;i<uiDelayLong;i++)
  709.    {
  710.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  711.           {
  712.              ; //一个分号相当于执行一条空语句
  713.           }
  714.    }
  715. }


  716. void initial_myself()  //第一区 初始化单片机
  717. {

  718.   led_dr=0;
  719.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  720.   hc595_drive(0x00,0x00);
  721.   TMOD=0x01;  //设置定时器0为工作方式1

  722.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  723.   TL0=0x0b;
  724. }
  725. void initial_peripheral() //第二区 初始化外围
  726. {


  727.   EA=1;     //开总中断
  728.   ET0=1;    //允许定时中断
  729.   TR0=1;    //启动定时中断



  730. }
复制代码

总结陈词:
这节讲了加法简易计算器的程序项目。为了让读者理解运动,按键,显示是如何有规律关联起来的,下节会继续讲一个相关的小项目程序。欲知详情,请听下回分解-----数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

38
 
第三十七节:数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

开场白:
    我在第24节中讲过按键控制跑马灯的方向,速度和运行状态的项目程序,只可惜那个程序不能直观地显示运行中的三种状态,这节我决定在24节的基础上,增加一个数码管显示作为类似汽车仪表盘的界面,实时显示跑马灯的方向,速度,和运行状态。
这一节要教会大家一个知识点:继续加深理解运动,按键与数码管三者之间的关联程序框架。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板。用S1键作为控制跑马灯的方向按键,S5键作为控制跑马灯方向的加速度按键,S9键作为控制跑马灯方向的减速度按键,S13键作为控制跑马灯方向的启动或者暂停按键。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
跑马灯运行:第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S13键,原来运行的跑马灯会暂停,原来暂停的跑马灯会运行。用S1来改变方向。用S5和S9来改变速度,每按一次按键的递增或者递减以10为单位。
数码管显示:本程序只有1个窗口,这个窗口分成3个局部显示。8,7,6位数码管显示运行状态,启动时显示“on”,停止时显示“oFF”。5位数码管显示数码管方向,正向显示“n”,反向显示“U”。4,3,2,1位数码管显示速度。数值越大速度越慢,最慢的速度是550,最快的速度是50。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间


  3. #define const_key_time1  20    //按键去抖动延时的时间
  4. #define const_key_time2  20    //按键去抖动延时的时间
  5. #define const_key_time3  20    //按键去抖动延时的时间
  6. #define const_key_time4  20    //按键去抖动延时的时间


  7. void initial_myself();   
  8. void initial_peripheral();
  9. void delay_short(unsigned int uiDelayShort);
  10. void delay_long(unsigned int uiDelaylong);

  11. //驱动数码管的74HC595
  12. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  13. void display_drive(); //显示数码管字模的驱动函数
  14. void display_service(); //显示的窗口菜单服务程序

  15. //驱动LED的74HC595
  16. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  17. void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  18. void led_update();  //LED更新函数

  19. void T0_time();  //定时中断函数
  20. void key_service(); //按键服务的应用程序
  21. void key_scan();//按键扫描函数 放在定时中断里


  22. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  23. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  24. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  25. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  26. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

  27. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平

  28. sbit led_dr=P3^5;  


  29. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  30. sbit dig_hc595_st_dr=P2^1;  
  31. sbit dig_hc595_ds_dr=P2^2;  

  32. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  33. sbit hc595_st_dr=P2^4;  
  34. sbit hc595_ds_dr=P2^5;  


  35. unsigned char ucKeySec=0;   //被触发的按键编号

  36. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  37. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  38. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  39. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  40. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  41. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志


  42. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  43. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

  44. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  45. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  46. unsigned char ucLed_dr2=0;
  47. unsigned char ucLed_dr3=0;
  48. unsigned char ucLed_dr4=0;
  49. unsigned char ucLed_dr5=0;
  50. unsigned char ucLed_dr6=0;
  51. unsigned char ucLed_dr7=0;
  52. unsigned char ucLed_dr8=0;
  53. unsigned char ucLed_dr9=0;
  54. unsigned char ucLed_dr10=0;
  55. unsigned char ucLed_dr11=0;
  56. unsigned char ucLed_dr12=0;
  57. unsigned char ucLed_dr13=0;
  58. unsigned char ucLed_dr14=0;
  59. unsigned char ucLed_dr15=0;
  60. unsigned char ucLed_dr16=0;

  61. unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


  62. unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
  63. unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

  64. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  65. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

  66. unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
  67. unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。
  68. unsigned char ucLedStartFlag=1;   //启动和暂停的变量,0代表暂停,1代表启动



  69. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  70. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  71. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  72. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  73. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  74. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  75. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  76. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  77. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  78. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  79. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  80. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  81. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  82. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  83. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  84. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  85. unsigned char ucDigShowTemp=0; //临时中间变量
  86. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  87. unsigned char ucWd1Part1Update=1;  //窗口1的局部1更新显示变量
  88. unsigned char ucWd1Part2Update=1;  //窗口1的局部2更新显示变量
  89. unsigned char ucWd1Part3Update=1;  //窗口1的局部3更新显示变量


  90. //根据原理图得出的共阴数码管字模表
  91. code unsigned char dig_table[]=
  92. {
  93. 0x3f,  //0       序号0
  94. 0x06,  //1       序号1
  95. 0x5b,  //2       序号2
  96. 0x4f,  //3       序号3
  97. 0x66,  //4       序号4
  98. 0x6d,  //5       序号5
  99. 0x7d,  //6       序号6
  100. 0x07,  //7       序号7
  101. 0x7f,  //8       序号8
  102. 0x6f,  //9       序号9
  103. 0x00,  //无      序号10
  104. 0x40,  //-       序号11
  105. 0x73,  //P       序号12
  106. 0x5c,  //o       序号13
  107. 0x71,  //F       序号14
  108. 0x3e,  //U       序号15
  109. 0x37,  //n       序号16
  110. };

  111. void main()
  112.   {
  113.    initial_myself();  
  114.    delay_long(100);   
  115.    initial_peripheral();
  116.    while(1)  
  117.    {
  118.       key_service(); //按键服务的应用程序
  119.       display_service(); //显示的窗口菜单服务程序

  120.       led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  121.           led_update();  //LED更新函数
  122.    }

  123. }



  124. /* 注释一:
  125. * 由于本程序只有1个窗口,而这个窗口又分成3个局部,因此可以省略去窗口变量uWd,
  126. * 只用三个局部变量ucWdxPartyUpdate就可以了。
  127. */

  128. void display_service() //显示的窗口菜单服务程序
  129. {


  130.     if(ucWd1Part1Update==1) //更新显示当前系统是处于运行还是暂停的状态
  131.         {
  132.        ucWd1Part1Update=0; //及时把更新变量清零,防止一直进来更新
  133.            if(ucLedStartFlag==1)  //启动,显示on
  134.            {
  135.                ucDigShow8=13;  //显示o
  136.            ucDigShow7=16;  //显示n
  137.            ucDigShow6=10;  //显示空
  138.            }
  139.            else  //暂停,显示oFF
  140.            {
  141.                       ucDigShow8=13;  //显示o
  142.            ucDigShow7=14;  //显示F
  143.            ucDigShow6=14;  //显示F
  144.            }
  145.         }

  146.     if(ucWd1Part2Update==1) //更新显示当前系统是处于正方向还是反方向
  147.         {
  148.        ucWd1Part2Update=0; //及时把更新变量清零,防止一直进来更新
  149.            if(ucLedDirFlag==0)  //正方向,向上,显示n
  150.            {
  151.                ucDigShow5=16;  //显示n
  152.            }
  153.            else  //反方向,向下,显示U
  154.            {
  155.                ucDigShow5=15;  //显示U
  156.            }
  157.         }

  158.     if(ucWd1Part3Update==1) //更新显示当前系统的速度,此数值越大速度越慢,此数值越小速度越快。
  159.         {
  160.        ucWd1Part3Update=0; //及时把更新变量清零,防止一直进来更新

  161.            ucDigShow4=10;  //显示空  这一位不用,作为空格

  162.            if(uiSetTimeLevel_09_16>=100)
  163.            {
  164.           ucDigShow3=uiSetTimeLevel_09_16/100;     //显示速度的百位
  165.            }
  166.            else
  167.            {
  168.           ucDigShow3=10;     //显示空
  169.            }

  170.            if(uiSetTimeLevel_09_16>=10)
  171.            {
  172.           ucDigShow2=uiSetTimeLevel_09_16%100/10;  //显示速度的十位
  173.            }
  174.            else
  175.            {
  176.           ucDigShow2=10;     //显示空
  177.            }

  178.        ucDigShow1=uiSetTimeLevel_09_16%10;      //显示速度的个位
  179.         }


  180.    


  181. }


  182. void key_scan()//按键扫描函数 放在定时中断里
  183. {  

  184.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  185.   {
  186.      ucKeyLock1=0; //按键自锁标志清零
  187.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  188.   }
  189.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  190.   {
  191.      uiKeyTimeCnt1++; //累加定时中断次数
  192.      if(uiKeyTimeCnt1>const_key_time1)
  193.      {
  194.         uiKeyTimeCnt1=0;
  195.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  196.         ucKeySec=1;    //触发1号键
  197.      }
  198.   }

  199.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  200.   {
  201.      ucKeyLock2=0; //按键自锁标志清零
  202.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  203.   }
  204.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  205.   {
  206.      uiKeyTimeCnt2++; //累加定时中断次数
  207.      if(uiKeyTimeCnt2>const_key_time2)
  208.      {
  209.         uiKeyTimeCnt2=0;
  210.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  211.         ucKeySec=2;    //触发2号键
  212.      }
  213.   }

  214.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  215.   {
  216.      ucKeyLock3=0; //按键自锁标志清零
  217.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  218.   }
  219.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  220.   {
  221.      uiKeyTimeCnt3++; //累加定时中断次数
  222.      if(uiKeyTimeCnt3>const_key_time3)
  223.      {
  224.         uiKeyTimeCnt3=0;
  225.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  226.         ucKeySec=3;    //触发3号键
  227.      }
  228.   }

  229.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  230.   {
  231.      ucKeyLock4=0; //按键自锁标志清零
  232.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  233.   }
  234.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  235.   {
  236.      uiKeyTimeCnt4++; //累加定时中断次数
  237.      if(uiKeyTimeCnt4>const_key_time4)
  238.      {
  239.         uiKeyTimeCnt4=0;
  240.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  241.         ucKeySec=4;    //触发4号键
  242.      }
  243.   }

  244. }


  245. void key_service() //按键服务的应用程序
  246. {
  247.   switch(ucKeySec) //按键服务状态切换
  248.   {
  249.     case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

  250.           if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
  251.                   {
  252.                      ucLedDirFlag=1;
  253.                   }
  254.                   else
  255.                   {
  256.                            ucLedDirFlag=0;
  257.                   }

  258.           ucWd1Part2Update=1; //及时更新显示方向

  259.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  260.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  261.           break;   
  262.    
  263.     case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
  264.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
  265.                   if(uiSetTimeLevel_09_16<50)  //最快限定在50
  266.                   {
  267.                       uiSetTimeLevel_09_16=50;
  268.                   }

  269.           ucWd1Part3Update=1; //及时更新显示速度

  270.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  271.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  272.           break;  

  273.     case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
  274.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
  275.                   if(uiSetTimeLevel_09_16>550)  //最慢限定在550
  276.                   {
  277.                       uiSetTimeLevel_09_16=550;
  278.                   }
  279.           ucWd1Part3Update=1; //及时更新显示速度
  280.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  281.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  282.           break;         
  283.          
  284.     case 4:// 启动和暂停按键 对应朱兆祺学习板的S13键  ucLedStartFlag为0时代表暂停,为1时代表启动

  285.               if(ucLedStartFlag==1)  //启动和暂停两种状态循环切换
  286.                   {
  287.                      ucLedStartFlag=0;
  288.                   }
  289.                   else                   //启动和暂停两种状态循环切换
  290.                   {
  291.                            ucLedStartFlag=1;
  292.                   }
  293.           ucWd1Part1Update=1; //及时更新显示系统的运行状态,是运行还是暂停.
  294.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  295.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  296.           break;   
  297.   }               
  298. }




  299. void led_update()  //LED更新函数
  300. {

  301.    if(ucLed_update==1)
  302.    {
  303.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  304.        if(ucLed_dr1==1)
  305.            {
  306.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  307.            }
  308.            else
  309.            {
  310.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  311.            }

  312.        if(ucLed_dr2==1)
  313.            {
  314.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  315.            }
  316.            else
  317.            {
  318.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  319.            }

  320.        if(ucLed_dr3==1)
  321.            {
  322.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  323.            }
  324.            else
  325.            {
  326.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  327.            }

  328.        if(ucLed_dr4==1)
  329.            {
  330.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  331.            }
  332.            else
  333.            {
  334.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  335.            }


  336.        if(ucLed_dr5==1)
  337.            {
  338.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  339.            }
  340.            else
  341.            {
  342.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  343.            }


  344.        if(ucLed_dr6==1)
  345.            {
  346.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  347.            }
  348.            else
  349.            {
  350.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  351.            }


  352.        if(ucLed_dr7==1)
  353.            {
  354.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  355.            }
  356.            else
  357.            {
  358.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  359.            }


  360.        if(ucLed_dr8==1)
  361.            {
  362.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  363.            }
  364.            else
  365.            {
  366.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  367.            }

  368.        if(ucLed_dr9==1)
  369.            {
  370.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  371.            }
  372.            else
  373.            {
  374.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  375.            }

  376.        if(ucLed_dr10==1)
  377.            {
  378.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  379.            }
  380.            else
  381.            {
  382.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  383.            }

  384.        if(ucLed_dr11==1)
  385.            {
  386.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  387.            }
  388.            else
  389.            {
  390.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  391.            }

  392.        if(ucLed_dr12==1)
  393.            {
  394.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  395.            }
  396.            else
  397.            {
  398.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  399.            }


  400.        if(ucLed_dr13==1)
  401.            {
  402.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  403.            }
  404.            else
  405.            {
  406.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  407.            }


  408.        if(ucLed_dr14==1)
  409.            {
  410.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  411.            }
  412.            else
  413.            {
  414.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  415.            }


  416.        if(ucLed_dr15==1)
  417.            {
  418.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  419.            }
  420.            else
  421.            {
  422.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  423.            }


  424.        if(ucLed_dr16==1)
  425.            {
  426.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  427.            }
  428.            else
  429.            {
  430.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  431.            }

  432.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  433.    }
  434. }


  435. void display_drive()  
  436. {
  437.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  438.    switch(ucDisplayDriveStep)
  439.    {
  440.       case 1:  //显示第1位
  441.            ucDigShowTemp=dig_table[ucDigShow1];
  442.                    if(ucDigDot1==1)
  443.                    {
  444.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  445.                    }
  446.            dig_hc595_drive(ucDigShowTemp,0xfe);
  447.                break;
  448.       case 2:  //显示第2位
  449.            ucDigShowTemp=dig_table[ucDigShow2];
  450.                    if(ucDigDot2==1)
  451.                    {
  452.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  453.                    }
  454.            dig_hc595_drive(ucDigShowTemp,0xfd);
  455.                break;
  456.       case 3:  //显示第3位
  457.            ucDigShowTemp=dig_table[ucDigShow3];
  458.                    if(ucDigDot3==1)
  459.                    {
  460.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  461.                    }
  462.            dig_hc595_drive(ucDigShowTemp,0xfb);
  463.                break;
  464.       case 4:  //显示第4位
  465.            ucDigShowTemp=dig_table[ucDigShow4];
  466.                    if(ucDigDot4==1)
  467.                    {
  468.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  469.                    }
  470.            dig_hc595_drive(ucDigShowTemp,0xf7);
  471.                break;
  472.       case 5:  //显示第5位
  473.            ucDigShowTemp=dig_table[ucDigShow5];
  474.                    if(ucDigDot5==1)
  475.                    {
  476.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  477.                    }
  478.            dig_hc595_drive(ucDigShowTemp,0xef);
  479.                break;
  480.       case 6:  //显示第6位
  481.            ucDigShowTemp=dig_table[ucDigShow6];
  482.                    if(ucDigDot6==1)
  483.                    {
  484.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  485.                    }
  486.            dig_hc595_drive(ucDigShowTemp,0xdf);
  487.                break;
  488.       case 7:  //显示第7位
  489.            ucDigShowTemp=dig_table[ucDigShow7];
  490.                    if(ucDigDot7==1)
  491.                    {
  492.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  493.            }
  494.            dig_hc595_drive(ucDigShowTemp,0xbf);
  495.                break;
  496.       case 8:  //显示第8位
  497.            ucDigShowTemp=dig_table[ucDigShow8];
  498.                    if(ucDigDot8==1)
  499.                    {
  500.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  501.                    }
  502.            dig_hc595_drive(ucDigShowTemp,0x7f);
  503.                break;
  504.    }

  505.    ucDisplayDriveStep++;
  506.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  507.    {
  508.      ucDisplayDriveStep=1;
  509.    }



  510. }


  511. //数码管的74HC595驱动函数
  512. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  513. {
  514.    unsigned char i;
  515.    unsigned char ucTempData;
  516.    dig_hc595_sh_dr=0;
  517.    dig_hc595_st_dr=0;

  518.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  519.    for(i=0;i<8;i++)
  520.    {
  521.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  522.          else dig_hc595_ds_dr=0;

  523.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  524.          delay_short(1);
  525.          dig_hc595_sh_dr=1;
  526.          delay_short(1);

  527.          ucTempData=ucTempData<<1;
  528.    }

  529.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  530.    for(i=0;i<8;i++)
  531.    {
  532.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  533.          else dig_hc595_ds_dr=0;

  534.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  535.          delay_short(1);
  536.          dig_hc595_sh_dr=1;
  537.          delay_short(1);

  538.          ucTempData=ucTempData<<1;
  539.    }

  540.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  541.    delay_short(1);
  542.    dig_hc595_st_dr=1;
  543.    delay_short(1);

  544.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  545.    dig_hc595_st_dr=0;
  546.    dig_hc595_ds_dr=0;

  547. }


  548. //LED灯的74HC595驱动函数
  549. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  550. {
  551.    unsigned char i;
  552.    unsigned char ucTempData;
  553.    hc595_sh_dr=0;
  554.    hc595_st_dr=0;

  555.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  556.    for(i=0;i<8;i++)
  557.    {
  558.          if(ucTempData>=0x80)hc595_ds_dr=1;
  559.          else hc595_ds_dr=0;

  560.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  561.          delay_short(1);
  562.          hc595_sh_dr=1;
  563.          delay_short(1);

  564.          ucTempData=ucTempData<<1;
  565.    }

  566.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  567.    for(i=0;i<8;i++)
  568.    {
  569.          if(ucTempData>=0x80)hc595_ds_dr=1;
  570.          else hc595_ds_dr=0;

  571.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  572.          delay_short(1);
  573.          hc595_sh_dr=1;
  574.          delay_short(1);

  575.          ucTempData=ucTempData<<1;
  576.    }

  577.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  578.    delay_short(1);
  579.    hc595_st_dr=1;
  580.    delay_short(1);

  581.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  582.    hc595_st_dr=0;
  583.    hc595_ds_dr=0;

  584. }


  585. void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  586. {
  587.   if(ucLedStartFlag==1)  //此变量为1时代表启动
  588.   {
  589.      switch(ucLedStep_09_16)
  590.      {
  591.      case 0:
  592.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  593.            {
  594.                uiTimeCnt_09_16=0; //时间计数器清零

  595.                            if(ucLedDirFlag==0)  //正方向
  596.                            {
  597.                   ucLed_dr16=0;  //第16个灭
  598.                   ucLed_dr9=1;  //第9个亮

  599.                   ucLed_update=1;  //更新显示
  600.                   ucLedStep_09_16=1; //切换到下一个步骤
  601.                            }
  602.                            else  //反方向
  603.                            {
  604.                   ucLed_dr15=1;  //第15个亮
  605.                   ucLed_dr16=0;  //第16个灭

  606.                   ucLed_update=1;  //更新显示
  607.                   ucLedStep_09_16=7; //返回上一个步骤
  608.                            }
  609.            }
  610.            break;
  611.      case 1:
  612.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  613.            {
  614.                uiTimeCnt_09_16=0; //时间计数器清零

  615.                            if(ucLedDirFlag==0)  //正方向
  616.                            {
  617.                   ucLed_dr9=0;  //第9个灭
  618.                   ucLed_dr10=1;  //第10个亮

  619.                   ucLed_update=1;  //更新显示
  620.                   ucLedStep_09_16=2; //切换到下一个步骤
  621.                            }
  622.                            else  //反方向
  623.                            {
  624.                   ucLed_dr16=1;  //第16个亮
  625.                   ucLed_dr9=0;  //第9个灭

  626.                   ucLed_update=1;  //更新显示
  627.                   ucLedStep_09_16=0; //返回上一个步骤
  628.                            }
  629.            }
  630.            break;
  631.      case 2:
  632.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  633.            {
  634.                uiTimeCnt_09_16=0; //时间计数器清零

  635.                            if(ucLedDirFlag==0)  //正方向
  636.                            {
  637.                   ucLed_dr10=0;  //第10个灭
  638.                   ucLed_dr11=1;  //第11个亮

  639.                   ucLed_update=1;  //更新显示
  640.                   ucLedStep_09_16=3; //切换到下一个步骤
  641.                            }
  642.                            else  //反方向
  643.                            {
  644.                   ucLed_dr9=1;  //第9个亮
  645.                   ucLed_dr10=0;  //第10个灭

  646.                   ucLed_update=1;  //更新显示
  647.                   ucLedStep_09_16=1; //返回上一个步骤
  648.                            }
  649.            }
  650.            break;
  651.      case 3:
  652.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  653.            {
  654.                uiTimeCnt_09_16=0; //时间计数器清零

  655.                            if(ucLedDirFlag==0)  //正方向
  656.                            {
  657.                   ucLed_dr11=0;  //第11个灭
  658.                   ucLed_dr12=1;  //第12个亮

  659.                   ucLed_update=1;  //更新显示
  660.                   ucLedStep_09_16=4; //切换到下一个步骤
  661.                            }
  662.                            else  //反方向
  663.                            {
  664.                   ucLed_dr10=1;  //第10个亮
  665.                   ucLed_dr11=0;  //第11个灭

  666.                   ucLed_update=1;  //更新显示
  667.                   ucLedStep_09_16=2; //返回上一个步骤
  668.                            }
  669.            }
  670.            break;
  671.      case 4:
  672.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  673.            {
  674.                uiTimeCnt_09_16=0; //时间计数器清零

  675.                            if(ucLedDirFlag==0)  //正方向
  676.                            {
  677.                   ucLed_dr12=0;  //第12个灭
  678.                   ucLed_dr13=1;  //第13个亮

  679.                   ucLed_update=1;  //更新显示
  680.                   ucLedStep_09_16=5; //切换到下一个步骤
  681.                            }
  682.                            else  //反方向
  683.                            {
  684.                   ucLed_dr11=1;  //第11个亮
  685.                   ucLed_dr12=0;  //第12个灭

  686.                   ucLed_update=1;  //更新显示
  687.                   ucLedStep_09_16=3; //返回上一个步骤
  688.                            }
  689.            }
  690.            break;
  691.      case 5:
  692.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  693.            {
  694.                uiTimeCnt_09_16=0; //时间计数器清零

  695.                            if(ucLedDirFlag==0)  //正方向
  696.                            {
  697.                   ucLed_dr13=0;  //第13个灭
  698.                   ucLed_dr14=1;  //第14个亮

  699.                   ucLed_update=1;  //更新显示
  700.                   ucLedStep_09_16=6; //切换到下一个步骤
  701.                            }
  702.                            else  //反方向
  703.                            {
  704.                   ucLed_dr12=1;  //第12个亮
  705.                   ucLed_dr13=0;  //第13个灭

  706.                   ucLed_update=1;  //更新显示
  707.                   ucLedStep_09_16=4; //返回上一个步骤
  708.                            }
  709.            }
  710.            break;
  711.      case 6:
  712.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  713.            {
  714.                uiTimeCnt_09_16=0; //时间计数器清零

  715.                            if(ucLedDirFlag==0)  //正方向
  716.                            {
  717.                   ucLed_dr14=0;  //第14个灭
  718.                   ucLed_dr15=1;  //第15个亮

  719.                   ucLed_update=1;  //更新显示
  720.                   ucLedStep_09_16=7; //切换到下一个步骤
  721.                            }
  722.                            else  //反方向
  723.                            {
  724.                   ucLed_dr13=1;  //第13个亮
  725.                   ucLed_dr14=0;  //第14个灭

  726.                   ucLed_update=1;  //更新显示
  727.                   ucLedStep_09_16=5; //返回上一个步骤
  728.                            }
  729.            }
  730.            break;
  731.      case 7:
  732.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  733.            {
  734.                uiTimeCnt_09_16=0; //时间计数器清零

  735.                            if(ucLedDirFlag==0)  //正方向
  736.                            {
  737.                   ucLed_dr15=0;  //第15个灭
  738.                   ucLed_dr16=1;  //第16个亮

  739.                   ucLed_update=1;  //更新显示
  740.                   ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
  741.                            }
  742.                            else  //反方向
  743.                            {
  744.                   ucLed_dr14=1;  //第14个亮
  745.                   ucLed_dr15=0;  //第15个灭

  746.                   ucLed_update=1;  //更新显示
  747.                   ucLedStep_09_16=6; //返回上一个步骤
  748.                            }
  749.            }
  750.            break;
  751.    
  752.       }
  753.    }

  754. }


  755. void T0_time() interrupt 1
  756. {
  757.   TF0=0;  //清除中断标志
  758.   TR0=0; //关中断


  759.   if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  760.   {
  761.       if(ucLedStartFlag==1)  //此变量为1时代表启动
  762.           {
  763.          uiTimeCnt_09_16++;  //累加定时中断的次数,
  764.           }
  765.   }

  766.   key_scan(); //按键扫描函数






  767.   if(uiVoiceCnt!=0)
  768.   {
  769.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  770.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  771. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  772.   }
  773.   else
  774.   {
  775.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  776.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  777. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  778.   }

  779.   display_drive();  //数码管字模的驱动函数


  780.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  781.   TL0=0x0b;
  782.   TR0=1;  //开中断
  783. }


  784. void delay_short(unsigned int uiDelayShort)
  785. {
  786.    unsigned int i;  
  787.    for(i=0;i<uiDelayShort;i++)
  788.    {
  789.      ;   //一个分号相当于执行一条空语句
  790.    }
  791. }


  792. void delay_long(unsigned int uiDelayLong)
  793. {
  794.    unsigned int i;
  795.    unsigned int j;
  796.    for(i=0;i<uiDelayLong;i++)
  797.    {
  798.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  799.           {
  800.              ; //一个分号相当于执行一条空语句
  801.           }
  802.    }
  803. }


  804. void initial_myself()  //第一区 初始化单片机
  805. {

  806. /* 注释二:
  807. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  808. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  809. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  810. */
  811.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  812.   led_dr=0;  //关闭独立LED灯
  813.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  814.   TMOD=0x01;  //设置定时器0为工作方式1

  815.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  816.   TL0=0x0b;

  817. }

  818. void initial_peripheral() //第二区 初始化外围
  819. {


  820.    ucDigDot8=0;   //小数点全部不显示
  821.    ucDigDot7=0;  
  822.    ucDigDot6=0;
  823.    ucDigDot5=0;  
  824.    ucDigDot4=0;
  825.    ucDigDot3=0;  
  826.    ucDigDot2=0;
  827.    ucDigDot1=0;

  828.    EA=1;     //开总中断
  829.    ET0=1;    //允许定时中断
  830.    TR0=1;    //启动定时中断

  831. }
复制代码

总结陈词:
    前面花了大量的章节在讲数码管显示,按键,运动的关联程序框架,从下一节开始,我将会用八节内容来讲我常用的串口程序框架,内容非常精彩和震撼,思路非常简单而又实用。欲知详情,请听下回分解-----判断数据尾来接收一串数据的串口通用程序框架。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

39
 
第三十八节:判断数据尾来接收一串数据的串口通用程序框架。

开场白:
    在实际项目中,串口通讯不可能一次通讯只发送或接收一个字节,大部分的项目都是一次发送或者接受一串的数据。我们还要在这一串数据里解析数据协议,提取有用的数据。
这一节要教会大家三个知识点:
第一个:如何识别一串数据已经发送接收完毕。
第二个:如何在已经接收到的一串数据中解析数据尾协议并且提取有效数据。
第三个:接收一串数据的通用程序框架涉及到main循环里的串口服务程序,定时器的计时程序,串口接收中断程序的密切配合。大家要理解它们三者之间是如何关联起来的。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板。

(2)实现功能:

波特率是:9600 。
通讯协议:XX YY  EB 00 55
          其中后三位 EB 00 55就是我所说的数据尾,它的有效数据XX YY在数据尾的前面。
        任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB00 55 ,并且此关键字前面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据尾和有效数据都是正确的。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);



  8. void T0_time(void);  //定时中断函数
  9. void usart_receive(void); //串口接收中断函数
  10. void usart_service(void);  //串口服务程序,在main函数里

  11. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  12. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  13. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  14. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  15. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  16. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  17. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器



  18. void main()
  19.   {
  20.    initial_myself();  
  21.    delay_long(100);   
  22.    initial_peripheral();
  23.    while(1)  
  24.    {
  25.        usart_service();  //串口服务程序
  26.    }

  27. }


  28. void usart_service(void)  //串口服务程序,在main函数里
  29. {

  30.         
  31. /* 注释一:
  32. * 识别一串数据是否已经全部接收完了的原理:
  33. * 在规定的时间里,如果没有接收到任何一个字节数据,那么就认为一串数据被接收完了,然后就进入数据协议
  34. * 解析和处理的阶段。这个功能的实现要配合定时中断,串口中断的程序一起阅读,要理解他们之间的关系。
  35. */
  36.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  37.      {

  38.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  39.                     //下面的代码进入数据协议解析和数据处理的阶段

  40.                     uiRcMoveIndex=uiRcregTotal; //由于是判断数据尾,所以下标移动变量从数组的最尾端开始向0移动
  41.             while(uiRcMoveIndex>=5)   //如果处理的数据量大于等于5(2个有效数据,3个数据头)说明还没有把缓冲区的数据处理完
  42.             {
  43.                if(ucRcregBuf[uiRcMoveIndex-3]==0xeb&&ucRcregBuf[uiRcMoveIndex-2]==0x00&&ucRcregBuf[uiRcMoveIndex-1]==0x55)  //数据尾eb 00 55的判断
  44.                {
  45.                               if(ucRcregBuf[uiRcMoveIndex-5]==0x01&&ucRcregBuf[uiRcMoveIndex-4]==0x02)  //有效数据01 02的判断
  46.                                   {
  47.                                       uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据尾和有效数据都接收正确
  48.                                   }
  49.                   break;   //退出循环
  50.                }
  51.                uiRcMoveIndex--; //因为是判断数据尾,下标向着0的方向移动
  52.            }
  53.                                          
  54.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  55.   
  56.      }
  57.                         
  58. }


  59. void T0_time(void) interrupt 1    //定时中断
  60. {
  61.   TF0=0;  //清除中断标志
  62.   TR0=0; //关中断


  63.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  64.   {
  65.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  66.       ucSendLock=1;     //开自锁标志
  67.   }

  68.   if(uiVoiceCnt!=0)
  69.   {
  70.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  71.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  72.   }
  73.   else
  74.   {
  75.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  76.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  77.   }


  78.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  79.   TL0=0x0b;
  80.   TR0=1;  //开中断
  81. }


  82. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  83. {        

  84.    if(RI==1)  
  85.    {
  86.         RI = 0;

  87.             ++uiRcregTotal;
  88.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  89.         {
  90.            uiRcregTotal=const_rc_size;
  91.         }
  92.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  93.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  94.    
  95.    }
  96.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  97.    {
  98.         TI = 0;
  99.    }
  100.                                                          
  101. }                                


  102. void delay_long(unsigned int uiDelayLong)
  103. {
  104.    unsigned int i;
  105.    unsigned int j;
  106.    for(i=0;i<uiDelayLong;i++)
  107.    {
  108.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  109.           {
  110.              ; //一个分号相当于执行一条空语句
  111.           }
  112.    }
  113. }


  114. void initial_myself(void)  //第一区 初始化单片机
  115. {

  116.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  117.   //配置定时器
  118.   TMOD=0x01;  //设置定时器0为工作方式1
  119.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  120.   TL0=0x0b;


  121.   //配置串口
  122.   SCON=0x50;
  123.   TMOD=0X21;
  124.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  125.   TR1=1;

  126. }

  127. void initial_peripheral(void) //第二区 初始化外围
  128. {

  129.    EA=1;     //开总中断
  130.    ES=1;     //允许串口中断
  131.    ET0=1;    //允许定时中断
  132.    TR0=1;    //启动定时中断

  133. }
复制代码

总结陈词:
     这一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这样的程序该怎么写?欲知详情,请听下回分解-----判断数据头来接收一串数据的串口通用程序框架。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

40
 
第三十九节:判断数据头来接收一串数据的串口通用程序框架。

开场白:
上一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这一节要教会大家两个知识点:
第一个:如何在已经接收到的一串数据中解析数据头协议并且提取有效数据。
第二个:无论是判断数据头还是判断数据尾,无论是单片机还是上位机,最好在固定协议前多发送一个填充的无效字节0x00,因为硬件原因,第一个字节往往容易丢失。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板。

(2)实现功能:

波特率是:9600 。
通讯协议:EB 00 55  XX YY  
加无效填充字节后,上位机实际上应该发送:00  EB 00 55  XX YY
其中第1位00是无效填充字节,防止由于硬件原因丢失第一个字节。
其中第2,3,4位EB 00 55就是数据头
           后2位XX YY就是有效数据
任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字后面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。

(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  4. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);



  8. void T0_time(void);  //定时中断函数
  9. void usart_receive(void); //串口接收中断函数
  10. void usart_service(void);  //串口服务程序,在main函数里

  11. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  12. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  13. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  14. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  15. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  16. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


  17. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器



  18. void main()
  19.   {
  20.    initial_myself();  
  21.    delay_long(100);   
  22.    initial_peripheral();
  23.    while(1)  
  24.    {
  25.        usart_service();  //串口服务程序
  26.    }

  27. }


  28. void usart_service(void)  //串口服务程序,在main函数里
  29. {

  30.         
  31. /* 注释一:
  32. * 识别一串数据是否已经全部接收完了的原理:
  33. * 在规定的时间里,如果没有接收到任何一个字节数据,那么就认为一串数据被接收完了,然后就进入数据协议
  34. * 解析和处理的阶段。这个功能的实现要配合定时中断,串口中断的程序一起阅读,要理解他们之间的关系。
  35. */
  36.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  37.      {

  38.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据



  39.                     //下面的代码进入数据协议解析和数据处理的阶段

  40.                     uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  41. /* 注释二:
  42. * 判断数据头,进入循环解析数据协议必须满足两个条件:
  43. * 第一:最大接收缓冲数据必须大于一串数据的长度(这里是5。包括2个有效数据,3个数据头)
  44. * 第二:游标uiRcMoveIndex必须小于等于最大接收缓冲数据减去一串数据的长度(这里是5。包括2个有效数据,3个数据头)
  45. */
  46.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  47.             {
  48.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  49.                {
  50.                               if(ucRcregBuf[uiRcMoveIndex+3]==0x01&&ucRcregBuf[uiRcMoveIndex+4]==0x02)  //有效数据01 02的判断
  51.                                   {
  52.                                       uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据头和有效数据都接收正确
  53.                                   }
  54.                   break;   //退出循环
  55.                }
  56.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  57.            }
  58.                                          
  59.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  60.   
  61.      }
  62.                         
  63. }


  64. void T0_time(void) interrupt 1    //定时中断
  65. {
  66.   TF0=0;  //清除中断标志
  67.   TR0=0; //关中断


  68.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  69.   {
  70.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  71.       ucSendLock=1;     //开自锁标志
  72.   }

  73.   if(uiVoiceCnt!=0)
  74.   {
  75.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  76.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  77.   }
  78.   else
  79.   {
  80.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  81.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  82.   }


  83.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  84.   TL0=0x0b;
  85.   TR0=1;  //开中断
  86. }


  87. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  88. {        

  89.    if(RI==1)  
  90.    {
  91.         RI = 0;

  92.             ++uiRcregTotal;
  93.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  94.         {
  95.            uiRcregTotal=const_rc_size;
  96.         }
  97.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  98.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  99.    
  100.    }
  101.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  102.    {
  103.         TI = 0;
  104.    }
  105.                                                          
  106. }                                


  107. void delay_long(unsigned int uiDelayLong)
  108. {
  109.    unsigned int i;
  110.    unsigned int j;
  111.    for(i=0;i<uiDelayLong;i++)
  112.    {
  113.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  114.           {
  115.              ; //一个分号相当于执行一条空语句
  116.           }
  117.    }
  118. }


  119. void initial_myself(void)  //第一区 初始化单片机
  120. {

  121.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  122.   //配置定时器
  123.   TMOD=0x01;  //设置定时器0为工作方式1
  124.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  125.   TL0=0x0b;


  126.   //配置串口
  127.   SCON=0x50;
  128.   TMOD=0X21;
  129.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  130.   TR1=1;

  131. }

  132. void initial_peripheral(void) //第二区 初始化外围
  133. {

  134.    EA=1;     //开总中断
  135.    ES=1;     //允许串口中断
  136.    ET0=1;    //允许定时中断
  137.    TR0=1;    //启动定时中断

  138. }
复制代码

总结陈词:
     这一节讲了常用的判断数据头来接收一串数据的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,有效数据长度,有效数据,数据校验的通讯协议。这样的程序该怎么写?欲知详情,请听下回分解-----常用的自定义串口通讯协议。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

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

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表