独闷闷网

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

[原创] 从业将近十年!手把手教你单片机程序框架(连载)

[复制链接]
41#
 楼主| 发表于 2014-4-3 01:39:15 | 显示全部楼层
第三十七节:数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

开场白:
    我在第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. }
复制代码



总结陈词:
    前面花了大量的章节在讲数码管显示,按键,运动的关联程序框架,从下一节开始,我将会用八节内容来讲我常用的串口程序框架,内容非常精彩和震撼,思路非常简单而又实用
。欲知详情,请听下回分解-----判断数据尾来接收一串数据的串口通用程序框架。

(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
42#
 楼主| 发表于 2014-4-5 11:19:52 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-4-6 22:29 编辑

第三十八节:判断数据尾来接收一串数据的串口通用程序框架。

开场白:
    在实际项目中,串口通讯不可能一次通讯只发送或接收一个字节,大部分的项目都是一次发送或者接受一串的数据。我们还要在这一串数据里解析数据协议,提取有用的数据。
这一节要教会大家三个知识点:
第一个:如何识别一串数据已经发送接收完毕。
第二个:如何在已经接收到的一串数据中解析数据尾协议并且提取有效数据。
第三个:接收一串数据的通用程序框架涉及到main循环里的串口服务程序,定时器的计时程序,串口接收中断程序的密切配合。大家要理解它们三者之间是如何关联起来的。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
波特率是:9600 。
通讯协议:XX YY  EB 00 55
           其中后三位 EB 00 55就是我所说的数据尾,它的有效数据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=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. }
复制代码

总结陈词:
     这一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这样的程序该怎么写?欲知详情,请听下回分解-----判断数据头来接收一串数据的串口通用程序框架。

(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
43#
 楼主| 发表于 2014-4-6 12:27:36 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-4-7 08:58 编辑

第三十九节:判断数据头来接收一串数据的串口通用程序框架。

开场白:
上一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这一节要教会大家两个知识点:
第一个:如何在已经接收到的一串数据中解析数据头协议并且提取有效数据。
第二个:无论是判断数据头还是判断数据尾,无论是单片机还是上位机,最好在固定协议前多发送一个填充的无效字节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. }
复制代码

总结陈词:
     这一节讲了常用的判断数据头来接收一串数据的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,有效数据长度,有效数据,数据校验的通讯协议。这样的程序该怎么写?欲知详情,请听下回分解-----常用的自定义串口通讯协议。

(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
44#
 楼主| 发表于 2014-4-7 13:14:48 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-7-21 00:17 编辑

第四十节:常用的自定义串口通讯协议。

开场白:
上一节讲了判断数据头的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,数据地址,有效数据长度,有效数据,数据校验的通讯协议。这一节要教会大家三个知识点:
第一个:常用自定义串口通讯协议的程序框架。
第二个:累加校验和的校验方法。累加和的意思是前面所有字节的数据相加,超过一个字节的溢出部分会按照固定的规则自动丢弃,不用我们管。比如以下数据:
      eb 00 55 01 00 02 0028 6b  
      其中eb 00 55为数据头,01为数据类型,00 02为有效数据长度,00 28 分别为具体的有效数据,6b为前面所有字节的累加和。累加和可以用电脑系统自带的计算器来验证。打开电脑上的计算器,点击“查看”下拉的菜单,选“科学型”,然后选左边的“十六进制”,最后选右边的“字节”,然后把前面所有的字节相加,它们的和就是6b,没错吧。
第三个:原子锁的使用方法,实际上是借鉴了"红金龙吸味"关于原子锁的建议,专门用来保护中断与主函数的共享数据。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
  波特率是:9600.
通讯协议:EB 00 55  GG HH HH XX XX …YYYY CY
其中第1,2,3位EB 00 55就是数据头
其中第4位GG就是数据类型。01代表驱动奉命,02代表驱动Led灯。
其中第5,6位HH就是有效数据长度。高位在左,低位在右。
其中第5,6位HH就是有效数据长度。高位在左,低位在右。
其中从第7位开始,到最后一个字节Cy之前,XX..YY都是具体的有效数据。
在本程序中,当数据类型是01时,有效数据代表蜂鸣器鸣叫的时间长度。当数据类型是02时,有效数据代表Led灯点亮的时间长度。
最后一个字节CY是累加和,前面所有字节的累加。
发送以下测试数据,将会分别控制蜂鸣器和Led灯的驱动时间长度。
蜂鸣器短叫发送:eb 00 55 01 00 02 00 28 6b  
蜂鸣器长叫发送:eb 00 55 01 00 02 00 fa 3d  
Led灯短亮发送:eb 00 55 02 00 02 00 28 6c
Led灯长亮发送:eb 00 55 02 00 02 00 fa3e  

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

  2. /* 注释一:
  3. * 请评估实际项目中一串数据的最大长度是多少,并且留点余量,然后调整const_rc_size的大小。
  4. * 本节程序把上一节的缓冲区数组大小10改成了20
  5. */
  6. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小

  7. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

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



  11. void T0_time(void);  //定时中断函数
  12. void usart_receive(void); //串口接收中断函数
  13. void usart_service(void);  //串口服务程序,在main函数里
  14. void led_service(void);  //Led灯的服务程序。

  15. sbit led_dr=P3^5;  //Led的驱动IO口
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量
  22. /* 注释二:
  23. * 为串口计时器多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  24. */
  25. unsigned char  ucSendCntLock=0; //串口计时器的原子锁

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

  27. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  28. unsigned char ucRcType=0;  //数据类型
  29. unsigned int  uiRcSize=0;  //数据长度
  30. unsigned char ucRcCy=0;  //校验累加和

  31. unsigned int  uiRcVoiceTime=0;  //蜂鸣器发出声音的持续时间

  32. unsigned int  uiRcLedTime=0; //在串口服务程序中,Led灯点亮时间长度的中间变量
  33. unsigned int  uiLedTime=0;  //Led灯点亮时间的长度
  34. unsigned int  uiLedCnt=0;   //Led灯点亮的计时器
  35. unsigned char ucLedLock=0;  //Led灯点亮时间的原子锁

  36. void main()
  37.   {
  38.    initial_myself();  
  39.    delay_long(100);   
  40.    initial_peripheral();
  41.    while(1)  
  42.    {
  43.        usart_service();  //串口服务程序
  44.        led_service(); //Led灯的服务程序
  45.    }

  46. }

  47. void led_service(void)
  48. {
  49.    if(uiLedCnt<uiLedTime)
  50.    {
  51.       led_dr=1; //开Led灯
  52.    }
  53.    else
  54.    {
  55.       led_dr=0; //关Led灯
  56.    }
  57. }

  58. void usart_service(void)  //串口服务程序,在main函数里
  59. {
  60. /* 注释三:
  61. * 我借鉴了朱兆祺的变量命名习惯,单个字母的变量比如i,j,k,h,这些变量只用作局部变量,直接在函数内部定义。
  62. */
  63.      unsigned int i;  
  64.         
  65.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  66.      {

  67.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据



  68.                     //下面的代码进入数据协议解析和数据处理的阶段

  69.                     uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动


  70.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  71.             {
  72.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  73.                {
  74.                          ucRcType=ucRcregBuf[uiRcMoveIndex+3];   //数据类型  一个字节

  75.                                          uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //数据长度  两个字节
  76.                                          uiRcSize=uiRcSize<<8;
  77.                                          uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  78.                                                                  
  79.                                          ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];   //记录最后一个字节的校验
  80.                      ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;  //清零最后一个字节的累加和变量
  81. /* 注释四:
  82. * 计算校验累加和的方法:除了最后一个字节,其它前面所有的字节累加起来,
  83. * 溢出的不用我们管,C语言编译器会按照固定的规则自动处理。
  84. * 以下for循环里的(3+1+2+uiRcSize),其中3代表3个字节数据头,1代表1个字节数据类型,
  85. * 2代表2个字节的数据长度变量,uiRcSize代表实际上一串数据中的有效数据个数。
  86. */
  87.                                           for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  88.                                          {
  89.                                                  ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  90.                      }        


  91.                                          if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])  //如果校验正确,则进入以下数据处理
  92.                                          {                                                  
  93.                          switch(ucRcType)   //根据不同的数据类型来做不同的数据处理
  94.                                              {

  95.                              case 0x01:   //驱动蜂鸣器发出声音,并且可以控制蜂鸣器持续发出声音的时间长度
  96.         
  97.                                   uiRcVoiceTime=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  98.                                   uiRcVoiceTime=uiRcVoiceTime<<8;  
  99.                                   uiRcVoiceTime=uiRcVoiceTime+ucRcregBuf[uiRcMoveIndex+7];

  100.                                   ucVoiceLock=1;  //共享数据的原子锁加锁
  101.                                   uiVoiceCnt=uiRcVoiceTime; //蜂鸣器发出声音
  102.                                   ucVoiceLock=0;  //共享数据的原子锁解锁

  103.                                                               break;        
  104.                                                                         
  105.                              case 0x02:   //点亮一个LED灯,并且可以控制LED灯持续亮的时间长度
  106.                                   uiRcLedTime=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  107.                                   uiRcLedTime=uiRcLedTime<<8;  
  108.                                   uiRcLedTime=uiRcLedTime+ucRcregBuf[uiRcMoveIndex+7];

  109.                                   ucLedLock=1;  //共享数据的原子锁加锁
  110.                                   uiLedTime=uiRcLedTime; //更改点亮Led灯的时间长度
  111.                                                                   uiLedCnt=0;  //在本程序中,清零计数器就等于自动点亮Led灯
  112.                                   ucLedLock=0;  //共享数据的原子锁解锁
  113.                                                               break;
  114.                                                                         
  115.                          }

  116.                                          }        

  117.                      break;   //退出循环
  118.                }
  119.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  120.            }
  121.                                          
  122.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  123.   
  124.      }
  125.                         
  126. }


  127. void T0_time(void) interrupt 1    //定时中断
  128. {
  129.   TF0=0;  //清除中断标志
  130.   TR0=0; //关中断

  131. /* 注释五:
  132.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  133.   */  
  134.   if(ucSendCntLock==0)  //原子锁判断
  135.   {
  136.      ucSendCntLock=1; //加锁
  137.      if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  138.      {
  139.         uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  140.         ucSendLock=1;     //开自锁标志
  141.      }
  142.      ucSendCntLock=0; //解锁
  143.   }

  144.   if(ucVoiceLock==0) //原子锁判断
  145.   {
  146.      if(uiVoiceCnt!=0)
  147.      {

  148.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  149.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  150.      
  151.      }
  152.      else
  153.      {

  154.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  155.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  156.         
  157.      }
  158.   }

  159.   if(ucLedLock==0)  //原子锁判断
  160.   {
  161.      if(uiLedCnt<uiLedTime)
  162.          {
  163.             uiLedCnt++;  //Led灯点亮的时间计时器
  164.          }
  165.   }

  166.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  167.   TL0=0x0b;
  168.   TR0=1;  //开中断
  169. }


  170. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  171. {        

  172.    if(RI==1)  
  173.    {
  174.         RI = 0;

  175.             ++uiRcregTotal;
  176.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  177.         {
  178.            uiRcregTotal=const_rc_size;
  179.         }
  180.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里

  181.         if(ucSendCntLock==0)  //原子锁判断
  182.         {
  183.             ucSendCntLock=1; //加锁
  184.             uiSendCnt=0;  //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
  185.                     ucSendCntLock=0; //解锁
  186.                 }
  187.    
  188.    }
  189.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  190.    {
  191.         TI = 0;
  192.    }
  193.                                                          
  194. }                                


  195. void delay_long(unsigned int uiDelayLong)
  196. {
  197.    unsigned int i;
  198.    unsigned int j;
  199.    for(i=0;i<uiDelayLong;i++)
  200.    {
  201.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  202.           {
  203.              ; //一个分号相当于执行一条空语句
  204.           }
  205.    }
  206. }


  207. void initial_myself(void)  //第一区 初始化单片机
  208. {
  209.   led_dr=0; //关Led灯
  210.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  211.   //配置定时器
  212.   TMOD=0x01;  //设置定时器0为工作方式1
  213.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  214.   TL0=0x0b;


  215.   //配置串口
  216.   SCON=0x50;
  217.   TMOD=0X21;
  218.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  219.   TR1=1;

  220. }

  221. void initial_peripheral(void) //第二区 初始化外围
  222. {

  223.    EA=1;     //开总中断
  224.    ES=1;     //允许串口中断
  225.    ET0=1;    //允许定时中断
  226.    TR0=1;    //启动定时中断

  227. }
复制代码



总结陈词:
这一节讲了常用的自定义串口通讯协议的程序框架,这种框架在判断一串数据是否接收完毕的时候,都是靠“超过规定的时间内,没有发现串口数据”来判定的,这是我做绝大多数项目的串口程序框架,但是在少数要求实时反应非常快的项目中,我会用另外一种响应速度更快的串口程序框架,这种程序框架是什么样的?欲知详情,请听下回分解-----在串口接收中断里即时解析数据头的特殊程序框架。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
45#
 楼主| 发表于 2014-4-19 11:51:19 | 显示全部楼层
第四十一节:在串口接收中断里即时解析数据头的特殊程序框架。

开场白:
上一节讲了常用的自定义串口通讯协议的程序框架,这种框架在判断一串数据是否接收完毕的时候,都是靠“超过规定的时间内,没有发现串口数据”来判定的,这是我做绝大多数项目的串口程序框架,但是在少数要求实时反应非常快的项目中,这样的程序框架可能会满足不了系统对速度的要求,这一节就是要介绍另外一种响应速度更加快的串口程序框架,要教会大家一个知识点:在串口接收中断里即时解析数据头的特殊程序框架。我在这种程序框架里,会尽量简化数据头和数据尾,同时也简化校验,目的都是为了提高响应速度。
具体内容,请看源代码讲解。

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
  波特率是:9600.
通讯协议:EB  GG XX XX XX XX ED
其中第1位EB就是数据头.
其中第2位GG就是数据类型。01代表驱动蜂鸣器,02代表驱动Led灯。
其中第3,4,5,6位XX就是有效数据长度。高位在左,低位在右。
其中第7位ED就是数据尾,在这里也起一部分校验的作用,虽然不是累加和的方式。

在本程序中,
当数据类型是01时,4个有效数据代表一个long类型数据,如果这个数据等于十进制的123456789,那么蜂鸣器就鸣叫一声表示正确。
当数据类型是02时,4个有效数据代表一个long类型数据,如果这个数据等于十进制的123456789,那么LED灯就会闪烁一下表示正确。
十进制的123456789等于十六进制的75bcd15 。
发送以下测试数据,将会分别控制蜂鸣器Led灯。
控制蜂鸣器发送:eb 01 07 5b cd 15 ed
控制LED灯发送:eb 02 07 5b cd 15 ed

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


  2. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小
  3. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  4. #define const_voice_short  80   //蜂鸣器短叫的持续时间
  5. #define const_led_short  80    //LED灯亮的持续时间

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



  9. void T0_time(void);  //定时中断函数
  10. void usart_receive(void); //串口接收中断函数
  11. void led_service(void);  //Led灯的服务程序。

  12. sbit led_dr=P3^5;  //Led的驱动IO口
  13. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口


  14. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  15. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组

  16. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  17. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁



  18. unsigned int  uiRcVoiceTime=0;  //蜂鸣器发出声音的持续时间

  19. unsigned int  uiLedCnt=0;   //Led灯点亮的计时器
  20. unsigned char ucLedLock=0;  //Led灯点亮时间的原子锁

  21. unsigned long ulBeepData=0; //蜂鸣器的数据
  22. unsigned long ulLedData=0; //LED的数据

  23. unsigned char ucUsartStep=0; //串口接收字节的步骤变量

  24. void main()
  25.   {
  26.    initial_myself();  
  27.    delay_long(100);   
  28.    initial_peripheral();
  29.    while(1)  
  30.    {
  31.        led_service(); //Led灯的服务程序
  32.    }

  33. }

  34. void led_service(void)
  35. {
  36.    if(uiLedCnt<const_led_short)
  37.    {
  38.       led_dr=1; //开Led灯
  39.    }
  40.    else
  41.    {
  42.       led_dr=0; //关Led灯
  43.    }
  44. }



  45. void T0_time(void) interrupt 1    //定时中断
  46. {
  47.   TF0=0;  //清除中断标志
  48.   TR0=0; //关中断

  49. /* 注释一:
  50.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  51.   */  

  52.   if(ucVoiceLock==0) //原子锁判断
  53.   {
  54.      if(uiVoiceCnt!=0)
  55.      {

  56.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  57.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  58.      
  59.      }
  60.      else
  61.      {

  62.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  63.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  64.         
  65.      }
  66.   }

  67.   if(ucLedLock==0)  //原子锁判断
  68.   {
  69.      if(uiLedCnt<const_led_short)
  70.      {
  71.             uiLedCnt++;  //Led灯点亮的时间计时器
  72.      }

  73.   }

  74.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  75.   TL0=0x0b;
  76.   TR0=1;  //开中断
  77. }


  78. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  79. {        
  80. /* 注释二:
  81.   * 以下就是吴坚鸿在串口接收中断里即时解析数据头的特殊程序框架,
  82.   * 它的特点是靠数据头来启动接受有效数据,靠数据尾来识别一串数据接受完毕,
  83.   * 这里的数据尾也起到一部分的校验作用,让数据更加可靠。这种程序结构适合应用
  84.   * 在传输的数据长度不是很长,而且要求响应速度非常高的实时场合。在这种实时要求
  85.   * 非常高的场合中,我就不像之前一样做数据累加和的复杂运算校验,只用数据尾来做简单的
  86.   * 校验确认,目的是尽可能提高处理速度。
  87.   */  

  88.    if(RI==1)  
  89.    {
  90.         RI = 0;

  91.         switch(ucUsartStep) //串口接收字节的步骤变量
  92.         {
  93.             case 0:
  94.                              ucRcregBuf[0]=SBUF;     
  95.                  if(ucRcregBuf[0]==0xeb)  //数据头判断
  96.                  {
  97.                                      ucRcregBuf[0]=0;  //数据头及时清零,为下一串数据的接受判断做准备
  98.                                      uiRcregTotal=1;  //缓存数组的下标初始化
  99.                      ucUsartStep=1;  //如果数据头正确,则切换到下一步,依次把上位机来的数据存入数组缓冲区
  100.                  }
  101.                  break;
  102.             case 1:
  103.                              ucRcregBuf[uiRcregTotal]=SBUF;  //依次把上位机来的数据存入数组缓冲区
  104.                                  uiRcregTotal++; //下标移动
  105.                                  if(uiRcregTotal>=7)  //已经接收了7个字节
  106.                                  {
  107.                    if(ucRcregBuf[6]==0xed)  //数据尾判断,也起到一部分校验的作用,让数据更加可靠,虽然没有用到累加和的检验方法
  108.                                    {
  109.                                        ucRcregBuf[6]=0;  //数据尾及时清零,为下一串数据的接受判断做准备                                       
  110.                                        switch(ucRcregBuf[1]) //根据不同的数据类型来做不同的数据处理
  111.                                            {
  112.                                                case 0x01:  //与蜂鸣器相关
  113.                                 ulBeepData=ucRcregBuf[2]; //把四个字节的数据合并成一个long型的数据
  114.                                                             ulBeepData=ulBeepData<<8;
  115.                                                             ulBeepData=ulBeepData+ucRcregBuf[3];
  116.                                                             ulBeepData=ulBeepData<<8;
  117.                                                             ulBeepData=ulBeepData+ucRcregBuf[4];
  118.                                                             ulBeepData=ulBeepData<<8;
  119.                                                             ulBeepData=ulBeepData+ucRcregBuf[5];
  120.                                                                 if(ulBeepData==123456789)  //如果此数据等于十进制的123456789,表示数据正确
  121.                                                                 {
  122.                                                                     ucVoiceLock=1;  //共享数据的原子锁加锁
  123.                                     uiVoiceCnt=const_voice_short; //蜂鸣器发出声音
  124.                                     ucVoiceLock=0;  //共享数据的原子锁解锁
  125.                                                                 }

  126.                                                         break;

  127.                                                case 0x02:  //与Led灯相关
  128.                                 ulLedData=ucRcregBuf[2]; //把四个字节的数据合并成一个long型的数据
  129.                                                             ulLedData=ulLedData<<8;
  130.                                                             ulLedData=ulLedData+ucRcregBuf[3];
  131.                                                             ulLedData=ulLedData<<8;
  132.                                                             ulLedData=ulLedData+ucRcregBuf[4];
  133.                                                             ulLedData=ulLedData<<8;
  134.                                                             ulLedData=ulLedData+ucRcregBuf[5];
  135.                                                                 if(ulLedData==123456789)  //如果此数据等于十进制的123456789,表示数据正确
  136.                                                                 {
  137.                                                                     ucLedLock=1;  //共享数据的原子锁加锁
  138.                                     uiLedCnt=0;  //在本程序中,清零计数器就等于自动点亮Led灯
  139.                                     ucLedLock=0;  //共享数据的原子锁解锁
  140.                                                                 }



  141.                                                         break;
  142.                                            }

  143.                                    }

  144.                    ucUsartStep=0;     //返回上一步数据头判断,为下一次的新数据接收做准备
  145.                                  }
  146.                  break;
  147.         }
  148.    
  149.    }
  150.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  151.    {
  152.         TI = 0;
  153.    }
  154.                                                          
  155. }                                


  156. void delay_long(unsigned int uiDelayLong)
  157. {
  158.    unsigned int i;
  159.    unsigned int j;
  160.    for(i=0;i<uiDelayLong;i++)
  161.    {
  162.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  163.           {
  164.              ; //一个分号相当于执行一条空语句
  165.           }
  166.    }
  167. }


  168. void initial_myself(void)  //第一区 初始化单片机
  169. {
  170.   led_dr=0; //关Led灯
  171.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  172.   //配置定时器
  173.   TMOD=0x01;  //设置定时器0为工作方式1
  174.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  175.   TL0=0x0b;


  176.   //配置串口
  177.   SCON=0x50;
  178.   TMOD=0X21;
  179.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  180.   TR1=1;

  181. }

  182. void initial_peripheral(void) //第二区 初始化外围
  183. {

  184.    EA=1;     //开总中断
  185.    ES=1;     //允许串口中断
  186.    ET0=1;    //允许定时中断
  187.    TR0=1;    //启动定时中断

  188. }
复制代码

总结陈词:
    前面花了4节内容仔细讲了各种串口接收数据的常用框架,从下一节开始,我开始讲串口发送数据的程序框架,这种程序框架是什么样的?欲知详情,请听下回分解-----通过串口用delay延时方式发送一串数据。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
46#
 楼主| 发表于 2014-4-22 10:57:04 | 显示全部楼层
第四十二节:通过串口用delay延时方式发送一串数据。

开场白:
   上一节讲了在串口接收中断里即时解析数据头的特殊程序框架。这节开始讲串口发送数据需要特别注意的地方和程序框架,要教会大家一个知识点:根据我个人的经验,在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。当然,也有一些朋友可能不增加延时,直接靠单片机自带的发送完成标志位来判断,但是我以前在做项目中,感觉单单靠发送完成标志位来判断还是容易出错(当然也有可能是我自身程序的问题),所以后来在大部分的项目中我就干脆靠延时来等待它发送完成。我在51,PIC单片机中都是这么做的。但是,凭我的经验,在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
  波特率是:9600.
按一次按键S1,单片机就往上位机发送以下一串数据:
eb 00 55 01 00 00 00 00 41

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


  2. #define const_send_size  10  //串口发送数据的缓冲区数组大小

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

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

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

  9. void eusart_send(unsigned char ucSendData);  //发送一个字节,内部自带每个字节之间的延时

  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数

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

  14. sbit led_dr=P3^5;  //Led的驱动IO口
  15. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  16. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  17. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平



  18. unsigned char ucSendregBuf[const_send_size]; //接收串口中断数据的缓冲区数组


  19. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  20. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

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

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

  24. void main()
  25. {
  26.    initial_myself();  
  27.    delay_long(100);   
  28.    initial_peripheral();
  29.    while(1)  
  30.    {
  31.       key_service(); //按键服务的应用程序
  32.    }

  33. }



  34. void eusart_send(unsigned char ucSendData)
  35. {

  36.   ES = 0; //关串口中断
  37.   TI = 0; //清零串口发送完成中断请求标志
  38.   SBUF =ucSendData; //发送一个字节

  39. /* 注释一:
  40.   * 根据我个人的经验,在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。
  41.   * 当然,也有一些朋友可能不增加延时,直接靠单片机自带的发送完成标志位来判断,但是我以前
  42.   * 在做项目中,感觉单单靠发送完成标志位来判断还是容易出错(当然也有可能是我自身程序的问题),
  43.   * 所以后来在大部分的项目中我就干脆靠延时来等待它发送完成。我在51,PIC单片机中都是这么做的。
  44.   * 但是,凭我的经验,在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。
  45.   */  

  46.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  47.   TI = 0; //清零串口发送完成中断请求标志
  48.   ES = 1; //允许串口中断

  49. }

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

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



  67. }


  68. void key_service() //第三区 按键服务的应用程序
  69. {
  70.   unsigned int i;

  71.   switch(ucKeySec) //按键服务状态切换
  72.   {
  73.     case 1:// 1号键 对应朱兆祺学习板的S1键
  74.           ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  75.           ucSendregBuf[1]=0x00;
  76.           ucSendregBuf[2]=0x55;
  77.           ucSendregBuf[3]=0x01;
  78.           ucSendregBuf[4]=0x00;
  79.           ucSendregBuf[5]=0x00;
  80.           ucSendregBuf[6]=0x00;
  81.           ucSendregBuf[7]=0x00;
  82.           ucSendregBuf[8]=0x41;

  83.                   for(i=0;i<9;i++)
  84.                   {
  85.                      eusart_send(ucSendregBuf[i]);  //发送一串数据给上位机
  86.                   }

  87.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  88.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  89.                   ucVoiceLock=0; //原子锁解锁

  90.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  91.           break;        
  92.   }        
  93. }



  94. void T0_time(void) interrupt 1    //定时中断
  95. {
  96.   TF0=0;  //清除中断标志
  97.   TR0=0; //关中断

  98. /* 注释二:
  99.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  100.   */  

  101.   if(ucVoiceLock==0) //原子锁判断
  102.   {
  103.      if(uiVoiceCnt!=0)
  104.      {

  105.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  106.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  107.      
  108.      }
  109.      else
  110.      {

  111.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  112.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  113.        
  114.      }
  115.   }

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


  117.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  118.   TL0=0x0b;
  119.   TR0=1;  //开中断
  120. }


  121. void usart_receive(void) interrupt 4                 //串口中断        
  122. {        

  123.    if(RI==1)  
  124.    {
  125.         RI = 0;   //接收中断,及时把接收中断标志位清零

  126.       
  127.    
  128.    }
  129.    else
  130.    {
  131.         TI = 0;    //发送中断,及时把发送中断标志位清零
  132.    }
  133.                                                          
  134. }                                

  135. void delay_short(unsigned int uiDelayShort)
  136. {
  137.    unsigned int i;  
  138.    for(i=0;i<uiDelayShort;i++)
  139.    {
  140.      ;   //一个分号相当于执行一条空语句
  141.    }
  142. }


  143. void delay_long(unsigned int uiDelayLong)
  144. {
  145.    unsigned int i;
  146.    unsigned int j;
  147.    for(i=0;i<uiDelayLong;i++)
  148.    {
  149.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  150.           {
  151.              ; //一个分号相当于执行一条空语句
  152.           }
  153.    }
  154. }


  155. void initial_myself(void)  //第一区 初始化单片机
  156. {
  157. /* 注释三:
  158. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  159. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  160. * 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
  161. */
  162.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  163.   led_dr=0; //关Led灯
  164.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  165.   //配置定时器
  166.   TMOD=0x01;  //设置定时器0为工作方式1
  167.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  168.   TL0=0x0b;


  169.   //配置串口
  170.   SCON=0x50;
  171.   TMOD=0X21;
  172.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率9600。
  173.   TR1=1;

  174. }

  175. void initial_peripheral(void) //第二区 初始化外围
  176. {

  177.    EA=1;     //开总中断
  178.    ES=1;     //允许串口中断
  179.    ET0=1;    //允许定时中断
  180.    TR0=1;    //启动定时中断

  181. }
复制代码

总结陈词:
这节在每个字节之间都添加了delay延时来等待每个字节的发送完成,由于delay(400)这个时间还不算很长,所以可以应用在很多简单任务的系统中。但是在某些任务量很多的系统中,实时运行的主任务不允许被长时间和经常性地中断,这个时候就需要用计数延时来替代delay延时,这种程序框架是什么样的?欲知详情,请听下回分解-----通过串口用计数延时方式发送一串数据。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
47#
 楼主| 发表于 2014-4-25 11:02:42 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-4-26 10:25 编辑

第四十三节:通过串口用计数延时方式发送一串数据。

开场白:
上一节讲了通过串口用delay延时方式发送一串数据,这种方式要求发送一串数据的时候一气呵成,期间不能执行其它任务,由于delay(400)这个时间还不算很长,所以可以应用在很多简单任务的系统中。但是在某些任务量很多的系统中,实时运行的主任务不允许被长时间和经常性地中断,这个时候就需要用计数延时来替代delay延时。本节要教会大家两个知识点:
第一个:用计数延时方式发送一串数据的程序框架。
第二个:环形消息队列的程序框架。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
     波特率是:9600.
用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
    按一次按键S1,发送EB 00 55 01 00 00 00 00 41
按一次按键S5,发送EB 00 55 02 00 00 00 00 42
按一次按键S9,发送EB 00 55 03 00 00 00 00 43
按一次按键S13,发送EB 00 55 04 00 00 00 00 44
(3)源代码讲解如下:
  1. #include "REG52.H"


  2. #define const_send_time  100  //累计主循环次数的计数延时 请根据项目实际情况来调整此数据大小

  3. #define const_send_size  10  //串口发送数据的缓冲区数组大小

  4. #define const_Message_size  10  //环形消息队列的缓冲区数组大小

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

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

  10. void initial_myself(void);   
  11. void initial_peripheral(void);
  12. //void delay_short(unsigned int uiDelayshort);
  13. void delay_long(unsigned int uiDelaylong);

  14. void eusart_send(unsigned char ucSendData);  //发送一个字节,内部没有每个字节之间的延时
  15. void send_service(void);  //利用累计主循环次数的计数延时方式来发送一串数据

  16. void T0_time(void);  //定时中断函数
  17. void usart_receive(void); //串口接收中断函数

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


  20. void insert_message(unsigned char ucMessageTemp);  //插入新的消息到环形消息队列里
  21. unsigned char get_message(void);  //从环形消息队列里提取消息



  22. sbit led_dr=P3^5;  //Led的驱动IO口
  23. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  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. unsigned char ucSendregBuf[const_send_size]; //串口发送数据的缓冲区数组

  30. unsigned char ucMessageBuf[const_Message_size]; //环形消息队列的缓冲区数据
  31. unsigned int  uiMessageCurrent=0;  //环形消息队列的取数据当前位置
  32. unsigned int  uiMessageInsert=0;  //环形消息队列的插入新消息时候的位置
  33. unsigned int  uiMessageCnt=0;  //统计环形消息队列的消息数量  等于0时表示消息队列里没有消息

  34. unsigned char ucMessage=0; //当前获取到的消息

  35. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  36. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  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 char ucSendStep=0;  //发送一串数据的运行步骤
  47. unsigned int  uiSendTimeCnt=0; //累计主循环次数的计数延时器

  48. unsigned int uiSendCnt=0; //发送数据时的中间变量

  49. void main()
  50. {
  51.    initial_myself();  
  52.    delay_long(100);   
  53.    initial_peripheral();
  54.    while(1)  
  55.    {
  56.       key_service(); //按键服务的应用程序
  57.           send_service();  //利用累计主循环次数的计数延时方式来发送一串数据
  58.    }

  59. }

  60. /* 注释一:
  61.   * 通过判断数组下标是否超范围的条件,把一个数组的首尾连接起来,就像一个环形,
  62.   * 因此命名为环形消息队列。环形消息队列有插入消息,获取消息两个核心函数,以及一个
  63.   * 统计消息总数的uiMessageCnt核心变量,通过此变量,我们可以知道消息队列里面是否有消息需要处理.
  64.   * 我在做项目中很少用消息队列的,印象中我只在两个项目中用过消息队列这种方法。大部分的单片机
  65.   * 项目其实直接用一两个中间变量就可以起到传递消息的作用,就能满足系统的要求。以下是各变量的含义:
  66.   * #define const_Message_size  10  //环形消息队列的缓冲区数组大小
  67.   * unsigned char ucMessageBuf[const_Message_size]; //环形消息队列的缓冲区数据
  68.   * unsigned int  uiMessageCurrent=0;  //环形消息队列的取数据当前位置
  69.   * unsigned int  uiMessageInsert=0;  //环形消息队列的插入新消息时候的位置
  70.   * unsigned int  uiMessageCnt=0;  //统计环形消息队列的消息数量  等于0时表示消息队列里没有消息
  71.   */  

  72. void insert_message(unsigned char ucMessageTemp)  //插入新的消息到环形消息队列里
  73. {
  74.    if(uiMessageCnt<const_Message_size)  //消息总数小于环形消息队列的缓冲区才允许插入新消息
  75.    {
  76.       ucMessageBuf[uiMessageInsert]=ucMessageTemp;

  77.           uiMessageInsert++;  //插入新消息时候的位置
  78.           if(uiMessageInsert>=const_Message_size) //到了缓冲区末尾,则从缓冲区的开头重新开始。数组的首尾连接,看起来就像环形
  79.           {
  80.              uiMessageInsert=0;
  81.           }
  82.       uiMessageCnt++; //消息数量累加  等于0时表示消息队列里没有消息
  83.    }
  84. }

  85. unsigned char get_message(void)  //从环形消息队列里提取消息
  86. {
  87.    unsigned char ucMessageTemp=0;  //返回的消息中间变量,默认为0

  88.    if(uiMessageCnt>0)  //只有消息数量大于0时才可以提取消息
  89.    {
  90.       ucMessageTemp=ucMessageBuf[uiMessageCurrent];
  91.           uiMessageCurrent++;  //环形消息队列的取数据当前位置
  92.           if(uiMessageCurrent>=const_Message_size) //到了缓冲区末尾,则从缓冲区的开头重新开始。数组的首尾连接,看起来就像环形
  93.           {
  94.              uiMessageCurrent=0;
  95.           }
  96.       uiMessageCnt--; //每提取一次,消息数量就减一  等于0时表示消息队列里没有消息
  97.    }

  98.    return ucMessageTemp;
  99. }


  100. void send_service(void)  //利用累计主循环次数的计数延时方式来发送一串数据
  101. {
  102.   switch(ucSendStep)  //发送一串数据的运行步骤
  103.   {
  104.     case 0:   //从环形消息队列里提取消息
  105.          if(uiMessageCnt>0)  //说明有消息需要处理
  106.                  {
  107.                     ucMessage=get_message();
  108.             switch(ucMessage)   //消息处理
  109.                         {
  110.                            case 1:
  111.                     ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  112.                     ucSendregBuf[1]=0x00;
  113.                     ucSendregBuf[2]=0x55;
  114.                     ucSendregBuf[3]=0x01;    //01代表1号键
  115.                     ucSendregBuf[4]=0x00;
  116.                     ucSendregBuf[5]=0x00;
  117.                     ucSendregBuf[6]=0x00;
  118.                     ucSendregBuf[7]=0x00;
  119.                     ucSendregBuf[8]=0x41;

  120.                     uiSendCnt=0; //发送数据的中间变量清零
  121.                     uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
  122.                     ucSendStep=1; //切换到下一步发送一串数据
  123.                                 break;
  124.                            case 2:
  125.                     ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  126.                     ucSendregBuf[1]=0x00;
  127.                     ucSendregBuf[2]=0x55;
  128.                     ucSendregBuf[3]=0x02;    //02代表2号键
  129.                     ucSendregBuf[4]=0x00;
  130.                     ucSendregBuf[5]=0x00;
  131.                     ucSendregBuf[6]=0x00;
  132.                     ucSendregBuf[7]=0x00;
  133.                     ucSendregBuf[8]=0x42;

  134.                     uiSendCnt=0; //发送数据的中间变量清零
  135.                     uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
  136.                     ucSendStep=1; //切换到下一步发送一串数据
  137.                                 break;
  138.                            case 3:
  139.                     ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  140.                     ucSendregBuf[1]=0x00;
  141.                     ucSendregBuf[2]=0x55;
  142.                     ucSendregBuf[3]=0x03;    //03代表3号键
  143.                     ucSendregBuf[4]=0x00;
  144.                     ucSendregBuf[5]=0x00;
  145.                     ucSendregBuf[6]=0x00;
  146.                     ucSendregBuf[7]=0x00;
  147.                     ucSendregBuf[8]=0x43;

  148.                     uiSendCnt=0; //发送数据的中间变量清零
  149.                     uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
  150.                     ucSendStep=1; //切换到下一步发送一串数据
  151.                                 break;
  152.                            case 4:
  153.                     ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  154.                     ucSendregBuf[1]=0x00;
  155.                     ucSendregBuf[2]=0x55;
  156.                     ucSendregBuf[3]=0x04;    //04代表4号键
  157.                     ucSendregBuf[4]=0x00;
  158.                     ucSendregBuf[5]=0x00;
  159.                     ucSendregBuf[6]=0x00;
  160.                     ucSendregBuf[7]=0x00;
  161.                     ucSendregBuf[8]=0x44;

  162.                     uiSendCnt=0; //发送数据的中间变量清零
  163.                     uiSendTimeCnt=0; //累计主循环次数的计数延时器清零
  164.                     ucSendStep=1; //切换到下一步发送一串数据
  165.                                 break;

  166.                default:  //如果没有符合要求的消息,则不处理

  167.                                 ucSendStep=0; //维持现状,不切换
  168.                                 break;
  169.                         }
  170.                  }
  171.              break;

  172.     case 1:  //利用累加主循环次数的计数延时方式来发送一串数据

  173. /* 注释二:
  174.   * 这里的计数延时为什么不用累计定时中断次数的延时,而用累计主循环次数的计数延时?
  175.   * 因为本程序定时器中断一次需要500个指令时间,时间分辨率太低,不方便微调时间。因此我
  176.   * 就用累计主循环次数的计数延时方式,在做项目的时候,各位读者应该根据系统的实际情况
  177.   * 来调整const_send_time的大小。
  178.   */  
  179.          uiSendTimeCnt++;  //累计主循环次数的计数延时,为每个字节之间增加延时,
  180.                  if(uiSendTimeCnt>const_send_time)  //请根据实际系统的情况,调整const_send_time的大小
  181.                  {
  182.                     uiSendTimeCnt=0;

  183.                         eusart_send(ucSendregBuf[uiSendCnt]);  //发送一串数据给上位机
  184.             uiSendCnt++;
  185.                         if(uiSendCnt>=9) //说明数据已经发送完毕
  186.                         {
  187.                            uiSendCnt=0;
  188.                ucSendStep=0; //返回到上一步,处理其它未处理的消息
  189.                         }
  190.                  }

  191.              break;  
  192.   }

  193. }


  194. void eusart_send(unsigned char ucSendData)
  195. {

  196.   ES = 0; //关串口中断
  197.   TI = 0; //清零串口发送完成中断请求标志
  198.   SBUF =ucSendData; //发送一个字节

  199. /* 注释三:
  200.   * 根据我个人的经验,在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。
  201.   * 当然,也有一些朋友可能不增加延时,直接靠单片机自带的发送完成标志位来判断,但是我以前
  202.   * 在做项目中,感觉单单靠发送完成标志位来判断还是容易出错(当然也有可能是我自身程序的问题),
  203.   * 所以后来在大部分的项目中我就干脆靠延时来等待它发送完成。我在51,PIC单片机中都是这么做的。
  204.   * 但是,凭我的经验,在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。
  205.   */  

  206. //  delay_short(400);  //因为外部在每个发送字节之间用了累计主循环次数的计数延时,因此不要此行的delay延时

  207.   TI = 0; //清零串口发送完成中断请求标志
  208.   ES = 1; //允许串口中断

  209. }


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


  212.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  213.   {
  214.      ucKeyLock1=0; //按键自锁标志清零
  215.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  216.   }
  217.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  218.   {
  219.      uiKeyTimeCnt1++; //累加定时中断次数
  220.      if(uiKeyTimeCnt1>const_key_time1)
  221.      {
  222.         uiKeyTimeCnt1=0;
  223.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  224.         ucKeySec=1;    //触发1号键
  225.      }
  226.   }

  227.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  228.   {
  229.      ucKeyLock2=0; //按键自锁标志清零
  230.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  231.   }
  232.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  233.   {
  234.      uiKeyTimeCnt2++; //累加定时中断次数
  235.      if(uiKeyTimeCnt2>const_key_time2)
  236.      {
  237.         uiKeyTimeCnt2=0;
  238.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  239.         ucKeySec=2;    //触发2号键
  240.      }
  241.   }

  242.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  243.   {
  244.      ucKeyLock3=0; //按键自锁标志清零
  245.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  246.   }
  247.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  248.   {
  249.      uiKeyTimeCnt3++; //累加定时中断次数
  250.      if(uiKeyTimeCnt3>const_key_time3)
  251.      {
  252.         uiKeyTimeCnt3=0;
  253.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  254.         ucKeySec=3;    //触发3号键
  255.      }
  256.   }

  257.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  258.   {
  259.      ucKeyLock4=0; //按键自锁标志清零
  260.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  261.   }
  262.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  263.   {
  264.      uiKeyTimeCnt4++; //累加定时中断次数
  265.      if(uiKeyTimeCnt4>const_key_time4)
  266.      {
  267.         uiKeyTimeCnt4=0;
  268.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  269.         ucKeySec=4;    //触发4号键
  270.      }
  271.   }


  272. }


  273. void key_service(void) //第三区 按键服务的应用程序
  274. {


  275.   switch(ucKeySec) //按键服务状态切换
  276.   {
  277.     case 1:// 1号键 对应朱兆祺学习板的S1键

  278.           insert_message(0x01);  //把新消息插入到环形消息队列里等待处理

  279.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  280.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  281.                   ucVoiceLock=0; //原子锁解锁

  282.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  283.           break;   
  284.     case 2:// 2号键 对应朱兆祺学习板的S5键

  285.           insert_message(0x02);  //把新消息插入到环形消息队列里等待处理

  286.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  287.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  288.                   ucVoiceLock=0; //原子锁解锁

  289.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  290.           break;     
  291.     case 3:// 3号键 对应朱兆祺学习板的S9键

  292.           insert_message(0x03);  //把新消息插入到环形消息队列里等待处理

  293.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  294.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  295.                   ucVoiceLock=0; //原子锁解锁

  296.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  297.           break;  
  298.     case 4:// 4号键 对应朱兆祺学习板的S13键

  299.           insert_message(0x04);  //把新消息插入到环形消息队列里等待处理

  300.           ucVoiceLock=1;  //原子锁加锁,保护中断与主函数的共享数据
  301.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  302.                   ucVoiceLock=0; //原子锁解锁

  303.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  304.           break;   

  305.   }        
  306. }



  307. void T0_time(void) interrupt 1    //定时中断
  308. {
  309.   TF0=0;  //清除中断标志
  310.   TR0=0; //关中断

  311. /* 注释四:
  312.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  313.   */  

  314.   if(ucVoiceLock==0) //原子锁判断
  315.   {
  316.      if(uiVoiceCnt!=0)
  317.      {

  318.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  319.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  320.      
  321.      }
  322.      else
  323.      {

  324.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  325.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  326.         
  327.      }
  328.   }

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


  330.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  331.   TL0=0x0b;
  332.   TR0=1;  //开中断
  333. }


  334. void usart_receive(void) interrupt 4                 //串口中断        
  335. {        

  336.    if(RI==1)  
  337.    {
  338.         RI = 0;   //接收中断,及时把接收中断标志位清零

  339.       
  340.    
  341.    }
  342.    else
  343.    {
  344.         TI = 0;    //发送中断,及时把发送中断标志位清零
  345.    }
  346.                                                          
  347. }                                



  348. //void delay_short(unsigned int uiDelayShort)
  349. //{
  350. //   unsigned int i;  
  351. //   for(i=0;i<uiDelayShort;i++)
  352. //   {
  353. //     ;   //一个分号相当于执行一条空语句
  354. //   }
  355. //}


  356. void delay_long(unsigned int uiDelayLong)
  357. {
  358.    unsigned int i;
  359.    unsigned int j;
  360.    for(i=0;i<uiDelayLong;i++)
  361.    {
  362.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  363.           {
  364.              ; //一个分号相当于执行一条空语句
  365.           }
  366.    }
  367. }


  368. void initial_myself(void)  //第一区 初始化单片机
  369. {
  370. /* 注释五:
  371. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  372. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  373. * 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
  374. */
  375.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  376.   led_dr=0; //关Led灯
  377.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

  378.   //配置定时器
  379.   TMOD=0x01;  //设置定时器0为工作方式1
  380.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  381.   TL0=0x0b;


  382.   //配置串口
  383.   SCON=0x50;
  384.   TMOD=0X21;
  385.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率9600。
  386.   TR1=1;

  387. }

  388. void initial_peripheral(void) //第二区 初始化外围
  389. {

  390.    EA=1;     //开总中断
  391.    ES=1;     //允许串口中断
  392.    ET0=1;    //允许定时中断
  393.    TR0=1;    //启动定时中断

  394. }
