98

帖子

0

TA的资源

一粒金砂(高级)

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

开场白:
上一节讲了判断数据头的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,数据地址,有效数据长度,有效数据,数据校验的通讯协议。这一节要教会大家三个知识点:
第一个:常用自定义串口通讯协议的程序框架。
第二个:累加校验和的校验方法。累加和的意思是前面所有字节的数据相加,超过一个字节的溢出部分会按照固定的规则自动丢弃,不用我们管。比如以下数据:
      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[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. }
复制代码

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

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

回复

98

帖子

0

TA的资源

一粒金砂(高级)

42
 
第四十一节:在串口接收中断里即时解析数据头的特殊程序框架。

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

(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延时方式发送一串数据。

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

回复

98

帖子

0

TA的资源

一粒金砂(高级)

43
 

第四十二节:通过串口用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延时,这种程序框架是什么样的?欲知详情,请听下回分解-----通过串口用计数延时方式发送一串数据。

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

回复

98

帖子

0

TA的资源

一粒金砂(高级)

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

开场白:
上一节讲了通过串口用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进行掉电后的数据保存。

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

回复

98

帖子

0

TA的资源

一粒金砂(高级)

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

开场白:
根据上一节的预告,本来这一节内容打算讲“利用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[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. }










复制代码

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

回复

98

帖子

0

TA的资源

一粒金砂(高级)

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

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

第一个:为了保证串口中断接收的数据不丢失,在初始化时必须设置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是启动发送数据和复位按键,当系统处于待机状态时,按下此按键会启动发送数据;当通讯超时蜂鸣器报警时,可以按下此键清除报警,返回到待机的状态。

第二:通过串口把更改的参数发送给从机。
波特率是: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秒钟,否则认为通讯超时主机会重发数据,如果连续三次都没有返回,则引发蜂鸣器报警。如果接收到得数据校验正确,主机继续发送新的一串数据,直到把4串数据发送完毕为止。

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

通过电脑的串口助手来模拟从机,返回不同的应答
从机返回校验正确应答:eb 00 55 f5 00 00 35
从机返回校验出错应答:eb 00 55 fa 00 00 3a

(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[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进行掉电后的数据保存。
  
(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

47
 
第四十六节:利用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单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

48
 
第四十七节:操作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做一个实时时钟  。
(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

49
 
第四十八节:利用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做一个温控器  。

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

回复

98

帖子

0

TA的资源

一粒金砂(高级)

50
 
第四十九节:利用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采集电压的模拟信号。

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

回复

98

帖子

0

TA的资源

一粒金砂(高级)

51
 
第五十节:利用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次一致性的方法进行滤波处理。

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

回复

9

帖子

0

TA的资源

一粒金砂(初级)

52
 
真的很不错哦,希望楼主坚持下去,我也会跟着好好学习。谢谢啊!
此帖出自51单片机论坛
 
 
 

回复

12

帖子

0

TA的资源

一粒金砂(初级)

53
 
学习了,不错不错
此帖出自51单片机论坛
 
 
 

回复

450

帖子

43

TA的资源

一粒金砂(高级)

54
 
好像没看到有用结构体,像多个按键哪个用结构体,结构会更清晰。
宏定义的使用似乎也很少看到
此帖出自51单片机论坛
 
个人签名一心一意,精益求精
 
 

回复

1799

帖子

0

TA的资源

五彩晶圆(初级)

55
 
看了前面几节,写的很不错,对新手有很大帮助。
我的框架跟你的类似,不过我把计时全部放在定时中断处理中
类似于:
  1. #pragma vector=__TA_MATCH_CAPTURE_vector                  
  2. __interrupt void TimerAInt(void)
  3. {
  4.         TACON&=0xfd;
  5.         timeCnt++;
  6.         timeCnt2++;;
  7.         if(timeCnt>=5)
  8.         {
  9.                 timeCnt=0;
  10.                 bit_5ms=1;
  11.         }
  12.         if(timeCnt2>=200)
  13.         {
  14.                 timeCnt2=0;
  15.                 bit_key200ms=1;
  16.         }

  17. }
复制代码

主程序则是规定了每个时间分区的时间长度
  1. while(1)
  2.         {                         
  3.                 if(bit_5ms)
  4.                   {
  5.                            bit_5ms=0;
  6.                         CLRWDT();
  7.                         taskTime++;
  8.                         switch(taskTime) //每10ms扫描一次主循环
  9.                         {
  10.                                 case 1:
  11.                                         WritePrgDataToIIC();
  12.                                         Time500msDo();
  13.                                         GetRoomTempAd();
  14.                                         RF_RX();
  15.                                         RF_TX();
  16.                                         break;
  17.                                 case 2:
  18.                                         SystemWork();
  19.                                         LcdDisp();
  20.                                         KeyScan();
  21.                                         KeyDeal();
  22.                                         WriteSysDataToEeprom();
  23.                                         taskTime=0;
  24.                                         break;
  25.                                 default:break;
  26.                         }
  27.                 }
  28.         }
  29. }
复制代码


不过这样做法要注意每个时间片的执行总时间长不能超过5ms,或者也可以更改每个时间片的时间长度
表达不是很清楚,如果写的不好,望大家指教
此帖出自51单片机论坛
 
 
 

回复

2700

帖子

0

TA的资源

五彩晶圆(初级)

56
 
很强大,顶楼主
此帖出自51单片机论坛
 
个人签名作为一个水军,就是尽量的多回帖,因为懂的技术少,所以回帖水分大,见谅!
EEWORLD开发板置换群:309018200,——电工们免费装β的天堂,商家勿入!加群暗号:喵
 
 

回复

2453

帖子

19

TA的资源

五彩晶圆(中级)

57
 
楼主辛苦了
此帖出自51单片机论坛
 
个人签名    懒得很
 
 

回复

3

帖子

0

TA的资源

一粒金砂(初级)

58
 
顶楼主!!!!学习学习了~~
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

59
 
Laspide 发表于 2014-6-10 08:46
好像没看到有用结构体,像多个按键哪个用结构体,结构会更清晰。
宏定义的使用似乎也很少看到

我的看法给你恰恰相反。我认为结构体和宏定义用的越少,程序越简单清晰。除非有一些需要经常更改的参数,否则尽量不要用宏定义,直接用数字比用英文字母直观多了。结构体我几乎在任何项目上都不用,到目前为止,我还没有领悟到结构体有什么特别优越之处。
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

60
 
Laspide 发表于 2014-6-10 08:46
好像没看到有用结构体,像多个按键哪个用结构体,结构会更清晰。
宏定义的使用似乎也很少看到

我的看法给你恰恰相反。我认为结构体和宏定义用的越少,程序越简单清晰。除非有一些需要经常更改的参数,否则尽量不要用宏定义,直接用数字比用英文字母直观多了。结构体我几乎在任何项目上都不用,到目前为止,我还没有领悟到结构体有什么特别优越之处。
此帖出自51单片机论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表