搜索
bottom↓
楼主: 吴坚鸿

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

  [复制链接]

出0入0汤圆

发表于 2014-4-12 11:20:15 | 显示全部楼层
原来楼主是这么牛的呀。那要好好的跟鸿哥学习了。

出0入0汤圆

发表于 2014-4-12 12:49:45 | 显示全部楼层
来瞄两眼

出0入0汤圆

发表于 2014-4-12 14:25:27 | 显示全部楼层
吴坚鸿 发表于 2014-4-11 22:59
你说的那个辞职创业的兄弟是不是发明液晶万能测试仪并且获得了国家发明专利那个牛人?没错,那个兄弟就是 ...

http://www.amobbs.com/forum.php? ... C%E5%88%9B%E4%B8%9A
这是是楼主的另外一个号吗?

出0入0汤圆

发表于 2014-4-12 14:51:13 | 显示全部楼层
感觉很强大呀

出0入0汤圆

 楼主| 发表于 2014-4-12 21:26:32 | 显示全部楼层
ericdai 发表于 2014-4-12 14:25
http://www.amobbs.com/forum.php?mod=viewthread&tid=5573070&highlight=%E8%BE%9E%E8%81%8C%E5%88%9B%E ...

误会了,这个不是我的号。我以前在其它论坛上写过一篇自己早期创业的亲身经历,叫《血与泪的发明创造之路》。

出0入0汤圆

发表于 2014-4-12 22:12:47 | 显示全部楼层
学习学习~~

出0入0汤圆

发表于 2014-4-13 09:44:07 | 显示全部楼层
太强大了

出0入0汤圆

发表于 2014-4-13 18:19:32 | 显示全部楼层
顶一下

出0入0汤圆

发表于 2014-4-13 21:04:12 | 显示全部楼层
收藏

出0入0汤圆

发表于 2014-4-13 21:23:00 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 12:32
谁说“一女不可以侍二夫”?
我到处混,没有许身于任何论坛网站。
我是很开放的,目的就是分 ...

顶楼主,开源精神

出50入0汤圆

发表于 2014-4-13 23:46:42 来自手机 | 显示全部楼层
马克。。。!!!

出0入0汤圆

发表于 2014-4-14 07:27:48 | 显示全部楼层
讲的很详细,通俗易懂。

出0入0汤圆

发表于 2014-4-16 15:34:07 | 显示全部楼层
好文章,来支持下

出0入0汤圆

发表于 2014-4-16 16:37:55 | 显示全部楼层
收获不少,实用的经验.

出0入0汤圆

发表于 2014-4-16 17:08:28 | 显示全部楼层
讲得不错      

出0入0汤圆

发表于 2014-4-16 17:43:20 | 显示全部楼层
好资料                  

出0入0汤圆

发表于 2014-4-16 18:11:36 | 显示全部楼层
牛贴,珍藏中

出0入0汤圆

发表于 2014-4-16 19:06:24 | 显示全部楼层
先支持一下,民以食为天,先去吃饭。等下慢慢看。

出0入0汤圆

发表于 2014-4-16 19:48:56 | 显示全部楼层
先MARK ,有空细看

出200入0汤圆

发表于 2014-4-16 20:23:52 来自手机 | 显示全部楼层
支持…………!

出0入0汤圆

发表于 2014-4-16 21:52:06 来自手机 | 显示全部楼层
先标记一下子吧,学习了。

出0入0汤圆

发表于 2014-4-17 08:42:30 | 显示全部楼层
mark,学习一下!

出0入0汤圆

发表于 2014-4-17 09:04:16 | 显示全部楼层
支持楼主!

出0入0汤圆

发表于 2014-4-17 09:06:41 | 显示全部楼层
支持!!!跟着楼主慢慢学习!!!

出0入0汤圆

发表于 2014-4-17 09:17:50 | 显示全部楼层
好帖。。。。

出0入0汤圆

发表于 2014-4-17 09:18:54 | 显示全部楼层
好资料,谢谢分享!

出0入0汤圆

发表于 2014-4-17 09:22:17 | 显示全部楼层
有需要时看看。。mark

出0入0汤圆

发表于 2014-4-17 10:09:55 | 显示全部楼层
很好,学习了

出0入0汤圆

发表于 2014-4-17 10:45:48 | 显示全部楼层
学习一下下。

出0入0汤圆