复制代码

总结陈词:
         前面几个章节中,每个章节要么独立地讲解串口收数据,要么独立地讲解发数据,实际上在大部分的项目中,串口都需要“一收一应答”的握手协议,上位机作为主机,单片机作为从机,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。读者只要把我的串口收发程序结合起来,就很容易实现这样的功能,我就不再详细讲解了。从下一节开始我讲解单片机掉电后数据保存的内容,欲知详情,请听下回分解-----利用AT24C02进行掉电后的数据保存。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
48#
 楼主| 发表于 2014-5-3 08:45:37 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-7-21 00:20 编辑

第四十四节:从机的串口收发综合程序框架

开场白:
根据上一节的预告,本来这一节内容打算讲“利用AT24C02进行掉电后的数据保存”的,但是由于网友“261854681”强烈建议我讲一个完整的串口收发程序实例,因此我决定再花两节篇幅讲讲这方面的内容。
实际上在大部分的项目中,串口都需要“一收一应答”的握手协议,上位机作为主机,单片机作为从机,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
这节先讲从机的收发端程序实例。要教会大家三个知识点:
第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置IP = 0x10,相当于把串口中断设置为最高优先级,这个时候,串口中断可以打断任何其他的中断服务函数,实现中断嵌套。
第二个:从机端的收发端程序框架。
第三个:从机的状态指示程序框架。可以指示待机,通讯中,超时出错三种状态。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
      一共有4个窗口。每个窗口显示一个参数。有两种更改参数的方式:
第一种:按键更改参数:
    第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
    第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。S13是复位按键,当通讯超时蜂鸣器报警时,可以按下此键清除报警。

第二种:通过串口来更改参数:
     波特率是:9600.
通讯协议:EB 00 55  GG 00 02 XX XX  CY
其中第1,2,3位EB 00 55就是数据头
其中第4位GG就是数据类型。01代表更改参数1,02代表更改参数2,03代表更改参数3,04代表更改参数4,
其中第5,6位00 02就是有效数据长度。高位在左,低位在右。
其中从第7,8位XX XX是被更改的参数。高位在左,低位在右。
第9位CY是累加和,前面所有字节的累加。
一个完整的通讯必须接收完4串数据,每串数据之间的间隔时间不能超过10秒钟,否则认为通讯超时出错引发蜂鸣器报警。如果接收到得数据校验正确,
则返回校验正确应答:eb 00        55 f5 00 00 35,
否则返回校验出错应答::eb 00        55 fa 00 00 3a。
   系统处于待机状态时,LED灯一直亮,
   系统处于非待机状态时,LED灯闪烁,
   系统处于通讯超时出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。


通过电脑的串口助手,依次发送以下测试数据,将会分别更改参数1,参数2,参数3,参数4。注意,每串数据之间的时间最大不能超过10秒,否则系统认为通讯超时报警。
把参数1更改为十进制的1:   eb 00 55 01 00 02 00 01 44
把参数2更改为十进制的12:  eb 00 55 02 00 02 00 0c 50
把参数3更改为十进制的123: eb 00 55 03 00 02 00 7b c0
把参数4更改为十进制的1234:eb 00 55 04 00 02 04 d2 1c

(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_led_0_5s  200   //大概0.5秒的时间
  8. #define const_led_1s    400   //大概1秒的时间

  9. #define const_send_time_out   4000  //通讯超时出错的时间 大概10秒

  10. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小
  11. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  12. #define const_send_size  10  //串口发送数据的缓冲区数组大小

  13. void initial_myself(void);   
  14. void initial_peripheral(void);
  15. void delay_short(unsigned int uiDelayShort);
  16. void delay_long(unsigned int uiDelaylong);
  17. //驱动数码管的74HC595
  18. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  19. void display_drive(void); //显示数码管字模的驱动函数
  20. void display_service(void); //显示的窗口菜单服务程序
  21. //驱动LED的74HC595
  22. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  23. void T0_time(void);  //定时中断函数
  24. void usart_receive(void); //串口接收中断函数
  25. void usart_service(void);  //串口服务程序,在main函数里
  26. void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时

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

  29. void status_service(void);  //状态显示的应用程序


  30. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  31. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  32. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  33. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
  34. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  35. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  36. sbit led_dr=P3^5;  //作为状态指示灯 亮的时候表示待机状态.闪烁表示非待机状态,处于正在发送数据或者出错的状态

  37. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  38. sbit dig_hc595_st_dr=P2^1;  
  39. sbit dig_hc595_ds_dr=P2^2;  
  40. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  41. sbit hc595_st_dr=P2^4;  
  42. sbit hc595_ds_dr=P2^5;  

  43. unsigned char ucSendregBuf[const_send_size]; //发送的缓冲区数组

  44. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  45. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  46. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  47. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  48. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  49. unsigned char  ucSendCntLock=0; //串口计时器的原子锁
  50. unsigned char ucRcType=0;  //数据类型
  51. unsigned int  uiRcSize=0;  //数据长度
  52. unsigned char ucRcCy=0;  //校验累加和

  53. unsigned int  uiLedCnt=0;  //控制Led闪烁的延时计时器
  54. unsigned int  uiSendTimeOutCnt=0; //用来识别接收数据超时的计时器
  55. unsigned char ucSendTimeOutLock=0; //原子锁


  56. unsigned char ucStatus=0; //当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错

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

  58. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  59. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  60. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  61. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  62. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  63. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  64. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  65. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志


  66. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  67. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  68. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  69. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  70. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  71. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  72. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  73. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  74. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  75. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  76. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  77. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  78. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  79. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  80. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  81. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  82. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  83. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  84. unsigned char ucDigShowTemp=0; //临时中间变量
  85. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  86. unsigned char ucWd1Update=1; //窗口1更新显示标志
  87. unsigned char ucWd2Update=0; //窗口2更新显示标志
  88. unsigned char ucWd3Update=0; //窗口3更新显示标志
  89. unsigned char ucWd4Update=0; //窗口4更新显示标志
  90. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  91. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  92. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  93. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  94. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  95. unsigned char ucTemp1=0;  //中间过渡变量
  96. unsigned char ucTemp2=0;  //中间过渡变量
  97. unsigned char ucTemp3=0;  //中间过渡变量
  98. unsigned char ucTemp4=0;  //中间过渡变量

  99. //根据原理图得出的共阴数码管字模表
  100. code unsigned char dig_table[]=
  101. {
  102. 0x3f,  //0       序号0
  103. 0x06,  //1       序号1
  104. 0x5b,  //2       序号2
  105. 0x4f,  //3       序号3
  106. 0x66,  //4       序号4
  107. 0x6d,  //5       序号5
  108. 0x7d,  //6       序号6
  109. 0x07,  //7       序号7
  110. 0x7f,  //8       序号8
  111. 0x6f,  //9       序号9
  112. 0x00,  //无      序号10
  113. 0x40,  //-       序号11
  114. 0x73,  //P       序号12
  115. };
  116. void main()
  117.   {
  118.    initial_myself();  
  119.    delay_long(100);   
  120.    initial_peripheral();
  121.    while(1)  
  122.    {
  123.       key_service(); //按键服务的应用程序
  124.           usart_service();  //串口服务程序
  125.       display_service(); //显示的窗口菜单服务程序
  126.           status_service();  //状态显示的应用程序
  127.    }
  128. }

  129. void status_service(void)  //状态显示的应用程序
  130. {
  131.    if(ucStatus!=0) //处于非待机的状态,Led闪烁
  132.    {
  133.       if(uiLedCnt<const_led_0_5s)  //大概0.5秒
  134.           {
  135.              led_dr=1;  //前半秒亮

  136.                  if(ucStatus==2)  //处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警
  137.                  {
  138.              ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  139.              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  140.              ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  141.                  }
  142.           }
  143.           else if(uiLedCnt<const_led_1s)  //大概1秒
  144.           {
  145.              led_dr=0; //前半秒灭
  146.           }
  147.           else
  148.           {
  149.              uiLedCnt=0; //延时计时器清零,让Led灯处于闪烁的反复循环中
  150.           }
  151.    
  152.    }
  153.    else  //处于待机状态,Led一直亮
  154.    {
  155.       led_dr=1;
  156.    
  157.    }



  158. }



  159. void usart_service(void)  //串口服务程序,在main函数里
  160. {

  161.      unsigned int i;  
  162.         
  163.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  164.      {

  165.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据
  166.             //下面的代码进入数据协议解析和数据处理的阶段

  167.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  168.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  169.             {

  170.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  171.                {

  172.                    ucRcType=ucRcregBuf[uiRcMoveIndex+3];   //数据类型  一个字节
  173.                    uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //数据长度  两个字节
  174.                    uiRcSize=uiRcSize<<8;
  175.                    uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  176.                                                                  
  177.                    ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];   //记录最后一个字节的校验
  178.                    ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;  //清零最后一个字节的累加和变量

  179.                    for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  180.                    {
  181.                       ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  182.                    }        


  183.                    if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])  //如果校验正确,则进入以下数据处理
  184.                    {                                                  
  185.                        switch(ucRcType)   //根据不同的数据类型来做不同的数据处理
  186.                        {
  187.                              case 0x01:   //设置参数1

  188.                                                               ucStatus=1; //从设置参数1开始,表示当前处于正在发送数据的状态

  189.                                   uiSetData1=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  190.                                   uiSetData1=uiSetData1<<8;  
  191.                                   uiSetData1=uiSetData1+ucRcregBuf[uiRcMoveIndex+7];
  192.                                   ucWd1Update=1; //窗口1更新显示
  193.                                   break;        
  194.                                                                         
  195.                              case 0x02:   //设置参数2

  196.                                   uiSetData2=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  197.                                   uiSetData2=uiSetData2<<8;  
  198.                                   uiSetData2=uiSetData2+ucRcregBuf[uiRcMoveIndex+7];
  199.                                   ucWd2Update=1; //窗口2更新显示
  200.                                   break;   

  201.                              case 0x03:   //设置参数3

  202.                                   uiSetData3=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  203.                                   uiSetData3=uiSetData3<<8;  
  204.                                   uiSetData3=uiSetData3+ucRcregBuf[uiRcMoveIndex+7];
  205.                                   ucWd3Update=1; //窗口3更新显示
  206.                                   break;  

  207.                              case 0x04:   //设置参数4

  208.                                                               ucStatus=0; //从设置参数4结束发送数据的状态,表示发送数据的过程成功,切换回待机状态

  209.                                   uiSetData4=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  210.                                   uiSetData4=uiSetData4<<8;  
  211.                                   uiSetData4=uiSetData4+ucRcregBuf[uiRcMoveIndex+7];
  212.                                   ucWd4Update=1; //窗口4更新显示
  213.                                   break;  

  214.                                                                         
  215.                         }


  216.                                                 ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  217.                         ucSendregBuf[1]=0x00;
  218.                         ucSendregBuf[2]=0x55;
  219.                         ucSendregBuf[3]=0xf5;  //代表校验正确
  220.                         ucSendregBuf[4]=0x00;
  221.                         ucSendregBuf[5]=0x00;
  222.                         ucSendregBuf[6]=0x35;

  223.                         for(i=0;i<7;i++)  //返回校验正确的应答指令
  224.                         {
  225.                            eusart_send(ucSendregBuf[i]);  //发送一串数据给上位机
  226.                         }

  227.                      }   
  228.                                       else
  229.                                          {
  230.                         ucSendTimeOutLock=1; //原子锁加锁
  231.                                             uiSendTimeOutCnt=0;  //超时计时器计时清零
  232.                         ucSendTimeOutLock=0; //原子锁解锁

  233.                                                 ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  234.                         ucSendregBuf[1]=0x00;
  235.                         ucSendregBuf[2]=0x55;
  236.                         ucSendregBuf[3]=0xfa;   //代表校验错误
  237.                         ucSendregBuf[4]=0x00;
  238.                         ucSendregBuf[5]=0x00;
  239.                         ucSendregBuf[6]=0x3a;   

  240.                         for(i=0;i<7;i++)  //返回校验错误的应答指令
  241.                         {
  242.                            eusart_send(ucSendregBuf[i]);  //发送一串数据给上位机
  243.                         }                                         
  244.                                          
  245.                                          }

  246.                                          ucSendTimeOutLock=1; //原子锁加锁
  247.                                          uiSendTimeOutCnt=0;  //超时计时器计时清零
  248.                      ucSendTimeOutLock=0; //原子锁解锁

  249.                      break;   //退出循环
  250.                }
  251.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  252.            }
  253.                                          
  254.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  255.   
  256.      }
  257.                         
  258. }


  259. void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
  260. {

  261.   ES = 0; //关串口中断
  262.   TI = 0; //清零串口发送完成中断请求标志
  263.   SBUF =ucSendData; //发送一个字节

  264.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  265.   TI = 0; //清零串口发送完成中断请求标志
  266.   ES = 1; //允许串口中断

  267. }


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

  270.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  271.    {
  272.        case 1:   //显示P--1窗口的数据
  273.             if(ucWd1Update==1)  //窗口1要全部更新显示
  274.    {
  275.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  276.                ucDigShow8=12;  //第8位数码管显示P
  277.                ucDigShow7=11;  //第7位数码管显示-
  278.                ucDigShow6=1;   //第6位数码管显示1
  279.                ucDigShow5=10;  //第5位数码管显示无

  280.               //先分解数据
  281.                        ucTemp4=uiSetData1/1000;     
  282.                        ucTemp3=uiSetData1%1000/100;
  283.                        ucTemp2=uiSetData1%100/10;
  284.                        ucTemp1=uiSetData1%10;
  285.   
  286.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  287.                if(uiSetData1<1000)   
  288.                            {
  289.                               ucDigShow4=10;  //如果小于1000,千位显示无
  290.                            }
  291.                else
  292.                            {
  293.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  294.                            }
  295.                if(uiSetData1<100)
  296.                            {
  297.                   ucDigShow3=10;  //如果小于100,百位显示无
  298.                            }
  299.                            else
  300.                            {
  301.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  302.                            }
  303.                if(uiSetData1<10)
  304.                            {
  305.                   ucDigShow2=10;  //如果小于10,十位显示无
  306.                            }
  307.                            else
  308.                            {
  309.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  310.                }
  311.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  312.             }
  313.             break;
  314.         case 2:  //显示P--2窗口的数据
  315.             if(ucWd2Update==1)  //窗口2要全部更新显示
  316.    {
  317.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  318.                ucDigShow8=12;  //第8位数码管显示P
  319.                ucDigShow7=11;  //第7位数码管显示-
  320.                ucDigShow6=2;  //第6位数码管显示2
  321.                ucDigShow5=10;   //第5位数码管显示无
  322.                        ucTemp4=uiSetData2/1000;     //分解数据
  323.                        ucTemp3=uiSetData2%1000/100;
  324.                        ucTemp2=uiSetData2%100/10;
  325.                        ucTemp1=uiSetData2%10;

  326.                if(uiSetData2<1000)   
  327.                            {
  328.                               ucDigShow4=10;  //如果小于1000,千位显示无
  329.                            }
  330.                else
  331.                            {
  332.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  333.                            }
  334.                if(uiSetData2<100)
  335.                            {
  336.                   ucDigShow3=10;  //如果小于100,百位显示无
  337.                            }
  338.                            else
  339.                            {
  340.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  341.                            }
  342.                if(uiSetData2<10)
  343.                            {
  344.                   ucDigShow2=10;  //如果小于10,十位显示无
  345.                            }
  346.                            else
  347.                            {
  348.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  349.                }
  350.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  351.     }
  352.              break;
  353.         case 3:  //显示P--3窗口的数据
  354.             if(ucWd3Update==1)  //窗口3要全部更新显示
  355.    {
  356.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  357.                ucDigShow8=12;  //第8位数码管显示P
  358.                ucDigShow7=11;  //第7位数码管显示-
  359.                ucDigShow6=3;  //第6位数码管显示3
  360.                ucDigShow5=10;   //第5位数码管显示无
  361.                        ucTemp4=uiSetData3/1000;     //分解数据
  362.                        ucTemp3=uiSetData3%1000/100;
  363.                        ucTemp2=uiSetData3%100/10;
  364.                        ucTemp1=uiSetData3%10;
  365.                if(uiSetData3<1000)   
  366.                            {
  367.                               ucDigShow4=10;  //如果小于1000,千位显示无
  368.                            }
  369.                else
  370.                            {
  371.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  372.                            }
  373.                if(uiSetData3<100)
  374.                            {
  375.                   ucDigShow3=10;  //如果小于100,百位显示无
  376.                            }
  377.                            else
  378.                            {
  379.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  380.                            }
  381.                if(uiSetData3<10)
  382.                            {
  383.                   ucDigShow2=10;  //如果小于10,十位显示无
  384.                            }
  385.                            else
  386.                            {
  387.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  388.                }
  389.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  390.    }
  391.             break;
  392.         case 4:  //显示P--4窗口的数据
  393.             if(ucWd4Update==1)  //窗口4要全部更新显示
  394.    {
  395.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  396.                ucDigShow8=12;  //第8位数码管显示P
  397.                ucDigShow7=11;  //第7位数码管显示-
  398.                ucDigShow6=4;  //第6位数码管显示4
  399.                ucDigShow5=10;   //第5位数码管显示无
  400.                        ucTemp4=uiSetData4/1000;     //分解数据
  401.                        ucTemp3=uiSetData4%1000/100;
  402.                        ucTemp2=uiSetData4%100/10;
  403.                        ucTemp1=uiSetData4%10;

  404.                if(uiSetData4<1000)   
  405.                            {
  406.                               ucDigShow4=10;  //如果小于1000,千位显示无
  407.                            }
  408.                else
  409.                            {
  410.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  411.                            }
  412.                if(uiSetData4<100)
  413.                            {
  414.                   ucDigShow3=10;  //如果小于100,百位显示无
  415.                            }
  416.                            else
  417.                            {
  418.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  419.                            }
  420.                if(uiSetData4<10)
  421.                            {
  422.                   ucDigShow2=10;  //如果小于10,十位显示无
  423.                            }
  424.                            else
  425.                            {
  426.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  427.                }
  428.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  429.     }
  430.              break;
  431.            }
  432.    

  433. }

  434. void key_scan(void)//按键扫描函数 放在定时中断里
  435. {  
  436.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  437.   {
  438.      ucKeyLock1=0; //按键自锁标志清零
  439.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  440.   }
  441.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  442.   {
  443.      uiKeyTimeCnt1++; //累加定时中断次数
  444.      if(uiKeyTimeCnt1>const_key_time1)
  445.      {
  446.         uiKeyTimeCnt1=0;
  447.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  448.         ucKeySec=1;    //触发1号键
  449.      }
  450.   }

  451.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  452.   {
  453.      ucKeyLock2=0; //按键自锁标志清零
  454.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  455.   }
  456.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  457.   {
  458.      uiKeyTimeCnt2++; //累加定时中断次数
  459.      if(uiKeyTimeCnt2>const_key_time2)
  460.      {
  461.         uiKeyTimeCnt2=0;
  462.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  463.         ucKeySec=2;    //触发2号键
  464.      }
  465.   }

  466.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  467.   {
  468.      ucKeyLock3=0; //按键自锁标志清零
  469.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  470.   }
  471.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  472.   {
  473.      uiKeyTimeCnt3++; //累加定时中断次数
  474.      if(uiKeyTimeCnt3>const_key_time3)
  475.      {
  476.         uiKeyTimeCnt3=0;
  477.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  478.         ucKeySec=3;    //触发3号键
  479.      }
  480.   }

  481.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  482.   {
  483.      ucKeyLock4=0; //按键自锁标志清零
  484.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  485.   }
  486.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  487.   {
  488.      uiKeyTimeCnt4++; //累加定时中断次数
  489.      if(uiKeyTimeCnt4>const_key_time4)
  490.      {
  491.         uiKeyTimeCnt4=0;
  492.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  493.         ucKeySec=4;    //触发4号键
  494.      }
  495.   }
  496. }

  497. void key_service(void) //按键服务的应用程序
  498. {

  499.   switch(ucKeySec) //按键服务状态切换
  500.   {
  501.     case 1:// 加按键 对应朱兆祺学习板的S1键
  502.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  503.                   {
  504.                      case 1:
  505.                   uiSetData1++;   
  506.                                   if(uiSetData1>9999) //最大值是9999
  507.                                   {
  508.                                      uiSetData1=9999;
  509.                                   }
  510.                            ucWd1Update=1;  //窗口1更新显示
  511.                               break;
  512.                      case 2:
  513.                   uiSetData2++;
  514.                                   if(uiSetData2>9999) //最大值是9999
  515.                                   {
  516.                                      uiSetData2=9999;
  517.                                   }
  518.                            ucWd2Update=1;  //窗口2更新显示
  519.                               break;
  520.                      case 3:
  521.                   uiSetData3++;
  522.                                   if(uiSetData3>9999) //最大值是9999
  523.                                   {
  524.                                      uiSetData3=9999;
  525.                                   }
  526.                            ucWd3Update=1;  //窗口3更新显示
  527.                               break;
  528.                      case 4:
  529.                   uiSetData4++;
  530.                                   if(uiSetData4>9999) //最大值是9999
  531.                                   {
  532.                                      uiSetData4=9999;
  533.                                   }
  534.                            ucWd4Update=1;  //窗口4更新显示
  535.                               break;
  536.                   }

  537.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  538.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  539.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  540.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  541.           break;   
  542.    
  543.     case 2:// 减按键 对应朱兆祺学习板的S5键
  544.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  545.                   {
  546.                      case 1:
  547.                   uiSetData1--;   

  548.                                   if(uiSetData1>9999)  
  549.                                   {
  550.                                      uiSetData1=0;  //最小值是0
  551.                                   }
  552.                            ucWd1Update=1;  //窗口1更新显示
  553.                               break;
  554.                      case 2:
  555.                   uiSetData2--;
  556.                                   if(uiSetData2>9999)
  557.                                   {
  558.                                      uiSetData2=0;  //最小值是0
  559.                                   }
  560.                            ucWd2Update=1;  //窗口2更新显示
  561.                               break;
  562.                      case 3:
  563.                   uiSetData3--;
  564.                                   if(uiSetData3>9999)
  565.                                   {
  566.                                      uiSetData3=0;  //最小值是0
  567.                                   }
  568.                            ucWd3Update=1;  //窗口3更新显示
  569.                               break;
  570.                      case 4:
  571.                   uiSetData4--;
  572.                                   if(uiSetData4>9999)
  573.                                   {
  574.                                      uiSetData4=0;  //最小值是0
  575.                                   }
  576.                            ucWd4Update=1;  //窗口4更新显示
  577.                               break;
  578.                   }

  579.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  580.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  581.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  582.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  583.           break;  

  584.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  585.           ucWd++;  //切换窗口
  586.                   if(ucWd>4)
  587.                   {
  588.                     ucWd=1;
  589.                   }
  590.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  591.                   {
  592.                      case 1:
  593.                            ucWd1Update=1;  //窗口1更新显示
  594.                               break;
  595.                      case 2:
  596.                            ucWd2Update=1;  //窗口2更新显示
  597.                               break;
  598.                      case 3:
  599.                            ucWd3Update=1;  //窗口3更新显示
  600.                               break;
  601.                      case 4:
  602.                            ucWd4Update=1;  //窗口4更新显示
  603.                               break;
  604.                   }
  605.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  606.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  607.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  608.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  609.           break;         

  610.     case 4:// 复位按键 对应朱兆祺学习板的S13键
  611.           switch(ucStatus)  //在不同的状态下,进行不同的操作
  612.           {
  613.              case 0:  //处于待机状态
  614.                   break;

  615.              case 1:  //处于正在通讯的过程
  616.                   break;

  617.              case 2: //发送数据出错,比如中间超时没有接收到数据
  618.                   ucStatus=0; //切换回待机的状态
  619.                   break;
  620.           }
  621.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  622.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  623.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  624.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发

  625.           break;   
  626.          
  627.   }               
  628. }

  629. void display_drive(void)  
  630. {
  631.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  632.    switch(ucDisplayDriveStep)
  633.    {
  634.       case 1:  //显示第1位
  635.            ucDigShowTemp=dig_table[ucDigShow1];
  636.                    if(ucDigDot1==1)
  637.                    {
  638.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  639.                    }
  640.            dig_hc595_drive(ucDigShowTemp,0xfe);
  641.                break;
  642.       case 2:  //显示第2位
  643.            ucDigShowTemp=dig_table[ucDigShow2];
  644.                    if(ucDigDot2==1)
  645.                    {
  646.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  647.                    }
  648.            dig_hc595_drive(ucDigShowTemp,0xfd);
  649.                break;
  650.       case 3:  //显示第3位
  651.            ucDigShowTemp=dig_table[ucDigShow3];
  652.                    if(ucDigDot3==1)
  653.                    {
  654.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  655.                    }
  656.            dig_hc595_drive(ucDigShowTemp,0xfb);
  657.                break;
  658.       case 4:  //显示第4位
  659.            ucDigShowTemp=dig_table[ucDigShow4];
  660.                    if(ucDigDot4==1)
  661.                    {
  662.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  663.                    }
  664.            dig_hc595_drive(ucDigShowTemp,0xf7);
  665.                break;
  666.       case 5:  //显示第5位
  667.            ucDigShowTemp=dig_table[ucDigShow5];
  668.                    if(ucDigDot5==1)
  669.                    {
  670.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  671.                    }
  672.            dig_hc595_drive(ucDigShowTemp,0xef);
  673.                break;
  674.       case 6:  //显示第6位
  675.            ucDigShowTemp=dig_table[ucDigShow6];
  676.                    if(ucDigDot6==1)
  677.                    {
  678.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  679.                    }
  680.            dig_hc595_drive(ucDigShowTemp,0xdf);
  681.                break;
  682.       case 7:  //显示第7位
  683.            ucDigShowTemp=dig_table[ucDigShow7];
  684.                    if(ucDigDot7==1)
  685.                    {
  686.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  687.            }
  688.            dig_hc595_drive(ucDigShowTemp,0xbf);
  689.                break;
  690.       case 8:  //显示第8位
  691.            ucDigShowTemp=dig_table[ucDigShow8];
  692.                    if(ucDigDot8==1)
  693.                    {
  694.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  695.                    }
  696.            dig_hc595_drive(ucDigShowTemp,0x7f);
  697.                break;
  698.    }
  699.    ucDisplayDriveStep++;
  700.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  701.    {
  702.      ucDisplayDriveStep=1;
  703.    }

  704. }

  705. //数码管的74HC595驱动函数
  706. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  707. {
  708.    unsigned char i;
  709.    unsigned char ucTempData;
  710.    dig_hc595_sh_dr=0;
  711.    dig_hc595_st_dr=0;
  712.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  713.    for(i=0;i<8;i++)
  714.    {
  715.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  716.          else dig_hc595_ds_dr=0;
  717.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  718.          delay_short(1);
  719.          dig_hc595_sh_dr=1;
  720.          delay_short(1);
  721.          ucTempData=ucTempData<<1;
  722.    }
  723.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  724.    for(i=0;i<8;i++)
  725.    {
  726.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  727.          else dig_hc595_ds_dr=0;
  728.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  729.          delay_short(1);
  730.          dig_hc595_sh_dr=1;
  731.          delay_short(1);
  732.          ucTempData=ucTempData<<1;
  733.    }
  734.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  735.    delay_short(1);
  736.    dig_hc595_st_dr=1;
  737.    delay_short(1);
  738.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  739.    dig_hc595_st_dr=0;
  740.    dig_hc595_ds_dr=0;
  741. }

  742. //LED灯的74HC595驱动函数
  743. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  744. {
  745.    unsigned char i;
  746.    unsigned char ucTempData;
  747.    hc595_sh_dr=0;
  748.    hc595_st_dr=0;
  749.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  750.    for(i=0;i<8;i++)
  751.    {
  752.          if(ucTempData>=0x80)hc595_ds_dr=1;
  753.          else hc595_ds_dr=0;
  754.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  755.          delay_short(1);
  756.          hc595_sh_dr=1;
  757.          delay_short(1);
  758.          ucTempData=ucTempData<<1;
  759.    }
  760.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  761.    for(i=0;i<8;i++)
  762.    {
  763.          if(ucTempData>=0x80)hc595_ds_dr=1;
  764.          else hc595_ds_dr=0;
  765.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  766.          delay_short(1);
  767.          hc595_sh_dr=1;
  768.          delay_short(1);
  769.          ucTempData=ucTempData<<1;
  770.    }
  771.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  772.    delay_short(1);
  773.    hc595_st_dr=1;
  774.    delay_short(1);
  775.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  776.    hc595_st_dr=0;
  777.    hc595_ds_dr=0;
  778. }


  779. void usart_receive(void) interrupt 4   //串口接收数据中断        
  780. {        

  781.    if(RI==1)  
  782.    {
  783.         RI = 0;

  784.          ++uiRcregTotal;
  785.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  786.         {
  787.            uiRcregTotal=const_rc_size;
  788.         }
  789.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里

  790.         if(ucSendCntLock==0)  //原子锁判断
  791.         {
  792.             ucSendCntLock=1; //加锁
  793.             uiSendCnt=0;  //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
  794.             ucSendCntLock=0; //解锁
  795.         }
  796.    
  797.    }
  798.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  799.    {
  800.         TI = 0;  //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
  801.    }
  802.                                                          
  803. }  

  804. void T0_time(void) interrupt 1   //定时中断
  805. {
  806.   TF0=0;  //清除中断标志
  807.   TR0=0; //关中断


  808. /* 注释一:
  809.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  810.   */  
  811.   if(ucSendCntLock==0)  //原子锁判断
  812.   {
  813.      ucSendCntLock=1; //加锁
  814.      if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  815.      {
  816.         uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  817.         ucSendLock=1;     //开自锁标志
  818.      }
  819.      ucSendCntLock=0; //解锁
  820.   }

  821.   if(ucVoiceLock==0) //原子锁判断
  822.   {
  823.      if(uiVoiceCnt!=0)
  824.      {

  825.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  826.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  827.      
  828.      }
  829.      else
  830.      {

  831.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  832.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  833.         
  834.      }
  835.   }

  836.   if(ucStatus!=0) //处于非待机的状态,Led闪烁
  837.   {
  838.      uiLedCnt++; //Led闪烁计时器不断累加
  839.   }

  840.   if(ucStatus==1) //处于正在通讯的状态,
  841.   {
  842.      if(ucSendTimeOutLock==0)  //原子锁判断
  843.          {
  844.         uiSendTimeOutCnt++;   //超时计时器累加
  845.             if(uiSendTimeOutCnt>const_send_time_out)  //超时出错
  846.             {
  847.                uiSendTimeOutCnt=0;
  848.                ucStatus=2;  //切换到出错报警状态
  849.              }
  850.          }
  851.   }



  852.   key_scan(); //按键扫描函数
  853.   display_drive();  //数码管字模的驱动函数

  854.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  855.   TL0=0x0b;
  856.   TR0=1;  //开中断
  857. }

  858. void delay_short(unsigned int uiDelayShort)
  859. {
  860.    unsigned int i;  
  861.    for(i=0;i<uiDelayShort;i++)
  862.    {
  863.      ;   //一个分号相当于执行一条空语句
  864.    }
  865. }

  866. void delay_long(unsigned int uiDelayLong)
  867. {
  868.    unsigned int i;
  869.    unsigned int j;
  870.    for(i=0;i<uiDelayLong;i++)
  871.    {
  872.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  873.           {
  874.              ; //一个分号相当于执行一条空语句
  875.           }
  876.    }
  877. }

  878. void initial_myself(void)  //第一区 初始化单片机
  879. {
  880. /* 注释二:
  881. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  882. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  883. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
  884. */
  885.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  886.   led_dr=1;  //点亮独立LED灯
  887.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  888.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  889.   TMOD=0x01;  //设置定时器0为工作方式1
  890.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  891.   TL0=0x0b;

  892.   //配置串口
  893.   SCON=0x50;
  894.   TMOD=0X21;

  895. /* 注释三:
  896. * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
  897. * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
  898. */
  899.   IP =0x10;  //把串口中断设置为最高优先级,必须的。

  900.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率为9600。
  901.   TR1=1;
  902. }
  903. void initial_peripheral(void) //第二区 初始化外围
  904. {

  905.    ucDigDot8=0;   //小数点全部不显示
  906.    ucDigDot7=0;  
  907.    ucDigDot6=0;
  908.    ucDigDot5=0;  
  909.    ucDigDot4=0;
  910.    ucDigDot3=0;  
  911.    ucDigDot2=0;
  912.    ucDigDot1=0;

  913.    EA=1;     //开总中断
  914.    ES=1;     //允许串口中断
  915.    ET0=1;    //允许定时中断
  916.    TR0=1;    //启动定时中断
  917. }
复制代码



总结陈词:
   这节详细讲了从机收发端的程序框架,而主机端的程序则用电脑的串口助手来模拟。实际上,主机端的程序也有很多内容,它包括依次发送每一串数据,根据返回的应答来决定是否需要重发数据,重发三次如果没反应则进行报错,以及超时没接收到数据等等内容。主机收发端的程序框架是什么样的?欲知详情,请听下回分解-----主机的串口收发综合程序框架
  