发表于 2014-4-17 10:48:49 | 显示全部楼层
好人一生平安~~~

出0入0汤圆

发表于 2014-4-17 11:01:20 | 显示全部楼层
看到楼主说的误区 我只想说“呵呵”

出0入0汤圆

发表于 2014-4-17 11:07:14 | 显示全部楼层
强大的经验之谈啊

出0入0汤圆

 楼主| 发表于 2014-4-19 11:40:02 | 显示全部楼层
第四十一节:在串口接收中断里即时解析数据头的特殊程序框架。

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

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

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

出0入0汤圆

发表于 2014-4-20 16:01:15 | 显示全部楼层
很好收藏了

出0入0汤圆

发表于 2014-4-20 19:56:47 | 显示全部楼层
面学C,等学好了再来学,收藏!

出0入0汤圆

发表于 2014-4-20 20:08:38 | 显示全部楼层
mark                 

出0入0汤圆

发表于 2014-4-20 22:18:44 | 显示全部楼层
写得不错,期待更新,学习中。

出0入0汤圆

发表于 2014-4-20 22:33:19 | 显示全部楼层
好好好  小弟拜膜了

出0入0汤圆

 楼主| 发表于 2014-4-22 10:35:59 | 显示全部楼层
第四十二节:通过串口用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延时,这种程序框架是什么样的?欲知详情,请听下回分解-----通过串口用计数延时方式发送一串数据。

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

出0入0汤圆

发表于 2014-4-22 10:41:57 | 显示全部楼层
认真看一遍。。。

出0入0汤圆

发表于 2014-4-22 11:33:18 | 显示全部楼层
谢谢鸿哥!!

出0入0汤圆

发表于 2014-4-22 21:42:51 | 显示全部楼层
好好学习,天天向上!

出0入0汤圆

发表于 2014-4-22 23:19:33 | 显示全部楼层
O(∩_∩)O谢谢呐!!欢迎欢迎

出0入0汤圆

发表于 2014-4-23 03:36:32 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 11:29
第三节:累计主循环次数使LED灯闪烁。

开场白:

累计主循环次数使LED灯闪烁,正好可以用在我的单键密码锁制作上http://www.amobbs.com/thread-5576508-1-1.html,由于我的LED灯端口还要做按键输入用,所以我做了点修改,从原理上看,就只能用累计主循环次数使LED灯闪烁而不能用累计中断循环次数使LED灯闪烁,这样亮的时候太短了。
我现在遇到一个问题:在按键灯长亮的时候,如果我此时短按输入密码,那么第一个按键指示灯看起来没反应,因为此时灯是长亮的。程序应该执行先关长亮灯,延时一会,然后再调用短按闪灯,可如何写这个代码呢?

出0入0汤圆

发表于 2014-4-23 07:59:01 来自手机 | 显示全部楼层
很有感觉,支持鸿哥

出0入0汤圆

发表于 2014-4-23 15:26:52 | 显示全部楼层
跟着老师学习了!

出0入0汤圆

发表于 2014-4-23 17:10:22 | 显示全部楼层
标记,有时间需要仔细阅读

出0入0汤圆

发表于 2014-4-23 19:49:02 | 显示全部楼层
     学习中,一般工业产品中会有几个发光二极管,表示状态,在特定条件下闪烁,可能闪烁的频率还不一样,正在
根据前几讲内容,用状态机写一个通用程序。
     另外:楼主你平时开发产品,用模块化的编程方法吗?还是就一个C文件?

出0入0汤圆

发表于 2014-4-23 21:45:31 | 显示全部楼层
再来听讲,期待后文!

出0入0汤圆

发表于 2014-4-23 21:56:49 来自手机 | 显示全部楼层
顶,又一牛人,谢谢

出0入0汤圆

发表于 2014-4-24 16:46:28 来自手机 | 显示全部楼层
学习学习

出0入0汤圆

发表于 2014-4-24 19:36:53 | 显示全部楼层