(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
49#
 楼主| 发表于 2014-5-5 12:10:13 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-11-29 22:52 编辑

第四十五节:主机的串口收发综合程序框架

开场白:
在大部分的项目中,串口都需要“一收一应答”的握手协议,主机先发一串数据,从机收到数据后进行校验判断,如果校验正确则返回正确应答指令,如果校验错误则返回错误应答指令,主机收到应答指令后,如果发现是正确应答指令则继续发送其它的新数据,如果发现是错误应答指令,或者超时没有接收到任何应答指令,则继续重发,如果连续重发三次都是错误应答或者无应答,主机就进行报错处理。
     上一节已经讲了从机,这节就讲主机的收发端程序实例。要教会大家四个知识点:

第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置IP = 0x10,相当于把串口中断设置为最高优先级,这个时候,串口中断可以打断任何其他的中断服务函数,实现中断嵌套。
第二个:主机端的收发端程序框架。包括重发,超时检测等等。
第三个:主机的状态指示程序框架。可以指示待机,通讯中,超时出错三种状态。
第四个:其实上一节的LED灯闪烁的时间里,我忘了加原子锁,不加原子锁的后果是,闪烁的时间有时候会不一致,所以这节多增加一个原子锁变量ucLedLock,再次感谢“红金龙吸味”关于原子锁的建议,真的很好用。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9,S13作为独立按键。
      一共有4个窗口。每个窗口显示一个参数。串口可以把当前设置的4个数据发送给从机。从机端可以用电脑的串口助手来模拟。
    按键更改参数:
    第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
    第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。S13是启动发送数据和复位按键,当系统处于待机状态时,按下此按键会启动发送数据,注意,单片机每发送一串数据,必须在电脑串口助手端发送应答信号,否则单片机重发3次后会引发超时报警;当通讯超时蜂鸣器报警时,可以按下此键清除报警,返回到待机的状态。
通过电脑的串口助手来模拟从机,返回不同的应答
从机返回校验正确应答:eb 00 55 f5 00 00 35
从机返回校验出错应答:eb 00 55 fa 00 00 3a

   系统处于待机状态时,LED灯一直亮,
   系统处于非待机状态时,LED灯闪烁,
   系统处于出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。


(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_led_0_5s  200   //大概0.5秒的时间
  8. #define const_led_1s    400   //大概1秒的时间

  9. #define const_send_time_out   4000  //通讯超时出错的时间 大概10秒

  10. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小
  11. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

  12. #define const_send_size  10  //串口发送数据的缓冲区数组大小

  13. void initial_myself(void);   
  14. void initial_peripheral(void);
  15. void delay_short(unsigned int uiDelayShort);
  16. void delay_long(unsigned int uiDelaylong);
  17. //驱动数码管的74HC595
  18. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  19. void display_drive(void); //显示数码管字模的驱动函数
  20. void display_service(void); //显示的窗口菜单服务程序
  21. //驱动LED的74HC595
  22. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

  23. void T0_time(void);  //定时中断函数
  24. void usart_receive(void); //串口接收中断函数
  25. void usart_service(void);  //串口接收服务程序,在main函数里
  26. void communication_service(void); //一发一收的通讯服务程序
  27. void eusart_send(unsigned char ucSendData); //发送一个字节,内部自带每个字节之间的delay延时

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

  30. void status_service(void);  //状态显示的应用程序


  31. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  32. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  33. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  34. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键
  35. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  36. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  37. sbit led_dr=P3^5;  //作为状态指示灯 亮的时候表示待机状态.闪烁表示非待机状态,处于正在发送数据或者出错的状态

  38. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  39. sbit dig_hc595_st_dr=P2^1;  
  40. sbit dig_hc595_ds_dr=P2^2;  
  41. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  42. sbit hc595_st_dr=P2^4;  
  43. sbit hc595_ds_dr=P2^5;  

  44. unsigned char ucSendregBuf[const_send_size]; //发送的缓冲区数组

  45. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  46. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  47. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  48. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  49. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  50. unsigned char  ucSendCntLock=0; //串口计时器的原子锁
  51. unsigned char ucRcType=0;  //数据类型
  52. unsigned int  uiRcSize=0;  //数据长度
  53. unsigned char ucRcCy=0;  //校验累加和

  54. unsigned char ucLedLock=0; //原子锁
  55. unsigned int  uiLedCnt=0;  //控制Led闪烁的延时计时器
  56. unsigned int  uiSendTimeOutCnt=0; //用来识别接收数据超时的计时器
  57. unsigned char ucSendTimeOutLock=0; //原子锁


  58. unsigned char ucStatus=0; //当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错
  59. unsigned char ucSendStep=0; //发送数据的过程步骤
  60. unsigned char ucErrorCnt=0; //累计错误总数
  61. unsigned char ucSendTotal=0; //记录当前已经发送了多少串数据
  62. unsigned char ucReceiveStatus=0; //返回的数据状态 0代表待机 1代表校验正确 2代表校验出错

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

  64. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  65. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  66. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  67. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  68. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  69. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  70. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  71. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志


  72. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  73. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  74. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  75. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  76. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  77. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  78. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  79. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  80. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  81. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  82. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  83. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  84. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  85. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  86. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  87. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  88. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  89. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  90. unsigned char ucDigShowTemp=0; //临时中间变量
  91. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  92. unsigned char ucWd1Update=1; //窗口1更新显示标志
  93. unsigned char ucWd2Update=0; //窗口2更新显示标志
  94. unsigned char ucWd3Update=0; //窗口3更新显示标志
  95. unsigned char ucWd4Update=0; //窗口4更新显示标志
  96. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  97. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  98. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  99. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  100. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  101. unsigned char ucTemp1=0;  //中间过渡变量
  102. unsigned char ucTemp2=0;  //中间过渡变量
  103. unsigned char ucTemp3=0;  //中间过渡变量
  104. unsigned char ucTemp4=0;  //中间过渡变量

  105. //根据原理图得出的共阴数码管字模表
  106. code unsigned char dig_table[]=
  107. {
  108. 0x3f,  //0       序号0
  109. 0x06,  //1       序号1
  110. 0x5b,  //2       序号2
  111. 0x4f,  //3       序号3
  112. 0x66,  //4       序号4
  113. 0x6d,  //5       序号5
  114. 0x7d,  //6       序号6
  115. 0x07,  //7       序号7
  116. 0x7f,  //8       序号8
  117. 0x6f,  //9       序号9
  118. 0x00,  //无      序号10
  119. 0x40,  //-       序号11
  120. 0x73,  //P       序号12
  121. };
  122. void main()
  123.   {
  124.    initial_myself();  
  125.    delay_long(100);   
  126.    initial_peripheral();
  127.    while(1)  
  128.    {
  129.       key_service(); //按键服务的应用程序
  130.       usart_service();  //串口接收服务程序
  131.       communication_service(); //一发一收的通讯服务程序
  132.       display_service(); //显示的窗口菜单服务程序
  133.       status_service();  //状态显示的应用程序
  134.    }
  135. }


  136. void communication_service(void) //一发一收的通讯服务程序
  137. {
  138.    unsigned int i;

  139.    if(ucStatus==1)  //处于正在通讯的过程中
  140.    {
  141.        switch(ucSendStep)
  142.            {
  143.                case 0: //通讯过程0  发送一串数据
  144.                 switch(ucSendTotal)  //根据当前已经发送到第几条数据来决定发送哪些参数
  145.                                 {
  146.                                    case 0:   //发送参数1
  147.                         ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  148.                         ucSendregBuf[1]=0x00;
  149.                         ucSendregBuf[2]=0x55;
  150.                         ucSendregBuf[3]=0x01;    //代表发送参数1
  151.                         ucSendregBuf[4]=0x00;
  152.                         ucSendregBuf[5]=0x02;    //代表发送2个字节的有效数据

  153.                                                 ucSendregBuf[6]=uiSetData1>>8;  //把int类型的参数分解成两个字节的数据
  154.                                                 ucSendregBuf[7]=uiSetData1;
  155.                                         break;

  156.                                    case 1:  //发送参数2
  157.                         ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  158.                         ucSendregBuf[1]=0x00;
  159.                         ucSendregBuf[2]=0x55;
  160.                         ucSendregBuf[3]=0x02;    //代表发送参数2
  161.                         ucSendregBuf[4]=0x00;
  162.                         ucSendregBuf[5]=0x02;    //代表发送2个字节的有效数据

  163.                                                 ucSendregBuf[6]=uiSetData2>>8;  //把int类型的参数分解成两个字节的数据
  164.                                                 ucSendregBuf[7]=uiSetData2;
  165.                                         break;

  166.                                    case 2:  //发送参数3
  167.                         ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  168.                         ucSendregBuf[1]=0x00;
  169.                         ucSendregBuf[2]=0x55;
  170.                         ucSendregBuf[3]=0x03;    //代表发送参数3
  171.                         ucSendregBuf[4]=0x00;
  172.                         ucSendregBuf[5]=0x02;    //代表发送2个字节的有效数据

  173.                                                 ucSendregBuf[6]=uiSetData3>>8;  //把int类型的参数分解成两个字节的数据
  174.                                                 ucSendregBuf[7]=uiSetData3;
  175.                                         break;

  176.                                    case 3:  //发送参数4
  177.                         ucSendregBuf[0]=0xeb;    //把准备发送的数据放入发送缓冲区
  178.                         ucSendregBuf[1]=0x00;
  179.                         ucSendregBuf[2]=0x55;
  180.                         ucSendregBuf[3]=0x04;    //代表发送参数4
  181.                         ucSendregBuf[4]=0x00;
  182.                         ucSendregBuf[5]=0x02;    //代表发送2个字节的有效数据

  183.                                                 ucSendregBuf[6]=uiSetData4>>8;  //把int类型的参数分解成两个字节的数据
  184.                                                 ucSendregBuf[7]=uiSetData4;
  185.                                         break;
  186.                                 }
  187.                                 

  188.                 ucSendregBuf[8]=0x00;  
  189.                 for(i=0;i<8;i++)  //最后一个字节是校验和,是前面所有字节累加,溢出部分不用我们管,系统会有规律的自动处理
  190.                 {
  191.                   ucSendregBuf[8]=ucSendregBuf[8]+ucSendregBuf[i];
  192.                 }

  193.                 for(i=0;i<9;i++)  
  194.                 {
  195.                     eusart_send(ucSendregBuf[i]);  //把一串完整的数据发送给下位机
  196.                 }

  197.                 ucSendTimeOutLock=1; //原子锁加锁
  198.                 uiSendTimeOutCnt=0;  //超时计时器计时清零
  199.                 ucSendTimeOutLock=0; //原子锁解锁

  200.                                 ucReceiveStatus=0;  //返回的数据状态清零
  201.                                 ucSendStep=1;  //切换到下一个步骤,等待返回的数据
  202.                         break;
  203.                case 1: //通讯过程1  判断返回的指令
  204.                         if(ucReceiveStatus==1)  //校验正确
  205.                                 {

  206.                                      ucErrorCnt=0; //累计校验错误总数清零

  207.                                    ucSendTotal++;  //累加当前发送了多少串数据

  208.                                    if(ucSendTotal>=4) //已经发送完全部4串数据,结束
  209.                                    {
  210.                       ucStatus=0;  //切换到结束时的待机状态
  211.                                    }
  212.                                    else  //还没发送完4串数据,则继续发送下一串新数据
  213.                                    {
  214.                                              ucSendStep=0;  //返回上一个步骤,继续发送新数据
  215.                                    }

  216.                                 }
  217.                         else if(ucReceiveStatus==2||uiSendTimeOutCnt>const_send_time_out)  //校验出错或者超时出错
  218.                                 {

  219.                                  ucErrorCnt++; //累计错误总数
  220.                    if(ucErrorCnt>=3)  //累加重发次数3次以上,则报错
  221.                                    {
  222.                       ucStatus=2;  //切换到出错报警状态
  223.                                    }
  224.                                    else  //重发还没超过3次,继续返回重发
  225.                                    {
  226.                                              ucSendStep=0;  //返回上一个步骤,重发一次数据
  227.                                    }
  228.                                 }
  229.                         break;           
  230.            
  231.            }
  232.    
  233.    }

  234. }

  235. void status_service(void)  //状态显示的应用程序
  236. {
  237.    if(ucStatus!=0) //处于非待机的状态,Led闪烁
  238.    {
  239.       if(uiLedCnt<const_led_0_5s)  //大概0.5秒
  240.           {
  241.              led_dr=1;  //前半秒亮

  242.                  if(ucStatus==2)  //处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警
  243.                  {
  244.              ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  245.              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  246.              ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  247.                  }
  248.           }
  249.           else if(uiLedCnt<const_led_1s)  //大概1秒
  250.           {
  251.              led_dr=0; //前半秒灭
  252.           }
  253.           else
  254.           {
  255.                      ucLedLock=1; //原子锁加锁
  256.              uiLedCnt=0; //延时计时器清零,让Led灯处于闪烁的反复循环中
  257.                          ucLedLock=0; //原子锁解锁
  258.           }
  259.    
  260.    }
  261.    else  //处于待机状态,Led一直亮
  262.    {
  263.       led_dr=1;
  264.    
  265.    }



  266. }



  267. void usart_service(void)  //串口接收服务程序,在main函数里
  268. {

  269.      unsigned int i;  
  270.         
  271.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  272.      {

  273.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据
  274.             //下面的代码进入数据协议解析和数据处理的阶段

  275.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  276.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  277.             {

  278.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  279.                {

  280.                    ucRcType=ucRcregBuf[uiRcMoveIndex+3];   //数据类型  一个字节
  281.                    uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //数据长度  两个字节
  282.                    uiRcSize=uiRcSize<<8;
  283.                    uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  284.                                                                  
  285.                    ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];   //记录最后一个字节的校验
  286.                    ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;  //清零最后一个字节的累加和变量

  287.                    for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  288.                    {
  289.                       ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[uiRcMoveIndex+i];
  290.                    }        


  291.                     if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])  //如果一串数据校验正确,则进入以下数据指令的判断
  292.                     {                                                  
  293.                        switch(ucRcType)   //根据不同的数据类型来做不同的数据处理
  294.                        {
  295.                              case 0xf5:   //返回的是正确的校验指令

  296.                                   ucReceiveStatus=1;//代表校验正确
  297.                                   break;        
  298.                                                                         
  299.                              case 0xfa:   //返回的是错误的校验指令

  300.                                   ucReceiveStatus=2;//代表校验错误
  301.                                   break;                                          
  302.                         }

  303.                      }   
  304.                      break;   //退出循环
  305.                }
  306.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  307.            }
  308.                                          
  309.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  310.   
  311.      }
  312.                         
  313. }


  314. void eusart_send(unsigned char ucSendData) //发送一个字节,内部自带每个字节之间的delay延时
  315. {

  316.   ES = 0; //关串口中断
  317.   TI = 0; //清零串口发送完成中断请求标志
  318.   SBUF =ucSendData; //发送一个字节

  319.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  320.   TI = 0; //清零串口发送完成中断请求标志
  321.   ES = 1; //允许串口中断

  322. }


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

  325.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  326.    {
  327.        case 1:   //显示P--1窗口的数据
  328.             if(ucWd1Update==1)  //窗口1要全部更新显示
  329.    {
  330.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  331.                ucDigShow8=12;  //第8位数码管显示P
  332.                ucDigShow7=11;  //第7位数码管显示-
  333.                ucDigShow6=1;   //第6位数码管显示1
  334.                ucDigShow5=10;  //第5位数码管显示无

  335.               //先分解数据
  336.                        ucTemp4=uiSetData1/1000;     
  337.                        ucTemp3=uiSetData1%1000/100;
  338.                        ucTemp2=uiSetData1%100/10;
  339.                        ucTemp1=uiSetData1%10;
  340.   
  341.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  342.                if(uiSetData1<1000)   
  343.                            {
  344.                               ucDigShow4=10;  //如果小于1000,千位显示无
  345.                            }
  346.                else
  347.                            {
  348.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  349.                            }
  350.                if(uiSetData1<100)
  351.                            {
  352.                   ucDigShow3=10;  //如果小于100,百位显示无
  353.                            }
  354.                            else
  355.                            {
  356.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  357.                            }
  358.                if(uiSetData1<10)
  359.                            {
  360.                   ucDigShow2=10;  //如果小于10,十位显示无
  361.                            }
  362.                            else
  363.                            {
  364.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  365.                }
  366.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  367.             }
  368.             break;
  369.         case 2:  //显示P--2窗口的数据
  370.             if(ucWd2Update==1)  //窗口2要全部更新显示
  371.    {
  372.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  373.                ucDigShow8=12;  //第8位数码管显示P
  374.                ucDigShow7=11;  //第7位数码管显示-
  375.                ucDigShow6=2;  //第6位数码管显示2
  376.                ucDigShow5=10;   //第5位数码管显示无
  377.                        ucTemp4=uiSetData2/1000;     //分解数据
  378.                        ucTemp3=uiSetData2%1000/100;
  379.                        ucTemp2=uiSetData2%100/10;
  380.                        ucTemp1=uiSetData2%10;

  381.                if(uiSetData2<1000)   
  382.                            {
  383.                               ucDigShow4=10;  //如果小于1000,千位显示无
  384.                            }
  385.                else
  386.                            {
  387.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  388.                            }
  389.                if(uiSetData2<100)
  390.                            {
  391.                   ucDigShow3=10;  //如果小于100,百位显示无
  392.                            }
  393.                            else
  394.                            {
  395.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  396.                            }
  397.                if(uiSetData2<10)
  398.                            {
  399.                   ucDigShow2=10;  //如果小于10,十位显示无
  400.                            }
  401.                            else
  402.                            {
  403.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  404.                }
  405.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  406.     }
  407.              break;
  408.         case 3:  //显示P--3窗口的数据
  409.             if(ucWd3Update==1)  //窗口3要全部更新显示
  410.    {
  411.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  412.                ucDigShow8=12;  //第8位数码管显示P
  413.                ucDigShow7=11;  //第7位数码管显示-
  414.                ucDigShow6=3;  //第6位数码管显示3
  415.                ucDigShow5=10;   //第5位数码管显示无
  416.                        ucTemp4=uiSetData3/1000;     //分解数据
  417.                        ucTemp3=uiSetData3%1000/100;
  418.                        ucTemp2=uiSetData3%100/10;
  419.                        ucTemp1=uiSetData3%10;
  420.                if(uiSetData3<1000)   
  421.                            {
  422.                               ucDigShow4=10;  //如果小于1000,千位显示无
  423.                            }
  424.                else
  425.                            {
  426.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  427.                            }
  428.                if(uiSetData3<100)
  429.                            {
  430.                   ucDigShow3=10;  //如果小于100,百位显示无
  431.                            }
  432.                            else
  433.                            {
  434.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  435.                            }
  436.                if(uiSetData3<10)
  437.                            {
  438.                   ucDigShow2=10;  //如果小于10,十位显示无
  439.                            }
  440.                            else
  441.                            {
  442.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  443.                }
  444.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  445.    }
  446.             break;
  447.         case 4:  //显示P--4窗口的数据
  448.             if(ucWd4Update==1)  //窗口4要全部更新显示
  449.    {
  450.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  451.                ucDigShow8=12;  //第8位数码管显示P
  452.                ucDigShow7=11;  //第7位数码管显示-
  453.                ucDigShow6=4;  //第6位数码管显示4
  454.                ucDigShow5=10;   //第5位数码管显示无
  455.                        ucTemp4=uiSetData4/1000;     //分解数据
  456.                        ucTemp3=uiSetData4%1000/100;
  457.                        ucTemp2=uiSetData4%100/10;
  458.                        ucTemp1=uiSetData4%10;

  459.                if(uiSetData4<1000)   
  460.                            {
  461.                               ucDigShow4=10;  //如果小于1000,千位显示无
  462.                            }
  463.                else
  464.                            {
  465.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  466.                            }
  467.                if(uiSetData4<100)
  468.                            {
  469.                   ucDigShow3=10;  //如果小于100,百位显示无
  470.                            }
  471.                            else
  472.                            {
  473.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  474.                            }
  475.                if(uiSetData4<10)
  476.                            {
  477.                   ucDigShow2=10;  //如果小于10,十位显示无
  478.                            }
  479.                            else
  480.                            {
  481.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  482.                }
  483.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  484.     }
  485.              break;
  486.            }
  487.    

  488. }

  489. void key_scan(void)//按键扫描函数 放在定时中断里
  490. {  
  491.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  492.   {
  493.      ucKeyLock1=0; //按键自锁标志清零
  494.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  495.   }
  496.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  497.   {
  498.      uiKeyTimeCnt1++; //累加定时中断次数
  499.      if(uiKeyTimeCnt1>const_key_time1)
  500.      {
  501.         uiKeyTimeCnt1=0;
  502.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  503.         ucKeySec=1;    //触发1号键
  504.      }
  505.   }

  506.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  507.   {
  508.      ucKeyLock2=0; //按键自锁标志清零
  509.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  510.   }
  511.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  512.   {
  513.      uiKeyTimeCnt2++; //累加定时中断次数
  514.      if(uiKeyTimeCnt2>const_key_time2)
  515.      {
  516.         uiKeyTimeCnt2=0;
  517.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  518.         ucKeySec=2;    //触发2号键
  519.      }
  520.   }

  521.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  522.   {
  523.      ucKeyLock3=0; //按键自锁标志清零
  524.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  525.   }
  526.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  527.   {
  528.      uiKeyTimeCnt3++; //累加定时中断次数
  529.      if(uiKeyTimeCnt3>const_key_time3)
  530.      {
  531.         uiKeyTimeCnt3=0;
  532.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  533.         ucKeySec=3;    //触发3号键
  534.      }
  535.   }

  536.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  537.   {
  538.      ucKeyLock4=0; //按键自锁标志清零
  539.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  540.   }
  541.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  542.   {
  543.      uiKeyTimeCnt4++; //累加定时中断次数
  544.      if(uiKeyTimeCnt4>const_key_time4)
  545.      {
  546.         uiKeyTimeCnt4=0;
  547.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  548.         ucKeySec=4;    //触发4号键
  549.      }
  550.   }
  551. }

  552. void key_service(void) //按键服务的应用程序
  553. {

  554.   switch(ucKeySec) //按键服务状态切换
  555.   {
  556.     case 1:// 加按键 对应朱兆祺学习板的S1键
  557.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  558.                   {
  559.                      case 1:
  560.                   uiSetData1++;   
  561.                                   if(uiSetData1>9999) //最大值是9999
  562.                                   {
  563.                                      uiSetData1=9999;
  564.                                   }
  565.                            ucWd1Update=1;  //窗口1更新显示
  566.                               break;
  567.                      case 2:
  568.                   uiSetData2++;
  569.                                   if(uiSetData2>9999) //最大值是9999
  570.                                   {
  571.                                      uiSetData2=9999;
  572.                                   }
  573.                            ucWd2Update=1;  //窗口2更新显示
  574.                               break;
  575.                      case 3:
  576.                   uiSetData3++;
  577.                                   if(uiSetData3>9999) //最大值是9999
  578.                                   {
  579.                                      uiSetData3=9999;
  580.                                   }
  581.                            ucWd3Update=1;  //窗口3更新显示
  582.                               break;
  583.                      case 4:
  584.                   uiSetData4++;
  585.                                   if(uiSetData4>9999) //最大值是9999
  586.                                   {
  587.                                      uiSetData4=9999;
  588.                                   }
  589.                            ucWd4Update=1;  //窗口4更新显示
  590.                               break;
  591.                   }

  592.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  593.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  594.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  595.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  596.           break;   
  597.    
  598.     case 2:// 减按键 对应朱兆祺学习板的S5键
  599.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  600.                   {
  601.                      case 1:
  602.                   uiSetData1--;   

  603.                                   if(uiSetData1>9999)  
  604.                                   {
  605.                                      uiSetData1=0;  //最小值是0
  606.                                   }
  607.                            ucWd1Update=1;  //窗口1更新显示
  608.                               break;
  609.                      case 2:
  610.                   uiSetData2--;
  611.                                   if(uiSetData2>9999)
  612.                                   {
  613.                                      uiSetData2=0;  //最小值是0
  614.                                   }
  615.                            ucWd2Update=1;  //窗口2更新显示
  616.                               break;
  617.                      case 3:
  618.                   uiSetData3--;
  619.                                   if(uiSetData3>9999)
  620.                                   {
  621.                                      uiSetData3=0;  //最小值是0
  622.                                   }
  623.                            ucWd3Update=1;  //窗口3更新显示
  624.                               break;
  625.                      case 4:
  626.                   uiSetData4--;
  627.                                   if(uiSetData4>9999)
  628.                                   {
  629.                                      uiSetData4=0;  //最小值是0
  630.                                   }
  631.                            ucWd4Update=1;  //窗口4更新显示
  632.                               break;
  633.                   }

  634.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  635.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  636.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  637.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  638.           break;  

  639.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  640.           ucWd++;  //切换窗口
  641.                   if(ucWd>4)
  642.                   {
  643.                     ucWd=1;
  644.                   }
  645.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  646.                   {
  647.                      case 1:
  648.                            ucWd1Update=1;  //窗口1更新显示
  649.                               break;
  650.                      case 2:
  651.                            ucWd2Update=1;  //窗口2更新显示
  652.                               break;
  653.                      case 3:
  654.                            ucWd3Update=1;  //窗口3更新显示
  655.                               break;
  656.                      case 4:
  657.                            ucWd4Update=1;  //窗口4更新显示
  658.                               break;
  659.                   }
  660.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  661.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  662.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  663.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  664.           break;         

  665.     case 4:// 启动发送数据和复位按键 对应朱兆祺学习板的S13键
  666.           switch(ucStatus)  //在不同的状态下,进行不同的操作
  667.           {
  668.              case 0:  //处于待机状态,则启动发送数据


  669.                   ucErrorCnt=0; //累计错误总数清零
  670.                   ucSendTotal=0; //已经发送串数据总数清零

  671.                                   ucSendStep=0; //发送数据的过程步骤清零,返回开始的步骤待命
  672.                   ucStatus=1; //启动发送数据,1代表正在通讯过程
  673.                   break;

  674.              case 1:  //处于正在通讯的过程
  675.                   break;

  676.              case 2: //发送数据出错,比如中间超时没有接收到数据
  677.                   ucStatus=0; //切换回待机的状态
  678.                   break;
  679.           }
  680.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  681.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  682.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  683.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发

  684.           break;   
  685.          
  686.   }               
  687. }

  688. void display_drive(void)  
  689. {
  690.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  691.    switch(ucDisplayDriveStep)
  692.    {
  693.       case 1:  //显示第1位
  694.            ucDigShowTemp=dig_table[ucDigShow1];
  695.                    if(ucDigDot1==1)
  696.                    {
  697.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  698.                    }
  699.            dig_hc595_drive(ucDigShowTemp,0xfe);
  700.                break;
  701.       case 2:  //显示第2位
  702.            ucDigShowTemp=dig_table[ucDigShow2];
  703.                    if(ucDigDot2==1)
  704.                    {
  705.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  706.                    }
  707.            dig_hc595_drive(ucDigShowTemp,0xfd);
  708.                break;
  709.       case 3:  //显示第3位
  710.            ucDigShowTemp=dig_table[ucDigShow3];
  711.                    if(ucDigDot3==1)
  712.                    {
  713.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  714.                    }
  715.            dig_hc595_drive(ucDigShowTemp,0xfb);
  716.                break;
  717.       case 4:  //显示第4位
  718.            ucDigShowTemp=dig_table[ucDigShow4];
  719.                    if(ucDigDot4==1)
  720.                    {
  721.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  722.                    }
  723.            dig_hc595_drive(ucDigShowTemp,0xf7);
  724.                break;
  725.       case 5:  //显示第5位
  726.            ucDigShowTemp=dig_table[ucDigShow5];
  727.                    if(ucDigDot5==1)
  728.                    {
  729.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  730.                    }
  731.            dig_hc595_drive(ucDigShowTemp,0xef);
  732.                break;
  733.       case 6:  //显示第6位
  734.            ucDigShowTemp=dig_table[ucDigShow6];
  735.                    if(ucDigDot6==1)
  736.                    {
  737.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  738.                    }
  739.            dig_hc595_drive(ucDigShowTemp,0xdf);
  740.                break;
  741.       case 7:  //显示第7位
  742.            ucDigShowTemp=dig_table[ucDigShow7];
  743.                    if(ucDigDot7==1)
  744.                    {
  745.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  746.            }
  747.            dig_hc595_drive(ucDigShowTemp,0xbf);
  748.                break;
  749.       case 8:  //显示第8位
  750.            ucDigShowTemp=dig_table[ucDigShow8];
  751.                    if(ucDigDot8==1)
  752.                    {
  753.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  754.                    }
  755.            dig_hc595_drive(ucDigShowTemp,0x7f);
  756.                break;
  757.    }
  758.    ucDisplayDriveStep++;
  759.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  760.    {
  761.      ucDisplayDriveStep=1;
  762.    }

  763. }

  764. //数码管的74HC595驱动函数
  765. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  766. {
  767.    unsigned char i;
  768.    unsigned char ucTempData;
  769.    dig_hc595_sh_dr=0;
  770.    dig_hc595_st_dr=0;
  771.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  772.    for(i=0;i<8;i++)
  773.    {
  774.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  775.          else dig_hc595_ds_dr=0;
  776.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  777.          delay_short(1);
  778.          dig_hc595_sh_dr=1;
  779.          delay_short(1);
  780.          ucTempData=ucTempData<<1;
  781.    }
  782.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  783.    for(i=0;i<8;i++)
  784.    {
  785.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  786.          else dig_hc595_ds_dr=0;
  787.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  788.          delay_short(1);
  789.          dig_hc595_sh_dr=1;
  790.          delay_short(1);
  791.          ucTempData=ucTempData<<1;
  792.    }
  793.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  794.    delay_short(1);
  795.    dig_hc595_st_dr=1;
  796.    delay_short(1);
  797.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  798.    dig_hc595_st_dr=0;
  799.    dig_hc595_ds_dr=0;
  800. }

  801. //LED灯的74HC595驱动函数
  802. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  803. {
  804.    unsigned char i;
  805.    unsigned char ucTempData;
  806.    hc595_sh_dr=0;
  807.    hc595_st_dr=0;
  808.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  809.    for(i=0;i<8;i++)
  810.    {
  811.          if(ucTempData>=0x80)hc595_ds_dr=1;
  812.          else hc595_ds_dr=0;
  813.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  814.          delay_short(1);
  815.          hc595_sh_dr=1;
  816.          delay_short(1);
  817.          ucTempData=ucTempData<<1;
  818.    }
  819.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  820.    for(i=0;i<8;i++)
  821.    {
  822.          if(ucTempData>=0x80)hc595_ds_dr=1;
  823.          else hc595_ds_dr=0;
  824.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  825.          delay_short(1);
  826.          hc595_sh_dr=1;
  827.          delay_short(1);
  828.          ucTempData=ucTempData<<1;
  829.    }
  830.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  831.    delay_short(1);
  832.    hc595_st_dr=1;
  833.    delay_short(1);
  834.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  835.    hc595_st_dr=0;
  836.    hc595_ds_dr=0;
  837. }


  838. void usart_receive(void) interrupt 4   //串口接收数据中断        
  839. {        

  840.    if(RI==1)  
  841.    {
  842.         RI = 0;

  843.          ++uiRcregTotal;
  844.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  845.         {
  846.            uiRcregTotal=const_rc_size;
  847.         }
  848.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里

  849.         if(ucSendCntLock==0)  //原子锁判断
  850.         {
  851.             ucSendCntLock=1; //加锁
  852.             uiSendCnt=0;  //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
  853.             ucSendCntLock=0; //解锁
  854.         }
  855.    
  856.    }
  857.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  858.    {
  859.         TI = 0;  //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
  860.    }
  861.                                                          
  862. }  

  863. void T0_time(void) interrupt 1   //定时中断
  864. {
  865.   TF0=0;  //清除中断标志
  866.   TR0=0; //关中断


  867. /* 注释一:
  868.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  869.   */  
  870.   if(ucSendCntLock==0)  //原子锁判断
  871.   {
  872.      ucSendCntLock=1; //加锁
  873.      if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  874.      {
  875.         uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  876.         ucSendLock=1;     //开自锁标志
  877.      }
  878.      ucSendCntLock=0; //解锁
  879.   }

  880.   if(ucVoiceLock==0) //原子锁判断
  881.   {
  882.      if(uiVoiceCnt!=0)
  883.      {

  884.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  885.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  886.      
  887.      }
  888.      else
  889.      {

  890.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  891.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  892.         
  893.      }
  894.   }

  895.   if(ucStatus!=0) //处于非待机的状态,Led闪烁
  896.   {
  897.      if(ucLedLock==0)//原子锁判断
  898.          {
  899.         uiLedCnt++; //Led闪烁计时器不断累加
  900.          }
  901.   }

  902.   if(ucStatus==1) //处于正在通讯的状态,
  903.   {
  904.      if(ucSendTimeOutLock==0)  //原子锁判断
  905.      {
  906.          uiSendTimeOutCnt++;   //超时计时器累加
  907.      }
  908.   }



  909.   key_scan(); //按键扫描函数
  910.   display_drive();  //数码管字模的驱动函数

  911.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  912.   TL0=0x0b;
  913.   TR0=1;  //开中断
  914. }

  915. void delay_short(unsigned int uiDelayShort)
  916. {
  917.    unsigned int i;  
  918.    for(i=0;i<uiDelayShort;i++)
  919.    {
  920.      ;   //一个分号相当于执行一条空语句
  921.    }
  922. }

  923. void delay_long(unsigned int uiDelayLong)
  924. {
  925.    unsigned int i;
  926.    unsigned int j;
  927.    for(i=0;i<uiDelayLong;i++)
  928.    {
  929.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  930.           {
  931.              ; //一个分号相当于执行一条空语句
  932.           }
  933.    }
  934. }

  935. void initial_myself(void)  //第一区 初始化单片机
  936. {
  937. /* 注释二:
  938. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  939. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  940. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
  941. */
  942.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  943.   led_dr=1;  //点亮独立LED灯
  944.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  945.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  946.   TMOD=0x01;  //设置定时器0为工作方式1
  947.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  948.   TL0=0x0b;

  949.   //配置串口
  950.   SCON=0x50;
  951.   TMOD=0X21;

  952. /* 注释三:
  953. * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
  954. * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
  955. */
  956.   IP =0x10;  //把串口中断设置为最高优先级,必须的。

  957.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率为9600。
  958.   TR1=1;
  959. }
  960. void initial_peripheral(void) //第二区 初始化外围
  961. {

  962.    ucDigDot8=0;   //小数点全部不显示
  963.    ucDigDot7=0;  
  964.    ucDigDot6=0;
  965.    ucDigDot5=0;  
  966.    ucDigDot4=0;
  967.    ucDigDot3=0;  
  968.    ucDigDot2=0;
  969.    ucDigDot1=0;

  970.    EA=1;     //开总中断
  971.    ES=1;     //允许串口中断
  972.    ET0=1;    //允许定时中断
  973.    TR0=1;    //启动定时中断
  974. }
复制代码



总结陈词:
前面花了大量篇幅详细地讲解了串口收发数据的程序框架,从下一节开始我讲解单片机掉电后数据保存的内容,欲知详情,请听下回分解-----利用AT24C02进行掉电后的数据保存。
  
(未完待续,下节更精彩,不要走开哦)


乐于分享,勇于质疑!
50#
 楼主| 发表于 2014-5-12 13:33:02 | 显示全部楼层
第四十六节:利用AT24C02进行掉电后的数据保存。