复制代码
  1. 根据鸿哥第四节改编:  可以设定闪烁频率,灯闪烁可以开关控制的多个LED灯闪烁程序。

  2. 主程序:
  3. #include "REG52.H"
  4. #include "led_flicker.h"

  5. void initial_myself();   
  6. void initial_peripheral();
  7. void delay_long(unsigned int uiDelaylong);   
  8. void T0_time();  //定时中断函数

  9. void main()
  10. {
  11.    initial_myself();  
  12.    delay_long(100);   
  13.    initial_peripheral();

  14.    led_flicker_start(); //启动LED灯闪烁

  15.    while(1)   
  16.    {
  17.       led_flicker();

  18.    }
  19. }


  20. void T0_time() interrupt 1   
  21. {
  22.                 TF0=0;                //清除中断标志
  23.                 TR0=0;                //关中断

  24.                 if(ui_Freq1_Cnt<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  25.                 {
  26.                         ui_Freq1_Cnt++;      //累加定时中断的次数,
  27.                         ui_Freq2_Cnt++;
  28.                         ui_Freq3_Cnt++;
  29.                 }

  30.                 TH0=0xf8;             //重装初始值(65535-2000)=63535=0xf82f
  31.                 TL0=0x2f;
  32.                 TR0=1;                //开中断
  33. }


  34. void delay_long(unsigned int uiDelayLong)
  35. {
  36.    unsigned int i;
  37.    unsigned int j;
  38.    for(i=0;i<uiDelayLong;i++)
  39.    {
  40.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  41.           {
  42.              ;            //一个分号相当于执行一条空语句
  43.           }
  44.    }
  45. }


  46. void initial_myself()  //第一区 初始化单片机
  47. {
  48.     TMOD=0x01;  //设置定时器0为工作方式1
  49.         TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  50.         TL0=0x2f;
  51.        
  52.     initial_led();  //LED灭
  53. }
  54. void initial_peripheral() //第二区 初始化外围
  55. {
  56.   EA=1;     //开总中断
  57.   ET0=1;    //允许定时中断
  58.   TR0=1;    //启动定时中断

  59. }

  60. 闪烁程序:led_flicker.c
  61. #include "REG52.H"
  62. #include "led_flicker.h"

  63. /*  硬件连接:LED灯与单片机I/O口的连接  */
  64. sbit led1_dr  = P0^0;
  65. sbit led2_dr  = P0^1;
  66. sbit led3_dr  = P0^2;
  67. sbit led4_dr  = P0^3;


  68. //闪烁频率的步骤状态变量
  69. unsigned char uc_Freq1_Step = 0;          
  70. unsigned char uc_Freq2_Step = 0;
  71. unsigned char uc_Freq3_Step = 0;

  72. //闪烁频率的定时计数变量  
  73. unsigned int  ui_Freq1_Cnt,  ui_Freq2_Cnt,  ui_Freq3_Cnt;   

  74. // LED灯的闪烁频率设定
  75. unsigned int uc_Led1_Freq, uc_Led2_Freq, uc_Led3_Freq, uc_Led4_Freq;

  76. // LED灯的启动状态
  77. unsigned char LED1_START = 0;
  78. unsigned char LED2_START = 0;
  79. unsigned char LED3_START = 0;
  80. unsigned char LED4_START = 0;

  81. //函数声明
  82. void initial_led(void);
  83. void led_flicker_start(void);
  84. void Freq1_state_change(void);
  85. void Freq2_state_change(void);
  86. void Freq3_state_change(void);
  87. void led_flicker(void);


  88. /*  LED灯状态初始化:关闭所有的LED灯   */
  89. void initial_led(void)
  90. {
  91.         LED1_OFF();        LED2_OFF(); LED3_OFF(); LED4_OFF(); //开机:关闭所有的LED灯

  92.         uc_Led1_Freq = Freq_2S;                   //设定各个LED初始闪烁频率
  93.         uc_Led2_Freq = Freq_1S;                   // 闪烁频率中途可以重新设定。
  94.         uc_Led3_Freq = Freq_05S;
  95.         uc_Led4_Freq = Freq_05S;

  96.         LED1_START = 0;                      //开机关闭LED灯闪烁。
  97.         LED2_START = 0;
  98.         LED3_START = 0;
  99.         LED4_START = 0;               
  100. }

  101. void led_flicker_start(void)
  102. {
  103.         LED1_START = 1;
  104.         LED2_START = 1;
  105.         LED3_START = 1;
  106.         LED4_START = 1;
  107. }


  108. void led_flicker(void)
  109. {
  110.         Freq1_state_change();
  111.         Freq2_state_change();
  112.         Freq3_state_change();

  113.         /*--------------------------闪烁频率1:-----------------------*/
  114.     if(uc_Freq1_Step==0)  // 亮灯
  115.     {   
  116.                 if ( (LED1_START==1)&(uc_Led1_Freq == Freq_2S) )   LED1_ON();
  117.                 if ( (LED2_START==1)&(uc_Led2_Freq == Freq_2S) )   LED2_ON();
  118.                 if ( (LED3_START==1)&(uc_Led3_Freq == Freq_2S) )   LED3_ON();
  119.                 if ( (LED4_START==1)&(uc_Led4_Freq == Freq_2S) )   LED4_ON();
  120.         }
  121.         else if(uc_Freq1_Step==1)
  122.         {
  123.                 if ( (LED1_START==1)&(uc_Led1_Freq == Freq_2S) )   LED1_OFF();
  124.                 if ( (LED2_START==1)&(uc_Led2_Freq == Freq_2S) )   LED2_OFF();
  125.                 if ( (LED3_START==1)&(uc_Led3_Freq == Freq_2S) )   LED3_OFF();
  126.                 if ( (LED4_START==1)&(uc_Led4_Freq == Freq_2S) )   LED4_OFF();
  127.         }

  128.         /*--------------------------闪烁频率2:-----------------------*/
  129.     if(uc_Freq2_Step==0)  // 亮灯
  130.     {   
  131.                 if ( (LED1_START==1)&(uc_Led1_Freq == Freq_1S) )   LED1_ON();
  132.                 if ( (LED2_START==1)&(uc_Led2_Freq == Freq_1S) )   LED2_ON();
  133.                 if ( (LED3_START==1)&(uc_Led3_Freq == Freq_1S) )   LED3_ON();
  134.                 if ( (LED4_START==1)&(uc_Led4_Freq == Freq_1S) )   LED4_ON();
  135.         }
  136.         else if(uc_Freq2_Step==1)
  137.         {
  138.                 if ( (LED1_START==1)&(uc_Led1_Freq == Freq_1S) )   LED1_OFF();
  139.                 if ( (LED2_START==1)&(uc_Led2_Freq == Freq_1S) )   LED2_OFF();
  140.                 if ( (LED3_START==1)&(uc_Led3_Freq == Freq_1S) )   LED3_OFF();
  141.                 if ( (LED4_START==1)&(uc_Led4_Freq == Freq_1S) )   LED4_OFF();
  142.         }

  143.         /*--------------------------闪烁频率3:-----------------------*/
  144.     if(uc_Freq3_Step==0)  // 亮灯
  145.     {   
  146.                 if ( (LED1_START==1)&(uc_Led1_Freq == Freq_05S) )   LED1_ON();
  147.                 if ( (LED2_START==1)&(uc_Led2_Freq == Freq_05S) )   LED2_ON();
  148.                 if ( (LED3_START==1)&(uc_Led3_Freq == Freq_05S) )   LED3_ON();
  149.                 if ( (LED4_START==1)&(uc_Led4_Freq == Freq_05S) )   LED4_ON();
  150.         }
  151.         else if(uc_Freq3_Step==1)
  152.         {
  153.                 if ( (LED1_START==1)&(uc_Led1_Freq == Freq_05S) )   LED1_OFF();
  154.                 if ( (LED2_START==1)&(uc_Led2_Freq == Freq_05S) )   LED2_OFF();
  155.                 if ( (LED3_START==1)&(uc_Led3_Freq == Freq_05S) )   LED3_OFF();
  156.                 if ( (LED4_START==1)&(uc_Led4_Freq == Freq_05S) )   LED4_OFF();
  157.         }

  158. }



  159. void Freq1_state_change(void) //第三区 LED闪烁应用程序
  160. {
  161.      switch(uc_Freq1_Step)
  162.          {
  163.                  case 0:                  
  164.                      if(ui_Freq1_Cnt >= Freq_2S)            // 闪烁频率1:定时计数到。
  165.                      {
  166.                                 ET0=0;         
  167.                         ui_Freq1_Cnt=0;   
  168.                                 ET0=1;              
  169.                         uc_Freq1_Step = 1;
  170.                       }
  171.                           break;

  172.                  case 1:                  
  173.                      if(ui_Freq1_Cnt >= Freq_2S)            // 闪烁频率1:定时计数到。
  174.                      {
  175.                                 ET0=0;         
  176.                         ui_Freq1_Cnt=0;   
  177.                                 ET0=1;              
  178.                         uc_Freq1_Step = 0;
  179.                       }
  180.                           break;
  181.          }
  182. }

  183. void Freq2_state_change(void) //第三区 LED闪烁应用程序
  184. {
  185.      switch(uc_Freq2_Step)
  186.          {
  187.                  case 0:                  
  188.                      if(ui_Freq2_Cnt >= Freq_1S)            // 闪烁频率1:定时计数到。
  189.                      {
  190.                                 ET0=0;         
  191.                         ui_Freq2_Cnt=0;   
  192.                                 ET0=1;              
  193.                         uc_Freq2_Step = 1;
  194.                       }
  195.                           break;
  196.                  case 1:                  
  197.                      if(ui_Freq2_Cnt >= Freq_1S)            // 闪烁频率1:定时计数到。
  198.                      {
  199.                                 ET0=0;         
  200.                         ui_Freq2_Cnt=0;   
  201.                                 ET0=1;              
  202.                         uc_Freq2_Step = 0;
  203.                       }
  204.                           break;
  205.          }
  206. }

  207. void Freq3_state_change(void) //第三区 LED闪烁应用程序
  208. {
  209.      switch(uc_Freq3_Step)
  210.          {
  211.                  case 0:                  
  212.                      if(ui_Freq3_Cnt >= Freq_05S)            // 闪烁频率1:定时计数到。
  213.                      {
  214.                                 ET0=0;         
  215.                         ui_Freq3_Cnt=0;   
  216.                                 ET0=1;              
  217.                         uc_Freq3_Step = 1;
  218.                       }
  219.                           break;
  220.                  case 1:                  
  221.                      if(ui_Freq3_Cnt >= Freq_05S)            // 闪烁频率1:定时计数到。
  222.                      {
  223.                                 ET0=0;         
  224.                         ui_Freq3_Cnt = 0;   
  225.                                 ET0=1;              
  226.                         uc_Freq3_Step = 0;
  227.                       }
  228.                           break;
  229.          }
  230. }

  231. 头文件:led_flicker.h
  232. #ifndef _LED_FLICKER_H_
  233. #define _LED_FLICKER_H_

  234. #define LED1_ON()  led1_dr=0      //  I/O口:低电平--->LED灯亮。
  235. #define LED1_OFF() led1_dr=1          //  I/O口:高电平--->LED灯灭。
  236. #define LED2_ON()  led2_dr=0      
  237. #define LED2_OFF() led2_dr=1          
  238. #define LED3_ON()  led3_dr=0      
  239. #define LED3_OFF() led3_dr=1
  240. #define LED4_ON()  led4_dr=0      
  241. #define LED4_OFF() led4_dr=1

  242. #define Freq_2S   500
  243. #define Freq_1S   100
  244. #define Freq_05S  50

  245. extern unsigned int  ui_Freq1_Cnt,  ui_Freq2_Cnt,  ui_Freq3_Cnt;
  246. extern unsigned char LED1_START,    LED2_START,    LED3_START,    LED4_START;
  247. extern unsigned int  uc_Led1_Freq,  uc_Led2_Freq,  uc_Led3_Freq,  uc_Led4_Freq;
  248. extern void led_flicker_start(void);
  249. extern void initial_led(void);
  250. extern void led_flicker(void);
  251.                                                                   
  252. #endif
复制代码


出0入0汤圆

发表于 2014-4-24 20:14:18 | 显示全部楼层
很实用,好好学习一下

出0入0汤圆

 楼主| 发表于 2014-4-25 00:18:36 | 显示全部楼层
biying 发表于 2014-4-23 03:36
累计主循环次数使LED灯闪烁,正好可以用在我的单键密码锁制作上http://www.amobbs.com/thread-5576508-1- ...

这样具体的程序代码问题,我帮不上忙。解铃还须系铃人,只能靠你自己不断调试解决。

出0入0汤圆

 楼主| 发表于 2014-4-25 00:20:45 | 显示全部楼层
liuzp001 发表于 2014-4-23 19:49
学习中,一般工业产品中会有几个发光二极管,表示状态,在特定条件下闪烁,可能闪烁的频率还不一样, ...

2012年之前,我一直是用一个C源文件的,2012之后到现在,我是用多文件编译的。

出0入0汤圆

 楼主| 发表于 2014-4-25 00:24:51 | 显示全部楼层

很不错。一定要自己多动手编程序才会进步。当做到一定程度的时候,就可以随心所欲,下笔如有神,达到同步翻译的境界。

出0入0汤圆

发表于 2014-4-25 02:31:12 | 显示全部楼层
此贴我一定认真拜读!感谢鸿哥!

出0入0汤圆

发表于 2014-4-25 09:35:37 | 显示全部楼层
楼主,你的文章我读了前几节,不知道有没有多任务调度的,尤其是设计多个定时器参与的。

出0入0汤圆

 楼主| 发表于 2014-4-25 10:54:50 | 显示全部楼层
ypengfei 发表于 2014-4-25 09:35
楼主,你的文章我读了前几节,不知道有没有多任务调度的,尤其是设计多个定时器参与的。 ...

我的程序没有任务调度。我程序的核心思想是用switch语句实现多任务的并行处理,外加一个定时中断作为时间源。我的程序表面上看起来很简单,实际上就是这么简单。

出0入0汤圆

 楼主| 发表于 2014-4-25 10:55:34 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-26 10:11 编辑

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

开场白:
上一节讲了通过串口用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)源代码讲解如下:
#include "REG52.H"


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

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

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

#define const_key_time1  20    //按键去抖动延时的时间
#define const_key_time2  20    //按键去抖动延时的时间
#define const_key_time3  20    //按键去抖动延时的时间
#define const_key_time4  20    //按键去抖动延时的时间

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

void initial_myself(void);   
void initial_peripheral(void);
//void delay_short(unsigned int uiDelayshort);
void delay_long(unsigned int uiDelaylong);

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

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

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


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



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

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

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



unsigned char ucSendregBuf[const_send_size]; //串口发送数据的缓冲区数组

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

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

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

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

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

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

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

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


unsigned char ucSendStep=0;  //发送一串数据的运行步骤
unsigned int  uiSendTimeCnt=0; //累计主循环次数的计数延时器

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

void main()
{
   initial_myself();  
   delay_long(100);   
   initial_peripheral();
   while(1)  
   {
      key_service(); //按键服务的应用程序
          send_service();  //利用累计主循环次数的计数延时方式来发送一串数据
   }

}

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

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

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

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

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

   return ucMessageTemp;
}


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

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

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

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

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

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

                                ucSendStep=0; //维持现状,不切换
                                break;
                        }
                 }
             break;

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

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

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

             break;  
  }

}


void eusart_send(unsigned char ucSendData)
{

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

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

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

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

}


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


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

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

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

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


}


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


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

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

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

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

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

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

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

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

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

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

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

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

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

  }        
}



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

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

  if(ucVoiceLock==0) //原子锁判断
  {
     if(uiVoiceCnt!=0)
     {

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

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

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


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


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

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

      
   
   }
   else
   {
        TI = 0;    //发送中断,及时把发送中断标志位清零
   }
                                                         
}                                