开场白:
一个AT24C02可以存储256个字节,地址范围是(0至255)。利用AT24C02存储数据时,要教会大家六个知识点:
第一个:单片机操作AT24C02的通讯过程也就是IIC的通讯过程, IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此在整个通讯过程中应该先关闭总中断,完成之后再开中断。
第二个:在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,应该适当继续把这个时间延长,尤其是在写入数据时。
第三个:如何初始化EEPROM数据的方法。系统第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。
第四个:在时序中,发送ACK确认信号时,要记得把数据线eeprom_sda_dr_s设置为输入的状态。对于51单片机来说,只要把eeprom_sda_dr_s=1就可以。而对于PIC或者AVR单片机来说,它们都是带方向寄存器的,就不能直接eeprom_sda_dr_s=1,而要直接修改方向寄存器,把它设置为输入状态。在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
第五个: 提醒各位读者在硬件上应该注意的问题,单片机跟AT24C02通讯的2根IO口都要加上一个4.7K左右的上拉电阻。凡是在IIC通讯场合,都要加上拉电阻。AT24C02的WP引脚一定要接地,否则存不进数据。
第六个:旧版的坚鸿51学习板在硬件上有一个bug,AT24C02的第8个引脚VCC悬空了!!!,读者记得把它飞线连接到5V电源处。新版的坚鸿51学习板已经改过来了。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:
    4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。
    显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9作为独立按键。
      一共有4个窗口。每个窗口显示一个参数。
     第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
     第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。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. void initial_myself(void);   
  7. void initial_peripheral(void);
  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(void); //显示数码管字模的驱动函数
  13. void display_service(void); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);


  16. void start24(void);  //开始位
  17. void ack24(void);  //确认位
  18. void stop24(void);  //停止位
  19. unsigned char read24(void);  //读取一个字节的时序
  20. void write24(unsigned char dd); //发送一个字节的时序
  21. unsigned char read_eeprom(unsigned int address);   //从一个地址读取出一个字节数据
  22. void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
  23. unsigned int read_eeprom_int(unsigned int address);   //从一个地址读取出一个int类型的数据
  24. void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据

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

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

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

  31. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  32. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  33. sbit eeprom_scl_dr=P3^7;    //时钟线
  34. sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线

  35. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  36. sbit dig_hc595_st_dr=P2^1;  
  37. sbit dig_hc595_ds_dr=P2^2;  
  38. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  39. sbit hc595_st_dr=P2^4;  
  40. sbit hc595_ds_dr=P2^5;  



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

  42. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  43. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  44. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  45. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  46. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  47. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志



  48. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  49. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  50. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  51. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  52. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  53. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  54. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  55. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  56. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  57. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  58. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  59. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  60. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  61. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  62. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  63. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  64. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  65. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  66. unsigned char ucDigShowTemp=0; //临时中间变量
  67. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  68. unsigned char ucWd1Update=1; //窗口1更新显示标志
  69. unsigned char ucWd2Update=0; //窗口2更新显示标志
  70. unsigned char ucWd3Update=0; //窗口3更新显示标志
  71. unsigned char ucWd4Update=0; //窗口4更新显示标志
  72. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  73. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  74. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  75. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  76. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  77. unsigned char ucTemp1=0;  //中间过渡变量
  78. unsigned char ucTemp2=0;  //中间过渡变量
  79. unsigned char ucTemp3=0;  //中间过渡变量
  80. unsigned char ucTemp4=0;  //中间过渡变量


  81. //根据原理图得出的共阴数码管字模表
  82. code unsigned char dig_table[]=
  83. {
  84. 0x3f,  //0       序号0
  85. 0x06,  //1       序号1
  86. 0x5b,  //2       序号2
  87. 0x4f,  //3       序号3
  88. 0x66,  //4       序号4
  89. 0x6d,  //5       序号5
  90. 0x7d,  //6       序号6
  91. 0x07,  //7       序号7
  92. 0x7f,  //8       序号8
  93. 0x6f,  //9       序号9
  94. 0x00,  //无      序号10
  95. 0x40,  //-       序号11
  96. 0x73,  //P       序号12
  97. };
  98. void main()
  99.   {
  100.    initial_myself();  
  101.    delay_long(100);   
  102.    initial_peripheral();
  103.    while(1)  
  104.    {
  105.       key_service(); //按键服务的应用程序
  106.       display_service(); //显示的窗口菜单服务程序
  107.    }
  108. }



  109. //AT24C02驱动程序
  110. void start24(void)  //开始位
  111. {

  112.     eeprom_sda_dr_sr=1;
  113.     eeprom_scl_dr=1;
  114.         delay_short(15);
  115.     eeprom_sda_dr_sr=0;
  116.         delay_short(15);
  117.     eeprom_scl_dr=0;   
  118. }


  119. void ack24(void)  //确认位时序
  120. {
  121.     eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入

  122.     eeprom_scl_dr=1;
  123.         delay_short(15);
  124.     eeprom_scl_dr=0;
  125.         delay_short(15);

  126. //在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
  127. //有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
  128. }

  129. void stop24(void)  //停止位
  130. {
  131.     eeprom_sda_dr_sr=0;
  132.     eeprom_scl_dr=1;
  133.         delay_short(15);
  134.     eeprom_sda_dr_sr=1;
  135. }



  136. unsigned char read24(void)  //读取一个字节的时序
  137. {
  138.         unsigned char outdata,tempdata;


  139.         outdata=0;
  140.                 eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  141.         delay_short(2);
  142.         for(tempdata=0;tempdata<8;tempdata++)
  143.         {
  144.             eeprom_scl_dr=0;
  145.             delay_short(2);
  146.             eeprom_scl_dr=1;
  147.             delay_short(2);
  148.             outdata<<=1;
  149.             if(eeprom_sda_dr_sr==1)outdata++;      
  150.             eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  151.             delay_short(2);
  152.         }
  153.     return(outdata);
  154.      
  155. }

  156. void write24(unsigned char dd) //发送一个字节的时序
  157. {

  158.         unsigned char tempdata;
  159.         for(tempdata=0;tempdata<8;tempdata++)
  160.         {
  161.                 if(dd>=0x80)eeprom_sda_dr_sr=1;
  162.                 else eeprom_sda_dr_sr=0;
  163.                 dd<<=1;
  164.                 delay_short(2);
  165.                 eeprom_scl_dr=1;
  166.                 delay_short(4);
  167.                 eeprom_scl_dr=0;
  168.         }


  169. }



  170. unsigned char read_eeprom(unsigned int address)   //从一个地址读取出一个字节数据
  171. {

  172.    unsigned char dd,cAddress;  

  173.    cAddress=address; //把低字节地址传递给一个字节变量。

  174. /* 注释一:
  175.   * IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此
  176.   * 在整个通讯过程中应该先关闭总中断,完成之后再开中断。但是,这样就会引起另外一个新
  177.   * 问题,如果关闭总中断的时间太长,会导致动态数码管不能及时均匀的扫描,在操作EEPROM时,
  178.   * 数码管就会出现闪烁的现象,解决这个问题最好的办法就是在做项目中尽量不要用动态扫描数码管
  179.   * 的方案,应该用静态显示的方案。那么程序上还有没有改善的方法?有的,下一节我会讲这个问题
  180.   * 的改善方法。
  181.   */
  182.    EA=0; //禁止中断

  183.    start24(); //IIC通讯开始

  184.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  185.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定

  186.    ack24(); //发送应答信号
  187.    write24(cAddress); //发送读取的存储地址(范围是0至255)
  188.    ack24(); //发送应答信号

  189.    start24(); //开始
  190.    write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
  191.                   //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  192.    ack24(); //发送应答信号
  193.    dd=read24(); //读取一个字节
  194.    ack24(); //发送应答信号
  195.    stop24();  //停止

  196. /* 注释二:
  197.   * 在写入或者读取完一个字节之后,一定要加上一段延时时间。在11.0592M晶振的系统中,
  198.   * 写入数据时经验值用delay_short(2000),读取数据时经验值用delay_short(800)。
  199.   * 否则在连续写入或者读取一串数据时容易丢失数据。如果一旦发现丢失数据,
  200.   * 应该适当继续把这个时间延长,尤其是在写入数据时。
  201.   */
  202.    delay_short(800);  //此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因
  203.    EA=1; //允许中断

  204.    return(dd);
  205. }

  206. void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
  207. {
  208.    unsigned char cAddress;   

  209.    cAddress=address; //把低字节地址传递给一个字节变量。


  210.    EA=0; //禁止中断

  211.    start24(); //IIC通讯开始

  212.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  213.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  214.    ack24(); //发送应答信号
  215.    write24(cAddress);   //发送写入的存储地址(范围是0至255)
  216.    ack24(); //发送应答信号
  217.    write24(dd);  //写入存储的数据
  218.    ack24(); //发送应答信号
  219.    stop24();  //停止
  220.    delay_short(2000);  //此处最关键,此处的延时时间一定要,而且要足够长,此处也是导致动态数码管闪烁的根本原因
  221.    EA=1; //允许中断

  222. }


  223. unsigned int read_eeprom_int(unsigned int address)   //从一个地址读取出一个int类型的数据
  224. {
  225.    unsigned char ucReadDataH;
  226.    unsigned char ucReadDataL;
  227.    unsigned int  uiReadDate;

  228.    ucReadDataH=read_eeprom(address);    //读取高字节
  229.    ucReadDataL=read_eeprom(address+1);  //读取低字节

  230.    uiReadDate=ucReadDataH;  //把两个字节合并成一个int类型数据
  231.    uiReadDate=uiReadDate<<8;
  232.    uiReadDate=uiReadDate+ucReadDataL;

  233.    return uiReadDate;

  234. }

  235. void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
  236. {
  237.    unsigned char ucWriteDataH;
  238.    unsigned char ucWriteDataL;

  239.    ucWriteDataH=uiWriteData>>8;
  240.    ucWriteDataL=uiWriteData;

  241.    write_eeprom(address,ucWriteDataH); //存入高字节
  242.    write_eeprom(address+1,ucWriteDataL); //存入低字节

  243. }


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

  246.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  247.    {
  248.        case 1:   //显示P--1窗口的数据
  249.             if(ucWd1Update==1)  //窗口1要全部更新显示
  250.    {
  251.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  252.                ucDigShow8=12;  //第8位数码管显示P
  253.                ucDigShow7=11;  //第7位数码管显示-
  254.                ucDigShow6=1;   //第6位数码管显示1
  255.                ucDigShow5=10;  //第5位数码管显示无

  256.               //先分解数据
  257.                        ucTemp4=uiSetData1/1000;     
  258.                        ucTemp3=uiSetData1%1000/100;
  259.                        ucTemp2=uiSetData1%100/10;
  260.                        ucTemp1=uiSetData1%10;
  261.   
  262.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  263.                if(uiSetData1<1000)   
  264.                            {
  265.                               ucDigShow4=10;  //如果小于1000,千位显示无
  266.                            }
  267.                else
  268.                            {
  269.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  270.                            }
  271.                if(uiSetData1<100)
  272.                            {
  273.                   ucDigShow3=10;  //如果小于100,百位显示无
  274.                            }
  275.                            else
  276.                            {
  277.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  278.                            }
  279.                if(uiSetData1<10)
  280.                            {
  281.                   ucDigShow2=10;  //如果小于10,十位显示无
  282.                            }
  283.                            else
  284.                            {
  285.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  286.                }
  287.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  288.             }
  289.             break;
  290.         case 2:  //显示P--2窗口的数据
  291.             if(ucWd2Update==1)  //窗口2要全部更新显示
  292.    {
  293.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  294.                ucDigShow8=12;  //第8位数码管显示P
  295.                ucDigShow7=11;  //第7位数码管显示-
  296.                ucDigShow6=2;  //第6位数码管显示2
  297.                ucDigShow5=10;   //第5位数码管显示无
  298.                        ucTemp4=uiSetData2/1000;     //分解数据
  299.                        ucTemp3=uiSetData2%1000/100;
  300.                        ucTemp2=uiSetData2%100/10;
  301.                        ucTemp1=uiSetData2%10;

  302.                if(uiSetData2<1000)   
  303.                            {
  304.                               ucDigShow4=10;  //如果小于1000,千位显示无
  305.                            }
  306.                else
  307.                            {
  308.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  309.                            }
  310.                if(uiSetData2<100)
  311.                            {
  312.                   ucDigShow3=10;  //如果小于100,百位显示无
  313.                            }
  314.                            else
  315.                            {
  316.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  317.                            }
  318.                if(uiSetData2<10)
  319.                            {
  320.                   ucDigShow2=10;  //如果小于10,十位显示无
  321.                            }
  322.                            else
  323.                            {
  324.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  325.                }
  326.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  327.     }
  328.              break;
  329.         case 3:  //显示P--3窗口的数据
  330.             if(ucWd3Update==1)  //窗口3要全部更新显示
  331.    {
  332.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  333.                ucDigShow8=12;  //第8位数码管显示P
  334.                ucDigShow7=11;  //第7位数码管显示-
  335.                ucDigShow6=3;  //第6位数码管显示3
  336.                ucDigShow5=10;   //第5位数码管显示无
  337.                        ucTemp4=uiSetData3/1000;     //分解数据
  338.                        ucTemp3=uiSetData3%1000/100;
  339.                        ucTemp2=uiSetData3%100/10;
  340.                        ucTemp1=uiSetData3%10;
  341.                if(uiSetData3<1000)   
  342.                            {
  343.                               ucDigShow4=10;  //如果小于1000,千位显示无
  344.                            }
  345.                else
  346.                            {
  347.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  348.                            }
  349.                if(uiSetData3<100)
  350.                            {
  351.                   ucDigShow3=10;  //如果小于100,百位显示无
  352.                            }
  353.                            else
  354.                            {
  355.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  356.                            }
  357.                if(uiSetData3<10)
  358.                            {
  359.                   ucDigShow2=10;  //如果小于10,十位显示无
  360.                            }
  361.                            else
  362.                            {
  363.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  364.                }
  365.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  366.    }
  367.             break;
  368.         case 4:  //显示P--4窗口的数据
  369.             if(ucWd4Update==1)  //窗口4要全部更新显示
  370.    {
  371.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  372.                ucDigShow8=12;  //第8位数码管显示P
  373.                ucDigShow7=11;  //第7位数码管显示-
  374.                ucDigShow6=4;  //第6位数码管显示4
  375.                ucDigShow5=10;   //第5位数码管显示无
  376.                        ucTemp4=uiSetData4/1000;     //分解数据
  377.                        ucTemp3=uiSetData4%1000/100;
  378.                        ucTemp2=uiSetData4%100/10;
  379.                        ucTemp1=uiSetData4%10;

  380.                if(uiSetData4<1000)   
  381.                            {
  382.                               ucDigShow4=10;  //如果小于1000,千位显示无
  383.                            }
  384.                else
  385.                            {
  386.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  387.                            }
  388.                if(uiSetData4<100)
  389.                            {
  390.                   ucDigShow3=10;  //如果小于100,百位显示无
  391.                            }
  392.                            else
  393.                            {
  394.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  395.                            }
  396.                if(uiSetData4<10)
  397.                            {
  398.                   ucDigShow2=10;  //如果小于10,十位显示无
  399.                            }
  400.                            else
  401.                            {
  402.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  403.                }
  404.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  405.     }
  406.              break;
  407.            }
  408.    

  409. }

  410. void key_scan(void)//按键扫描函数 放在定时中断里
  411. {  
  412.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  413.   {
  414.      ucKeyLock1=0; //按键自锁标志清零
  415.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  416.   }
  417.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  418.   {
  419.      uiKeyTimeCnt1++; //累加定时中断次数
  420.      if(uiKeyTimeCnt1>const_key_time1)
  421.      {
  422.         uiKeyTimeCnt1=0;
  423.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  424.         ucKeySec=1;    //触发1号键
  425.      }
  426.   }

  427.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  428.   {
  429.      ucKeyLock2=0; //按键自锁标志清零
  430.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  431.   }
  432.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  433.   {
  434.      uiKeyTimeCnt2++; //累加定时中断次数
  435.      if(uiKeyTimeCnt2>const_key_time2)
  436.      {
  437.         uiKeyTimeCnt2=0;
  438.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  439.         ucKeySec=2;    //触发2号键
  440.      }
  441.   }

  442.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  443.   {
  444.      ucKeyLock3=0; //按键自锁标志清零
  445.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  446.   }
  447.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  448.   {
  449.      uiKeyTimeCnt3++; //累加定时中断次数
  450.      if(uiKeyTimeCnt3>const_key_time3)
  451.      {
  452.         uiKeyTimeCnt3=0;
  453.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  454.         ucKeySec=3;    //触发3号键
  455.      }
  456.   }


  457. }

  458. void key_service(void) //按键服务的应用程序
  459. {

  460.   switch(ucKeySec) //按键服务状态切换
  461.   {
  462.     case 1:// 加按键 对应朱兆祺学习板的S1键
  463.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  464.                   {
  465.                      case 1:
  466.                   uiSetData1++;   
  467.                                   if(uiSetData1>9999) //最大值是9999
  468.                                   {
  469.                                      uiSetData1=9999;
  470.                                   }

  471.                            write_eeprom_int(0,uiSetData1); //存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁

  472.                            ucWd1Update=1;  //窗口1更新显示
  473.                               break;
  474.                      case 2:
  475.                   uiSetData2++;
  476.                                   if(uiSetData2>9999) //最大值是9999
  477.                                   {
  478.                                      uiSetData2=9999;
  479.                                   }


  480.                            write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁

  481.                            ucWd2Update=1;  //窗口2更新显示
  482.                               break;
  483.                      case 3:
  484.                   uiSetData3++;
  485.                                   if(uiSetData3>9999) //最大值是9999
  486.                                   {
  487.                                      uiSetData3=9999;
  488.                                   }
  489.                            write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  490.                            ucWd3Update=1;  //窗口3更新显示
  491.                               break;
  492.                      case 4:
  493.                   uiSetData4++;
  494.                                   if(uiSetData4>9999) //最大值是9999
  495.                                   {
  496.                                      uiSetData4=9999;
  497.                                   }
  498.                            write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  499.                            ucWd4Update=1;  //窗口4更新显示
  500.                               break;
  501.                   }

  502.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  503.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  504.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  505.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  506.           break;   
  507.    
  508.     case 2:// 减按键 对应朱兆祺学习板的S5键
  509.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  510.                   {
  511.                      case 1:
  512.                   uiSetData1--;   

  513.                                   if(uiSetData1>9999)  
  514.                                   {
  515.                                      uiSetData1=0;  //最小值是0
  516.                                   }

  517.                            write_eeprom_int(0,uiSetData1); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁

  518.                            ucWd1Update=1;  //窗口1更新显示
  519.                               break;
  520.                      case 2:
  521.                   uiSetData2--;
  522.                                   if(uiSetData2>9999)
  523.                                   {
  524.                                      uiSetData2=0;  //最小值是0
  525.                                   }
  526.                            write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  527.                            ucWd2Update=1;  //窗口2更新显示
  528.                               break;
  529.                      case 3:
  530.                   uiSetData3--;
  531.                                   if(uiSetData3>9999)
  532.                                   {
  533.                                      uiSetData3=0;  //最小值是0
  534.                                   }

  535.                            write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  536.                            ucWd3Update=1;  //窗口3更新显示
  537.                               break;
  538.                      case 4:
  539.                   uiSetData4--;
  540.                                   if(uiSetData4>9999)
  541.                                   {
  542.                                      uiSetData4=0;  //最小值是0
  543.                                   }
  544.                            write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  545.                            ucWd4Update=1;  //窗口4更新显示
  546.                               break;
  547.                   }

  548.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  549.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  550.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  551.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  552.           break;  

  553.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  554.           ucWd++;  //切换窗口
  555.                   if(ucWd>4)
  556.                   {
  557.                     ucWd=1;
  558.                   }
  559.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  560.                   {
  561.                      case 1:
  562.                            ucWd1Update=1;  //窗口1更新显示
  563.                               break;
  564.                      case 2:
  565.                            ucWd2Update=1;  //窗口2更新显示
  566.                               break;
  567.                      case 3:
  568.                            ucWd3Update=1;  //窗口3更新显示
  569.                               break;
  570.                      case 4:
  571.                            ucWd4Update=1;  //窗口4更新显示
  572.                               break;
  573.                   }
  574.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  575.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  576.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  577.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  578.           break;         

  579.          
  580.   }               
  581. }

  582. void display_drive(void)  
  583. {
  584.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  585.    switch(ucDisplayDriveStep)
  586.    {
  587.       case 1:  //显示第1位
  588.            ucDigShowTemp=dig_table[ucDigShow1];
  589.                    if(ucDigDot1==1)
  590.                    {
  591.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  592.                    }
  593.            dig_hc595_drive(ucDigShowTemp,0xfe);
  594.                break;
  595.       case 2:  //显示第2位
  596.            ucDigShowTemp=dig_table[ucDigShow2];
  597.                    if(ucDigDot2==1)
  598.                    {
  599.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  600.                    }
  601.            dig_hc595_drive(ucDigShowTemp,0xfd);
  602.                break;
  603.       case 3:  //显示第3位
  604.            ucDigShowTemp=dig_table[ucDigShow3];
  605.                    if(ucDigDot3==1)
  606.                    {
  607.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  608.                    }
  609.            dig_hc595_drive(ucDigShowTemp,0xfb);
  610.                break;
  611.       case 4:  //显示第4位
  612.            ucDigShowTemp=dig_table[ucDigShow4];
  613.                    if(ucDigDot4==1)
  614.                    {
  615.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  616.                    }
  617.            dig_hc595_drive(ucDigShowTemp,0xf7);
  618.                break;
  619.       case 5:  //显示第5位
  620.            ucDigShowTemp=dig_table[ucDigShow5];
  621.                    if(ucDigDot5==1)
  622.                    {
  623.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  624.                    }
  625.            dig_hc595_drive(ucDigShowTemp,0xef);
  626.                break;
  627.       case 6:  //显示第6位
  628.            ucDigShowTemp=dig_table[ucDigShow6];
  629.                    if(ucDigDot6==1)
  630.                    {
  631.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  632.                    }
  633.            dig_hc595_drive(ucDigShowTemp,0xdf);
  634.                break;
  635.       case 7:  //显示第7位
  636.            ucDigShowTemp=dig_table[ucDigShow7];
  637.                    if(ucDigDot7==1)
  638.                    {
  639.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  640.            }
  641.            dig_hc595_drive(ucDigShowTemp,0xbf);
  642.                break;
  643.       case 8:  //显示第8位
  644.            ucDigShowTemp=dig_table[ucDigShow8];
  645.                    if(ucDigDot8==1)
  646.                    {
  647.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  648.                    }
  649.            dig_hc595_drive(ucDigShowTemp,0x7f);
  650.                break;
  651.    }
  652.    ucDisplayDriveStep++;
  653.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  654.    {
  655.      ucDisplayDriveStep=1;
  656.    }

  657. }

  658. //数码管的74HC595驱动函数
  659. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  660. {
  661.    unsigned char i;
  662.    unsigned char ucTempData;
  663.    dig_hc595_sh_dr=0;
  664.    dig_hc595_st_dr=0;
  665.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  666.    for(i=0;i<8;i++)
  667.    {
  668.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  669.          else dig_hc595_ds_dr=0;
  670.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  671.          delay_short(1);
  672.          dig_hc595_sh_dr=1;
  673.          delay_short(1);
  674.          ucTempData=ucTempData<<1;
  675.    }
  676.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  677.    for(i=0;i<8;i++)
  678.    {
  679.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  680.          else dig_hc595_ds_dr=0;
  681.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  682.          delay_short(1);
  683.          dig_hc595_sh_dr=1;
  684.          delay_short(1);
  685.          ucTempData=ucTempData<<1;
  686.    }
  687.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  688.    delay_short(1);
  689.    dig_hc595_st_dr=1;
  690.    delay_short(1);
  691.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  692.    dig_hc595_st_dr=0;
  693.    dig_hc595_ds_dr=0;
  694. }

  695. //LED灯的74HC595驱动函数
  696. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  697. {
  698.    unsigned char i;
  699.    unsigned char ucTempData;
  700.    hc595_sh_dr=0;
  701.    hc595_st_dr=0;
  702.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  703.    for(i=0;i<8;i++)
  704.    {
  705.          if(ucTempData>=0x80)hc595_ds_dr=1;
  706.          else hc595_ds_dr=0;
  707.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  708.          delay_short(1);
  709.          hc595_sh_dr=1;
  710.          delay_short(1);
  711.          ucTempData=ucTempData<<1;
  712.    }
  713.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  714.    for(i=0;i<8;i++)
  715.    {
  716.          if(ucTempData>=0x80)hc595_ds_dr=1;
  717.          else hc595_ds_dr=0;
  718.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  719.          delay_short(1);
  720.          hc595_sh_dr=1;
  721.          delay_short(1);
  722.          ucTempData=ucTempData<<1;
  723.    }
  724.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  725.    delay_short(1);
  726.    hc595_st_dr=1;
  727.    delay_short(1);
  728.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  729.    hc595_st_dr=0;
  730.    hc595_ds_dr=0;
  731. }


  732. void T0_time(void) interrupt 1   //定时中断
  733. {
  734.   TF0=0;  //清除中断标志
  735.   TR0=0; //关中断


  736. /* 注释三:
  737.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  738.   */  

  739.   if(ucVoiceLock==0) //原子锁判断
  740.   {
  741.      if(uiVoiceCnt!=0)
  742.      {

  743.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  744.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  745.      
  746.      }
  747.      else
  748.      {

  749.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  750.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  751.         
  752.      }
  753.   }




  754.   key_scan(); //按键扫描函数
  755.   display_drive();  //数码管字模的驱动函数

  756.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  757.   TL0=0x0b;
  758.   TR0=1;  //开中断
  759. }

  760. void delay_short(unsigned int uiDelayShort)
  761. {
  762.    unsigned int i;  
  763.    for(i=0;i<uiDelayShort;i++)
  764.    {
  765.      ;   //一个分号相当于执行一条空语句
  766.    }
  767. }

  768. void delay_long(unsigned int uiDelayLong)
  769. {
  770.    unsigned int i;
  771.    unsigned int j;
  772.    for(i=0;i<uiDelayLong;i++)
  773.    {
  774.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  775.           {
  776.              ; //一个分号相当于执行一条空语句
  777.           }
  778.    }
  779. }

  780. void initial_myself(void)  //第一区 初始化单片机
  781. {
  782. /* 注释四:
  783. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  784. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  785. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
  786. */
  787.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  788.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  789.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  790.   TMOD=0x01;  //设置定时器0为工作方式1
  791.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  792.   TL0=0x0b;

  793. }
  794. void initial_peripheral(void) //第二区 初始化外围
  795. {

  796.    ucDigDot8=0;   //小数点全部不显示
  797.    ucDigDot7=0;  
  798.    ucDigDot6=0;
  799.    ucDigDot5=0;  
  800.    ucDigDot4=0;
  801.    ucDigDot3=0;  
  802.    ucDigDot2=0;
  803.    ucDigDot1=0;

  804.    EA=1;     //开总中断
  805.    ET0=1;    //允许定时中断
  806.    TR0=1;    //启动定时中断

  807. /* 注释五:
  808. * 如何初始化EEPROM数据的方法。在使用EEPROM时,这一步初始化很关键!
  809. * 第一次上电时,我们从EEPROM读取出来的数据有可能超出了范围,可能是ff。
  810. * 这个时候我们应该给它填入一个初始化的数据,这一步千万别漏了。另外,
  811. * 由于int类型数据占用2个字节,所以以下4个数据挨着的地址是0,2,4,6.
  812. */

  813.    uiSetData1=read_eeprom_int(0);  //读取uiSetData1,内部占用2个字节地址
  814.    if(uiSetData1>9999)   //不在范围内
  815.    {
  816.        uiSetData1=0;   //填入一个初始化数据
  817.        write_eeprom_int(0,uiSetData1); //存入uiSetData1,内部占用2个字节地址
  818.    }

  819.    uiSetData2=read_eeprom_int(2);  //读取uiSetData2,内部占用2个字节地址
  820.    if(uiSetData2>9999)//不在范围内
  821.    {
  822.        uiSetData2=0;  //填入一个初始化数据
  823.        write_eeprom_int(2,uiSetData2); //存入uiSetData2,内部占用2个字节地址
  824.    }

  825.    uiSetData3=read_eeprom_int(4);  //读取uiSetData3,内部占用2个字节地址
  826.    if(uiSetData3>9999)//不在范围内
  827.    {
  828.        uiSetData3=0;  //填入一个初始化数据
  829.        write_eeprom_int(4,uiSetData3); //存入uiSetData3,内部占用2个字节地址
  830.    }

  831.    uiSetData4=read_eeprom_int(6);  //读取uiSetData4,内部占用2个字节地址
  832.    if(uiSetData4>9999)//不在范围内
  833.    {
  834.        uiSetData4=0;  //填入一个初始化数据
  835.        write_eeprom_int(6,uiSetData4); //存入uiSetData4,内部占用2个字节地址
  836.    }



  837. }
复制代码

总结陈词:
   IIC通讯过程是一个要求一气呵成的通讯过程,中间不能被其它中断影响时序出错,因此,在整个通讯过程中应该先关闭总中断,完成之后再开中断。但是,这样就会引起另外一个新问题,如果关闭总中断的时间太长,会导致动态数码管不能及时均匀的扫描,在按键更改参数,内部操作EEPROM时,数码管就会出现短暂明显的闪烁现象,解决这个问题最好的办法就是在做项目中尽量不要用动态扫描数码管的方案,应该用静态显示的方案。那么在程序上还有没有改善这种现象的方法?当然有。欲知详情,请听下回分解-----操作AT24C02时,利用“一气呵成的定时器方式”改善数码管的闪烁现象。
  
(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
51#
 楼主| 发表于 2014-5-15 12:55:05 | 显示全部楼层
第四十七节:操作AT24C02时,利用“一气呵成的定时器延时”改善数码管的闪烁现象。

开场白:
上一节在按键更改参数时,会出现短暂明显的数码管闪烁现象。这节通过教大家使用新型延时函数可以有效的改善闪烁现象。要教会大家三个知识点:
第一个:如何编写一气呵成的定时器延时函数。
第二个:如何编写检查EEPROM芯片是否存在短路,虚焊或者芯片坏了的监控程序。
第三个:经过网友“cjseng”的提醒,我建议大家以后在用EEPROM芯片时,如果单片机IO口足够多,WP引脚应该专门接一个IO口,并且加一个上拉电阻,需要更改EEPROM存储数据时置低,其他任何一个时刻都置高,这样可以更加有效地保护EEPROM内部数据不会被意外更改。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。旧版的坚鸿51学习板在硬件上有一个bug,AT24C02的第8个引脚VCC悬空了!!!,读者记得把它飞线连接到5V电源处。新版的坚鸿51学习板已经改过来了。


(2)实现功能:
    4个被更改后的参数断电后不丢失,数据可以保存,断电再上电后还是上一次最新被修改的数据。如果AT24C02短路,虚焊,或者坏了,系统可以检查出来,并且蜂鸣器会间歇性鸣叫报警。按更改参数按键时,数码管比上一节大大降低了闪烁现象。
    显示和独立按键部分根据第29节的程序来改编,用坚鸿51单片机学习板中的S1,S5,S9作为独立按键。
      一共有4个窗口。每个窗口显示一个参数。
     第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
     第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。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_eeprom_1s    400   //大概1秒的时间

  7. void initial_myself(void);   
  8. void initial_peripheral(void);
  9. void delay_short(unsigned int uiDelayShort);
  10. void delay_long(unsigned int uiDelaylong);
  11. void delay_timer(unsigned int uiDelayTimerTemp); //一气呵成的定时器延时方式

  12. //驱动数码管的74HC595
  13. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  14. void display_drive(void); //显示数码管字模的驱动函数
  15. void display_service(void); //显示的窗口菜单服务程序
  16. //驱动LED的74HC595
  17. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);


  18. void start24(void);  //开始位
  19. void ack24(void);  //确认位
  20. void stop24(void);  //停止位
  21. unsigned char read24(void);  //读取一个字节的时序
  22. void write24(unsigned char dd); //发送一个字节的时序
  23. unsigned char read_eeprom(unsigned int address);   //从一个地址读取出一个字节数据
  24. void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
  25. unsigned int read_eeprom_int(unsigned int address);   //从一个地址读取出一个int类型的数据
  26. void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据

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

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

  30. void eeprom_alarm_service(void); //EEPROM出错报警


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

  34. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  35. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  36. sbit eeprom_scl_dr=P3^7;    //时钟线
  37. sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线

  38. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  39. sbit dig_hc595_st_dr=P2^1;  
  40. sbit dig_hc595_ds_dr=P2^2;  
  41. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  42. sbit hc595_st_dr=P2^4;  
  43. sbit hc595_ds_dr=P2^5;  



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

  45. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  46. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  47. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  48. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  49. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  50. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志



  51. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  52. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  53. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  54. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  55. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  56. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  57. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  58. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  59. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  60. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  61. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  62. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  63. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  64. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  65. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  66. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  67. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  68. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  69. unsigned char ucDigShowTemp=0; //临时中间变量
  70. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  71. unsigned char ucWd1Update=1; //窗口1更新显示标志
  72. unsigned char ucWd2Update=0; //窗口2更新显示标志
  73. unsigned char ucWd3Update=0; //窗口3更新显示标志
  74. unsigned char ucWd4Update=0; //窗口4更新显示标志
  75. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  76. unsigned int  uiSetData1=0;  //本程序中需要被设置的参数1
  77. unsigned int  uiSetData2=0;  //本程序中需要被设置的参数2
  78. unsigned int  uiSetData3=0;  //本程序中需要被设置的参数3
  79. unsigned int  uiSetData4=0;  //本程序中需要被设置的参数4

  80. unsigned char ucTemp1=0;  //中间过渡变量
  81. unsigned char ucTemp2=0;  //中间过渡变量
  82. unsigned char ucTemp3=0;  //中间过渡变量
  83. unsigned char ucTemp4=0;  //中间过渡变量

  84. unsigned char ucDelayTimerLock=0; //原子锁
  85. unsigned int  uiDelayTimer=0;

  86. unsigned char ucCheckEeprom=0;  //检查EEPROM芯片是否正常
  87. unsigned char ucEepromError=0; //EEPROM芯片是否正常的标志

  88. unsigned char ucEepromLock=0;//原子锁
  89. unsigned int  uiEepromCnt=0; //间歇性蜂鸣器报警的计时器

  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. };
  107. void main()
  108.   {
  109.    initial_myself();  
  110.    delay_long(100);   
  111.    initial_peripheral();
  112.    while(1)  
  113.    {
  114.       key_service(); //按键服务的应用程序
  115.       display_service(); //显示的窗口菜单服务程序
  116.           eeprom_alarm_service(); //EEPROM出错报警
  117.    }
  118. }


  119. void eeprom_alarm_service(void) //EEPROM出错报警
  120. {

  121.   if(ucEepromError==1) //EEPROM出错
  122.   {
  123.       if(uiEepromCnt<const_eeprom_1s)  //大概1秒钟蜂鸣器响一次
  124.       {
  125.              ucEepromLock=1;  //原子锁加锁
  126.          uiEepromCnt=0; //计时器清零
  127.              ucEepromLock=0;  //原子锁解锁


  128.          ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  129.          uiVoiceCnt=const_voice_short; //蜂鸣器声音触发,滴一声就停。
  130.          ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  131.       }
  132.   }

  133. }


  134. //AT24C02驱动程序
  135. void start24(void)  //开始位
  136. {

  137.     eeprom_sda_dr_sr=1;
  138.     eeprom_scl_dr=1;
  139.         delay_short(15);
  140.     eeprom_sda_dr_sr=0;
  141.         delay_short(15);
  142.     eeprom_scl_dr=0;   
  143. }


  144. void ack24(void)  //确认位时序
  145. {
  146.     eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入

  147.     eeprom_scl_dr=1;
  148.         delay_short(15);
  149.     eeprom_scl_dr=0;
  150.         delay_short(15);

  151. //在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
  152. //有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
  153. }

  154. void stop24(void)  //停止位
  155. {
  156.     eeprom_sda_dr_sr=0;
  157.     eeprom_scl_dr=1;
  158.         delay_short(15);
  159.     eeprom_sda_dr_sr=1;
  160. }



  161. unsigned char read24(void)  //读取一个字节的时序
  162. {
  163.         unsigned char outdata,tempdata;


  164.         outdata=0;
  165.                 eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  166.         delay_short(2);
  167.         for(tempdata=0;tempdata<8;tempdata++)
  168.         {
  169.             eeprom_scl_dr=0;
  170.             delay_short(2);
  171.             eeprom_scl_dr=1;
  172.             delay_short(2);
  173.             outdata<<=1;
  174.             if(eeprom_sda_dr_sr==1)outdata++;      
  175.             eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  176.             delay_short(2);
  177.         }
  178.     return(outdata);
  179.      
  180. }

  181. void write24(unsigned char dd) //发送一个字节的时序
  182. {

  183.         unsigned char tempdata;
  184.         for(tempdata=0;tempdata<8;tempdata++)
  185.         {
  186.                 if(dd>=0x80)eeprom_sda_dr_sr=1;
  187.                 else eeprom_sda_dr_sr=0;
  188.                 dd<<=1;
  189.                 delay_short(2);
  190.                 eeprom_scl_dr=1;
  191.                 delay_short(4);
  192.                 eeprom_scl_dr=0;
  193.         }


  194. }



  195. unsigned char read_eeprom(unsigned int address)   //从一个地址读取出一个字节数据
  196. {

  197.    unsigned char dd,cAddress;  

  198.    cAddress=address; //把低字节地址传递给一个字节变量。

  199.    EA=0; //禁止中断

  200.    start24(); //IIC通讯开始

  201.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  202.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定

  203.    ack24(); //发送应答信号
  204.    write24(cAddress); //发送读取的存储地址(范围是0至255)
  205.    ack24(); //发送应答信号

  206.    start24(); //开始
  207.    write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
  208.                   //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  209.    ack24(); //发送应答信号
  210.    dd=read24(); //读取一个字节
  211.    ack24(); //发送应答信号
  212.    stop24();  //停止
  213.    EA=1; //允许中断
  214.    delay_timer(2); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

  215.    return(dd);
  216. }

  217. void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
  218. {
  219.    unsigned char cAddress;   

  220.    cAddress=address; //把低字节地址传递给一个字节变量。


  221.    EA=0; //禁止中断

  222.    start24(); //IIC通讯开始

  223.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  224.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  225.    ack24(); //发送应答信号
  226.    write24(cAddress);   //发送写入的存储地址(范围是0至255)
  227.    ack24(); //发送应答信号
  228.    write24(dd);  //写入存储的数据
  229.    ack24(); //发送应答信号
  230.    stop24();  //停止
  231.    EA=1; //允许中断
  232.    delay_timer(4); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

  233. }


  234. unsigned int read_eeprom_int(unsigned int address)   //从一个地址读取出一个int类型的数据
  235. {
  236.    unsigned char ucReadDataH;
  237.    unsigned char ucReadDataL;
  238.    unsigned int  uiReadDate;

  239.    ucReadDataH=read_eeprom(address);    //读取高字节
  240.    ucReadDataL=read_eeprom(address+1);  //读取低字节

  241.    uiReadDate=ucReadDataH;  //把两个字节合并成一个int类型数据
  242.    uiReadDate=uiReadDate<<8;
  243.    uiReadDate=uiReadDate+ucReadDataL;

  244.    return uiReadDate;

  245. }

  246. void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
  247. {
  248.    unsigned char ucWriteDataH;
  249.    unsigned char ucWriteDataL;

  250.    ucWriteDataH=uiWriteData>>8;
  251.    ucWriteDataL=uiWriteData;

  252.    write_eeprom(address,ucWriteDataH); //存入高字节
  253.    write_eeprom(address+1,ucWriteDataL); //存入低字节

  254. }


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

  257.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  258.    {
  259.        case 1:   //显示P--1窗口的数据
  260.             if(ucWd1Update==1)  //窗口1要全部更新显示
  261.    {
  262.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  263.                ucDigShow8=12;  //第8位数码管显示P
  264.                ucDigShow7=11;  //第7位数码管显示-
  265.                ucDigShow6=1;   //第6位数码管显示1
  266.                ucDigShow5=10;  //第5位数码管显示无

  267.               //先分解数据
  268.                        ucTemp4=uiSetData1/1000;     
  269.                        ucTemp3=uiSetData1%1000/100;
  270.                        ucTemp2=uiSetData1%100/10;
  271.                        ucTemp1=uiSetData1%10;
  272.   
  273.                           //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  274.                if(uiSetData1<1000)   
  275.                            {
  276.                               ucDigShow4=10;  //如果小于1000,千位显示无
  277.                            }
  278.                else
  279.                            {
  280.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  281.                            }
  282.                if(uiSetData1<100)
  283.                            {
  284.                   ucDigShow3=10;  //如果小于100,百位显示无
  285.                            }
  286.                            else
  287.                            {
  288.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  289.                            }
  290.                if(uiSetData1<10)
  291.                            {
  292.                   ucDigShow2=10;  //如果小于10,十位显示无
  293.                            }
  294.                            else
  295.                            {
  296.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  297.                }
  298.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  299.             }
  300.             break;
  301.         case 2:  //显示P--2窗口的数据
  302.             if(ucWd2Update==1)  //窗口2要全部更新显示
  303.    {
  304.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描
  305.                ucDigShow8=12;  //第8位数码管显示P
  306.                ucDigShow7=11;  //第7位数码管显示-
  307.                ucDigShow6=2;  //第6位数码管显示2
  308.                ucDigShow5=10;   //第5位数码管显示无
  309.                        ucTemp4=uiSetData2/1000;     //分解数据
  310.                        ucTemp3=uiSetData2%1000/100;
  311.                        ucTemp2=uiSetData2%100/10;
  312.                        ucTemp1=uiSetData2%10;

  313.                if(uiSetData2<1000)   
  314.                            {
  315.                               ucDigShow4=10;  //如果小于1000,千位显示无
  316.                            }
  317.                else
  318.                            {
  319.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  320.                            }
  321.                if(uiSetData2<100)
  322.                            {
  323.                   ucDigShow3=10;  //如果小于100,百位显示无
  324.                            }
  325.                            else
  326.                            {
  327.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  328.                            }
  329.                if(uiSetData2<10)
  330.                            {
  331.                   ucDigShow2=10;  //如果小于10,十位显示无
  332.                            }
  333.                            else
  334.                            {
  335.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  336.                }
  337.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  338.     }
  339.              break;
  340.         case 3:  //显示P--3窗口的数据
  341.             if(ucWd3Update==1)  //窗口3要全部更新显示
  342.    {
  343.                ucWd3Update=0;  //及时清零标志,避免一直进来扫描
  344.                ucDigShow8=12;  //第8位数码管显示P
  345.                ucDigShow7=11;  //第7位数码管显示-
  346.                ucDigShow6=3;  //第6位数码管显示3
  347.                ucDigShow5=10;   //第5位数码管显示无
  348.                        ucTemp4=uiSetData3/1000;     //分解数据
  349.                        ucTemp3=uiSetData3%1000/100;
  350.                        ucTemp2=uiSetData3%100/10;
  351.                        ucTemp1=uiSetData3%10;
  352.                if(uiSetData3<1000)   
  353.                            {
  354.                               ucDigShow4=10;  //如果小于1000,千位显示无
  355.                            }
  356.                else
  357.                            {
  358.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  359.                            }
  360.                if(uiSetData3<100)
  361.                            {
  362.                   ucDigShow3=10;  //如果小于100,百位显示无
  363.                            }
  364.                            else
  365.                            {
  366.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  367.                            }
  368.                if(uiSetData3<10)
  369.                            {
  370.                   ucDigShow2=10;  //如果小于10,十位显示无
  371.                            }
  372.                            else
  373.                            {
  374.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  375.                }
  376.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  377.    }
  378.             break;
  379.         case 4:  //显示P--4窗口的数据
  380.             if(ucWd4Update==1)  //窗口4要全部更新显示
  381.    {
  382.                ucWd4Update=0;  //及时清零标志,避免一直进来扫描
  383.                ucDigShow8=12;  //第8位数码管显示P
  384.                ucDigShow7=11;  //第7位数码管显示-
  385.                ucDigShow6=4;  //第6位数码管显示4
  386.                ucDigShow5=10;   //第5位数码管显示无
  387.                        ucTemp4=uiSetData4/1000;     //分解数据
  388.                        ucTemp3=uiSetData4%1000/100;
  389.                        ucTemp2=uiSetData4%100/10;
  390.                        ucTemp1=uiSetData4%10;

  391.                if(uiSetData4<1000)   
  392.                            {
  393.                               ucDigShow4=10;  //如果小于1000,千位显示无
  394.                            }
  395.                else
  396.                            {
  397.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  398.                            }
  399.                if(uiSetData4<100)
  400.                            {
  401.                   ucDigShow3=10;  //如果小于100,百位显示无
  402.                            }
  403.                            else
  404.                            {
  405.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  406.                            }
  407.                if(uiSetData4<10)
  408.                            {
  409.                   ucDigShow2=10;  //如果小于10,十位显示无
  410.                            }
  411.                            else
  412.                            {
  413.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  414.                }
  415.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  416.     }
  417.              break;
  418.            }
  419.    

  420. }

  421. void key_scan(void)//按键扫描函数 放在定时中断里
  422. {  
  423.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  424.   {
  425.      ucKeyLock1=0; //按键自锁标志清零
  426.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  427.   }
  428.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  429.   {
  430.      uiKeyTimeCnt1++; //累加定时中断次数
  431.      if(uiKeyTimeCnt1>const_key_time1)
  432.      {
  433.         uiKeyTimeCnt1=0;
  434.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  435.         ucKeySec=1;    //触发1号键
  436.      }
  437.   }

  438.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  439.   {
  440.      ucKeyLock2=0; //按键自锁标志清零
  441.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  442.   }
  443.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  444.   {
  445.      uiKeyTimeCnt2++; //累加定时中断次数
  446.      if(uiKeyTimeCnt2>const_key_time2)
  447.      {
  448.         uiKeyTimeCnt2=0;
  449.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  450.         ucKeySec=2;    //触发2号键
  451.      }
  452.   }

  453.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  454.   {
  455.      ucKeyLock3=0; //按键自锁标志清零
  456.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  457.   }
  458.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  459.   {
  460.      uiKeyTimeCnt3++; //累加定时中断次数
  461.      if(uiKeyTimeCnt3>const_key_time3)
  462.      {
  463.         uiKeyTimeCnt3=0;
  464.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  465.         ucKeySec=3;    //触发3号键
  466.      }
  467.   }


  468. }

  469. void key_service(void) //按键服务的应用程序
  470. {

  471.   switch(ucKeySec) //按键服务状态切换
  472.   {
  473.     case 1:// 加按键 对应朱兆祺学习板的S1键
  474.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  475.                   {
  476.                      case 1:
  477.                   uiSetData1++;   
  478.                                   if(uiSetData1>9999) //最大值是9999
  479.                                   {
  480.                                      uiSetData1=9999;
  481.                                   }

  482.                            write_eeprom_int(0,uiSetData1); //存入EEPROM 由于内部有延时函数,所以此处会引起数码管闪烁

  483.                            ucWd1Update=1;  //窗口1更新显示
  484.                               break;
  485.                      case 2:
  486.                   uiSetData2++;
  487.                                   if(uiSetData2>9999) //最大值是9999
  488.                                   {
  489.                                      uiSetData2=9999;
  490.                                   }


  491.                            write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁

  492.                            ucWd2Update=1;  //窗口2更新显示
  493.                               break;
  494.                      case 3:
  495.                   uiSetData3++;
  496.                                   if(uiSetData3>9999) //最大值是9999
  497.                                   {
  498.                                      uiSetData3=9999;
  499.                                   }
  500.                            write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  501.                            ucWd3Update=1;  //窗口3更新显示
  502.                               break;
  503.                      case 4:
  504.                   uiSetData4++;
  505.                                   if(uiSetData4>9999) //最大值是9999
  506.                                   {
  507.                                      uiSetData4=9999;
  508.                                   }
  509.                            write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  510.                            ucWd4Update=1;  //窗口4更新显示
  511.                               break;
  512.                   }

  513.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  514.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  515.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  516.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  517.           break;   
  518.    
  519.     case 2:// 减按键 对应朱兆祺学习板的S5键
  520.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  521.                   {
  522.                      case 1:
  523.                   uiSetData1--;   

  524.                                   if(uiSetData1>9999)  
  525.                                   {
  526.                                      uiSetData1=0;  //最小值是0
  527.                                   }

  528.                            write_eeprom_int(0,uiSetData1); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁

  529.                            ucWd1Update=1;  //窗口1更新显示
  530.                               break;
  531.                      case 2:
  532.                   uiSetData2--;
  533.                                   if(uiSetData2>9999)
  534.                                   {
  535.                                      uiSetData2=0;  //最小值是0
  536.                                   }
  537.                            write_eeprom_int(2,uiSetData2); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  538.                            ucWd2Update=1;  //窗口2更新显示
  539.                               break;
  540.                      case 3:
  541.                   uiSetData3--;
  542.                                   if(uiSetData3>9999)
  543.                                   {
  544.                                      uiSetData3=0;  //最小值是0
  545.                                   }

  546.                            write_eeprom_int(4,uiSetData3); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  547.                            ucWd3Update=1;  //窗口3更新显示
  548.                               break;
  549.                      case 4:
  550.                   uiSetData4--;
  551.                                   if(uiSetData4>9999)
  552.                                   {
  553.                                      uiSetData4=0;  //最小值是0
  554.                                   }
  555.                            write_eeprom_int(6,uiSetData4); //存入EEPROM,由于内部有延时函数,所以此处会引起数码管闪烁
  556.                            ucWd4Update=1;  //窗口4更新显示
  557.                               break;
  558.                   }

  559.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  560.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  561.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  562.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  563.           break;  

  564.     case 3:// 切换窗口按键 对应朱兆祺学习板的S9键
  565.           ucWd++;  //切换窗口
  566.                   if(ucWd>4)
  567.                   {
  568.                     ucWd=1;
  569.                   }
  570.           switch(ucWd)  //在不同的窗口下,在不同的窗口下,更新显示不同的窗口
  571.                   {
  572.                      case 1:
  573.                            ucWd1Update=1;  //窗口1更新显示
  574.                               break;
  575.                      case 2:
  576.                            ucWd2Update=1;  //窗口2更新显示
  577.                               break;
  578.                      case 3:
  579.                            ucWd3Update=1;  //窗口3更新显示
  580.                               break;
  581.                      case 4:
  582.                            ucWd4Update=1;  //窗口4更新显示
  583.                               break;
  584.                   }
  585.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  586.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  587.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  588.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  589.           break;         

  590.          
  591.   }               
  592. }

  593. void display_drive(void)  
  594. {
  595.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  596.    switch(ucDisplayDriveStep)
  597.    {
  598.       case 1:  //显示第1位
  599.            ucDigShowTemp=dig_table[ucDigShow1];
  600.                    if(ucDigDot1==1)
  601.                    {
  602.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  603.                    }
  604.            dig_hc595_drive(ucDigShowTemp,0xfe);
  605.                break;
  606.       case 2:  //显示第2位
  607.            ucDigShowTemp=dig_table[ucDigShow2];
  608.                    if(ucDigDot2==1)
  609.                    {
  610.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  611.                    }
  612.            dig_hc595_drive(ucDigShowTemp,0xfd);
  613.                break;
  614.       case 3:  //显示第3位
  615.            ucDigShowTemp=dig_table[ucDigShow3];
  616.                    if(ucDigDot3==1)
  617.                    {
  618.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  619.                    }
  620.            dig_hc595_drive(ucDigShowTemp,0xfb);
  621.                break;
  622.       case 4:  //显示第4位
  623.            ucDigShowTemp=dig_table[ucDigShow4];
  624.                    if(ucDigDot4==1)
  625.                    {
  626.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  627.                    }
  628.            dig_hc595_drive(ucDigShowTemp,0xf7);
  629.                break;
  630.       case 5:  //显示第5位
  631.            ucDigShowTemp=dig_table[ucDigShow5];
  632.                    if(ucDigDot5==1)
  633.                    {
  634.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  635.                    }
  636.            dig_hc595_drive(ucDigShowTemp,0xef);
  637.                break;
  638.       case 6:  //显示第6位
  639.            ucDigShowTemp=dig_table[ucDigShow6];
  640.                    if(ucDigDot6==1)
  641.                    {
  642.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  643.                    }
  644.            dig_hc595_drive(ucDigShowTemp,0xdf);
  645.                break;
  646.       case 7:  //显示第7位
  647.            ucDigShowTemp=dig_table[ucDigShow7];
  648.                    if(ucDigDot7==1)
  649.                    {
  650.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  651.            }
  652.            dig_hc595_drive(ucDigShowTemp,0xbf);
  653.                break;
  654.       case 8:  //显示第8位
  655.            ucDigShowTemp=dig_table[ucDigShow8];
  656.                    if(ucDigDot8==1)
  657.                    {
  658.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  659.                    }
  660.            dig_hc595_drive(ucDigShowTemp,0x7f);
  661.                break;
  662.    }
  663.    ucDisplayDriveStep++;
  664.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  665.    {
  666.      ucDisplayDriveStep=1;
  667.    }

  668. }

  669. //数码管的74HC595驱动函数
  670. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  671. {
  672.    unsigned char i;
  673.    unsigned char ucTempData;
  674.    dig_hc595_sh_dr=0;
  675.    dig_hc595_st_dr=0;
  676.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  677.    for(i=0;i<8;i++)
  678.    {
  679.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  680.          else dig_hc595_ds_dr=0;
  681.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  682.          delay_short(1);
  683.          dig_hc595_sh_dr=1;
  684.          delay_short(1);
  685.          ucTempData=ucTempData<<1;
  686.    }
  687.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  688.    for(i=0;i<8;i++)
  689.    {
  690.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  691.          else dig_hc595_ds_dr=0;
  692.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  693.          delay_short(1);
  694.          dig_hc595_sh_dr=1;
  695.          delay_short(1);
  696.          ucTempData=ucTempData<<1;
  697.    }
  698.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  699.    delay_short(1);
  700.    dig_hc595_st_dr=1;
  701.    delay_short(1);
  702.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  703.    dig_hc595_st_dr=0;
  704.    dig_hc595_ds_dr=0;
  705. }

  706. //LED灯的74HC595驱动函数
  707. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  708. {
  709.    unsigned char i;
  710.    unsigned char ucTempData;
  711.    hc595_sh_dr=0;
  712.    hc595_st_dr=0;
  713.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  714.    for(i=0;i<8;i++)
  715.    {
  716.          if(ucTempData>=0x80)hc595_ds_dr=1;
  717.          else hc595_ds_dr=0;
  718.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  719.          delay_short(1);
  720.          hc595_sh_dr=1;
  721.          delay_short(1);
  722.          ucTempData=ucTempData<<1;
  723.    }
  724.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  725.    for(i=0;i<8;i++)
  726.    {
  727.          if(ucTempData>=0x80)hc595_ds_dr=1;
  728.          else hc595_ds_dr=0;
  729.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  730.          delay_short(1);
  731.          hc595_sh_dr=1;
  732.          delay_short(1);
  733.          ucTempData=ucTempData<<1;
  734.    }
  735.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  736.    delay_short(1);
  737.    hc595_st_dr=1;
  738.    delay_short(1);
  739.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  740.    hc595_st_dr=0;
  741.    hc595_ds_dr=0;
  742. }


  743. void T0_time(void) interrupt 1   //定时中断
  744. {
  745.   TF0=0;  //清除中断标志
  746.   TR0=0; //关中断


  747.   if(ucVoiceLock==0) //原子锁判断
  748.   {
  749.      if(uiVoiceCnt!=0)
  750.      {

  751.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  752.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  753.      
  754.      }
  755.      else
  756.      {

  757.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  758.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  759.         
  760.      }
  761.   }

  762.   if(ucDelayTimerLock==0) //原子锁判断
  763.   {
  764.      if(uiDelayTimer>0)
  765.          {
  766.            uiDelayTimer--;   //一气呵成的定时器延时方式的计时器
  767.          }
  768.   
  769.   }


  770.   if(ucEepromError==1) //EEPROM出错
  771.   {
  772.       if(ucEepromLock==0)//原子锁判断
  773.           {
  774.              uiEepromCnt++;  //间歇性蜂鸣器报警的计时器
  775.           }
  776.   }




  777.   key_scan(); //按键扫描函数
  778.   display_drive();  //数码管字模的驱动函数

  779.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  780.   TL0=0x0b;
  781.   TR0=1;  //开中断
  782. }

  783. void delay_short(unsigned int uiDelayShort)
  784. {
  785.    unsigned int i;  
  786.    for(i=0;i<uiDelayShort;i++)
  787.    {
  788.      ;   //一个分号相当于执行一条空语句
  789.    }
  790. }

  791. void delay_long(unsigned int uiDelayLong)
  792. {
  793.    unsigned int i;
  794.    unsigned int j;
  795.    for(i=0;i<uiDelayLong;i++)
  796.    {
  797.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  798.           {
  799.              ; //一个分号相当于执行一条空语句
  800.           }
  801.    }
  802. }

  803. void delay_timer(unsigned int uiDelayTimerTemp)
  804. {
  805.     ucDelayTimerLock=1; //原子锁加锁
  806.     uiDelayTimer=uiDelayTimerTemp;
  807.     ucDelayTimerLock=0; //原子锁解锁   

  808. /* 注释一:
  809.   *延时等待,一直等到定时中断把它减到0为止.这种一气呵成的定时器方式,
  810.   *可以在延时的时候动态扫描数码管,改善数码管的闪烁现象
  811.   */
  812.     while(uiDelayTimer!=0);  //一气呵成的定时器方式延时等待

  813. }


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

  816.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  817.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  818.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  819.   TMOD=0x01;  //设置定时器0为工作方式1
  820.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  821.   TL0=0x0b;

  822. }
  823. void initial_peripheral(void) //第二区 初始化外围
  824. {

  825.    ucDigDot8=0;   //小数点全部不显示
  826.    ucDigDot7=0;  
  827.    ucDigDot6=0;
  828.    ucDigDot5=0;  
  829.    ucDigDot4=0;
  830.    ucDigDot3=0;  
  831.    ucDigDot2=0;
  832.    ucDigDot1=0;

  833.    EA=1;     //开总中断
  834.    ET0=1;    //允许定时中断
  835.    TR0=1;    //启动定时中断


  836. /* 注释二:
  837.   * 检查AT24C02芯片是否存在短路,虚焊,芯片坏了等不工作现象。
  838.   * 在一个特定的地址里把数据读出来,如果发现不等于0x5a,则重新写入0x5a,再读出来
  839.   * 判断是不是等于0x5a,如果不相等,则芯片有问题,出错报警提示。
  840.   */
  841.    ucCheckEeprom=read_eeprom(254); //判断AT24C02是否正常
  842.    if(ucCheckEeprom!=0x5a)  //如果不等于特定内容。则重新写入数据再判断一次
  843.    {
  844.      write_eeprom(254,0x5a);  //重新写入标志数据
  845.      ucCheckEeprom=read_eeprom(254); //判断AT24C02是否正常
  846.          if(ucCheckEeprom!=0x5a)  //如果还是不等于特定数字,则芯片不正常
  847.          {
  848.             ucEepromError=1;  //表示AT24C02芯片出错报警
  849.          }
  850.    }

  851.    uiSetData1=read_eeprom_int(0);  //读取uiSetData1,内部占用2个字节地址
  852.    if(uiSetData1>9999)   //不在范围内
  853.    {
  854.        uiSetData1=0;   //填入一个初始化数据
  855.        write_eeprom_int(0,uiSetData1); //存入uiSetData1,内部占用2个字节地址
  856.    }

  857.    uiSetData2=read_eeprom_int(2);  //读取uiSetData2,内部占用2个字节地址
  858.    if(uiSetData2>9999)//不在范围内
  859.    {
  860.        uiSetData2=0;  //填入一个初始化数据
  861.        write_eeprom_int(2,uiSetData2); //存入uiSetData2,内部占用2个字节地址
  862.    }

  863.    uiSetData3=read_eeprom_int(4);  //读取uiSetData3,内部占用2个字节地址
  864.    if(uiSetData3>9999)//不在范围内
  865.    {
  866.        uiSetData3=0;  //填入一个初始化数据
  867.        write_eeprom_int(4,uiSetData3); //存入uiSetData3,内部占用2个字节地址
  868.    }

  869.    uiSetData4=read_eeprom_int(6);  //读取uiSetData4,内部占用2个字节地址
  870.    if(uiSetData4>9999)//不在范围内
  871.    {
  872.        uiSetData4=0;  //填入一个初始化数据
  873.        write_eeprom_int(6,uiSetData4); //存入uiSetData4,内部占用2个字节地址
  874.    }



  875. }
复制代码

总结陈词:
下一节开始讲关于单片机驱动实时时钟芯片的内容,欲知详情,请听下回分解-----利用DS1302做一个实时时钟  。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
52#
 楼主| 发表于 2014-5-21 11:16:30 | 显示全部楼层
第四十八节:利用DS1302做一个实时时钟  。

开场白:
DS1302有两路独立电源输入,我们只要在其中一路电源上挂一个纽扣电池就可以实现掉电时钟继续跑的功能,纽扣电池作为备用电源必须比主电源的电压低一点。DS1302还给我们预留了一片RAM区,我们可以把一些数据存入到DS1302,只要DS1302的电池有电,那么它就相当于一个EEPROM。这个RAM区有什么用呢?因为RAM区的数据只要一掉电,所有的数据都会变成0x00或者0xff,也就是数据掉电会丢失,我们可以利用这个特点,可以在里面存入标志位数据,一旦发现这个数据改变了,就知道时钟的数据需要重新设置过,或者说明电池没电了。
       在移植DS1302驱动程序中,有一个地方最容易出错,就是DS1302芯片的数据线DIO。我们编程时要特别留意这个IO口什么时候作为数据输入,什么时候作为数据输出,以便及时更改方向寄存器。对于51单片机,IO口在读取数据之前,要先置1。
这一节要教会大家六个知识点:
第一个:DS1302做实时时钟时,修改时间和读取时间的常见程序框架。
第二个:如何编写监控备用电池电量耗尽的监控程序。
第三个:在往DS1302写入数据修改时间之前,必须注意调整一下“日”的变量,因为每个月的最大天数是不一样的,有的一个月28天,有的一个月29天,有的一个月30天,有的一个月31天。
第四个:本程序第一次出现了电平按键,跟之前讲的下降沿按键不一样,请留意我是何如用软件滤波的,以及它具体的实现代码。
第五个:本程序第一次出现了一个按键按下去后,如果不松手就会触发两次事件,第一次是短按,第二次是长按3秒。请留意我是如何在之前的按键上略做修改就实现此功能的具体代码。
第六个:继续加深了解按键与显示是如何紧密关联起来的程序框架。

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

(1)        硬件平台.
基于坚鸿51单片机学习板。
旧版的坚鸿51学习板在硬件上有一个bug,DS1302芯片附近的R43,R42两个电阻应该去掉,并且把R41的电阻换成0欧姆的电阻,或者直接短接起来。新版的坚鸿51学习板已经改过来了。

(2)实现功能:
     本程序有2两个窗口。
     第1个窗口显示日期。显示格式“年-月-日”。注意中间有“-”分开。
     第2个窗口显示时间。显示格式“时 分 秒”。注意中间没“-”,只有空格分开。
     系统上电后,默认显示第2个窗口,实时显示动态的“时 分 秒”时间。此时按下S13按键不松手就会切换到显示日期的第1个窗口。松手后自动切换回第2个显示动态时间的窗口。
     需要更改时间的时候,长按S9按键不松手超过3秒后,系统将进入修改时间的状态,切换到第1个日期窗口,并且显示“年”的两位数码管会闪烁,此时可以按S1或者S5加减按键修改年的参数,修改完年后,继续短按S9按键,会切换到“月”的参数闪烁状态,只要依次不断按下S9按键,就会依次切换年,月,日,时,分,秒的参数闪烁状态,最后修改完秒的参数后,系统会自动把我们修改设置的日期时间一次性写入DS1302芯片内部,达到修改日期时间的目的。
S13是电平变化按键,用来切换窗口的,专门用来查看当前日期。按下S13按键时显示日期窗口,松手后返回到显示实时时间的窗口。