//void delay_short(unsigned int uiDelayShort)
//{
//   unsigned int i;  
//   for(i=0;i<uiDelayShort;i++)
//   {
//     ;   //一个分号相当于执行一条空语句
//   }
//}


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)  //内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


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

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

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


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

}

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

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

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

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

出0入0汤圆

发表于 2014-4-25 11:10:49 | 显示全部楼层
mark下!

出0入0汤圆

发表于 2014-4-25 11:40:59 | 显示全部楼层
先找个位子坐下,慢慢学习。

出0入0汤圆

发表于 2014-4-25 11:41:50 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 16:25
第十九节:依次逐个点亮LED之后,再依次逐个熄灭LED的跑马灯程序。

开场白:

顶楼主一个!!!

出0入0汤圆

发表于 2014-4-26 10:52:55 | 显示全部楼层
mark, 非常好!

出0入0汤圆

发表于 2014-4-26 11:11:16 | 显示全部楼层
hao                  

出50入0汤圆

发表于 2014-4-27 13:34:27 | 显示全部楼层
43:     前面几个章节中,每个章节要么独立地讲解串口收数据,要么独立地讲解发数据,实际上在大部分的项目中,串口都需要“一收一应答”的握手协议,下一节开始我会把串口收发数据综合起来讲,这种完整的串口收发程序框架是什么样的?欲知详情,请听下回分解----- delay延时方式的串口收发综合程序实例


    坐等第44章内容:结果等不到了,或许对于楼主来说完整的收发程序会很简单,但对于初学者来说还是很渴望此部分内容,楼主可否。。。不管结果如何,还是感谢