本程序在使用过程中的注意事项:
(a)第一次上电时,蜂鸣器会报警,只要DS1302芯片的备用电池电量充足,这个时候断电再重启一次,就不会报警了。
(b)第一次上电时,时间没有走动,需要重新设置一下日期时间才可以。长按S9按键可以进入修改日期时间的状态。

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

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

  4. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  5. #define const_key_time1  20    //按键去抖动延时的时间
  6. #define const_key_time2  20    //按键去抖动延时的时间
  7. #define const_key_time3  20    //按键去抖动延时的时间
  8. #define const_key_time4  20    //按键去抖动延时的时间

  9. #define const_key_time17  1200  //长按超过3秒的时间
  10. #define const_ds1302_0_5s  200   //大概0.5秒的时间

  11. #define const_ds1302_sampling_time    360   //累计主循环次数的时间,每次刷新采样时钟芯片的时间

  12. #define WRITE_SECOND    0x80    //DS1302内部的相关地址
  13. #define WRITE_MINUTE    0x82
  14. #define WRITE_HOUR      0x84
  15. #define WRITE_DATE      0x86
  16. #define WRITE_MONTH     0x88
  17. #define WRITE_YEAR      0x8C

  18. #define WRITE_CHECK     0xC2  //用来检查芯片的备用电池是否用完了的地址
  19. #define READ_CHECK      0xC3  //用来检查芯片的备用电池是否用完了的地址

  20. #define READ_SECOND     0x81
  21. #define READ_MINUTE     0x83
  22. #define READ_HOUR       0x85
  23. #define READ_DATE       0x87
  24. #define READ_MONTH      0x89
  25. #define READ_YEAR       0x8D

  26. #define WRITE_PROTECT   0x8E

  27. void initial_myself(void);   
  28. void initial_peripheral(void);
  29. void delay_short(unsigned int uiDelayShort);
  30. void delay_long(unsigned int uiDelaylong);


  31. //驱动数码管的74HC595
  32. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  33. void display_drive(void); //显示数码管字模的驱动函数
  34. void display_service(void); //显示的窗口菜单服务程序
  35. //驱动LED的74HC595
  36. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

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

  40. void ds1302_alarm_service(void); //ds1302出错报警
  41. void ds1302_sampling(void); //ds1302采样程序,内部每秒钟采集更新一次
  42. void Write1302 ( unsigned char addr, unsigned char dat );//修改时间的驱动
  43. unsigned char Read1302 ( unsigned char addr );//读取时间的驱动

  44. unsigned char bcd_to_number(unsigned char ucBcdTemp);  //BCD转原始数值
  45. unsigned char number_to_bcd(unsigned char ucNumberTemp); //原始数值转BCD

  46. //日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
  47. unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp); //日调整

  48. sbit SCLK_dr      =P1^3;  
  49. sbit DIO_dr_sr    =P1^4;  
  50. sbit DS1302_CE_dr =P1^5;  

  51. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  52. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  53. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  54. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

  55. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  56. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  57. sbit eeprom_scl_dr=P3^7;    //时钟线
  58. sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线

  59. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  60. sbit dig_hc595_st_dr=P2^1;  
  61. sbit dig_hc595_ds_dr=P2^2;  
  62. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  63. sbit hc595_st_dr=P2^4;  
  64. sbit hc595_ds_dr=P2^5;  


  65. unsigned int uiSampingCnt=0;   //采集Ds1302的计时器,每秒钟更新采集一次

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

  67. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  68. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  69. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  70. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  71. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  72. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  73. unsigned int uiKey4Cnt1=0;  //在软件滤波中,用到的变量
  74. unsigned int uiKey4Cnt2=0;
  75. unsigned char ucKey4Sr=1;  //实时反映按键的电平状态
  76. unsigned char ucKey4SrRecord=0; //记录上一次按键的电平状态

  77. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  78. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  79. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  80. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  81. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  82. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  83. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  84. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  85. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  86. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  87. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  88. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  89. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  90. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  91. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  92. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  93. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  94. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  95. unsigned char ucDigShowTemp=0; //临时中间变量
  96. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


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

  99. unsigned char ucWd1Update=0; //窗口1更新显示标志
  100. unsigned char ucWd2Update=1; //窗口2更新显示标志

  101. unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
  102. unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
  103. unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志

  104. unsigned char ucWd2Part1Update=0;  //在窗口2中,局部1的更新显示标志
  105. unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志
  106. unsigned char ucWd2Part3Update=0; //在窗口2中,局部3的更新显示标志

  107. unsigned char  ucYear=0;    //原始数据
  108. unsigned char  ucMonth=0;  
  109. unsigned char  ucDate=0;  
  110. unsigned char  ucHour=0;  
  111. unsigned char  ucMinute=0;  
  112. unsigned char  ucSecond=0;  

  113. unsigned char  ucYearBCD=0;   //BCD码的数据
  114. unsigned char  ucMonthBCD=0;  
  115. unsigned char  ucDateBCD=0;  
  116. unsigned char  ucHourBCD=0;  
  117. unsigned char  ucMinuteBCD=0;  
  118. unsigned char  ucSecondBCD=0;  

  119. unsigned char ucTemp1=0;  //中间过渡变量
  120. unsigned char ucTemp2=0;  //中间过渡变量

  121. unsigned char ucTemp4=0;  //中间过渡变量
  122. unsigned char ucTemp5=0;  //中间过渡变量

  123. unsigned char ucTemp7=0;  //中间过渡变量
  124. unsigned char ucTemp8=0;  //中间过渡变量

  125. unsigned char ucDelayTimerLock=0; //原子锁
  126. unsigned int  uiDelayTimer=0;

  127. unsigned char ucCheckDs1302=0;  //检查Ds1302芯片是否正常
  128. unsigned char ucDs1302Error=0; //Ds1302芯片的备用电池是否用完了的报警标志

  129. unsigned char ucDs1302Lock=0;//原子锁
  130. unsigned int  uiDs1302Cnt=0; //间歇性蜂鸣器报警的计时器

  131. unsigned char ucDpyTimeLock=0; //原子锁
  132. unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

  133. //根据原理图得出的共阴数码管字模表
  134. code unsigned char dig_table[]=
  135. {
  136. 0x3f,  //0       序号0
  137. 0x06,  //1       序号1
  138. 0x5b,  //2       序号2
  139. 0x4f,  //3       序号3
  140. 0x66,  //4       序号4
  141. 0x6d,  //5       序号5
  142. 0x7d,  //6       序号6
  143. 0x07,  //7       序号7
  144. 0x7f,  //8       序号8
  145. 0x6f,  //9       序号9
  146. 0x00,  //无      序号10
  147. 0x40,  //-       序号11
  148. 0x73,  //P       序号12
  149. };
  150. void main()
  151.   {
  152.    initial_myself();  
  153.    delay_long(100);   
  154.    initial_peripheral();
  155.    while(1)  
  156.    {
  157.       key_service(); //按键服务的应用程序
  158.           ds1302_sampling(); //ds1302采样程序,内部每秒钟采集更新一次
  159.       display_service(); //显示的窗口菜单服务程序
  160.           ds1302_alarm_service(); //ds1302出错报警
  161.    }
  162. }


  163. /* 注释一:
  164.   * 系统不用时时刻刻采集ds1302的内部数据,每隔一段时间更新采集一次就可以了。
  165.   * 这个间隔时间应该小于或者等于1秒钟的时间,否则在数码管上看不到每秒钟的时间变化。
  166.   */
  167. void ds1302_sampling(void) //ds1302采样程序,内部每秒钟采集更新一次
  168. {
  169.    if(ucPart==0)  //当系统不是处于设置日期和时间的情况下
  170.    {
  171.       ++uiSampingCnt;  //累计主循环次数的时间
  172.       if(uiSampingCnt>const_ds1302_sampling_time)  //每隔一段时间就更新采集一次Ds1302数据
  173.           {

  174.           uiSampingCnt=0;

  175.   
  176.           ucYearBCD=Read1302(READ_YEAR); //读取年
  177.           ucMonthBCD=Read1302(READ_MONTH); //读取月
  178.           ucDateBCD=Read1302(READ_DATE); //读取日
  179.           ucHourBCD=Read1302(READ_HOUR); //读取时
  180.           ucMinuteBCD=Read1302(READ_MINUTE); //读取分
  181.           ucSecondBCD=Read1302(READ_SECOND); //读取秒


  182.                   ucYear=bcd_to_number(ucYearBCD);  //BCD转原始数值
  183.                   ucMonth=bcd_to_number(ucMonthBCD);  //BCD转原始数值
  184.                   ucDate=bcd_to_number(ucDateBCD);  //BCD转原始数值
  185.                   ucHour=bcd_to_number(ucHourBCD);  //BCD转原始数值
  186.                   ucMinute=bcd_to_number(ucMinuteBCD);  //BCD转原始数值
  187.                   ucSecond=bcd_to_number(ucSecondBCD);  //BCD转原始数值

  188.           ucWd2Update=1; //窗口2更新显示时间
  189.           }

  190.    }
  191. }

  192. //修改ds1302时间的驱动 ,注意,此处写入的是BCD码,
  193. void Write1302 ( unsigned char addr, unsigned char dat )
  194. {
  195.      unsigned char i,temp;         //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
  196.      DS1302_CE_dr=0;                                            //CE引脚为低,数据传送中止
  197.      delay_short(1);
  198.      SCLK_dr=0;                                                 //清零时钟总线
  199.      delay_short(1);
  200.      DS1302_CE_dr = 1;                                          //CE引脚为高,逻辑控制有效
  201.      delay_short(1);
  202.                                                              //发送地址
  203.      for ( i=0; i<8; i++ )                                   //循环8次移位
  204.      {
  205.         DIO_dr_sr = 0;
  206.         temp = addr;
  207.         if(temp&0x01)
  208.         {
  209.             DIO_dr_sr =1;
  210.         }
  211.         else
  212.         {
  213.             DIO_dr_sr =0;
  214.         }
  215.         delay_short(1);
  216.         addr >>= 1;                                           //右移一位

  217.         SCLK_dr = 1;
  218.         delay_short(1);
  219.         SCLK_dr = 0;
  220.         delay_short(1);
  221.      }

  222.                                                               //发送数据
  223.      for ( i=0; i<8; i++ )                                    //循环8次移位
  224.      {
  225.         DIO_dr_sr = 0;
  226.         temp = dat;
  227.         if(temp&0x01)
  228.         {
  229.             DIO_dr_sr =1;
  230.         }
  231.         else
  232.         {
  233.            DIO_dr_sr =0;
  234.         }
  235.         delay_short(1);
  236.         dat >>= 1;                                             //右移一位

  237.         SCLK_dr = 1;
  238.         delay_short(1);
  239.         SCLK_dr = 0;
  240.         delay_short(1);
  241.      }
  242.      DS1302_CE_dr = 0;
  243.      delay_short(1);
  244. }


  245. //读取Ds1302时间的驱动 ,注意,此处读取的是BCD码,
  246. unsigned char Read1302 ( unsigned char addr )
  247. {
  248.     unsigned char i,temp,dat1;
  249.     DS1302_CE_dr=0;      //单片机驱动DS1302属于SPI通讯方式,根据我的经验,不用关闭中断
  250.     delay_short(1);
  251.     SCLK_dr=0;
  252.     delay_short(1);
  253.     DS1302_CE_dr = 1;
  254.     delay_short(1);

  255.                                                                //发送地址
  256.     for ( i=0; i<8; i++ )                                      //循环8次移位
  257.     {
  258.        DIO_dr_sr = 0;

  259.        temp = addr;
  260.        if(temp&0x01)
  261.        {
  262.           DIO_dr_sr =1;
  263.        }
  264.        else
  265.        {
  266.           DIO_dr_sr =0;
  267.        }
  268.        delay_short(1);
  269.        addr >>= 1;                                             //右移一位

  270.        SCLK_dr = 1;
  271.        delay_short(1);
  272.        SCLK_dr = 0;
  273.        delay_short(1);
  274.     }
  275.                                                                
  276. /* 注释二:
  277.   * 51单片机IO口的特点,在读取数据之前必须先输出高电平,
  278.   * 如果是PIC,AVR等单片机,这里应该把IO方向寄存器设置为输入
  279.   */
  280.    DIO_dr_sr =1;   //51单片机IO口的特点,在读取数据之前必须先输出高电平,
  281.    temp=0;
  282.    for ( i=0; i<8; i++ )
  283.    {
  284.       temp>>=1;

  285.       if(DIO_dr_sr==1)
  286.       {
  287.          temp=temp+0x80;
  288.       }
  289.           DIO_dr_sr =1;  //51单片机IO口的特点,在读取数据之前必须先输出高电平

  290.       delay_short(1);
  291.       SCLK_dr = 1;
  292.       delay_short(1);
  293.       SCLK_dr = 0;
  294.       delay_short(1);
  295.     }
  296.     DS1302_CE_dr=0;
  297.     delay_short(1);
  298.     dat1=temp;

  299.     return (dat1);
  300. }

  301. unsigned char bcd_to_number(unsigned char ucBcdTemp)  //BCD转原始数值
  302. {
  303.    unsigned char ucNumberResult=0;
  304.    unsigned char ucBcdTemp10;
  305.    unsigned char ucBcdTemp1;

  306.    ucBcdTemp10=ucBcdTemp;
  307.    ucBcdTemp10=ucBcdTemp10>>4;

  308.    ucBcdTemp1=ucBcdTemp;
  309.    ucBcdTemp1=ucBcdTemp1&0x0f;


  310.    ucNumberResult=ucBcdTemp10*10+ucBcdTemp1;

  311.    return ucNumberResult;


  312. }

  313. unsigned char number_to_bcd(unsigned char ucNumberTemp) //原始数值转BCD
  314. {
  315.    unsigned char ucBcdResult=0;
  316.    unsigned char ucNumberTemp10;
  317.    unsigned char ucNumberTemp1;

  318.    ucNumberTemp10=ucNumberTemp;
  319.    ucNumberTemp10=ucNumberTemp10/10;
  320.    ucNumberTemp10=ucNumberTemp10<<4;
  321.    ucNumberTemp10=ucNumberTemp10&0xf0;

  322.    ucNumberTemp1=ucNumberTemp;
  323.    ucNumberTemp1=ucNumberTemp1%10;

  324.    ucBcdResult=ucNumberTemp10|ucNumberTemp1;

  325.    return ucBcdResult;

  326. }


  327. //日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
  328. unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp) //日调整
  329. {


  330.    unsigned char ucDayResult;
  331.    unsigned int uiYearTemp;
  332.    unsigned int uiYearYu;
  333.    

  334.    ucDayResult=ucDateTemp;

  335.    switch(ucMonthTemp)  //根据不同的月份来修正不同的日最大值
  336.    {
  337.       case 2:  //二月份要计算是否是闰年
  338.            uiYearTemp=2000+ucYearTemp;  
  339.            uiYearYu=uiYearTemp%4;
  340.            if(uiYearYu==0) //闰年
  341.            {
  342.                if(ucDayResult>29)
  343.                {
  344.                   ucDayResult=29;
  345.                }
  346.            }
  347.            else
  348.            {
  349.                if(ucDayResult>28)
  350.                {
  351.                   ucDayResult=28;
  352.                }
  353.            }
  354.            break;
  355.       case 4:
  356.       case 6:
  357.       case 9:
  358.       case 11:
  359.            if(ucDayResult>30)
  360.            {
  361.               ucDayResult=30;
  362.            }
  363.            break;

  364.    }

  365.    return ucDayResult;

  366. }


  367. void ds1302_alarm_service(void) //ds1302出错报警
  368. {
  369.     if(ucDs1302Error==1)  //备用电池的电量用完了报警提示
  370.         {
  371.            if(uiDs1302Cnt>const_ds1302_0_5s)  //大概0.5秒钟蜂鸣器响一次
  372.            {
  373.                    ucDs1302Lock=1;  //原子锁加锁
  374.                uiDs1302Cnt=0; //计时器清零
  375.                    ucDs1302Lock=0;  //原子锁解锁

  376.                ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  377.                uiVoiceCnt=const_voice_short; //蜂鸣器声音触发,滴一声就停。
  378.                ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt
  379.            
  380.            }
  381.    }
  382. }



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

  385.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  386.    {
  387.        case 1:   //显示日期窗口的数据  数据格式 NN-YY-RR 年-月-日
  388.             if(ucWd1Update==1)  //窗口1要全部更新显示
  389.             {
  390.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  391.                ucDigShow6=11;  //显示一杠"-"
  392.                ucDigShow3=11;  //显示一杠"-"

  393.                ucWd1Part1Update=1;  //局部年更新显示
  394.                ucWd1Part2Update=1;  //局部月更新显示
  395.                ucWd1Part3Update=1;  //局部日更新显示
  396.             }

  397.                         if(ucWd1Part1Update==1)//局部年更新显示
  398.                         {
  399.                            ucWd1Part1Update=0;
  400.                ucTemp8=ucYear/10;  //年
  401.                ucTemp7=ucYear%10;

  402.                ucDigShow8=ucTemp8; //数码管显示实际内容
  403.                ucDigShow7=ucTemp7;
  404.                         }


  405.                         if(ucWd1Part2Update==1)//局部月更新显示
  406.                         {
  407.                            ucWd1Part2Update=0;
  408.                ucTemp5=ucMonth/10;  //月
  409.                ucTemp4=ucMonth%10;

  410.                ucDigShow5=ucTemp5; //数码管显示实际内容
  411.                ucDigShow4=ucTemp4;
  412.                         }


  413.                         if(ucWd1Part3Update==1) //局部日更新显示
  414.                         {
  415.                            ucWd1Part3Update=0;
  416.                ucTemp2=ucDate/10;  //日
  417.                ucTemp1=ucDate%10;
  418.                        
  419.                ucDigShow2=ucTemp2; //数码管显示实际内容
  420.                ucDigShow1=ucTemp1;
  421.                         }
  422.               //数码管闪烁
  423.             switch(ucPart)  //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
  424.             {
  425.                 case 0:  //都不闪烁
  426.                      break;
  427.                 case 1:  //年参数闪烁
  428.                      if(uiDpyTimeCnt==const_dpy_time_half)
  429.                      {
  430.                            ucDigShow8=ucTemp8; //数码管显示实际内容
  431.                            ucDigShow7=ucTemp7;
  432.                       }
  433.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  434.                      {
  435.                                            ucDpyTimeLock=1; //原子锁加锁
  436.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  437.                                                    ucDpyTimeLock=0;  //原子锁解锁

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

  440.                      }
  441.                      break;
  442.                 case 2:   //月参数闪烁
  443.                      if(uiDpyTimeCnt==const_dpy_time_half)
  444.                      {
  445.                            ucDigShow5=ucTemp5; //数码管显示实际内容
  446.                            ucDigShow4=ucTemp4;
  447.                       }
  448.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  449.                      {
  450.                                            ucDpyTimeLock=1; //原子锁加锁
  451.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  452.                                                    ucDpyTimeLock=0;  //原子锁解锁

  453.                            ucDigShow5=10;   //数码管显示空,什么都不显示
  454.                            ucDigShow4=10;

  455.                      }
  456.                     break;
  457.                 case 3:   //日参数闪烁
  458.                      if(uiDpyTimeCnt==const_dpy_time_half)
  459.                      {
  460.                            ucDigShow2=ucTemp2; //数码管显示实际内容
  461.                            ucDigShow1=ucTemp1;
  462.                       }
  463.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  464.                      {
  465.                                            ucDpyTimeLock=1; //原子锁加锁
  466.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  467.                                                    ucDpyTimeLock=0;  //原子锁解锁

  468.                            ucDigShow2=10;   //数码管显示空,什么都不显示
  469.                            ucDigShow1=10;

  470.                      }
  471.                     break;      
  472.             }

  473.             break;
  474.        case 2:   //显示时间窗口的数据  数据格式 SS FF MM 时 分 秒
  475.             if(ucWd2Update==1)  //窗口2要全部更新显示
  476.             {
  477.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描

  478.                ucDigShow6=10;  //显示空
  479.                ucDigShow3=10;  //显示空

  480.                ucWd2Part3Update=1;  //局部时更新显示
  481.                ucWd2Part2Update=1;  //局部分更新显示
  482.                ucWd2Part1Update=1;  //局部秒更新显示
  483.             }

  484.                         if(ucWd2Part1Update==1)//局部时更新显示
  485.                         {
  486.                            ucWd2Part1Update=0;
  487.                ucTemp8=ucHour/10;  //时
  488.                ucTemp7=ucHour%10;

  489.                ucDigShow8=ucTemp8; //数码管显示实际内容
  490.                ucDigShow7=ucTemp7;
  491.                         }


  492.                         if(ucWd2Part2Update==1)//局部分更新显示
  493.                         {
  494.                            ucWd2Part2Update=0;
  495.                ucTemp5=ucMinute/10;  //分
  496.                ucTemp4=ucMinute%10;

  497.                ucDigShow5=ucTemp5; //数码管显示实际内容
  498.                ucDigShow4=ucTemp4;
  499.                         }


  500.                         if(ucWd2Part3Update==1) //局部秒更新显示
  501.                         {
  502.                            ucWd2Part3Update=0;
  503.                ucTemp2=ucSecond/10;  //秒
  504.                ucTemp1=ucSecond%10;               
  505.        
  506.                ucDigShow2=ucTemp2; //数码管显示实际内容
  507.                ucDigShow1=ucTemp1;
  508.                         }
  509.               //数码管闪烁
  510.             switch(ucPart)  //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
  511.             {
  512.                 case 0:  //都不闪烁
  513.                      break;
  514.                 case 1:  //时参数闪烁
  515.                      if(uiDpyTimeCnt==const_dpy_time_half)
  516.                      {
  517.                            ucDigShow8=ucTemp8; //数码管显示实际内容
  518.                            ucDigShow7=ucTemp7;
  519.                       }
  520.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  521.                      {
  522.                                            ucDpyTimeLock=1; //原子锁加锁
  523.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  524.                                                    ucDpyTimeLock=0;  //原子锁解锁

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

  527.                      }
  528.                      break;
  529.                 case 2:   //分参数闪烁
  530.                      if(uiDpyTimeCnt==const_dpy_time_half)
  531.                      {
  532.                            ucDigShow5=ucTemp5; //数码管显示实际内容
  533.                            ucDigShow4=ucTemp4;
  534.                       }
  535.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  536.                      {
  537.                                            ucDpyTimeLock=1; //原子锁加锁
  538.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  539.                                                    ucDpyTimeLock=0;  //原子锁解锁

  540.                            ucDigShow5=10;   //数码管显示空,什么都不显示
  541.                            ucDigShow4=10;

  542.                      }
  543.                     break;
  544.                 case 3:   //秒参数闪烁
  545.                      if(uiDpyTimeCnt==const_dpy_time_half)
  546.                      {
  547.                            ucDigShow2=ucTemp2; //数码管显示实际内容
  548.                            ucDigShow1=ucTemp1;
  549.                       }
  550.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  551.                      {
  552.                                            ucDpyTimeLock=1; //原子锁加锁
  553.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  554.                                                    ucDpyTimeLock=0;  //原子锁解锁

  555.                            ucDigShow2=10;   //数码管显示空,什么都不显示
  556.                            ucDigShow1=10;

  557.                      }
  558.                     break;      
  559.             }


  560.             break;
  561.       }
  562.    

  563. }

  564. void key_scan(void)//按键扫描函数 放在定时中断里
  565. {  
  566.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  567.   {
  568.      ucKeyLock1=0; //按键自锁标志清零
  569.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  570.   }
  571.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  572.   {
  573.      uiKeyTimeCnt1++; //累加定时中断次数
  574.      if(uiKeyTimeCnt1>const_key_time1)
  575.      {
  576.         uiKeyTimeCnt1=0;
  577.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  578.         ucKeySec=1;    //触发1号键
  579.      }
  580.   }

  581.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  582.   {
  583.      ucKeyLock2=0; //按键自锁标志清零
  584.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  585.   }
  586.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  587.   {
  588.      uiKeyTimeCnt2++; //累加定时中断次数
  589.      if(uiKeyTimeCnt2>const_key_time2)
  590.      {
  591.         uiKeyTimeCnt2=0;
  592.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  593.         ucKeySec=2;    //触发2号键
  594.      }
  595.   }



  596. /* 注释三:
  597.   * 注意,此处把一个按键的短按和长按的功能都实现了。
  598.   */

  599.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  600.   {
  601.      ucKeyLock3=0; //按键自锁标志清零
  602.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  603.   }
  604.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  605.   {
  606.      uiKeyTimeCnt3++; //累加定时中断次数
  607.      if(uiKeyTimeCnt3>const_key_time3)
  608.      {
  609.         uiKeyTimeCnt3=0;
  610.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  611.         ucKeySec=3;    //短按触发3号键
  612.      }
  613.   }
  614.   else if(uiKeyTimeCnt3<const_key_time17)   //长按3秒
  615.   {
  616.      uiKeyTimeCnt3++; //累加定时中断次数
  617.          if(uiKeyTimeCnt3==const_key_time17)  //等于3秒钟,触发17号长按按键
  618.          {
  619.             ucKeySec=17;    //长按3秒触发17号键  
  620.          }
  621.   }


  622. /* 注释四:
  623.   * 注意,此处是电平按键的滤波抗干扰处理
  624.   */
  625.    if(key_sr4==1)  //对应朱兆祺学习板的S13键  
  626.    {
  627.        uiKey4Cnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  628.        uiKey4Cnt2++; //类似独立按键去抖动的软件抗干扰处理
  629.        if(uiKey4Cnt2>const_key_time4)
  630.        {
  631.            uiKey4Cnt2=0;
  632.            ucKey4Sr=1;  //实时反映按键松手时的电平状态
  633.        }
  634.    }
  635.    else   
  636.    {
  637.        uiKey4Cnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  638.        uiKey4Cnt1++;
  639.        if(uiKey4Cnt1>const_key_time4)
  640.        {
  641.           uiKey4Cnt1=0;
  642.           ucKey4Sr=0;  //实时反映按键按下时的电平状态
  643.        }
  644.    }


  645. }

  646. void key_service(void) //按键服务的应用程序
  647. {

  648.   switch(ucKeySec) //按键服务状态切换
  649.   {
  650.     case 1:// 加按键 对应朱兆祺学习板的S1键
  651.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  652.           {
  653.                case 1:
  654.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  655.                                         {
  656.                                            case 1:  //年
  657.                                                 ucYear++;
  658.                                                         if(ucYear>99)
  659.                                                         {
  660.                                                            ucYear=99;
  661.                                                         }
  662.                                         ucWd1Part1Update=1;  //更新显示
  663.                                                 break;
  664.                                            case 2: //月
  665.                                                 ucMonth++;
  666.                                                         if(ucMonth>12)
  667.                                                         {
  668.                                                            ucMonth=12;
  669.                                                         }
  670.                                         ucWd1Part2Update=1;  //更新显示                                               
  671.                                                 break;
  672.                                            case 3: //日
  673.                                                 ucDate++;
  674.                                                         if(ucDate>31)
  675.                                                         {
  676.                                                            ucDate=31;
  677.                                                         }
  678.                                         ucWd1Part3Update=1;  //更新显示               
  679.                                                 break;                                       

  680.                                         }


  681.                     break;
  682.                case 2:
  683.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  684.                                         {
  685.                                            case 1:  //时
  686.                                                 ucHour++;
  687.                                                         if(ucHour>23)
  688.                                                         {
  689.                                                            ucHour=23;
  690.                                                         }
  691.                                         ucWd2Part1Update=1;  //更新显示                                               
  692.                                                 break;
  693.                                            case 2: //分
  694.                                                 ucMinute++;
  695.                                                         if(ucMinute>59)
  696.                                                         {
  697.                                                            ucMinute=59;
  698.                                                         }
  699.                                         ucWd2Part2Update=1;  //更新显示                                                       
  700.                                                 break;
  701.                                            case 3: //秒
  702.                                                 ucSecond++;
  703.                                                         if(ucSecond>59)
  704.                                                         {
  705.                                                            ucSecond=59;
  706.                                                         }
  707.                                         ucWd2Part3Update=1;  //更新显示       
  708.                                                 break;                                       

  709.                                         }
  710.                     break;
  711.          
  712.           }

  713.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  714.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  715.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  716.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  717.           break;   
  718.    
  719.     case 2:// 减按键 对应朱兆祺学习板的S5键
  720.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  721.           {
  722.                case 1:
  723.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  724.                                         {
  725.                                            case 1:  //年
  726.                                                 ucYear--;
  727.                                                         if(ucYear>99)
  728.                                                         {
  729.                                                            ucYear=0;
  730.                                                         }
  731.                                         ucWd1Part1Update=1;  //更新显示
  732.                                                 break;
  733.                                            case 2: //月
  734.                                                 ucMonth--;
  735.                                                         if(ucMonth<1)
  736.                                                         {
  737.                                                            ucMonth=1;
  738.                                                         }
  739.                                         ucWd1Part2Update=1;  //更新显示                                               
  740.                                                 break;
  741.                                            case 3: //日
  742.                                                 ucDate--;
  743.                                                         if(ucDate<1)
  744.                                                         {
  745.                                                            ucDate=1;
  746.                                                         }
  747.                                         ucWd1Part3Update=1;  //更新显示               
  748.                                                 break;                                       

  749.                                         }


  750.                     break;
  751.                case 2:
  752.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  753.                                         {
  754.                                            case 1:  //时
  755.                                                 ucHour--;
  756.                                                         if(ucHour>23)
  757.                                                         {
  758.                                                            ucHour=0;
  759.                                                         }
  760.                                         ucWd2Part1Update=1;  //更新显示                                               
  761.                                                 break;
  762.                                            case 2: //分
  763.                                                 ucMinute--;
  764.                                                         if(ucMinute>59)
  765.                                                         {
  766.                                                            ucMinute=0;
  767.                                                         }
  768.                                         ucWd2Part2Update=1;  //更新显示                                                       
  769.                                                 break;
  770.                                            case 3: //秒
  771.                                                 ucSecond--;
  772.                                                         if(ucSecond>59)
  773.                                                         {
  774.                                                            ucSecond=0;
  775.                                                         }
  776.                                         ucWd2Part3Update=1;  //更新显示       
  777.                                                 break;                                       

  778.                                         }
  779.                     break;
  780.          
  781.           }

  782.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  783.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  784.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  785.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  786.           break;  

  787.     case 3://短按设置按键 对应朱兆祺学习板的S9键
  788.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  789.           {
  790.                case 1:
  791.                     ucPart++;
  792.                                         if(ucPart>3)
  793.                                         {
  794.                                            ucPart=1;
  795.                                            ucWd=2; //切换到第二个窗口,设置时分秒
  796.                                            ucWd2Update=1;  //窗口2更新显示
  797.                                         }
  798.                                     ucWd1Update=1;  //窗口1更新显示
  799.                     break;
  800.                case 2:
  801.                                 if(ucPart>0) //在窗口2的时候,要第一次激活设置时间,必须是长按3秒才可以,这里短按激活不了第一次
  802.                                         {
  803.                        ucPart++;
  804.                                               if(ucPart>3)  //设置时间结束
  805.                                            {
  806.                                                ucPart=0;



  807. /* 注释五:
  808.   * 每个月份的天数最大值是不一样的,在写入ds1302时钟芯片内部数据前,应该做一次调整。
  809.   * 有的月份最大28天,有的月份最大29天,有的月份最大30天,有的月份最大31天,
  810.   */                                                  
  811.                            ucDate=date_adjust(ucYear,ucMonth,ucDate); //日调整 避免日的数值在某个月份超范围

  812.                                    ucYearBCD=number_to_bcd(ucYear);  //原始数值转BCD
  813.                                    ucMonthBCD=number_to_bcd(ucMonth); //原始数值转BCD
  814.                                      ucDateBCD=number_to_bcd(ucDate);  //原始数值转BCD
  815.                                    ucHourBCD=number_to_bcd(ucHour);  //原始数值转BCD
  816.                                    ucMinuteBCD=number_to_bcd(ucMinute);  //原始数值转BCD
  817.                                    ucSecondBCD=number_to_bcd(ucSecond);  //原始数值转BCD

  818.                                                    Write1302 (WRITE_PROTECT,0X00);          //禁止写保护
  819.                            Write1302 (WRITE_YEAR,ucYearBCD);        //年修改
  820.                            Write1302 (WRITE_MONTH,ucMonthBCD);      //月修改
  821.                            Write1302 (WRITE_DATE,ucDateBCD);        //日修改
  822.                            Write1302 (WRITE_HOUR,ucHourBCD);        //小时修改
  823.                            Write1302 (WRITE_MINUTE,ucMinuteBCD);    //分钟修改
  824.                            Write1302 (WRITE_SECOND,ucSecondBCD);    //秒位修改
  825.                            Write1302 (WRITE_PROTECT,0x80);          //允许写保护
  826.                                              }
  827.                                               ucWd2Update=1;  //窗口2更新显示
  828.                                         }

  829.                     break;
  830.          
  831.           }

  832.          
  833.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  834.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  835.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  836.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  837.           break;         
  838.     case 17://长按3秒设置按键 对应朱兆祺学习板的S9键
  839.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  840.           {
  841.                case 2:
  842.                                 if(ucPart==0) //处于非设置时间的状态下,要第一次激活设置时间,必须是长按3秒才可以
  843.                                         {
  844.                                             ucWd=1;
  845.                        ucPart=1;  //进入到设置日期的状态下
  846.                                             ucWd1Update=1;  //窗口1更新显示
  847.                                         }
  848.                     break;
  849.          
  850.           }
  851.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  852.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  853.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  854.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  855.           break;   
  856.          
  857.   }         
  858.   

  859. /* 注释六:
  860.   * 注意,此处就是第一次出现的电平按键程序,跟以往的下降沿按键不一样。
  861.   * ucKey4Sr是经过软件滤波处理后,直接反应IO口电平状态的变量.当电平发生
  862.   * 变化时,就会切换到不同的显示界面,这里多用了一个ucKey4SrRecord变量
  863.   * 记录上一次的电平状态,是为了避免一直刷新显示。
  864.   */
  865.   if(ucKey4Sr!=ucKey4SrRecord)  //说明S13的切换按键电平状态发生变化
  866.   {
  867.      ucKey4SrRecord=ucKey4Sr;//及时记录当前最新的按键电平状态  避免一直进来触发

  868.          if(ucKey4Sr==1) //松手后切换到显示时间的窗口
  869.          {
  870.             ucWd=2;    //显示时分秒的窗口
  871.                 ucPart=0;  //进入到非设置时间的状态下
  872.             ucWd2Update=1;  //窗口2更新显示
  873.          }
  874.          else  //按下去切换到显示日期的窗口
  875.          {
  876.             ucWd=1;   //显示年月日的窗口
  877.                 ucPart=0;  //进入到非设置时间的状态下
  878.             ucWd1Update=1;  //窗口1更新显示
  879.          }
  880.   
  881.   }
  882. }

  883. void display_drive(void)  
  884. {
  885.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  886.    switch(ucDisplayDriveStep)
  887.    {
  888.       case 1:  //显示第1位
  889.            ucDigShowTemp=dig_table[ucDigShow1];
  890.                    if(ucDigDot1==1)
  891.                    {
  892.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  893.                    }
  894.            dig_hc595_drive(ucDigShowTemp,0xfe);
  895.                break;
  896.       case 2:  //显示第2位
  897.            ucDigShowTemp=dig_table[ucDigShow2];
  898.                    if(ucDigDot2==1)
  899.                    {
  900.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  901.                    }
  902.            dig_hc595_drive(ucDigShowTemp,0xfd);
  903.                break;
  904.       case 3:  //显示第3位
  905.            ucDigShowTemp=dig_table[ucDigShow3];
  906.                    if(ucDigDot3==1)
  907.                    {
  908.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  909.                    }
  910.            dig_hc595_drive(ucDigShowTemp,0xfb);
  911.                break;
  912.       case 4:  //显示第4位
  913.            ucDigShowTemp=dig_table[ucDigShow4];
  914.                    if(ucDigDot4==1)
  915.                    {
  916.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  917.                    }
  918.            dig_hc595_drive(ucDigShowTemp,0xf7);
  919.                break;
  920.       case 5:  //显示第5位
  921.            ucDigShowTemp=dig_table[ucDigShow5];
  922.                    if(ucDigDot5==1)
  923.                    {
  924.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  925.                    }
  926.            dig_hc595_drive(ucDigShowTemp,0xef);
  927.                break;
  928.       case 6:  //显示第6位
  929.            ucDigShowTemp=dig_table[ucDigShow6];
  930.                    if(ucDigDot6==1)
  931.                    {
  932.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  933.                    }
  934.            dig_hc595_drive(ucDigShowTemp,0xdf);
  935.                break;
  936.       case 7:  //显示第7位
  937.            ucDigShowTemp=dig_table[ucDigShow7];
  938.                    if(ucDigDot7==1)
  939.                    {
  940.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  941.            }
  942.            dig_hc595_drive(ucDigShowTemp,0xbf);
  943.                break;
  944.       case 8:  //显示第8位
  945.            ucDigShowTemp=dig_table[ucDigShow8];
  946.                    if(ucDigDot8==1)
  947.                    {
  948.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  949.                    }
  950.            dig_hc595_drive(ucDigShowTemp,0x7f);
  951.                break;
  952.    }
  953.    ucDisplayDriveStep++;
  954.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  955.    {
  956.      ucDisplayDriveStep=1;
  957.    }

  958. }

  959. //数码管的74HC595驱动函数
  960. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  961. {
  962.    unsigned char i;
  963.    unsigned char ucTempData;
  964.    dig_hc595_sh_dr=0;
  965.    dig_hc595_st_dr=0;
  966.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  967.    for(i=0;i<8;i++)
  968.    {
  969.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  970.          else dig_hc595_ds_dr=0;
  971.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  972.          delay_short(1);
  973.          dig_hc595_sh_dr=1;
  974.          delay_short(1);
  975.          ucTempData=ucTempData<<1;
  976.    }
  977.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  978.    for(i=0;i<8;i++)
  979.    {
  980.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  981.          else dig_hc595_ds_dr=0;
  982.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  983.          delay_short(1);
  984.          dig_hc595_sh_dr=1;
  985.          delay_short(1);
  986.          ucTempData=ucTempData<<1;
  987.    }
  988.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  989.    delay_short(1);
  990.    dig_hc595_st_dr=1;
  991.    delay_short(1);
  992.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  993.    dig_hc595_st_dr=0;
  994.    dig_hc595_ds_dr=0;
  995. }

  996. //LED灯的74HC595驱动函数
  997. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  998. {
  999.    unsigned char i;
  1000.    unsigned char ucTempData;
  1001.    hc595_sh_dr=0;
  1002.    hc595_st_dr=0;
  1003.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  1004.    for(i=0;i<8;i++)
  1005.    {
  1006.          if(ucTempData>=0x80)hc595_ds_dr=1;
  1007.          else hc595_ds_dr=0;
  1008.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  1009.          delay_short(1);
  1010.          hc595_sh_dr=1;
  1011.          delay_short(1);
  1012.          ucTempData=ucTempData<<1;
  1013.    }
  1014.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  1015.    for(i=0;i<8;i++)
  1016.    {
  1017.          if(ucTempData>=0x80)hc595_ds_dr=1;
  1018.          else hc595_ds_dr=0;
  1019.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  1020.          delay_short(1);
  1021.          hc595_sh_dr=1;
  1022.          delay_short(1);
  1023.          ucTempData=ucTempData<<1;
  1024.    }
  1025.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  1026.    delay_short(1);
  1027.    hc595_st_dr=1;
  1028.    delay_short(1);
  1029.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  1030.    hc595_st_dr=0;
  1031.    hc595_ds_dr=0;
  1032. }


  1033. void T0_time(void) interrupt 1   //定时中断
  1034. {
  1035.   TF0=0;  //清除中断标志
  1036.   TR0=0; //关中断


  1037.   if(ucVoiceLock==0) //原子锁判断
  1038.   {
  1039.      if(uiVoiceCnt!=0)
  1040.      {

  1041.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  1042.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  1043.      
  1044.      }
  1045.      else
  1046.      {

  1047.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  1048.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  1049.         
  1050.      }
  1051.   }




  1052.   if(ucDs1302Error>0) //EEPROM出错
  1053.   {
  1054.       if(ucDs1302Lock==0)//原子锁判断
  1055.           {
  1056.              uiDs1302Cnt++;  //间歇性蜂鸣器报警的计时器
  1057.           }
  1058.   }


  1059.   if(ucDpyTimeLock==0) //原子锁判断
  1060.   {
  1061.      uiDpyTimeCnt++;  //数码管的闪烁计时器
  1062.   }



  1063.   key_scan(); //按键扫描函数
  1064.   display_drive();  //数码管字模的驱动函数

  1065.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  1066.   TL0=0x0b;
  1067.   TR0=1;  //开中断
  1068. }

  1069. void delay_short(unsigned int uiDelayShort)
  1070. {
  1071.    unsigned int i;  
  1072.    for(i=0;i<uiDelayShort;i++)
  1073.    {
  1074.      ;   //一个分号相当于执行一条空语句
  1075.    }
  1076. }

  1077. void delay_long(unsigned int uiDelayLong)
  1078. {
  1079.    unsigned int i;
  1080.    unsigned int j;
  1081.    for(i=0;i<uiDelayLong;i++)
  1082.    {
  1083.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  1084.           {
  1085.              ; //一个分号相当于执行一条空语句
  1086.           }
  1087.    }
  1088. }

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

  1091.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  1092.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  1093.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  1094.   TMOD=0x01;  //设置定时器0为工作方式1
  1095.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  1096.   TL0=0x0b;

  1097. }
  1098. void initial_peripheral(void) //第二区 初始化外围
  1099. {

  1100.    ucDigDot8=0;   //小数点全部不显示
  1101.    ucDigDot7=0;  
  1102.    ucDigDot6=0;
  1103.    ucDigDot5=0;  
  1104.    ucDigDot4=0;
  1105.    ucDigDot3=0;  
  1106.    ucDigDot2=0;
  1107.    ucDigDot1=0;

  1108.    EA=1;     //开总中断
  1109.    ET0=1;    //允许定时中断
  1110.    TR0=1;    //启动定时中断


  1111. /* 注释七:
  1112.   * 检查ds1302芯片的备用电池电量是否用完了。
  1113.   * 在上电的时候,在一个特定的地址里把数据读出来,如果发现不等于0x5a,
  1114.   * 则是因为备用电池电量用完了,导致保存在ds1302内部RAM数据区的数据被更改,
  1115.   * 与此同时,应该重新写入一次0x5a,为下一次通电判断做准备
  1116.   */
  1117.    ucCheckDs1302=Read1302(READ_CHECK); //判断ds1302内部的数据是否被更改
  1118.    if(ucCheckDs1302!=0x5a)  
  1119.    {
  1120.           Write1302 (WRITE_PROTECT,0X00);          //禁止写保护
  1121.       Write1302 (WRITE_CHECK,0x5a);            //重新写入标志数据,方便下一次更换新电池后的判断
  1122.       Write1302 (WRITE_PROTECT,0x80);          //允许写保护

  1123.           ucDs1302Error=1;  //表示ds1302备用电池没电了,报警提示更换新电池
  1124.    }


  1125. }
复制代码

总结陈词:
下一节开始讲单片机驱动温度传感器芯片的内容,欲知详情,请听下回分解-----利用DS18B20做一个温控器  。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
53#
 楼主| 发表于 2014-5-28 12:31:31 | 显示全部楼层
第四十九节:利用DS18B20做一个温控器  。

开场白:
      DS18B20是一款常用的温度传感器芯片,它只占用单片机一根IO口,使用起来也特别方便。需要特别注意的是,正因为它只用一根IO口跟单片机通讯,因此读取一次温度值的通讯时间比较长,而且时序要求严格,在通讯期间不允许被单片机其它的中断干扰,因此在实际项目中,系统一旦选用了这款传感器芯片,就千万不要选用动态扫描数码管的显示方式。否则在关闭中断读取温度的时候,数码管的显示会有略微的“闪烁”现象。
      DS18B20的测温范围是-55度至125度。在-10度至85度的温度范围内误差是+-0.5度,能满足大部分常用的测温要求。
这一节要教会大家三个知识点:
第一个:大概了解一下DS18B20的驱动程序。
第二个:做温控设备的时候,为了避免继电器在临界温度附近频繁跳动切换,应该设置一个缓冲温差。本程序的缓冲温差是2度。
第三个:继续加深了解按键,显示,传感器它们三者是如何紧密关联起来的程序框架。

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

(1)        硬件平台.
基于坚鸿51单片机学习板。

(2)实现功能:
     本程序只有1个窗口。这个窗口有2个局部显示。
第1个局部是第7,6,5位数码管,显示设定的温度。
第2个局部是第4,3,2,1位数码管,显示实际环境温度。其中第4位数码管显示正负符号位。
S1按键是加键,S5按键是减键。通过它们可以直接设置“设定温度”。
一个LED灯用来模拟工控的继电器。
当实际温度低于或者等于设定温度2度以下时,模拟继电器的LED灯亮。
当实际温度等于或者大于设定温度时,模拟继电器的LED灯灭。
当实际温度处于设定温度和设定温度减去2度的范围内,模拟继电器的LED维持现状,这个2度范围用来做缓冲温差,避免继电器在临界温度附近频繁跳动切换。

(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_ds18b20_sampling_time    180   //累计主循环次数的时间,每次刷新采样时钟芯片的时间


  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  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(void); //显示数码管字模的驱动函数
  13. void display_service(void); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

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

  19. void temper_control_service(void); //温控程序
  20. void ds18b20_sampling(void); //ds18b20采样程序

  21. void ds18b20_reset(); //复位ds18b20的时序
  22. unsigned char ds_read_byte(void ); //读一字节
  23. void ds_write_byte(unsigned char dat); //写一个字节
  24. unsigned int get_temper();  //读取一次没有经过换算的温度数值

  25. sbit dq_dr_sr=P2^6; //ds18b20的数据驱动线

  26. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  27. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键

  28. sbit led_dr=P3^5;  //LED灯,模拟工控中的继电器

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

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



  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 int uiSampingCnt=0;   //采集Ds1302的计时器,每秒钟更新采集一次
  38. unsigned char ucSignFlag=0; //正负符号。0代表正数,1代表负数,表示零下多少度。
  39. unsigned long ulCurrentTemper=33; //实际温度
  40. unsigned long ulSetTemper=26; //设定温度

  41. unsigned int uiTemperTemp=0; //中间变量

  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  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  49. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  50. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  51. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  52. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  53. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  54. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  55. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  56. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  57. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  58. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  59. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  60. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  61. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  62. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  63. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  64. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  65. unsigned char ucDigShowTemp=0; //临时中间变量
  66. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  67. unsigned char ucWd=1;  //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要

  68. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  69. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  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. //根据原理图得出的共阴数码管字模表
  79. code unsigned char dig_table[]=
  80. {
  81. 0x3f,  //0       序号0
  82. 0x06,  //1       序号1
  83. 0x5b,  //2       序号2
  84. 0x4f,  //3       序号3
  85. 0x66,  //4       序号4
  86. 0x6d,  //5       序号5
  87. 0x7d,  //6       序号6
  88. 0x07,  //7       序号7
  89. 0x7f,  //8       序号8
  90. 0x6f,  //9       序号9
  91. 0x00,  //无      序号10
  92. 0x40,  //-       序号11
  93. 0x73,  //P       序号12
  94. };
  95. void main()
  96.   {
  97.    initial_myself();  
  98.    delay_long(100);   
  99.    initial_peripheral();
  100.    while(1)  
  101.    {
  102.       key_service(); //按键服务的应用程序
  103.       ds18b20_sampling(); //ds18b20采样程序
  104.       temper_control_service(); //温控程序
  105.       display_service(); //显示的窗口菜单服务程序
  106.    }
  107. }

  108. /* 注释一:
  109.   * 做温控设备的时候,为了避免继电器在临界温度附近频繁跳动切换,应该设置一个
  110.   * 缓冲温差。本程序的缓冲温差是2度。
  111.   */
  112. void temper_control_service(void) //温控程序
  113. {
  114.    if(ucSignFlag==0) //是正数的前提下
  115.    {
  116.       if(ulCurrentTemper>=ulSetTemper)  //当实际温度大于等于设定温度时
  117.       {
  118.         led_dr=0; //模拟继电器的LED灯熄灭
  119.       }
  120.       else if(ulCurrentTemper<=(ulSetTemper-2))  //当实际温度小于等于设定温度2读以下时,这里的2是缓冲温差2度
  121.       {
  122.         led_dr=1; //模拟继电器的LED灯点亮
  123.       }
  124.    }
  125.    else  //是负数,说明是零下多少度的情况下
  126.    {
  127.       led_dr=1; //模拟继电器的LED灯点亮
  128.    }

  129. }


  130. void ds18b20_sampling(void) //ds18b20采样程序
  131. {

  132.       ++uiSampingCnt;  //累计主循环次数的时间
  133.       if(uiSampingCnt>const_ds18b20_sampling_time)  //每隔一段时间就更新采集一次Ds18b20数据
  134.           {
  135.           uiSampingCnt=0;

  136.           ET0=0;  //禁止定时中断
  137.           uiTemperTemp=get_temper();  //读取一次没有经过换算的温度数值
  138.           ET0=1; //开启定时中断

  139.           if((uiTemperTemp&0xf800)==0xf800) //是负号
  140.           {
  141.                          ucSignFlag=1;

  142.              uiTemperTemp=~uiTemperTemp;  //求补码
  143.              uiTemperTemp=uiTemperTemp+1;

  144.           }
  145.           else //是正号
  146.           {
  147.                          ucSignFlag=0;

  148.           }



  149.           ulCurrentTemper=0; //把int数据类型赋给long类型之前要先清零
  150.           ulCurrentTemper=uiTemperTemp;

  151.           ulCurrentTemper=ulCurrentTemper*10; //为了先保留一位小数点,所以放大10倍,
  152.           ulCurrentTemper=ulCurrentTemper>>4;  //往右边移动4位,相当于乘以0.0625. 此时保留了1位小数点,

  153.           ulCurrentTemper=ulCurrentTemper+5;  //四舍五入
  154.           ulCurrentTemper=ulCurrentTemper/10; //四舍五入后,去掉小数点

  155.           ucWd1Part2Update=1; //局部2更新显示实时温度
  156.           }
  157. }


  158. //ds18b20驱动程序
  159. unsigned int get_temper()  //读取一次没有经过换算的温度数值
  160. {
  161. unsigned char temper_H;
  162. unsigned char temper_L;
  163. unsigned int ds18b20_data=0;

  164. ds18b20_reset(); //复位ds18b20的时序
  165. ds_write_byte(0xCC);
  166. ds_write_byte(0x44);

  167. ds18b20_reset(); //复位ds18b20的时序
  168. ds_write_byte(0xCC);
  169. ds_write_byte(0xBE);
  170. temper_L=ds_read_byte();
  171. temper_H=ds_read_byte();

  172. ds18b20_data=temper_H;     //把两个字节合并成一个int数据类型
  173. ds18b20_data=ds18b20_data<<8;
  174. ds18b20_data=ds18b20_data|temper_L;
  175. return ds18b20_data;
  176. }



  177. void ds18b20_reset() //复位ds18b20的时序
  178. {
  179.   unsigned char x;
  180.   dq_dr_sr=1;
  181.   delay_short(8);
  182.   dq_dr_sr=0;
  183.   delay_short(80);
  184.   dq_dr_sr=1;
  185.   delay_short(14);
  186.   x=dq_dr_sr;
  187.   delay_short(20);

  188. }

  189. void ds_write_byte(unsigned char date) //写一个字节
  190. {
  191. unsigned char  i;

  192. for(i=0;i<8;i++)
  193. {
  194.   dq_dr_sr=0;
  195.   dq_dr_sr=date&0x01;
  196.   delay_short(5);
  197.   dq_dr_sr=1;
  198.   date=date>>1;
  199. }
  200. }

  201. unsigned char ds_read_byte(void ) //读一字节
  202. {
  203. unsigned char i;
  204. unsigned char date=0;
  205. for(i=0;i<8;i++)
  206. {
  207.   dq_dr_sr=0;
  208.   date=date>>1;
  209.   dq_dr_sr=1;
  210.   if(dq_dr_sr)
  211.   {
  212.      date=date|0x80;
  213.   }
  214.   delay_short(5);
  215. }
  216. return (date);
  217. }



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

  220.    switch(ucWd)  //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  221.    {
  222.        case 1:  

  223.                         if(ucWd1Part1Update==1)//局部设定温度更新显示
  224.                         {
  225.                            ucWd1Part1Update=0;

  226.                ucTemp8=10; //显示空

  227.                            if(ulSetTemper>=100)
  228.                            {
  229.                   ucTemp7=ulSetTemper%1000/100; //显示设定温度的百位
  230.                            }
  231.                            else
  232.                            {
  233.                               ucTemp7=10; //显示空
  234.                            }

  235.                            if(ulSetTemper>=10)
  236.                            {
  237.                   ucTemp6=ulSetTemper%100/10; //显示设定温度的十位
  238.                            }
  239.                            else
  240.                            {
  241.                               ucTemp6=10; //显示空
  242.                            }

  243.                ucTemp5=ulSetTemper%10; //显示设定温度的个位


  244.                ucDigShow8=ucTemp8; //数码管显示实际内容
  245.                ucDigShow7=ucTemp7;
  246.                ucDigShow6=ucTemp6;
  247.                ucDigShow5=ucTemp5;
  248.                         }


  249.                         if(ucWd1Part2Update==1)//局部实际温度更新显示
  250.                         {
  251.                            if(ucSignFlag==0)  //正数
  252.                            {
  253.                   ucTemp4=10; //显示空
  254.                            }
  255.                            else  //负数,说明是零下多少度的情况下
  256.                            {
  257.                   ucTemp4=11; //显示负号-
  258.                            }

  259.                            if(ulCurrentTemper>=100)
  260.                            {
  261.                   ucTemp3=ulCurrentTemper%100/100; //显示实际温度的百位
  262.                            }
  263.                            else
  264.                            {
  265.                           ucTemp3=10; //显示空
  266.                            }


  267.                            if(ulCurrentTemper>=10)
  268.                            {
  269.                   ucTemp2=ulCurrentTemper%100/10;  //显示实际温度的十位
  270.                            }
  271.                            else
  272.                            {
  273.                   ucTemp2=10;  //显示空
  274.                            }

  275.                ucTemp1=ulCurrentTemper%10; //显示实际温度的个数位

  276.                ucDigShow4=ucTemp4; //数码管显示实际内容
  277.                ucDigShow3=ucTemp3;
  278.                ucDigShow2=ucTemp2;
  279.                ucDigShow1=ucTemp1;
  280.                         }

  281.             break;

  282.       }
  283.    

  284. }

  285. void key_scan(void)//按键扫描函数 放在定时中断里
  286. {  
  287.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  288.   {
  289.      ucKeyLock1=0; //按键自锁标志清零
  290.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  291.   }
  292.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  293.   {
  294.      uiKeyTimeCnt1++; //累加定时中断次数
  295.      if(uiKeyTimeCnt1>const_key_time1)
  296.      {
  297.         uiKeyTimeCnt1=0;
  298.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  299.         ucKeySec=1;    //触发1号键
  300.      }
  301.   }

  302.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  303.   {
  304.      ucKeyLock2=0; //按键自锁标志清零
  305.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  306.   }
  307.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  308.   {
  309.      uiKeyTimeCnt2++; //累加定时中断次数
  310.      if(uiKeyTimeCnt2>const_key_time2)
  311.      {
  312.         uiKeyTimeCnt2=0;
  313.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  314.         ucKeySec=2;    //触发2号键
  315.      }
  316.   }





  317. }

  318. void key_service(void) //按键服务的应用程序
  319. {

  320.   switch(ucKeySec) //按键服务状态切换
  321.   {
  322.     case 1:// 加按键 对应朱兆祺学习板的S1键
  323.           switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  324.           {
  325.               case 1: //在窗口1下设置设定温度
  326.                    ulSetTemper++;
  327.                                    if(ulSetTemper>125)
  328.                                    {
  329.                                      ulSetTemper=125;
  330.                                    }

  331.                                ucWd1Part1Update=1; //更新显示设定温度
  332.                    break;
  333.           }

  334.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  335.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  336.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  337.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  338.           break;   
  339.    
  340.     case 2:// 减按键 对应朱兆祺学习板的S5键
  341.           switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  342.           {
  343.                case 1: //在窗口1下设置设定温度
  344.                     if(ulSetTemper>2)  //由于缓冲温差是2度,所以我人为规定最小允许设定的温度不能低于2度
  345.                                         {
  346.                                            ulSetTemper--;
  347.                                         }

  348.                           ucWd1Part1Update=1; //更新显示设定温度
  349.                     break;
  350.          
  351.           }

  352.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  353.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  354.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  355.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  356.           break;  


  357.          
  358.   }         
  359.   

  360. }

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

  436. }

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

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


  511. void T0_time(void) interrupt 1   //定时中断
  512. {
  513.   TF0=0;  //清除中断标志
  514.   TR0=0; //关中断


  515.   if(ucVoiceLock==0) //原子锁判断
  516.   {
  517.      if(uiVoiceCnt!=0)
  518.      {

  519.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  520.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  521.      
  522.      }
  523.      else
  524.      {

  525.         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  526.         beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  527.         
  528.      }
  529.   }


  530.   key_scan(); //按键扫描函数
  531.   display_drive();  //数码管字模的驱动函数

  532.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  533.   TL0=0x0b;
  534.   TR0=1;  //开中断
  535. }

  536. void delay_short(unsigned int uiDelayShort)
  537. {
  538.    unsigned int i;  
  539.    for(i=0;i<uiDelayShort;i++)
  540.    {
  541.      ;   //一个分号相当于执行一条空语句
  542.    }
  543. }

  544. void delay_long(unsigned int uiDelayLong)
  545. {
  546.    unsigned int i;
  547.    unsigned int j;
  548.    for(i=0;i<uiDelayLong;i++)
  549.    {
  550.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  551.           {
  552.              ; //一个分号相当于执行一条空语句
  553.           }
  554.    }
  555. }


  556. void initial_myself(void)  //第一区 初始化单片机
  557. {
  558.   led_dr=0;//此处的LED灯模拟工控中的继电器
  559.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  560.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  561.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  562.   TMOD=0x01;  //设置定时器0为工作方式1
  563.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  564.   TL0=0x0b;

  565. }
  566. void initial_peripheral(void) //第二区 初始化外围
  567. {

  568.    ucDigDot8=0;   //小数点全部不显示
  569.    ucDigDot7=0;  
  570.    ucDigDot6=0;
  571.    ucDigDot5=0;  
  572.    ucDigDot4=0;
  573.    ucDigDot3=0;  
  574.    ucDigDot2=0;
  575.    ucDigDot1=0;

  576.    EA=1;     //开总中断
  577.    ET0=1;    //允许定时中断
  578.    TR0=1;    //启动定时中断

  579. }
复制代码

总结陈词:
下一节开始讲单片机采集模拟信号的内容,欲知详情,请听下回分解-----利用ADC0832采集电压的模拟信号。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
54#
 楼主| 发表于 2014-6-5 15:56:31 | 显示全部楼层
第五十节:利用ADC0832采集电压信号,用平均法和区间法进行软件滤波处理。

开场白:
ADC0832是一款常用的8位AD采样芯片,通过它可以把外部的模拟电压信号转换成数字信号,然后给单片机进行换算,显示等处理。
这一节要教会大家五个知识点:
第一个:分辨率的算法。有些书上说8位AD最高分辩可达到256级(0xff+1),当输入电压是0---5V时,电压精度为19.53mV(5000mV除以256),我认为这种说法是错误的。8位AD的最高分辨率应该是255级(0xff),当输入电压是0---5V时,电压精度为19.61mV(5000mV除以255)。
第二个:用求平均值的滤波法,可以使AD采样的数据更加圆滑,去除小毛刺。
第三个:用区间滤波法,在一些干扰很大的场合,可以避免末尾小数点的数据频繁跳动。
第四个:如何使系统可以采集到更高的电压。由于ADC0832直接采集的电压最大不能超过5V,如果要采集的最大电压是25V该怎么办?我们只要在外部多增加1个10K的电阻和1个40K的电阻组成分压电路,把25V分压成5V,然后再让ADC0832采样,这时采样到的数据只要乘以5的系数,就可以得到超过5V的实际电压。选择分压电阻时,阻值尽量不要太小,一般要10K级别以上,阻值大一点,对被采样的系统干扰影响就越小。
第五个:如何有效保护AD通道口。我在一些电压不稳定的工控场合,一般是在AD通道口对负极反接一个瞬变二极管SA5.0A。当电压超过5V时,瞬变二极管会导通吸收掉多余的能量,把电压降下来,避免AD通道口烧坏。

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

(1)        硬件平台.
基于坚鸿51单片机学习板。

(2)实现功能:
     本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过平均法,区间法滤波的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象

系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。

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

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

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


  7. //驱动数码管的74HC595
  8. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  9. void display_drive(void); //显示数码管字模的驱动函数
  10. void display_service(void); //显示的窗口菜单服务程序
  11. //驱动LED的74HC595
  12. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

  14. void ad_sampling_service(void); //AD采样与处理的服务程序


  15. sbit led_dr=P3^5;  //LED灯
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口



  17. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  18. sbit dig_hc595_st_dr=P2^1;  
  19. sbit dig_hc595_ds_dr=P2^2;  
  20. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  21. sbit hc595_st_dr=P2^4;  
  22. sbit hc595_ds_dr=P2^5;  


  23. sbit adc0832_clk_dr     = P1^2;  // 定义adc0832的引脚
  24. sbit adc0832_cs_dr      = P1^0;
  25. sbit adc0832_data_sr_dr = P1^1;


  26. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  27. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  28. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  29. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  30. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  31. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  32. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  33. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  34. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  35. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  36. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  37. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  38. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  39. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  40. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  41. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  42. unsigned char ucDigShowTemp=0; //临时中间变量
  43. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  44. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  45. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  46. unsigned char ucTemp1=0;  //中间过渡变量
  47. unsigned char ucTemp2=0;  //中间过渡变量
  48. unsigned char ucTemp3=0;  //中间过渡变量
  49. unsigned char ucTemp4=0;  //中间过渡变量
  50. unsigned char ucTemp5=0;  //中间过渡变量
  51. unsigned char ucTemp6=0;  //中间过渡变量
  52. unsigned char ucTemp7=0;  //中间过渡变量
  53. unsigned char ucTemp8=0;  //中间过渡变量

  54. unsigned char ucAD=0;   //AD值
  55. unsigned char ucCheckAD=0; //用来做校验对比的AD值


  56. unsigned long ulTemp=0;  //参与换算的中间变量
  57. unsigned long ulTempFilterV=0; //参与换算的中间变量
  58. unsigned long ulBackupFilterV=5000;  //备份最新采样数据的中间变量
  59. unsigned char ucSamplingCnt=0; //统计采样的次数  本程序采样8次后求平均值

  60. unsigned long ulV=0; //未经滤波处理的实时电压值
  61. unsigned long ulFilterV=0; //经过滤波后的实时电压值


  62. //根据原理图得出的共阴数码管字模表
  63. code unsigned char dig_table[]=
  64. {
  65. 0x3f,  //0       序号0
  66. 0x06,  //1       序号1
  67. 0x5b,  //2       序号2
  68. 0x4f,  //3       序号3
  69. 0x66,  //4       序号4
  70. 0x6d,  //5       序号5
  71. 0x7d,  //6       序号6
  72. 0x07,  //7       序号7
  73. 0x7f,  //8       序号8
  74. 0x6f,  //9       序号9
  75. 0x00,  //无      序号10
  76. 0x40,  //-       序号11
  77. 0x73,  //P       序号12
  78. };
  79. void main()
  80.   {
  81.    initial_myself();  
  82.    delay_long(100);   
  83.    initial_peripheral();
  84.    while(1)  
  85.    {
  86.       ad_sampling_service(); //AD采样与处理的服务程序
  87.       display_service(); //显示的窗口菜单服务程序
  88.    }
  89. }

  90. void ad_sampling_service(void) //AD采样与处理的服务程序
  91. {
  92.     unsigned char i;

  93.     ucAD=0;   //AD值
  94.     ucCheckAD=0; //用来做校验对比的AD值


  95.     /* 片选信号置为低电平 */
  96.     adc0832_cs_dr = 0;

  97.         /* 第一个脉冲,开始位 */
  98.         adc0832_data_sr_dr = 1;
  99.         adc0832_clk_dr  = 0;
  100.     delay_short(1);
  101.         adc0832_clk_dr  = 1;

  102.         /* 第二个脉冲,选择通道 */
  103.         adc0832_data_sr_dr = 1;
  104.         adc0832_clk_dr  = 0;
  105.         adc0832_clk_dr  = 1;

  106.         /* 第三个脉冲,选择通道 */
  107.         adc0832_data_sr_dr = 0;
  108.         adc0832_clk_dr  = 0;
  109.         adc0832_clk_dr  = 1;

  110.     /* 数据线输出高电平 */
  111.         adc0832_data_sr_dr = 1;
  112.     delay_short(2);

  113.         /* 第一个下降沿 */
  114.         adc0832_clk_dr  = 1;
  115.         adc0832_clk_dr  = 0;
  116.     delay_short(1);


  117.         /* AD值开始送出 */
  118.         for (i = 0; i < 8; i++)
  119.         {
  120.         ucAD <<= 1;
  121.                 adc0832_clk_dr = 1;
  122.                 adc0832_clk_dr = 0;
  123.                 if (adc0832_data_sr_dr==1)
  124.                 {
  125.             ucAD |= 0x01;
  126.                 }
  127.         }

  128.         /* 用于校验的AD值开始送出 */
  129.         for (i = 0; i < 8; i++)
  130.         {
  131.         ucCheckAD >>= 1;
  132.                 if (adc0832_data_sr_dr==1)
  133.                 {
  134.            ucCheckAD |= 0x80;
  135.                 }
  136.                 adc0832_clk_dr = 1;
  137.                 adc0832_clk_dr = 0;
  138.         }
  139.        
  140.         /* 片选信号置为高电平 */
  141.         adc0832_cs_dr = 1;


  142.         if(ucCheckAD==ucAD)  //检验相等
  143.         {
  144.        
  145.             ulTemp=0;  //把char类型数据赋值给long类型数据之前,必须先清零
  146.         ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型

  147. /* 注释一:
  148. * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
  149. * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
  150. * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
  151. */
  152.         ulTemp=5000*ulTemp/255;  //进行电压换算

  153.         ulV=ulTemp; //得到未经滤波处理的实时电压值
  154.         ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压


  155.                 ulTempFilterV=ulTempFilterV+ulTemp;  //累加8次后求平均值
  156.         ucSamplingCnt++;  //统计已经采样累计的次数
  157.                 if(ucSamplingCnt>=8)
  158.                 {

  159. /* 注释二:
  160. * 求平均值滤波法,为了得到的数据更加圆滑,去除小毛刺。
  161. * 向右边移动3位相当于除以8。
  162. */

  163.                      ulTempFilterV=ulTempFilterV>>3; //求平均值滤波法


  164. /* 注释三:
  165. * 以下区间滤波法,为了避免末尾小数点的数据频繁跳动。
  166. * 这里的20用于区间滤波法的正负偏差,这里的20代表0.020V。
  167. * 意思是只要最近采集到的数据在正负0.020V偏差范围内,就不更新。
  168. */
  169.                     if(ulBackupFilterV>=20)  //最近备份的上一次数据大于等于0.02V的情况下
  170.                     {
  171.                        if(ulTempFilterV<(ulBackupFilterV-20)||ulTempFilterV>(ulBackupFilterV+20)) //在正负0.020V偏差范围外,更新
  172.                        {
  173.                            ulBackupFilterV=ulTempFilterV;  //备份最新采样的数据,方便下一次对比判断

  174.                    ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  175.                    ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
  176.                               }
  177.                     }
  178.                     else   //最近备份的上一次数据小于0.02V的情况下
  179.                     {
  180.                        if(ulTempFilterV>(ulBackupFilterV+20))  //在正0.020V偏差范围外,更新
  181.                        {
  182.                            ulBackupFilterV=ulTempFilterV;  //备份最新采样的数据,方便下一次对比判断

  183.                    ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  184.                    ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
  185.                            }
  186.                   
  187.                     }


  188.                     ucSamplingCnt=0;  //清零,为下一轮采样滤波作准备。
  189.                     ulTempFilterV=0;
  190.                 }
  191.        
  192.         }

  193. }

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

  196.                         if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
  197.                         {
  198.                            ucWd1Part1Update=0;

  199.                ucTemp8=ulV%10000/1000;  //显示电压值个位
  200.                ucTemp7=ulV%1000/100;    //显示电压值小数点后第1位
  201.                ucTemp6=ulV%100/10;      //显示电压值小数点后第2位
  202.                ucTemp5=ulV%10;          //显示电压值小数点后第3位


  203.                ucDigShow8=ucTemp8; //数码管显示实际内容
  204.                ucDigShow7=ucTemp7;
  205.                ucDigShow6=ucTemp6;
  206.                ucDigShow5=ucTemp5;
  207.                         }


  208.                         if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
  209.                         {
  210.                              ucWd1Part2Update=0;

  211.                ucTemp4=ulFilterV%10000/1000;  //显示电压值个位
  212.                ucTemp3=ulFilterV%1000/100;    //显示电压值小数点后第1位
  213.                ucTemp2=ulFilterV%100/10;      //显示电压值小数点后第2位
  214.                ucTemp1=ulFilterV%10;          //显示电压值小数点后第3位


  215.                ucDigShow4=ucTemp4; //数码管显示实际内容
  216.                ucDigShow3=ucTemp3;
  217.                ucDigShow2=ucTemp2;
  218.                ucDigShow1=ucTemp1;
  219.                         }


  220. }



  221. void display_drive(void)  
  222. {
  223.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  224.    switch(ucDisplayDriveStep)
  225.    {
  226.       case 1:  //显示第1位
  227.            ucDigShowTemp=dig_table[ucDigShow1];
  228.                    if(ucDigDot1==1)
  229.                    {
  230.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  231.                    }
  232.            dig_hc595_drive(ucDigShowTemp,0xfe);
  233.                break;
  234.       case 2:  //显示第2位
  235.            ucDigShowTemp=dig_table[ucDigShow2];
  236.                    if(ucDigDot2==1)
  237.                    {
  238.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  239.                    }
  240.            dig_hc595_drive(ucDigShowTemp,0xfd);
  241.                break;
  242.       case 3:  //显示第3位
  243.            ucDigShowTemp=dig_table[ucDigShow3];
  244.                    if(ucDigDot3==1)
  245.                    {
  246.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  247.                    }
  248.            dig_hc595_drive(ucDigShowTemp,0xfb);
  249.                break;
  250.       case 4:  //显示第4位
  251.            ucDigShowTemp=dig_table[ucDigShow4];
  252.                    if(ucDigDot4==1)
  253.                    {
  254.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  255.                    }
  256.            dig_hc595_drive(ucDigShowTemp,0xf7);
  257.                break;
  258.       case 5:  //显示第5位
  259.            ucDigShowTemp=dig_table[ucDigShow5];
  260.                    if(ucDigDot5==1)
  261.                    {
  262.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  263.                    }
  264.            dig_hc595_drive(ucDigShowTemp,0xef);
  265.                break;
  266.       case 6:  //显示第6位
  267.            ucDigShowTemp=dig_table[ucDigShow6];
  268.                    if(ucDigDot6==1)
  269.                    {
  270.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  271.                    }
  272.            dig_hc595_drive(ucDigShowTemp,0xdf);
  273.                break;
  274.       case 7:  //显示第7位
  275.            ucDigShowTemp=dig_table[ucDigShow7];
  276.                    if(ucDigDot7==1)
  277.                    {
  278.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  279.            }
  280.            dig_hc595_drive(ucDigShowTemp,0xbf);
  281.                break;
  282.       case 8:  //显示第8位
  283.            ucDigShowTemp=dig_table[ucDigShow8];
  284.                    if(ucDigDot8==1)
  285.                    {
  286.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  287.                    }
  288.            dig_hc595_drive(ucDigShowTemp,0x7f);
  289.                break;
  290.    }
  291.    ucDisplayDriveStep++;
  292.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  293.    {
  294.      ucDisplayDriveStep=1;
  295.    }

  296. }

  297. //数码管的74HC595驱动函数
  298. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  299. {
  300.    unsigned char i;
  301.    unsigned char ucTempData;
  302.    dig_hc595_sh_dr=0;
  303.    dig_hc595_st_dr=0;
  304.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  305.    for(i=0;i<8;i++)
  306.    {
  307.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  308.          else dig_hc595_ds_dr=0;
  309.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  310.          delay_short(1);
  311.          dig_hc595_sh_dr=1;
  312.          delay_short(1);
  313.          ucTempData=ucTempData<<1;
  314.    }
  315.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  316.    for(i=0;i<8;i++)
  317.    {
  318.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  319.          else dig_hc595_ds_dr=0;
  320.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  321.          delay_short(1);
  322.          dig_hc595_sh_dr=1;
  323.          delay_short(1);
  324.          ucTempData=ucTempData<<1;
  325.    }
  326.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  327.    delay_short(1);
  328.    dig_hc595_st_dr=1;
  329.    delay_short(1);
  330.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  331.    dig_hc595_st_dr=0;
  332.    dig_hc595_ds_dr=0;
  333. }

  334. //LED灯的74HC595驱动函数
  335. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  336. {
  337.    unsigned char i;
  338.    unsigned char ucTempData;
  339.    hc595_sh_dr=0;
  340.    hc595_st_dr=0;
  341.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  342.    for(i=0;i<8;i++)
  343.    {
  344.          if(ucTempData>=0x80)hc595_ds_dr=1;
  345.          else hc595_ds_dr=0;
  346.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  347.          delay_short(1);
  348.          hc595_sh_dr=1;
  349.          delay_short(1);
  350.          ucTempData=ucTempData<<1;
  351.    }
  352.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  353.    for(i=0;i<8;i++)
  354.    {
  355.          if(ucTempData>=0x80)hc595_ds_dr=1;
  356.          else hc595_ds_dr=0;
  357.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  358.          delay_short(1);
  359.          hc595_sh_dr=1;
  360.          delay_short(1);
  361.          ucTempData=ucTempData<<1;
  362.    }
  363.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  364.    delay_short(1);
  365.    hc595_st_dr=1;
  366.    delay_short(1);
  367.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  368.    hc595_st_dr=0;
  369.    hc595_ds_dr=0;
  370. }


  371. void T0_time(void) interrupt 1   //定时中断
  372. {
  373.   TF0=0;  //清除中断标志
  374.   TR0=0; //关中断


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

  376.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  377.   TL0=0x0b;
  378.   TR0=1;  //开中断
  379. }

  380. void delay_short(unsigned int uiDelayShort)
  381. {
  382.    unsigned int i;  
  383.    for(i=0;i<uiDelayShort;i++)
  384.    {
  385.      ;   //一个分号相当于执行一条空语句
  386.    }
  387. }

  388. void delay_long(unsigned int uiDelayLong)
  389. {
  390.    unsigned int i;
  391.    unsigned int j;
  392.    for(i=0;i<uiDelayLong;i++)
  393.    {
  394.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  395.           {
  396.              ; //一个分号相当于执行一条空语句
  397.           }
  398.    }
  399. }


  400. void initial_myself(void)  //第一区 初始化单片机
  401. {
  402.   led_dr=0;//LED灯默认关闭
  403.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  404.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  405.   TMOD=0x01;  //设置定时器0为工作方式1
  406.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  407.   TL0=0x0b;

  408. }
  409. void initial_peripheral(void) //第二区 初始化外围
  410. {

  411.    ucDigDot8=1;   //显示未经过滤波电压的小数点
  412.    ucDigDot7=0;  
  413.    ucDigDot6=0;
  414.    ucDigDot5=0;  
  415.    ucDigDot4=1;  //显示经过滤波后电压的小数点
  416.    ucDigDot3=0;  
  417.    ucDigDot2=0;
  418.    ucDigDot1=0;

  419.    EA=1;     //开总中断
  420.    ET0=1;    //允许定时中断
  421.    TR0=1;    //启动定时中断

  422. }
复制代码

总结陈词:
这节用区间滤波法虽然可以解决小数点后面的数据出现频繁跳动的现象,但是也存在一个小问题,就是精度受到了影响,比如我们设置的正负偏差是0.02V,那就意味着系统存在0.02V的误差。有没有更好的办法解决这个问题?如果系统的末尾数据一直不断处于频繁跳动中,那么只能牺牲一点精度,我认为用区间法已经是最好的解决办法了,但是经过本次实验,我观察到未经过滤波处理的数据只是偶尔跳动,并非频繁跳动,所以下一节我会给大家介绍一种不用牺牲精度,又可以很好滤波的方法。欲知详情,请听下回分解-----利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
55#
 楼主| 发表于 2014-6-15 12:21:25 | 显示全部楼层
第五十一节:利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。

开场白:
连续判断N次一致性的滤波法,是为了避免末尾小数点的数据偶尔跳动。这种滤波方法的原理跟我在按键扫描中去抖动的原理是一模一样的,被我频繁地应用在大量的工控项目中。
这一节要教会大家一个知识点:连续判断N次一致性的滤波法。
具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。

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

(1)硬件平台.
基于坚鸿51单片机学习板。

(2)实现功能:
本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过特定算法滤波后的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象。而且显示的电压值跟未经过滤波的电压值几乎是完全一致,不会出现上一节用区间滤波法所留下的0.02V误差问题。
系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。

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

  2. #define const_N   8  //连续判断N次一致性滤波方法中,N的取值
  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间

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


  8. //驱动数码管的74HC595
  9. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  10. void display_drive(void); //显示数码管字模的驱动函数
  11. void display_service(void); //显示的窗口菜单服务程序
  12. //驱动LED的74HC595
  13. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

  15. void ad_sampling_service(void); //AD采样与处理的服务程序


  16. sbit led_dr=P3^5;  //LED灯
  17. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口



  18. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  19. sbit dig_hc595_st_dr=P2^1;  
  20. sbit dig_hc595_ds_dr=P2^2;  
  21. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  22. sbit hc595_st_dr=P2^4;  
  23. sbit hc595_ds_dr=P2^5;  


  24. sbit adc0832_clk_dr     = P1^2;  // 定义adc0832的引脚
  25. sbit adc0832_cs_dr      = P1^0;
  26. sbit adc0832_data_sr_dr = P1^1;


  27. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  28. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  29. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  30. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  31. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  32. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  33. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  34. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  35. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  36. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  37. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  38. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  39. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  40. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  41. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  42. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  43. unsigned char ucDigShowTemp=0; //临时中间变量
  44. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  45. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  46. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  47. unsigned char ucTemp1=0;  //中间过渡变量
  48. unsigned char ucTemp2=0;  //中间过渡变量
  49. unsigned char ucTemp3=0;  //中间过渡变量
  50. unsigned char ucTemp4=0;  //中间过渡变量
  51. unsigned char ucTemp5=0;  //中间过渡变量
  52. unsigned char ucTemp6=0;  //中间过渡变量
  53. unsigned char ucTemp7=0;  //中间过渡变量
  54. unsigned char ucTemp8=0;  //中间过渡变量

  55. unsigned char ucAD=0;   //AD值
  56. unsigned char ucCheckAD=0; //用来做校验对比的AD值


  57. unsigned long ulTemp=0;  //参与换算的中间变量
  58. unsigned long ulTempFilterV=0; //参与换算的中间变量
  59. unsigned long ulBackupFilterV=5000;  //备份最新采样数据的中间变量
  60. unsigned char ucSamplingCnt=0; //记录连续N次采样的计数器

  61. unsigned long ulV=0; //未经滤波处理的实时电压值
  62. unsigned long ulFilterV=0; //经过滤波后的实时电压值


  63. //根据原理图得出的共阴数码管字模表
  64. code unsigned char dig_table[]=
  65. {
  66. 0x3f,  //0       序号0
  67. 0x06,  //1       序号1
  68. 0x5b,  //2       序号2
  69. 0x4f,  //3       序号3
  70. 0x66,  //4       序号4
  71. 0x6d,  //5       序号5
  72. 0x7d,  //6       序号6
  73. 0x07,  //7       序号7
  74. 0x7f,  //8       序号8
  75. 0x6f,  //9       序号9
  76. 0x00,  //无      序号10
  77. 0x40,  //-       序号11
  78. 0x73,  //P       序号12
  79. };
  80. void main()
  81.   {
  82.    initial_myself();  
  83.    delay_long(100);   
  84.    initial_peripheral();
  85.    while(1)  
  86.    {
  87.       ad_sampling_service(); //AD采样与处理的服务程序
  88.       display_service(); //显示的窗口菜单服务程序
  89.    }
  90. }

  91. void ad_sampling_service(void) //AD采样与处理的服务程序
  92. {
  93.     unsigned char i;

  94.     ucAD=0;   //AD值
  95.     ucCheckAD=0; //用来做校验对比的AD值


  96.     /* 片选信号置为低电平 */
  97.     adc0832_cs_dr = 0;

  98.         /* 第一个脉冲,开始位 */
  99.         adc0832_data_sr_dr = 1;
  100.         adc0832_clk_dr  = 0;
  101.     delay_short(1);
  102.         adc0832_clk_dr  = 1;

  103.         /* 第二个脉冲,选择通道 */
  104.         adc0832_data_sr_dr = 1;
  105.         adc0832_clk_dr  = 0;
  106.         adc0832_clk_dr  = 1;

  107.         /* 第三个脉冲,选择通道 */
  108.         adc0832_data_sr_dr = 0;
  109.         adc0832_clk_dr  = 0;
  110.         adc0832_clk_dr  = 1;

  111.     /* 数据线输出高电平 */
  112.         adc0832_data_sr_dr = 1;
  113.     delay_short(2);

  114.         /* 第一个下降沿 */
  115.         adc0832_clk_dr  = 1;
  116.         adc0832_clk_dr  = 0;
  117.     delay_short(1);


  118.         /* AD值开始送出 */
  119.         for (i = 0; i < 8; i++)
  120.         {
  121.         ucAD <<= 1;
  122.                 adc0832_clk_dr = 1;
  123.                 adc0832_clk_dr = 0;
  124.                 if (adc0832_data_sr_dr==1)
  125.                 {
  126.             ucAD |= 0x01;
  127.                 }
  128.         }

  129.         /* 用于校验的AD值开始送出 */
  130.         for (i = 0; i < 8; i++)
  131.         {
  132.         ucCheckAD >>= 1;
  133.                 if (adc0832_data_sr_dr==1)
  134.                 {
  135.            ucCheckAD |= 0x80;
  136.                 }
  137.                 adc0832_clk_dr = 1;
  138.                 adc0832_clk_dr = 0;
  139.         }
  140.         
  141.         /* 片选信号置为高电平 */
  142.         adc0832_cs_dr = 1;


  143.         if(ucCheckAD==ucAD)  //检验相等
  144.         {
  145.         
  146.             ulTemp=0;  //把char类型数据赋值给long类型数据之前,必须先清零
  147.             ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型

  148. /* 注释一:
  149. * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
  150. * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
  151. * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
  152. */
  153.             ulTemp=5000*ulTemp/255;  //进行电压换算
  154.             ulV=ulTemp; //得到未经滤波处理的实时电压值
  155.             ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压


  156. /* 注释二:
  157. * 以下连续判断N次一致性的滤波法,为了避免末尾小数点的数据偶尔跳动。
  158. * 这种滤波方法的原理跟我在按键扫描中的去抖动原理是一模一样的,被我频繁
  159. * 地应用在大量的工控项目中。
  160. * 具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。
  161. * 另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,
  162. * 我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间
  163. * 只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
  164. *
  165. */
  166.                       if(ulTempFilterV!=ulTemp) //发现变量有变化
  167.                      {
  168.                         ucSamplingCnt++;    //计数器累加
  169.                               if(ucSamplingCnt>const_N)  //如果连续N次都是一致的,则认为不是干扰。确实有数据需要更新显示。这里的const_N取值是8
  170.                             {
  171.                                 ucSamplingCnt=0;

  172.                                 ulTempFilterV=ulTemp;   //及时保存更新了的数据,方便下一次有新数据对比做准备

  173.                     ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  174.                     ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压                         
  175.                             }
  176.                        }
  177.                     else
  178.                     {
  179.                          ucSamplingCnt=0;  //只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
  180.                     }



  181.         
  182.         }

  183. }

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

  186.                         if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
  187.                         {
  188.                            ucWd1Part1Update=0;

  189.                ucTemp8=ulV%10000/1000;  //显示电压值个位
  190.                ucTemp7=ulV%1000/100;    //显示电压值小数点后第1位
  191.                ucTemp6=ulV%100/10;      //显示电压值小数点后第2位
  192.                ucTemp5=ulV%10;          //显示电压值小数点后第3位


  193.                ucDigShow8=ucTemp8; //数码管显示实际内容
  194.                ucDigShow7=ucTemp7;
  195.                ucDigShow6=ucTemp6;
  196.                ucDigShow5=ucTemp5;
  197.                         }


  198.                         if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
  199.                         {
  200.                              ucWd1Part2Update=0;

  201.                ucTemp4=ulFilterV%10000/1000;  //显示电压值个位
  202.                ucTemp3=ulFilterV%1000/100;    //显示电压值小数点后第1位
  203.                ucTemp2=ulFilterV%100/10;      //显示电压值小数点后第2位
  204.                ucTemp1=ulFilterV%10;          //显示电压值小数点后第3位


  205.                ucDigShow4=ucTemp4; //数码管显示实际内容
  206.                ucDigShow3=ucTemp3;
  207.                ucDigShow2=ucTemp2;
  208.                ucDigShow1=ucTemp1;
  209.                         }


  210. }



  211. void display_drive(void)  
  212. {
  213.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  214.    switch(ucDisplayDriveStep)
  215.    {
  216.       case 1:  //显示第1位
  217.            ucDigShowTemp=dig_table[ucDigShow1];
  218.                    if(ucDigDot1==1)
  219.                    {
  220.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  221.                    }
  222.            dig_hc595_drive(ucDigShowTemp,0xfe);
  223.                break;
  224.       case 2:  //显示第2位
  225.            ucDigShowTemp=dig_table[ucDigShow2];
  226.                    if(ucDigDot2==1)
  227.                    {
  228.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  229.                    }
  230.            dig_hc595_drive(ucDigShowTemp,0xfd);
  231.                break;
  232.       case 3:  //显示第3位
  233.            ucDigShowTemp=dig_table[ucDigShow3];
  234.                    if(ucDigDot3==1)
  235.                    {
  236.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  237.                    }
  238.            dig_hc595_drive(ucDigShowTemp,0xfb);
  239.                break;
  240.       case 4:  //显示第4位
  241.            ucDigShowTemp=dig_table[ucDigShow4];
  242.                    if(ucDigDot4==1)
  243.                    {
  244.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  245.                    }
  246.            dig_hc595_drive(ucDigShowTemp,0xf7);
  247.                break;
  248.       case 5:  //显示第5位
  249.            ucDigShowTemp=dig_table[ucDigShow5];
  250.                    if(ucDigDot5==1)
  251.                    {
  252.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  253.                    }
  254.            dig_hc595_drive(ucDigShowTemp,0xef);
  255.                break;
  256.       case 6:  //显示第6位
  257.            ucDigShowTemp=dig_table[ucDigShow6];
  258.                    if(ucDigDot6==1)
  259.                    {
  260.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  261.                    }
  262.            dig_hc595_drive(ucDigShowTemp,0xdf);
  263.                break;
  264.       case 7:  //显示第7位
  265.            ucDigShowTemp=dig_table[ucDigShow7];
  266.                    if(ucDigDot7==1)
  267.                    {
  268.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  269.            }
  270.            dig_hc595_drive(ucDigShowTemp,0xbf);
  271.                break;
  272.       case 8:  //显示第8位
  273.            ucDigShowTemp=dig_table[ucDigShow8];
  274.                    if(ucDigDot8==1)
  275.                    {
  276.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  277.                    }
  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.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  300.          delay_short(1);
  301.          dig_hc595_sh_dr=1;
  302.          delay_short(1);
  303.          ucTempData=ucTempData<<1;
  304.    }
  305.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  306.    for(i=0;i<8;i++)
  307.    {
  308.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  309.          else dig_hc595_ds_dr=0;
  310.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  311.          delay_short(1);
  312.          dig_hc595_sh_dr=1;
  313.          delay_short(1);
  314.          ucTempData=ucTempData<<1;
  315.    }
  316.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  317.    delay_short(1);
  318.    dig_hc595_st_dr=1;
  319.    delay_short(1);
  320.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  321.    dig_hc595_st_dr=0;
  322.    dig_hc595_ds_dr=0;
  323. }

  324. //LED灯的74HC595驱动函数
  325. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  326. {
  327.    unsigned char i;
  328.    unsigned char ucTempData;
  329.    hc595_sh_dr=0;
  330.    hc595_st_dr=0;
  331.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  332.    for(i=0;i<8;i++)
  333.    {
  334.          if(ucTempData>=0x80)hc595_ds_dr=1;
  335.          else hc595_ds_dr=0;
  336.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  337.          delay_short(1);
  338.          hc595_sh_dr=1;
  339.          delay_short(1);
  340.          ucTempData=ucTempData<<1;
  341.    }
  342.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  343.    for(i=0;i<8;i++)
  344.    {
  345.          if(ucTempData>=0x80)hc595_ds_dr=1;
  346.          else hc595_ds_dr=0;
  347.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  348.          delay_short(1);
  349.          hc595_sh_dr=1;
  350.          delay_short(1);
  351.          ucTempData=ucTempData<<1;
  352.    }
  353.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  354.    delay_short(1);
  355.    hc595_st_dr=1;
  356.    delay_short(1);
  357.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  358.    hc595_st_dr=0;
  359.    hc595_ds_dr=0;
  360. }


  361. void T0_time(void) interrupt 1   //定时中断
  362. {
  363.   TF0=0;  //清除中断标志
  364.   TR0=0; //关中断


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

  366.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  367.   TL0=0x0b;
  368.   TR0=1;  //开中断
  369. }

  370. void delay_short(unsigned int uiDelayShort)
  371. {
  372.    unsigned int i;  
  373.    for(i=0;i<uiDelayShort;i++)
  374.    {
  375.      ;   //一个分号相当于执行一条空语句
  376.    }
  377. }

  378. void delay_long(unsigned int uiDelayLong)
  379. {
  380.    unsigned int i;
  381.    unsigned int j;
  382.    for(i=0;i<uiDelayLong;i++)
  383.    {
  384.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  385.           {
  386.              ; //一个分号相当于执行一条空语句
  387.           }
  388.    }
  389. }


  390. void initial_myself(void)  //第一区 初始化单片机
  391. {
  392.   led_dr=0;//LED灯默认关闭
  393.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  394.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  395.   TMOD=0x01;  //设置定时器0为工作方式1
  396.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  397.   TL0=0x0b;

  398. }
  399. void initial_peripheral(void) //第二区 初始化外围
  400. {

  401.    ucDigDot8=1;   //显示未经过滤波电压的小数点
  402.    ucDigDot7=0;  
  403.    ucDigDot6=0;
  404.    ucDigDot5=0;  
  405.    ucDigDot4=1;  //显示经过滤波后电压的小数点
  406.    ucDigDot3=0;  
  407.    ucDigDot2=0;
  408.    ucDigDot1=0;

  409.    EA=1;     //开总中断
  410.    ET0=1;    //允许定时中断
  411.    TR0=1;    //启动定时中断

  412. }
复制代码

总结陈词:
在单片机AD采样的系统中,我常用的滤波方法有求平均值法,区间法,连续判断N次一致性这三种方法。读者可以根据不同的系统特点选择对应的滤波方法,有一些要求高的系统还可以把三种滤波方法混合在一起用。关于AD采样的知识到本节已经讲完,下一节会讲什么新内容呢?欲知详情,请听下回分解-----return语句鲜为人知的用法。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
56#
 楼主| 发表于 2014-6-22 02:05:36 | 显示全部楼层
第五十二节:程序后续升级修改的利器,return语句鲜为人知的用法。

开场白:
return语句经常用在带参数返回的函数中,字面上理解就是返回的意思,因此很多单片机初学者很容易忽略了return语句还有中断强行退出的功能。利用这个强行退出的功能,在项目后续程序的升级修改上很方便,还可以有效减少if语句的嵌套层数,使程序阅读起来很简洁。这一节要教大家return语句三个鲜为人知的用法:
第一个鲜为人知的用法:在空函数里,可以插入很多个return语句,不仅仅是一个。
第二个鲜为人知的用法:return语句可以有效较少程序里条件判断语句的嵌套层数。
第三个鲜为人知的用法:return语句本身已经包含了类似break语句的功能,不管当前处于几层的内部循环嵌套,只要遇到return语句都可以强行退出全部循环,并且直接退出当前子程序,不执行当前子程序后面的任何语句,这个功能实在是太强大,太铁腕了。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
本程序实现的功能跟第三十九节是一摸一样的,唯一的差别就是在第三十九节的基础上,插入了几个return语句,用新的return语句替代原来的条件和循环判断语句。

波特率是: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,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。

也就是说,当在 串口助手往单片机发送十六进制数据串:  eb 00 55 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. /* 注释一:
  29. * 以下函数说明了,在空函数里,可以插入很多个return语句。
  30. * 用return语句非常便于后续程序的升级修改。
  31. */
  32. void usart_service(void)  //串口服务程序,在main函数里
  33. {

  34.         

  35. //     if(uiSendCnt>=const_receive_time&&ucSendLock==1) //原来的语句,现在被两个return语句替代了
  36. //     {

  37.        if(uiSendCnt<const_receive_time)  //延时还没超过规定时间,直接退出本程序,不执行return后的任何语句。
  38.            {
  39.               return;  //强行退出本子程序,不执行以下任何语句
  40.            }

  41.            if(ucSendLock==0)  //不是最新一次接收到串口数据,直接退出本程序,不执行return后的任何语句。
  42.            {
  43.               return;  //强行退出本子程序,不执行以下任何语句
  44.            }
  45. /* 注释二:
  46. * 以上两条return语句就相当于原来的一条if(uiSendCnt>=const_receive_time&&ucSendLock==1)语句。
  47. * 用了return语句后,就明显减少了一个if嵌套。
  48. */


  49.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  50.                     //下面的代码进入数据协议解析和数据处理的阶段

  51.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动


  52. //           while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5)) //原来的语句,现在被两个return语句替代了
  53.             while(1) //死循环可以被以下return或者break语句中断,return本身已经包含了break语句功能。
  54.             {
  55.                if(uiRcregTotal<5)  //串口接收到的数据太少
  56.                            {
  57.                               uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  58.                                   return;  //强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句
  59.                            }

  60.                            if(uiRcMoveIndex>(uiRcregTotal-5)) //数组缓冲区的数据已经处理完
  61.                            {
  62.                               uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  63.                                   return;  //强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句
  64.                            }
  65. /* 注释三:
  66. * 以上两条return语句就相当于原来的一条while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))语句。
  67. * 以上两个return语句的用法,同时说明了return本身已经包含了break语句功能,不管当前处于几层的内部循环嵌套,
  68. * 都可以强行退出循环,并且直接退出本程序。
  69. */


  70.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  71.                {
  72.                   if(ucRcregBuf[uiRcMoveIndex+3]==0x01&&ucRcregBuf[uiRcMoveIndex+4]==0x02)  //有效数据01 02的判断
  73.                   {
  74.                        uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据头和有效数据都接收正确
  75.                   }
  76.                   break;   //退出while(1)循环
  77.                }
  78.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  79.            }
  80.                                          
  81.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  82.   
  83. //     }
  84.                         
  85. }


  86. void T0_time(void) interrupt 1    //定时中断
  87. {
  88.   TF0=0;  //清除中断标志
  89.   TR0=0; //关中断


  90.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  91.   {
  92.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  93.       ucSendLock=1;     //开自锁标志
  94.   }

  95.   if(uiVoiceCnt!=0)
  96.   {
  97.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  98.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  99.   }
  100.   else
  101.   {
  102.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  103.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  104.   }


  105.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  106.   TL0=0x0b;
  107.   TR0=1;  //开中断
  108. }


  109. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  110. {        

  111.    if(RI==1)  
  112.    {
  113.         RI = 0;

  114.             ++uiRcregTotal;
  115.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  116.         {
  117.            uiRcregTotal=const_rc_size;
  118.         }
  119.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  120.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  121.    
  122.    }
  123.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  124.    {
  125.         TI = 0;
  126.    }
  127.                                                          
  128. }                                


  129. void delay_long(unsigned int uiDelayLong)
  130. {
  131.    unsigned int i;
  132.    unsigned int j;
  133.    for(i=0;i<uiDelayLong;i++)
  134.    {
  135.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  136.           {
  137.              ; //一个分号相当于执行一条空语句
  138.           }
  139.    }
  140. }


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

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

  144.   //配置定时器
  145.   TMOD=0x01;  //设置定时器0为工作方式1
  146.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  147.   TL0=0x0b;


  148.   //配置串口
  149.   SCON=0x50;
  150.   TMOD=0X21;
  151.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  152.   TR1=1;

  153. }

  154. void initial_peripheral(void) //第二区 初始化外围
  155. {

  156.    EA=1;     //开总中断
  157.    ES=1;     //允许串口中断
  158.    ET0=1;    //允许定时中断
  159.    TR0=1;    //启动定时中断

  160. }