出0入0汤圆

发表于 2014-4-27 20:53:06 | 显示全部楼层
等待楼主的下文!

出0入0汤圆

发表于 2014-4-27 22:31:20 | 显示全部楼层
看了好几章,感觉很好。群主后续能不能用多文件编译解说项目啊。

出0入0汤圆

发表于 2014-4-27 22:44:26 | 显示全部楼层
原來在電子發燒友論壇看過 吳堅鴻的帖子,很不錯,值得推薦!!

出0入0汤圆

 楼主| 发表于 2014-4-28 02:52:21 | 显示全部楼层
261854681 发表于 2014-4-27 13:34
43:     前面几个章节中,每个章节要么独立地讲解串口收数据,要么独立地讲解发数据,实际上在大部分的项目 ...

其实只要把第40节分别与第42或者第43结合起来,很容易实现这个功能。好吧,既然你提出来了,我下一节就继续讲串口的收发综合程序,满足你的要求。

出0入0汤圆

 楼主| 发表于 2014-4-28 02:56:42 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-28 02:58 编辑
xxzzhy 发表于 2014-4-27 22:31
看了好几章,感觉很好。群主后续能不能用多文件编译解说项目啊。


后续我不打算用多文件编译来解说项目,因为那样把本来很简单的程序分解成几大块来讲解,反而会显得更复杂,不清晰。但是,以后我会专门抽一两节来专门讲讲多文件编译的技术并且举例子讲解。