复制代码

总结陈词:
我在第一节就告诉读者了,搞单片机开发如果不会C语言的指针也没关系,不会影响做项目。我本人平时做项目时,也很少用指针,只有在三种场合下我才会用指针,因为在这三种场合下,用了指针感觉程序阅读起来更加清爽了。所以,指针还是有它独到的好处,有哪三种好处?欲知详情,请听下回分解-----指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
57#
 楼主| 发表于 2014-6-29 07:32:57 | 显示全部楼层
第五十三节:指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

开场白:
当我们想把某种算法通过一个函数来实现的时候,如果不会指针,那么只有两种方法。
第1种:用不带参数返回的空函数。这是最原始的做法,也是我当年刚毕业就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口。
第2种:用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,如果要用在返回多个输出结果的函数中,就无能为力了,这时候该怎么办?就必须用指针了,也就是我下面讲到的第3种方法。
这一节要教大家一个知识点:通过指针,让函数可以返回多个变量。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
通过电脑串口调试助手,往单片机发送EB 00 55 XX YY  指令,其中EB 00 55是数据头, XX是被除数,YY是除数。单片机收到指令后就会返回6个数据,最前面两个数据是第1种运算方式的商和余数,中间两个数据是第2种运算方式的商和余数,最后两个数据是第3种运算方式的商和余数。
比如电脑发送:EB 00 55 08 02
单片机就返回:04 00 04 00 04 00  (04是商,00是余数)

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

(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 delay_short(unsigned int uiDelayShort);


  9. void T0_time(void);  //定时中断函数
  10. void usart_receive(void); //串口接收中断函数
  11. void usart_service(void);  //串口服务程序,在main函数里


  12. void eusart_send(unsigned char ucSendData);
  13. void chu_fa_yun_suan_1(void);//第1种方法 求商和余数
  14. unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求商
  15. unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求余数
  16. void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp);//第3种方法 求商和余数

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

  18. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  19. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  20. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  21. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  22. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


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


  24. unsigned char ucBeiChuShu_1=0;  //第1种方法中的被除数
  25. unsigned char ucChuShu_1=1;     //第1种方法中的除数
  26. unsigned char ucShang_1=0;      //第1种方法中的商
  27. unsigned char ucYu_1=0;         //第1种方法中的余数

  28. unsigned char ucBeiChuShu_2=0;  //第2种方法中的被除数
  29. unsigned char ucChuShu_2=1;     //第2种方法中的除数
  30. unsigned char ucShang_2=0;      //第2种方法中的商
  31. unsigned char ucYu_2=0;         //第2种方法中的余数

  32. unsigned char ucBeiChuShu_3=0;  //第3种方法中的被除数
  33. unsigned char ucChuShu_3=1;     //第3种方法中的除数
  34. unsigned char ucShang_3=0;      //第3种方法中的商
  35. unsigned char ucYu_3=0;         //第3种方法中的余数

  36. void main()
  37.   {
  38.    initial_myself();  
  39.    delay_long(100);   
  40.    initial_peripheral();
  41.    while(1)  
  42.    {
  43.        usart_service();  //串口服务程序
  44.    }

  45. }


  46. /* 注释一:
  47. * 第1种方法,用不带参数返回的空函数,这是最原始的做法,也是我当年刚毕业
  48. * 就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。
  49. * 我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,
  50. * 调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。
  51. * 在本函数中,被除数ucBeiChuShu_1和除数ucChuShu_1就是输入全局变量,
  52. * 商ucShang_1和余数ucYu_1就是输出全局变量。这种方法的缺点是阅读不直观,
  53. * 封装性不强,没有面对用户的输入输出接口,
  54. */
  55. void chu_fa_yun_suan_1(void)//第1种方法 求商和余数
  56. {
  57.    if(ucChuShu_1==0) //如果除数为0,则商和余数都为0
  58.    {
  59.       ucShang_1=0;
  60.           ucYu_1=0;
  61.    }
  62.    else
  63.    {
  64.       ucShang_1=ucBeiChuShu_1/ucChuShu_1;  //求商
  65.       ucYu_1=ucBeiChuShu_1%ucChuShu_1;  //求余数
  66.    }

  67. }


  68. /* 注释二:
  69. * 第2种方法,用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,
  70. * 比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,
  71. * 如果要用在返回多个输出结果的函数中,就无能为力了。比如本程序,就不能同时输出
  72. * 商和余数,只能分两个函数来做。如果要在一个函数中同时输出商和余数,该怎么办?
  73. * 这个时候就必须用指针了,也就是我下面讲到的第3种方法。
  74. */
  75. unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求商
  76. {
  77.    unsigned char ucShangTemp;
  78.    if(ucChuShuTemp==0) //如果除数为0,则商为0
  79.    {
  80.       ucShangTemp=0;
  81.    }
  82.    else
  83.    {
  84.       ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp;  //求商
  85.    }

  86.    return ucShangTemp; //返回运算后的结果 商
  87. }

  88. unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求余数
  89. {
  90.    unsigned char ucYuTemp;
  91.    if(ucChuShuTemp==0) //如果除数为0,则余数为0
  92.    {
  93.       ucYuTemp=0;
  94.    }
  95.    else
  96.    {
  97.       ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp;   //求余数
  98.    }

  99.    return ucYuTemp; //返回运算后的结果 余数
  100. }

  101. /* 注释三:
  102. * 第3种方法,用带指针的函数,就可以顺心所欲,不受return的局限,想输出多少个
  103. * 运算结果都可以,赞一个!在本函数中,ucBeiChuShuTemp和ucChuShuTemp是输入变量,
  104. * 它们不是指针,所以不具备输出接口属性。*p_ucShangTemp和*p_ucYuTemp是输出变量,
  105. * 因为它们是指针,所以具备输出接口属性。
  106. */
  107. void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp)//第3种方法 求商和余数
  108. {
  109.    if(ucChuShuTemp==0) //如果除数为0,则商和余数都为0
  110.    {
  111.       *p_ucShangTemp=0;
  112.           *p_ucYuTemp=0;
  113.    }
  114.    else
  115.    {
  116.       *p_ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp;  //求商
  117.       *p_ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp;  //求余数
  118.    }

  119. }

  120. void usart_service(void)  //串口服务程序,在main函数里
  121. {

  122.         

  123.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  124.      {

  125.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  126.             //下面的代码进入数据协议解析和数据处理的阶段

  127.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  128.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  129.             {
  130.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  131.                {

  132.                   //第1种运算方法,依靠全局变量
  133.                   ucBeiChuShu_1=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  134.                   ucChuShu_1=ucRcregBuf[uiRcMoveIndex+4];  //除数
  135.                                   chu_fa_yun_suan_1(); //调用一次空函数就出结果了,结果保存在ucShang_1和ucYu_1全局变量中
  136.                                   eusart_send(ucShang_1); //把运算结果返回给上位机观察
  137.                                   eusart_send(ucYu_1);//把运算结果返回给上位机观察

  138.                   //第2种运算方法,依靠两个带return语句的返回函数
  139.                   ucBeiChuShu_2=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  140.                   ucChuShu_2=ucRcregBuf[uiRcMoveIndex+4];  //除数
  141.                   ucShang_2=get_shang_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求商
  142.                   ucYu_2=get_yu_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求余数
  143.                                   eusart_send(ucShang_2); //把运算结果返回给上位机观察
  144.                                   eusart_send(ucYu_2);//把运算结果返回给上位机观察

  145.                   //第3种运算方法,依靠指针
  146.                   ucBeiChuShu_3=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  147.                   ucChuShu_3=ucRcregBuf[uiRcMoveIndex+4];  //除数
  148. /* 注释四:
  149. * 注意,由于商和余数是指针形参,我们代入的变量必须带地址符号& 。比如&ucShang_3和&ucYu_3。
  150. * 因为我们是把变量的地址传递进去的。
  151. */
  152.                                   chu_fa_yun_suan_3(ucBeiChuShu_3,ucChuShu_3,&ucShang_3,&ucYu_3);//第3种方法 求商和余数
  153.                                   eusart_send(ucShang_3); //把运算结果返回给上位机观察
  154.                                   eusart_send(ucYu_3);//把运算结果返回给上位机观察


  155.                   break;   //退出循环
  156.                }
  157.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  158.            }
  159.                                          
  160.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  161.   
  162.      }
  163.                         
  164. }

  165. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  166. {

  167.   ES = 0; //关串口中断
  168.   TI = 0; //清零串口发送完成中断请求标志
  169.   SBUF =ucSendData; //发送一个字节

  170.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  171.   TI = 0; //清零串口发送完成中断请求标志
  172.   ES = 1; //允许串口中断

  173. }



  174. void T0_time(void) interrupt 1    //定时中断
  175. {
  176.   TF0=0;  //清除中断标志
  177.   TR0=0; //关中断


  178.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  179.   {
  180.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  181.       ucSendLock=1;     //开自锁标志
  182.   }

  183.   if(uiVoiceCnt!=0)
  184.   {
  185.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  186.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

  187.   }
  188.   else
  189.   {
  190.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  191.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  192.   }


  193.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  194.   TL0=0x0b;
  195.   TR0=1;  //开中断
  196. }


  197. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  198. {        

  199.    if(RI==1)  
  200.    {
  201.         RI = 0;

  202.             ++uiRcregTotal;
  203.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  204.         {
  205.            uiRcregTotal=const_rc_size;
  206.         }
  207.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  208.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  209.    
  210.    }
  211.    else  //发送中断,及时把发送中断标志位清零
  212.    {
  213.         TI = 0;
  214.    }
  215.                                                          
  216. }                                


  217. void delay_long(unsigned int uiDelayLong)
  218. {
  219.    unsigned int i;
  220.    unsigned int j;
  221.    for(i=0;i<uiDelayLong;i++)
  222.    {
  223.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  224.           {
  225.              ; //一个分号相当于执行一条空语句
  226.           }
  227.    }
  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 initial_myself(void)  //第一区 初始化单片机
  238. {

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

  240.   //配置定时器
  241.   TMOD=0x01;  //设置定时器0为工作方式1
  242.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  243.   TL0=0x0b;


  244.   //配置串口
  245.   SCON=0x50;
  246.   TMOD=0X21;
  247.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  248.   TR1=1;

  249. }

  250. void initial_peripheral(void) //第二区 初始化外围
  251. {

  252.    EA=1;     //开总中断
  253.    ES=1;     //允许串口中断
  254.    ET0=1;    //允许定时中断
  255.    TR0=1;    //启动定时中断

  256. }
复制代码

总结陈词:
这节讲了指针的第一大好处,它的第二大好处是什么?欲知详情,请听下回分解-----指针的第二大好处,指针作为数组在函数内部的化身。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
58#
 楼主| 发表于 2014-7-6 11:26:01 | 显示全部楼层
第五十四节:指针的第二大好处,指针作为数组在函数中的输入接口。

开场白:
如果不会指针,当我们想把一个数组的数据传递进某个函数内部的时候,只能通过全局变量的方式,这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入接口。
针对以上问题,这一节要教大家一个知识点:通过指针,为函数增加一个数组输入接口。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07  指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第1种方法的排序结果,中间3个数据EE EE EE是第1种和第2种的分割线,为了方便观察,没实际意义。最后5个数据是第2种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

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


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

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


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里


  13. void eusart_send(unsigned char ucSendData);

  14. void big_to_small_sort_1(void);//第1种方法 把一个数组从大小小排序
  15. void big_to_small_sort_2(unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大小小排序

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

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  22. unsigned char ucUsartBuffer[const_array_size];  //从串口接收到的需要排序的原始数据
  23. unsigned char ucGlobalBuffer_1[const_array_size]; //第1种方法,参与具体排序算法的全局变量数组
  24. unsigned char ucGlobalBuffer_2[const_array_size]; //第2种方法,参与具体排序算法的全局变量数组
  25. void main()
  26.   {
  27.    initial_myself();  
  28.    delay_long(100);   
  29.    initial_peripheral();
  30.    while(1)  
  31.    {
  32.        usart_service();  //串口服务程序
  33.    }

  34. }


  35. /* 注释一:
  36. * 第1种方法,用不带输入输出接口的空函数,这是最原始的做法,它完全依靠
  37. * 全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算
  38. * 的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的
  39. * 输出全局变量,这些输出全局变量就是我们要的结果。
  40. * 在本函数中,ucGlobalBuffer_1[const_array_size]既是输入全局变量,也是输出全局变量,
  41. * 这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口,
  42. */
  43. void big_to_small_sort_1(void)//第1种方法 把一个数组从大小小排序
  44. {
  45.    unsigned char i;
  46.    unsigned char k;
  47.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量

  48. /* 注释二:
  49. * 以下就是著名的 冒泡法排序。这个方法几乎所有的C语言大学教材都讲过了。大家在百度上可以直接
  50. * 搜索到它的工作原理和详细的讲解步骤,我就不再详细讲解了。
  51. */
  52.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  53.    {
  54.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  55.           {
  56.              if(ucGlobalBuffer_1[const_array_size-1-k]>ucGlobalBuffer_1[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  57.                  {
  58.                      ucTemp=ucGlobalBuffer_1[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  59.              ucGlobalBuffer_1[const_array_size-1-1-k]=ucGlobalBuffer_1[const_array_size-1-k];
  60.              ucGlobalBuffer_1[const_array_size-1-k]=ucTemp;
  61.                  }
  62.           
  63.           }
  64.    }

  65. }

  66. /* 注释三:
  67. * 第2种方法,为了改进第1种方法的用户体验,用指针为函数增加一个输入接口。
  68. * 为什么要用指针?因为C语言的函数中,数组不能直接用来做函数的形参,只能用指针作为数组的形参。
  69. * 比如,你不能这样写一个函数void big_to_small_sort_2(unsigned char a[5]),否则编译就会出错不通过。
  70. * 在本函数中,*p_ucInputBuffer指针就是输入接口,而输出接口仍然是全局变量数组ucGlobalBuffer_2。
  71. * 这种方法由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了。
  72. */
  73. void big_to_small_sort_2(unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大小小排序
  74. {
  75.    unsigned char i;
  76.    unsigned char k;
  77.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


  78.    for(i=0;i<const_array_size;i++)  
  79.    {
  80.       ucGlobalBuffer_2[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
  81.    }


  82.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  83.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  84.    {
  85.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  86.           {
  87.              if(ucGlobalBuffer_2[const_array_size-1-k]>ucGlobalBuffer_2[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  88.                  {
  89.                      ucTemp=ucGlobalBuffer_2[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  90.              ucGlobalBuffer_2[const_array_size-1-1-k]=ucGlobalBuffer_2[const_array_size-1-k];
  91.              ucGlobalBuffer_2[const_array_size-1-k]=ucTemp;
  92.                  }
  93.           
  94.           }
  95.    }

  96. }



  97. void usart_service(void)  //串口服务程序,在main函数里
  98. {

  99.      unsigned char i=0;   

  100.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  101.      {

  102.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  103.             //下面的代码进入数据协议解析和数据处理的阶段

  104.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  105.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  106.             {
  107.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  108.                {


  109.                                   for(i=0;i<const_array_size;i++)
  110.                                   {
  111.                      ucUsartBuffer[i]=ucRcregBuf[uiRcMoveIndex+3+i]; //从串口接收到的需要被排序的原始数据
  112.                                   }


  113.                   //第1种运算方法,依靠全局变量
  114.                                   for(i=0;i<const_array_size;i++)
  115.                                   {
  116.                                      ucGlobalBuffer_1[i]=ucUsartBuffer[i];  //把需要被排列的数据放进输入全局变量数组
  117.                                   }
  118.                   big_to_small_sort_1(); //调用一次空函数就出结果了,结果还是保存在ucGlobalBuffer_1全局变量数组中
  119.                   for(i=0;i<const_array_size;i++)
  120.                                   {
  121.                                     eusart_send(ucGlobalBuffer_1[i]);  ////把用第1种方法排序后的结果返回给上位机观察
  122.                                   }


  123.                                   eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为第1种方法与第2种方法的分割线
  124.                                   eusart_send(0xee);
  125.                                   eusart_send(0xee);

  126.                   //第2种运算方法,依靠指针为函数增加一个数组的输入接口
  127.                                   //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
  128.                   big_to_small_sort_2(ucUsartBuffer);
  129.                   for(i=0;i<const_array_size;i++)
  130.                                   {
  131.                                     eusart_send(ucGlobalBuffer_2[i]);  //把用第2种方法排序后的结果返回给上位机观察
  132.                                   }





  133.                   break;   //退出循环
  134.                }
  135.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  136.            }
  137.                                          
  138.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  139.   
  140.      }
  141.                         
  142. }

  143. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  144. {

  145.   ES = 0; //关串口中断
  146.   TI = 0; //清零串口发送完成中断请求标志
  147.   SBUF =ucSendData; //发送一个字节

  148.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  149.   TI = 0; //清零串口发送完成中断请求标志
  150.   ES = 1; //允许串口中断

  151. }



  152. void T0_time(void) interrupt 1    //定时中断
  153. {
  154.   TF0=0;  //清除中断标志
  155.   TR0=0; //关中断


  156.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  157.   {
  158.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  159.       ucSendLock=1;     //开自锁标志
  160.   }



  161.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  162.   TL0=0x0b;
  163.   TR0=1;  //开中断
  164. }


  165. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  166. {        

  167.    if(RI==1)  
  168.    {
  169.         RI = 0;

  170.             ++uiRcregTotal;
  171.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  172.         {
  173.            uiRcregTotal=const_rc_size;
  174.         }
  175.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  176.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  177.    
  178.    }
  179.    else  //发送中断,及时把发送中断标志位清零
  180.    {
  181.         TI = 0;
  182.    }
  183.                                                          
  184. }                                


  185. void delay_long(unsigned int uiDelayLong)
  186. {
  187.    unsigned int i;
  188.    unsigned int j;
  189.    for(i=0;i<uiDelayLong;i++)
  190.    {
  191.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  192.           {
  193.              ; //一个分号相当于执行一条空语句
  194.           }
  195.    }
  196. }

  197. void delay_short(unsigned int uiDelayShort)
  198. {
  199.    unsigned int i;  
  200.    for(i=0;i<uiDelayShort;i++)
  201.    {
  202.      ;   //一个分号相当于执行一条空语句
  203.    }
  204. }


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

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

  208.   //配置定时器
  209.   TMOD=0x01;  //设置定时器0为工作方式1
  210.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  211.   TL0=0x0b;


  212.   //配置串口
  213.   SCON=0x50;
  214.   TMOD=0X21;
  215.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  216.   TR1=1;

  217. }

  218. void initial_peripheral(void) //第二区 初始化外围
  219. {

  220.    EA=1;     //开总中断
  221.    ES=1;     //允许串口中断
  222.    ET0=1;    //允许定时中断
  223.    TR0=1;    //启动定时中断

  224. }
复制代码

总结陈词:
第2种方法通过指针,为函数增加了一个数组输入接口,已经比第1种纯粹用全局变量的方法直观多了,但是还有一个小小的遗憾,因为它的输出排序结果仍然要靠全局变量。为了让函数更加完美,我们能不能为函数再增加一个输出接口?当然可以。欲知详情,请听下回分解-----指针的第三大好处,指针作为数组在函数中的输出接口。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
59#
 楼主| 发表于 2014-7-10 15:42:28 | 显示全部楼层
第五十五节:指针的第三大好处,指针作为数组在函数中的输出接口。

开场白:
上一节介绍的第2种方法,由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了,但是由于只有输入接口,没有输出接口,输出接口仍然要靠全局变量数组,所以还是有一个小小的遗憾,这节介绍的第3种方法就是为了改变这个遗憾,为数组在函数中多增加一个输出接口,这样,函数既有输入接口,又有输出接口,这样的函数才算完美直观。这一节要教大家一个知识点:通过指针,为函数增加一个数组输出接口。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07  指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第2种方法的排序结果,中间3个数据EE EE EE是第2种和第3种的分割线,为了方便观察,没实际意义。最后5个数据是第3种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

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


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

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


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里


  13. void eusart_send(unsigned char ucSendData);


  14. void big_to_small_sort_2(unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大到小排序
  15. void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer);//第3种方法 把一个数组从大到小排序
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  22. unsigned char ucUsartBuffer[const_array_size];  //从串口接收到的需要排序的原始数据

  23. unsigned char ucGlobalBuffer_2[const_array_size]; //第2种方法,参与具体排序算法的全局变量数组
  24. unsigned char ucGlobalBuffer_3[const_array_size]; //第3种方法,用来接收输出接口数据的全局变量数组

  25. void main()
  26.   {
  27.    initial_myself();  
  28.    delay_long(100);   
  29.    initial_peripheral();
  30.    while(1)  
  31.    {
  32.        usart_service();  //串口服务程序
  33.    }

  34. }



  35. /* 注释一:
  36. * 第2种方法,为了改进第1种方法的用户体验,用指针为函数增加一个输入接口。
  37. * 为什么要用指针?因为C语言的函数中,数组不能直接用来做函数的形参,只能用指针作为数组的形参。
  38. * 比如,你不能这样写一个函数void big_to_small_sort_2(unsigned char a[5]),否则编译就会出错不通过。
  39. * 在本函数中,*p_ucInputBuffer指针就是输入接口,而输出接口仍然是全局变量数组ucGlobalBuffer_2。
  40. * 这种方法由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了,但是由于只有输入接口,
  41. * 没有输出接口,输出接口仍然要靠全局变量,所以还是有点小遗憾,以下第3种方法就是为了改变这个遗憾。
  42. */
  43. void big_to_small_sort_2(unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大到小排序
  44. {
  45.    unsigned char i;
  46.    unsigned char k;
  47.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


  48.    for(i=0;i<const_array_size;i++)  
  49.    {
  50.       ucGlobalBuffer_2[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
  51.    }


  52.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  53.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  54.    {
  55.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  56.           {
  57.              if(ucGlobalBuffer_2[const_array_size-1-k]>ucGlobalBuffer_2[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  58.                  {
  59.                      ucTemp=ucGlobalBuffer_2[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  60.              ucGlobalBuffer_2[const_array_size-1-1-k]=ucGlobalBuffer_2[const_array_size-1-k];
  61.              ucGlobalBuffer_2[const_array_size-1-k]=ucTemp;
  62.                  }
  63.           
  64.           }
  65.    }

  66. }



  67. /* 注释二:
  68. * 第3种方法,为了改进第2种方法的用户体验,用指针为函数多增加一个数组输出接口。
  69. * 这样,函数的数组既有输入接口,又有输出接口,已经堪称完美了。
  70. * 本程序中*p_ucInputBuffer输入接口,*p_ucOutputBuffer是输出接口。
  71. */
  72. void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer)//第3种方法 把一个数组从大到小排序
  73. {
  74.    unsigned char i;
  75.    unsigned char k;
  76.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量
  77.    unsigned char ucBuffer_3[const_array_size]; //第3种方法,参与具体排序算法的局部变量数组

  78.    for(i=0;i<const_array_size;i++)  
  79.    {
  80.       ucBuffer_3[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到局部变量数组中。
  81.    }


  82.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  83.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  84.    {
  85.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  86.           {
  87.              if(ucBuffer_3[const_array_size-1-k]>ucBuffer_3[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  88.                  {
  89.                      ucTemp=ucBuffer_3[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  90.              ucBuffer_3[const_array_size-1-1-k]=ucBuffer_3[const_array_size-1-k];
  91.              ucBuffer_3[const_array_size-1-k]=ucTemp;
  92.                  }
  93.           
  94.           }
  95.    }


  96.    for(i=0;i<const_array_size;i++)  
  97.    {
  98.       p_ucOutputBuffer[i]=ucBuffer_3[i];  //参与排序算法之后,把运算结果的数据全部搬移到输出接口中,方便外面程序调用
  99.    }
  100. }




  101. void usart_service(void)  //串口服务程序,在main函数里
  102. {

  103.      unsigned char i=0;   

  104.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  105.      {

  106.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  107.             //下面的代码进入数据协议解析和数据处理的阶段

  108.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  109.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  110.             {
  111.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  112.                {


  113.                                   for(i=0;i<const_array_size;i++)
  114.                                   {
  115.                      ucUsartBuffer[i]=ucRcregBuf[uiRcMoveIndex+3+i]; //从串口接收到的需要被排序的原始数据
  116.                                   }


  117.                   //第2种运算方法,依靠指针为函数增加一个数组的输入接口
  118.                                   //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
  119.                   big_to_small_sort_2(ucUsartBuffer);
  120.                   for(i=0;i<const_array_size;i++)
  121.                                   {
  122.                                     eusart_send(ucGlobalBuffer_2[i]);  //把用第2种方法排序后的结果返回给上位机观察
  123.                                   }


  124.                                   eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为第2种方法与第3种方法的分割线
  125.                                   eusart_send(0xee);
  126.                                   eusart_send(0xee);

  127.                   //第3种运算方法,依靠指针为函数增加一个数组的输出接口
  128.                                   //通过指针输出接口,排序运算后的结果直接从这个输出口中导出到ucGlobalBuffer_3数组中
  129.                   big_to_small_sort_3(ucUsartBuffer,ucGlobalBuffer_3);   //ucUsartBuffer是输入的数组,ucGlobalBuffer_3是接收排序结果的数组
  130.                   for(i=0;i<const_array_size;i++)
  131.                                   {
  132.                                     eusart_send(ucGlobalBuffer_3[i]);  //把用第3种方法排序后的结果返回给上位机观察
  133.                                   }



  134.                   break;   //退出循环
  135.                }
  136.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  137.            }
  138.                                          
  139.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  140.   
  141.      }
  142.                         
  143. }

  144. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  145. {

  146.   ES = 0; //关串口中断
  147.   TI = 0; //清零串口发送完成中断请求标志
  148.   SBUF =ucSendData; //发送一个字节

  149.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  150.   TI = 0; //清零串口发送完成中断请求标志
  151.   ES = 1; //允许串口中断

  152. }



  153. void T0_time(void) interrupt 1    //定时中断
  154. {
  155.   TF0=0;  //清除中断标志
  156.   TR0=0; //关中断


  157.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  158.   {
  159.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  160.       ucSendLock=1;     //开自锁标志
  161.   }



  162.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  163.   TL0=0x0b;
  164.   TR0=1;  //开中断
  165. }


  166. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  167. {        

  168.    if(RI==1)  
  169.    {
  170.         RI = 0;

  171.             ++uiRcregTotal;
  172.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  173.         {
  174.            uiRcregTotal=const_rc_size;
  175.         }
  176.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  177.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  178.    
  179.    }
  180.    else  //发送中断,及时把发送中断标志位清零
  181.    {
  182.         TI = 0;
  183.    }
  184.                                                          
  185. }                                


  186. void delay_long(unsigned int uiDelayLong)
  187. {
  188.    unsigned int i;
  189.    unsigned int j;
  190.    for(i=0;i<uiDelayLong;i++)
  191.    {
  192.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  193.           {
  194.              ; //一个分号相当于执行一条空语句
  195.           }
  196.    }
  197. }

  198. void delay_short(unsigned int uiDelayShort)
  199. {
  200.    unsigned int i;  
  201.    for(i=0;i<uiDelayShort;i++)
  202.    {
  203.      ;   //一个分号相当于执行一条空语句
  204.    }
  205. }


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

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

  209.   //配置定时器
  210.   TMOD=0x01;  //设置定时器0为工作方式1
  211.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  212.   TL0=0x0b;


  213.   //配置串口
  214.   SCON=0x50;
  215.   TMOD=0X21;
  216.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  217.   TR1=1;

  218. }

  219. void initial_peripheral(void) //第二区 初始化外围
  220. {

  221.    EA=1;     //开总中断
  222.    ES=1;     //允许串口中断
  223.    ET0=1;    //允许定时中断
  224.    TR0=1;    //启动定时中断

  225. }
复制代码

总结陈词:
通过本节程序的讲解,一部分细心的读者可能会发现一个规律,其实所谓指针作为数组在函数中的输入接口和输出接口,输入接口的指针跟输出接口的指针在语法上没有任何区别,我没有用到C语言中专门的关键词去限定某个指针是输入,某个指针是输出,因此,这个告诉我们什么道理?指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,不像普通的函数变量形参只能做输入。发现了这个秘密,我们可不可以把本节程序中的输入接口和输出接口合并成一个输入输出接口呢?当然可以。欲知详情,请听下回分解-----指针的第四大好处,指针作为数组在函数中的输入输出接口。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
60#
 楼主| 发表于 2014-7-10 17:52:19 | 显示全部楼层
第五十六节:指针的第四大好处,指针作为数组在函数中的输入输出接口。

开场白:
通过前面几个章节的学习,我们知道指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,类似全局变量的特点。我们根据实际项目的情况,在必要的时候可以直接把输入接口和输出接口合并在一起,这种方法的缺点是没有把输入和输出分开,没有那么直观。但是优点也是很明显的,就是比较省程序ROM容量和数据RAM容量,而且运行效率也比较快。这一节要教大家一个知识点:指针作为数组在函数中输入输出接口的特点。

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

(1)硬件平台:
基于坚鸿51单片机学习板。

(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07  指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第3种方法的排序结果,中间3个数据EE EE EE是第3种和第4种的分割线,为了方便观察,没实际意义。最后5个数据是第4种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

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


  2. #define const_array_size  5  //参与排序的数组大小

  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  4. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

  5. #define const_receive_time  5  //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

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


  10. void T0_time(void);  //定时中断函数
  11. void usart_receive(void); //串口接收中断函数
  12. void usart_service(void);  //串口服务程序,在main函数里


  13. void eusart_send(unsigned char ucSendData);

  14. void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer);//第3种方法 把一个数组从大到小排序
  15. void big_to_small_sort_4(unsigned char *p_ucInputAndOutputBuffer);//第4种方法 把一个数组从大到小排序
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

  22. unsigned char ucUsartBuffer[const_array_size];  //从串口接收到的需要排序的原始数据

  23. unsigned char ucGlobalBuffer_3[const_array_size]; //第3种方法,用来接收输出接口数据的全局变量数组
  24. unsigned char ucGlobalBuffer_4[const_array_size]; //第4种方法,用来输入和输出接口数据的全局变量数组
  25. void main()
  26.   {
  27.    initial_myself();  
  28.    delay_long(100);   
  29.    initial_peripheral();
  30.    while(1)  
  31.    {
  32.        usart_service();  //串口服务程序
  33.    }

  34. }




  35. /* 注释一:
  36. * 第3种方法,为了改进第2种方法的用户体验,用指针为函数多增加一个数组输出接口。
  37. * 这样,函数的数组既有输入接口,又有输出接口,已经堪称完美了。
  38. * 本程序中*p_ucInputBuffer输入接口,*p_ucOutputBuffer是输出接口。
  39. */
  40. void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer)//第3种方法 把一个数组从大到小排序
  41. {
  42.    unsigned char i;
  43.    unsigned char k;
  44.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量
  45.    unsigned char ucBuffer_3[const_array_size]; //第3种方法,参与具体排序算法的局部变量数组

  46.    for(i=0;i<const_array_size;i++)  
  47.    {
  48.       ucBuffer_3[i]=p_ucInputBuffer[i];  //参与排序算法之前,先把输入接口的数据全部搬移到局部变量数组中。
  49.    }


  50.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  51.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  52.    {
  53.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  54.           {
  55.              if(ucBuffer_3[const_array_size-1-k]>ucBuffer_3[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  56.                  {
  57.                      ucTemp=ucBuffer_3[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  58.              ucBuffer_3[const_array_size-1-1-k]=ucBuffer_3[const_array_size-1-k];
  59.              ucBuffer_3[const_array_size-1-k]=ucTemp;
  60.                  }
  61.           
  62.           }
  63.    }


  64.    for(i=0;i<const_array_size;i++)  
  65.    {
  66.       p_ucOutputBuffer[i]=ucBuffer_3[i];  //参与排序算法之后,把运算结果的数据全部搬移到输出接口中,方便外面程序调用
  67.    }
  68. }


  69. /* 注释二:
  70. * 第4种方法.指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,类似全局变量的特点。
  71. * 我们可以根据实际项目的情况,在必要的时候可以直接把输入接口和输出接口合并在一起,
  72. * 这种方法的缺点是没有把输入和输出分开,没有那么直观。但是优点也是很明显的,就是比较
  73. * 省程序ROM容量和数据RAM容量,而且运行效率也比较快。现在介绍给大家。
  74. * 本程序的*p_ucInputAndOutputBuffer是输入输出接口。
  75. */
  76. void big_to_small_sort_4(unsigned char *p_ucInputAndOutputBuffer)//第4种方法 把一个数组从大到小排序
  77. {
  78.    unsigned char i;
  79.    unsigned char k;
  80.    unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量

  81.    //以下就是著名的 冒泡法排序。详细讲解请找百度。
  82.    for(i=0;i<(const_array_size-1);i++)  //冒泡的次数是(const_array_size-1)次
  83.    {
  84.       for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
  85.           {
  86.              if(p_ucInputAndOutputBuffer[const_array_size-1-k]>p_ucInputAndOutputBuffer[const_array_size-1-1-k])  //后一个与前一个数据两两比较
  87.                  {
  88.                      ucTemp=p_ucInputAndOutputBuffer[const_array_size-1-1-k];     //通过一个中间变量实现两个数据交换
  89.              p_ucInputAndOutputBuffer[const_array_size-1-1-k]=p_ucInputAndOutputBuffer[const_array_size-1-k];
  90.              p_ucInputAndOutputBuffer[const_array_size-1-k]=ucTemp;
  91.                  }
  92.           
  93.           }
  94.    }


  95. }


  96. void usart_service(void)  //串口服务程序,在main函数里
  97. {

  98.      unsigned char i=0;   

  99.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  100.      {

  101.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

  102.             //下面的代码进入数据协议解析和数据处理的阶段

  103.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

  104.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  105.             {
  106.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  107.                {


  108.                                   for(i=0;i<const_array_size;i++)
  109.                                   {
  110.                      ucUsartBuffer[i]=ucRcregBuf[uiRcMoveIndex+3+i]; //从串口接收到的需要被排序的原始数据
  111.                                   }


  112.                   //第3种运算方法,依靠指针为函数增加一个数组的输出接口
  113.                                   //通过指针输出接口,排序运算后的结果直接从这个输出口中导出到ucGlobalBuffer_3数组中
  114.                   big_to_small_sort_3(ucUsartBuffer,ucGlobalBuffer_3);   //ucUsartBuffer是输入的数组,ucGlobalBuffer_3是接收排序结果的数组
  115.                   for(i=0;i<const_array_size;i++)
  116.                                   {
  117.                                     eusart_send(ucGlobalBuffer_3[i]);  //把用第3种方法排序后的结果返回给上位机观察
  118.                                   }

  119.                                   eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为第2种方法与第3种方法的分割线
  120.                                   eusart_send(0xee);
  121.                                   eusart_send(0xee);

  122.                   //第4种运算方法,依靠一个指针作为函数的输入输出接口。
  123.                                   //通过这个指针输入输出接口,ucGlobalBuffer_4数组既是输入数组,也是输出数组,排序运算后的结果直接存放在它本身,类似于全局变量的特点。
  124.                                   for(i=0;i<const_array_size;i++)
  125.                                   {
  126.                      ucGlobalBuffer_4[i]=ucUsartBuffer[i]; //把需要被排序的原始数据传递给接收输入输出数组ucGlobalBuffer_4,
  127.                                   }
  128.                   big_to_small_sort_4(ucGlobalBuffer_4);  
  129.                   for(i=0;i<const_array_size;i++)
  130.                                   {
  131.                                     eusart_send(ucGlobalBuffer_4[i]);  //把用第4种方法排序后的结果返回给上位机观察
  132.                                   }


  133.                   break;   //退出循环
  134.                }
  135.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  136.            }
  137.                                          
  138.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  139.   
  140.      }
  141.                         
  142. }

  143. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  144. {

  145.   ES = 0; //关串口中断
  146.   TI = 0; //清零串口发送完成中断请求标志
  147.   SBUF =ucSendData; //发送一个字节

  148.   delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

  149.   TI = 0; //清零串口发送完成中断请求标志
  150.   ES = 1; //允许串口中断

  151. }



  152. void T0_time(void) interrupt 1    //定时中断
  153. {
  154.   TF0=0;  //清除中断标志
  155.   TR0=0; //关中断


  156.   if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  157.   {
  158.           uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  159.       ucSendLock=1;     //开自锁标志
  160.   }



  161.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  162.   TL0=0x0b;
  163.   TR0=1;  //开中断
  164. }


  165. void usart_receive(void) interrupt 4                 //串口接收数据中断        
  166. {        

  167.    if(RI==1)  
  168.    {
  169.         RI = 0;

  170.             ++uiRcregTotal;
  171.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  172.         {
  173.            uiRcregTotal=const_rc_size;
  174.         }
  175.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  176.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  177.    
  178.    }
  179.    else  //发送中断,及时把发送中断标志位清零
  180.    {
  181.         TI = 0;
  182.    }
  183.                                                          
  184. }                                


  185. void delay_long(unsigned int uiDelayLong)
  186. {
  187.    unsigned int i;
  188.    unsigned int j;
  189.    for(i=0;i<uiDelayLong;i++)
  190.    {
  191.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  192.           {
  193.              ; //一个分号相当于执行一条空语句
  194.           }
  195.    }
  196. }

  197. void delay_short(unsigned int uiDelayShort)
  198. {
  199.    unsigned int i;  
  200.    for(i=0;i<uiDelayShort;i++)
  201.    {
  202.      ;   //一个分号相当于执行一条空语句
  203.    }
  204. }


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

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

  208.   //配置定时器
  209.   TMOD=0x01;  //设置定时器0为工作方式1
  210.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  211.   TL0=0x0b;


  212.   //配置串口
  213.   SCON=0x50;
  214.   TMOD=0X21;
  215.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  216.   TR1=1;

  217. }

  218. void initial_peripheral(void) //第二区 初始化外围
  219. {

  220.    EA=1;     //开总中断
  221.    ES=1;     //允许串口中断
  222.    ET0=1;    //允许定时中断
  223.    TR0=1;    //启动定时中断

  224. }
复制代码

总结陈词:
通过本章的学习,我们知道指针在函数接口中的双向性,这个双向性是一把双刃剑,既给我们带来便捷,也给我们在以下两个场合中带来隐患。
第一个场合:当需要把输入接口和输出接口分开时,我们希望输入接口的参数不要被意外改变,改变的仅仅只能是输出接口的数据。但是指针的双向性,就有可能导致我们在写函数内部代码的时候一不小心改变而没有发觉。
第二个场合:如果是一个现成封装好的函数直接给我们调用,当我们发现是指针作为接口的时候,我们就不敢确定这个接口是输入接口,还是输出接口,或者是输入输出接口,我们传递进去的参数可能会更改,除非用之前进行数据备份,否则是没有安全感可言的。
有没有办法巧妙的解决以上两个问题?当然有。欲知详情,请听下回分解-----为指针加上紧箍咒const,避免意外修改了只做输入接口的数据。

(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-5-19 06:50 , Processed in 0.519168 second(s), 16 queries .

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