出0入0汤圆

 楼主| 发表于 2014-4-28 03:00:19 | 显示全部楼层
clarkewayne 发表于 2014-4-27 22:44
原來在電子發燒友論壇看過 吳堅鴻的帖子,很不錯,值得推薦!!

感谢你的肯定和支持。

出0入0汤圆

发表于 2014-4-28 08:15:39 | 显示全部楼层
咋没有第44讲了,楼主

出0入0汤圆

发表于 2014-4-28 09:35:27 | 显示全部楼层
太有料了,赞!

出0入0汤圆

发表于 2014-4-28 10:13:37 | 显示全部楼层
期待群主多文件编译技术解说早点出来。顶一个!!!!!!

出0入0汤圆

发表于 2014-4-28 21:54:12 | 显示全部楼层
吴坚鸿 发表于 2014-3-12 23:07
我一路走来,都是靠自己不断积累不断摸索的,积累到一定程度才形成现在的理论。我懂初学者缺什么,我知道 ...

大牛水平,跟心境一样高度!膜拜!

出0入0汤圆

 楼主| 发表于 2014-4-28 23:35:46 | 显示全部楼层
51ding 发表于 2014-4-28 08:15
咋没有第44讲了,楼主

会有的,这几天内会专门抽时间出来更新,请继续关注。

出0入0汤圆

发表于 2014-4-29 00:43:58 | 显示全部楼层
很好,边学边用,看了这个贴让我学会了很多东西,如果早一两年给我看到就好了。那我的工作就可能不是现在的位置了哈哈。

出0入0汤圆

 楼主| 发表于 2014-4-29 09:20:23 | 显示全部楼层
jeoo8888 发表于 2014-4-29 00:43
很好,边学边用,看了这个贴让我学会了很多东西,如果早一两年给我看到就好了。那我的工作就可能不是现在的 ...

谢谢你的高度赞扬,对我是一种鼓励。

出0入0汤圆

发表于 2014-4-29 11:43:17 | 显示全部楼层
期待早点看到楼主的更新

出0入0汤圆

发表于 2014-4-29 12:15:17 | 显示全部楼层

支持一下,mark学习!

出0入0汤圆

发表于 2014-4-29 16:59:34 | 显示全部楼层
鸿哥你好!
能对比一下两种菜单设计方法,结构体,函数指针的方法,与你的方法。
各自优缺点。

出0入0汤圆

 楼主| 发表于 2014-4-30 00:08:13 | 显示全部楼层
liuzp001 发表于 2014-4-29 16:59
鸿哥你好!
能对比一下两种菜单设计方法,结构体,函数指针的方法,与你的方法。
各自优缺点。 ...

我没了解过你所说的结构体,函数指针的菜单设计方法,因此我不方便对比评论。我只知道我用switch语句的菜单方法是万能的,可以应付任何菜单项目的设计,而且简单易懂,非常直观。我觉得结构体和指针就像古代的文言文,字虽少但是不易懂,我本人更喜欢白话文,因此我编程序尽量不用结构体和指针,所以我现在分享的连载技术贴非常受初学者欢迎。

出0入0汤圆

发表于 2014-4-30 09:20:31 | 显示全部楼层
真心感谢!

出0入0汤圆

发表于 2014-5-1 10:56:25 | 显示全部楼层
楼主44章还没有好呀。期待啊

出0入0汤圆

发表于 2014-5-1 11:41:40 | 显示全部楼层
顶 鸿哥 好资料

出0入0汤圆

发表于 2014-5-1 11:46:01 | 显示全部楼层
想不到这么长,支持。

出0入0汤圆

发表于 2014-5-1 12:24:37 | 显示全部楼层
作为初学者,非常支持楼主的无私奉献

出0入0汤圆

发表于 2014-5-1 15:01:22 | 显示全部楼层
初学者,走过

出0入0汤圆

发表于 2014-5-1 17:44:02 | 显示全部楼层
必须Mark 正在学习中

出0入0汤圆

发表于 2014-5-2 15:33:13 来自手机 | 显示全部楼层
期待!!!!

出0入0汤圆

发表于 2014-5-3 00:24:14 | 显示全部楼层
楼主很资深这不假,但是楼主你写的代码能复用吗? 你考虑过设计模式吗? 你做了近十年的单片机,现在还是一个项目从头做起吗? C语言的好处在于指针和结构体,你的第一篇里就否定了这两项,真的好么?

出0入0汤圆

发表于 2014-5-3 00:51:12 | 显示全部楼层
真是非常好的贴子,需要学习一下。

出0入0汤圆

发表于 2014-5-3 07:49:40 | 显示全部楼层
群主..........

出0入0汤圆

 楼主| 发表于 2014-5-3 08:04:48 | 显示全部楼层
第四十四节:从机的串口收发综合程序框架

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

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

出0入0汤圆

 楼主| 发表于 2014-5-3 08:21:44 | 显示全部楼层
千樱硕 发表于 2014-5-3 00:24
楼主很资深这不假,但是楼主你写的代码能复用吗? 你考虑过设计模式吗? 你做了近十年的单片机,现在还是一 ...

(1)我代码的最大优点就是可以复用,正因为可以复用,我才会分享给大家。我相信真正深入学习我代码的初学者,一定会有非常强烈的认同感,他们一定会发现我的代码不但简单,并且非常有规律,规律就是你所说的复用。
(2)我并没有否认C语言中指针与结构体的价值。有很多初学者一开始可能学不了那么多C语言的高级功能,比如像我,当初毕业后才知道可以用C语言开发单片机,然后我重新拿起C语言的书来看,当时我就是不理解C语言的很多高级应用,但是并不影响我开发单片机项目,直到我工作两年后,编的程序多了,我就自然而然理解了指针和结构体。总结:我的观点是,一部分学习能力不是非常强的初学者在连一个程序都还没编过的情况下,这个时候最好的办法就是让他删繁就简,快速进入到开发编程的状态,先尝试编程的快乐再循序渐进地学,学习效率更好。

出0入0汤圆

 楼主| 发表于 2014-5-3 08:25:57 | 显示全部楼层
261854681 发表于 2014-4-27 13:34
43:     前面几个章节中,每个章节要么独立地讲解串口收数据,要么独立地讲解发数据,实际上在大部分的项目 ...

根据你的要求,44节已经更新发布了,请查看。

出0入0汤圆

发表于 2014-5-3 08:28:18 | 显示全部楼层
为楼主的这种精神,果断顶起!国内需要你这样的人!

出0入0汤圆

发表于 2014-5-3 15:27:46 | 显示全部楼层
感谢群主的无私奉献。资料很不错。能否快点出一节解说多文件编译的啊

出0入0汤圆

 楼主| 发表于 2014-5-3 16:33:33 | 显示全部楼层
xxzzhy 发表于 2014-5-3 15:27
感谢群主的无私奉献。资料很不错。能否快点出一节解说多文件编译的啊

多文件编译我短期内不会那么快。你急用的话可以在百度上找找其它资料。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 21:25

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表