搜索
bottom↓
楼主: 吴坚鸿

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

  [复制链接]

出0入0汤圆

 楼主| 发表于 2018-9-18 11:01:16 | 显示全部楼层
第一百二十九节: 接收带“动态密匙”与“累加和”校验数据的串口程序框架。

【129.1   “累加和”与“动态密匙”。】

      上一节讲了串口基本的程序框架,但是没有讲到校验。校验在很多通信项目中是必不可少的。比如,在事关金融或者生命安全的项目,是不允许有任何的数据丢失或错误的;在容易受干扰的工业环境,或者在无线通信的项目中,这些项目往往容易丢失数据;还有一种常见的人为过失是,在编写程序的层面,因为超时重发的时间与从机不匹配,导致反馈的信息延时而造成数据丢失,如果这种情况也加上校验,通信会稳定可靠很多。
      上一节讲到“数据头,数据类型,数据长度,其它数据”这四个元素,本节在此基础上,增加两个校验的元素,分别是“动态密匙”与“累加和”。“动态密匙”占用2个字节,“累加和”占用1个字节,因此,这两个元素一共占用最后面的3个字节。分析如下:
       数据头(EB):占1个字节,作为“起始字节”,起到“接头暗号”的作用,平时用来过滤无关的数据。
       数据类型(01):占用1个字节。数据类型是用来定义这串数据的用途。
       数据长度(00 00 00 0B):占4个字节。用来告诉通信的对方,这串数据一共有多少个字节。
       其它数据(03 E8):此数据根据不同的“数据类型”可以用来做不同的用途,根据具体的项目而定。
       动态密匙(00 01):这两个字节代表一个unsigned int类型的数据,数据范围是从0到65535,但是考虑到数据更加安全可靠,一般丢弃了首尾的0(十六进制的00 00)与65535(十六进制的FF FF),只保留从1到65534的变化。大部分的通信模型都是主机对从机的“一问一应答”模式,也就是,主机每发送一条指令给从机,从机才返回一条消息作为应答。如果主机发送了信息后,在规定的时间内,没有收到从机的应答指令,主机就继续发送信息给从机,但是此时,从机本来应该应答主机当前指令的,可能因为某种情况导致反馈的信息发生了延时,导致此时应答的数据是主机的上一条指令,从而造成“一问一应答”的数据帧发送了错位,这种情况加上“动态密匙”就能使问题得到有效的解决。主机每发送一条信息,信息里都携带了2个字节的“动态密匙”,从机每收到主机的一条信息,在应答此信息时都把收到的“动态密匙”原封不动的反馈给主机,主机再查看发送的“动态密匙”与接收到的“动态密匙”是否一致,以此来判断应答数据是否有效。“动态密匙”像流水号一样,每发送一次指令后都累加1,不断发生变化,从1到65534,依次循环。这就是数据校验的一种方式。
       累加和(E3)。“累加和”放在数据串的最后一个字节,是前面所有字节的累加之和(不包括自己本身的字节),累加的结果高于一个字节的那部分自动溢出丢掉,只保留低8位的一个字节的数据。比如:本例子中,数据串是:EB 01 00 00 00 0B 03 E8 00 01 E3。其中最后一个字节E3就是“累加和”,前面所有字节相加等于十六进制的0x1E3,只保留低8位的一个字节的数据,因此为十六进制的0xE3。验证“累加和”的方法,可以借用电脑“附件”自带的“计算器”软件来实现,打开“计算器”软件后,在“查看”的下拉菜单里,选择“程序员”,然后选择“十六进制”。不管是主机还是从机,每接收到一串数据后,都要自己计算一次“累加和”,把自己计算得到的“累加和”与接收到的最后一个字节的“累加和”进行对比,来判断接收到的数据是否发生了丢失或者错误。

【129.2   程序例程。】


   
       上图129.2.1  有源蜂鸣器电路




   
       上图129.2.2  232串口电路

程序功能如下:
      (1)单片机模拟从机,上位机的串口助手模拟主机。在上位机的串口助手里,发送一串数据,控制蜂鸣器发出不同长度的声音。
      (2)本节因为还没有讲到数据发送的内容,因此应答那部分的代码暂时不写,只写“累加和”那部分的代码。
      (3)波特率9600,校验位NONE(无),数据位8,停止位1。
      (4)十六进制的数据格式:EB 01 00 00 00 0B XX XX YY YY ZZ 。其中:
       EB是数据头。
       01是代表数据类型。
       00 00 00 0B代表数据长度是11个(十进制)。
       XX XX代表一个unsigned int的数据,此数据的大小决定了蜂鸣器发出声音的长度。
       YY YY代表一个unsigned int的动态密匙,每收发一条指令,此数据累加一次1,范围从1到65534。
       ZZ 代表前面所有字节的累加和。
       比如:
       让蜂鸣器鸣叫1000毫秒,密匙为00 01,发送十六进制的:EB 01 00 00 00 0B 03 E8 00 01 E3
       让蜂鸣器鸣叫100毫秒, 密匙为00 02,发送十六进制的:EB 01 00 00 00 0B 00 64 00 02 5D

  1. #include "REG52.H"

  2. #define RECE_TIME_OUT    2000  //通信过程中字节之间的超时时间2000ms
  3. #define REC_BUFFER_SIZE  20    //接收数据的缓存数组的长度


  4. void usart(void);  //串口接收的中断函数
  5. void T0_time();    //定时器的中断函数

  6. void UsartTask(void);    //串口接收的任务函数,放在主函数内

  7. void SystemInitial(void) ;
  8. void Delay(unsigned long u32DelayTime) ;
  9. void PeripheralInitial(void) ;

  10. void BeepOpen(void);   
  11. void BeepClose(void);
  12. void VoiceScan(void);

  13. sbit P3_4=P3^4;  

  14. volatile unsigned char vGu8BeepTimerFlag=0;  
  15. volatile unsigned int vGu16BeepTimerCnt=0;  

  16. unsigned char Gu8ReceBuffer[REC_BUFFER_SIZE]; //开辟一片接收数据的缓存
  17. unsigned long Gu32ReceCnt=0;  //接收缓存数组的下标
  18. unsigned char Gu8ReceStep=0;  //接收中断函数里的步骤变量
  19. unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作变量。
  20. unsigned char Gu8ReceType=0; //接收的数据类型
  21. unsigned int Gu16ReceYY=0; //接收的动态密匙
  22. unsigned char Gu8ReceZZ=0; //接收的累加和,必须是unsigned char的数据类型
  23. unsigned long Gu32ReceDataLength=0;  //接收的数据长度
  24. unsigned char Gu8FinishFlag=0;  //是否已接收完成一串数据的标志
  25. unsigned long *pu32Data; //用于数据转换的指针
  26. volatile unsigned char vGu8ReceTimeOutFlag=0;//通信过程中字节之间的超时定时器的开关
  27. volatile unsigned int vGu16ReceTimeOutCnt=0; //通信过程中字节之间的超时定时器,“喂狗”的对象

  28. void main()
  29. {
  30. SystemInitial();            
  31. Delay(10000);               
  32. PeripheralInitial();      
  33.     while(1)  
  34. {  
  35.    UsartTask();   //串口接收的任务函数
  36.     }
  37. }

  38. void usart(void) interrupt 4   //串口接发的中断函数,中断号为4         
  39. {        
  40.    if(1==RI)  //接收完一个字节后引起的中断
  41.    {
  42.         RI = 0; //及时清零,避免一直无缘无故的进入中断。

  43. /* 注释一:
  44. * 以下Gu8FinishFlag变量的用途。
  45. * 此变量一箭双雕,0代表正处于接收数据的状态,1代表已经接收完毕并且及时通知主函数中的处理函数
  46. * UsartTask()去处理新接收到的一串数据。除此之外,还起到一种“自锁自保护”的功能,在新数据还
  47. * 没有被主函数处理完毕的时候,禁止接收其它新的数据,避免新数据覆盖了尚未处理的数据。
  48. */
  49.            if(0==Gu8FinishFlag)  //1代表已经完成接收了一串新数据,并且禁止接收其它新的数据
  50.            {

  51. /* 注释二:
  52. * 以下Gu8ReceFeedDog变量的用途。
  53. * 此变量是用来检测并且识别通信过程中相邻的字节之间是否存在超时的情况。
  54. * 如果大家听说过单片机中的“看门狗”这个概念,那么每接收到一个数据此变量就“置1”一次,它的
  55. * 作用就是起到及时“喂狗”的作用。每接收到一个数据此变量就“置1”一次,在主函数里,相关
  56. * 的定时器就会被重新赋值,只要这个定时器能不断及时的被补充新的“能量”新的值,那么这个定时器
  57. * 就永远不会变成0,只要不变成0就不会超时。如果两个字节之间通信时间超过了固定的长度,就意味
  58. * 着此定时器变成了0,这时就需要把中断函数里的接收步骤Gu8Step及时切换到“接头暗号”的步骤。
  59. */
  60.                   Gu8ReceFeedDog=1; //每接收到一个字节的数据,此标志就置1及时更新定时器的值。
  61.                   switch(Gu8ReceStep)
  62.                   {
  63.                           case 0:     //接头暗号的步骤。判断数据头的步骤。
  64.                                    Gu8ReceBuffer[0]=SBUF; //直接读取刚接收完的一个字节的数据。
  65.                                    if(0xeb==Gu8ReceBuffer[0])  //等于数据头0xeb,接头暗号吻合。
  66.                                    {
  67.                                           Gu32ReceCnt=1; //接收缓存的下标
  68.                                           Gu8ReceStep=1;  //切换到下一个步骤,接收其它有效的数据
  69.                                    }
  70.                                    break;               
  71.                                        
  72.                           case 1:     //数据类型和长度
  73.                                    Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
  74.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备
  75.                                    if(Gu32ReceCnt>=6)  //前6个数据。接收完了“数据类型”和“数据长度”。
  76.                                    {
  77.                                             Gu8ReceType=Gu8ReceBuffer[1];  //提取“数据类型”
  78. //以下的数据转换,在第62节讲解过的指针法
  79.                                                 pu32Data=(unsigned long *)&Gu8ReceBuffer[2]; //数据转换
  80.                                                 Gu32ReceDataLength=*pu32Data; //提取“数据长度”
  81.                                             if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
  82.                                                 {
  83.                                                         Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  84.                                                         Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  85.                                                 }
  86.                                                 else   //如果还没结束,继续切换到下一个步骤,接收“其它数据”
  87.                                                 {
  88.                                                         Gu8ReceStep=2;   //切换到下一个步骤
  89.                                                 }                                                        
  90.                                    }
  91.                                    break;               
  92.                           case 2:     //其它数据
  93.                                    Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
  94.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备

  95. //靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
  96.                                    if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=REC_BUFFER_SIZE)
  97. {
  98.                                           Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  99.                                           Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  100.                                    }
  101.                                    break;        
  102.                   }
  103.        }
  104.    }
  105.    else  //发送数据引起的中断
  106.    {
  107.         TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  108.         //以下可以添加一个全局变量的标志位的相关代码,通知主函数已经发送完一个字节的数据了。
  109.    }                                                      
  110. }  


  111. void UsartTask(void)    //串口接收的任务函数,放在主函数内
  112. {
  113. static unsigned int *pSu16Data; //数据转换的指针
  114. static unsigned int Su16Data;  //转换后的数据
  115. static unsigned int i;
  116. static unsigned char Su8RecZZ=0;  //计算的“累加和”,必须是unsigned char的数据类型



  117.     if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及时更新一次“超时检测的定时器”的初值
  118.         {
  119.                 Gu8ReceFeedDog=0;
  120.                                 
  121.                 vGu8ReceTimeOutFlag=0;
  122.         vGu16ReceTimeOutCnt=RECE_TIME_OUT;//更新一次“超时检测的定时器”的初值
  123.                 vGu8ReceTimeOutFlag=1;
  124.         }
  125.         else if(Gu8ReceStep>0&&0==vGu16ReceTimeOutCnt) //超时,并且步骤不在接头暗号的步骤
  126.         {
  127.             Gu8ReceStep=0; //串口接收数据的中断函数及时切换回接头暗号的步骤
  128.     }

  129.         
  130.         if(1==Gu8FinishFlag)  //1代表已经接收完毕一串新的数据,需要马上去处理
  131.         {
  132.                 switch(Gu8ReceType)  //接收到的数据类型
  133.                 {
  134.                         case 0x01:   //驱动蜂鸣器
  135. //以下的数据转换,在第62节讲解过的指针法

  136.                                  pSu16Data=(unsigned int *)&Gu8ReceBuffer[Gu32ReceDataLength-3]; //数据转换
  137. Gu16ReceYY=*pSu16Data; //提取“动态密匙”。本例子中暂时不做返回应答的处理

  138. Gu8ReceZZ=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取“累加和”

  139. Su8RecZZ=0;
  140. for(i=0;i<(Gu32ReceDataLength-1);i++)
  141. {
  142. Su8RecZZ=Su8RecZZ+Gu8ReceBuffer[i];   //计算“累加和”
  143. }

  144. if(Su8RecZZ==Gu8ReceZZ) //验证“累加和”,“计算的”与“接收的”是否一致
  145. {
  146.                               pSu16Data=(unsigned int *)&Gu8ReceBuffer[6]; //数据转换。
  147.                                      Su16Data=*pSu16Data; //提取“蜂鸣器声音的长度”

  148. vGu8BeepTimerFlag=0;  
  149. vGu16BeepTimerCnt=Su16Data;   //让蜂鸣器鸣叫
  150. vGu8BeepTimerFlag=1;  
  151. }

  152.                
  153.                   break;
  154.         }

  155.         Gu8FinishFlag=0;  //上面处理完数据再清零标志,为下一次接收新的数据做准备
  156.     }
  157. }

  158. void T0_time() interrupt 1     
  159. {
  160. VoiceScan();  

  161. if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通信过程中字节之间的超时定时器
  162.         {
  163.                  vGu16ReceTimeOutCnt--;        
  164. }  

  165. TH0=0xfc;   
  166. TL0=0x66;   
  167. }


  168. void SystemInitial(void)
  169. {
  170. unsigned char u8_TMOD_Temp=0;

  171. //以下是定时器0的中断的配置
  172. TMOD=0x01;  
  173. TH0=0xfc;   
  174. TL0=0x66;   
  175. EA=1;      
  176. ET0=1;      
  177. TR0=1;   

  178. //以下是串口接收中断的配置
  179. //串口的波特率与内置的定时器1直接相关,因此配置此定时器1就等效于配置波特率。
  180. u8_TMOD_Temp=0x20; //即将把定时器1设置为:工作方式2,初值自动重装的8位定时器。
  181. TMOD=TMOD&0x0f; //此寄存器低4位是跟定时器0相关,高4位是跟定时器1相关。先清零定时器1。
  182. TMOD=TMOD|u8_TMOD_Temp;  //把高4位的定时器1填入0x2,低4位的定时器0保持不变。
  183. TH1=256-(11059200L/12/32/9600);  //波特率为9600。11059200代表晶振11.0592MHz,
  184. TL1=256-(11059200L/12/32/9600);  //L代表long的长类型数据。根据芯片手册提供的计算公式。
  185. TR1=1;  //开启定时器1

  186. SM0=0;  
  187. SM1=1;  //SM0与SM1的设置:选择10位异步通信,波特率根据定时器1可变  
  188. REN=1;  //允许串口接收数据

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

  192. ES=1;         //允许串口中断  
  193. EA=1;         //允许总中断
  194. }

  195. void Delay(unsigned long u32DelayTime)
  196. {
  197.     for(;u32DelayTime>0;u32DelayTime--);
  198. }

  199. void PeripheralInitial(void)
  200. {

  201. }

  202. void BeepOpen(void)
  203. {
  204. P3_4=0;  
  205. }

  206. void BeepClose(void)
  207. {
  208. P3_4=1;  
  209. }

  210. void VoiceScan(void)
  211. {

  212.           static unsigned char Su8Lock=0;  

  213. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  214.           {
  215.                   if(0==Su8Lock)
  216.                   {
  217.                    Su8Lock=1;  
  218. BeepOpen();
  219.      }
  220.     else  
  221. {     

  222.                        vGu16BeepTimerCnt--;         

  223.                    if(0==vGu16BeepTimerCnt)
  224.                    {
  225.                            Su8Lock=0;     
  226. BeepClose();  
  227.                    }

  228. }
  229.           }         
  230. }



复制代码

本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2018-9-18 11:50:39 | 显示全部楼层
谢谢鸿哥!

出0入0汤圆

发表于 2018-9-19 18:03:26 | 显示全部楼层
牛,有没有整理出完整的文档呢?有书卖吗?

出0入0汤圆

 楼主| 发表于 2018-10-4 11:14:10 | 显示全部楼层
第一百三十节: 接收带“动态密匙”与“异或”校验数据的串口程序框架。

【130.1   “异或”的校验。】

      通信的校验常用有两种,一种是“累加和”,另一种是“异或”。“异或”算法的详细介绍请看前面章节的第32节。
      上一节讲的“累加和”,放在数据串的最后一个字节,是前面所有字节的累加之和(不包括自己本身的字节),累加的结果高于一个字节的那部分自动溢出丢掉,只保留低8位的一个字节的数据。本节讲的“异或”,也是放在数据串的最后一个字节,是前面所有字节的异或结果(不包括自己本身的字节)。本节在上一节的基础上,只更改以下这段校验算法的代码即可。

      上一节的“累加和”算法如下:

  1. Gu8ReceZZ=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取“累加和”

  2. Su8RecZZ=0;
  3. for(i=0;i<(Gu32ReceDataLength-1);i++)
  4. {
  5. Su8RecZZ=Su8RecZZ+Gu8ReceBuffer[i];   //计算“累加和”
  6. }

  7. if(Su8RecZZ==Gu8ReceZZ) //验证“累加和”,“计算的”与“接收的”是否一致
  8. {
  9.     //此处省去若干代码
  10. }
复制代码

      本节的“异或”算法如下:

  1. Gu8ReceZZ=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取接收到的“异或”

  2. Su8RecZZ=Gu8ReceBuffer[0]; //提取数据串第“i=0”个数据作为异或的原始数据
  3. for(i=1;i<(Gu32ReceDataLength-1);i++) //注意,这里是从第“i=1”个数据开始
  4. {
  5. Su8RecZZ=Su8RecZZ^Gu8ReceBuffer[i];   //计算“异或”
  6. }

  7. if(Su8RecZZ==Gu8ReceZZ) //验证“异或”,“计算的”与“接收的”是否一致
  8. {
  9.     //此处省去若干代码
  10. }
复制代码

【130.2   通信协议。】

       数据头(EB):占1个字节,作为“起始字节”,起到“接头暗号”的作用,平时用来过滤无关的数据。
       数据类型(01):占用1个字节。数据类型是用来定义这串数据的用途。
       数据长度(00 00 00 0B):占4个字节。用来告诉通信的对方,这串数据一共有多少个字节。
       其它数据(03 E8):此数据根据不同的“数据类型”可以用来做不同的用途,根据具体的项目而定。
       动态密匙(00 01):这两个字节代表一个unsigned int类型的数据,数据范围是从0到65535,但是考虑到数据更加安全可靠,一般丢弃了首尾的0(十六进制的00 00)与65535(十六进制的FF FF),只保留从1到65534的变化。大部分的通信模型都是主机对从机的“一问一应答”模式,也就是,主机每发送一条指令给从机,从机才返回一条消息作为应答。如果主机发送了信息后,在规定的时间内,没有收到从机的应答指令,主机就继续发送信息给从机,但是此时,从机本来应该应答主机当前指令的,可能因为某种情况导致反馈的信息发生了延时,导致此时应答的数据是主机的上一条指令,从而造成“一问一应答”的数据帧发送了错位,这种情况加上“动态密匙”就能使问题得到有效的解决。主机每发送一条信息,信息里都携带了2个字节的“动态密匙”,从机每收到主机的一条信息,在应答此信息时都把收到的“动态密匙”原封不动的反馈给主机,主机再查看发送的“动态密匙”与接收到的“动态密匙”是否一致,以此来判断应答数据是否有效。“动态密匙”像流水号一样,每发送一次指令后都累加1,不断发生变化,从1到65534,依次循环。这是数据校验的一种方式。
       异或(0B)。“异或”放在数据串的最后一个字节,是前面所有字节的异或结果(不包括自己本身的字节)。比如:本例子中,数据串是:EB 01 00 00 00 0B 03 E8 00 01 0B。其中最后一个字节0B就是“异或”字节,前面所有字节相“异或”等于十六进制的0B。验证“异或”的方法,可以借用电脑“附件”自带的“计算器”软件来实现,打开“计算器”软件后,在“查看”的下拉菜单里,选择“程序员”,然后选择“十六进制”,该计算器软件的异或运算按键是“Xor”。不管是主机还是从机,每接收到一串数据后,都要自己计算一次“异或”,把自己计算得到的“异或”与接收到的最后一个字节的“异或”进行对比,来判断接收到的数据是否发生了丢失或者错误。

【130.3   程序例程。】


      
       上图130.3.1  有源蜂鸣器电路




      
       上图130.3.2  232串口电路

程序功能如下:
      (1)单片机模拟从机,上位机的串口助手模拟主机。在上位机的串口助手里,发送一串数据,控制蜂鸣器发出不同长度的声音。
      (2)本节因为还没有讲到数据发送的内容,因此应答“动态密匙”那部分的代码暂时不写,只写验证“异或”那部分的代码。
      (3)波特率9600,校验位NONE(无),数据位8,停止位1。
      (4)十六进制的数据格式:EB 01 00 00 00 0B XX XX YY YY ZZ 。其中:
              EB是数据头。
              01是代表数据类型。
              00 00 00 0B代表数据长度是11个(十进制)。
              XX XX代表一个unsigned int的数据,此数据的大小决定了蜂鸣器发出声音的长度。
              YY YY代表一个unsigned int的动态密匙,每收发一条指令,此数据累加一次1,范围从1到65534。
              ZZ 代表前面所有字节的异或结果。
比如:
       让蜂鸣器鸣叫1000毫秒,密匙为00 01,发送十六进制的:EB 01 00 00 00 0B 03 E8 00 01 0B
       让蜂鸣器鸣叫100毫秒, 密匙为00 02,发送十六进制的:EB 01 00 00 00 0B 00 64 00 02 87


  1. #include "REG52.H"

  2. #define RECE_TIME_OUT    2000  //通信过程中字节之间的超时时间2000ms
  3. #define REC_BUFFER_SIZE  20    //接收数据的缓存数组的长度


  4. void usart(void);  //串口接收的中断函数
  5. void T0_time();    //定时器的中断函数

  6. void UsartTask(void);    //串口接收的任务函数,放在主函数内

  7. void SystemInitial(void) ;
  8. void Delay(unsigned long u32DelayTime) ;
  9. void PeripheralInitial(void) ;

  10. void BeepOpen(void);   
  11. void BeepClose(void);
  12. void VoiceScan(void);

  13. sbit P3_4=P3^4;  

  14. volatile unsigned char vGu8BeepTimerFlag=0;  
  15. volatile unsigned int vGu16BeepTimerCnt=0;  

  16. unsigned char Gu8ReceBuffer[REC_BUFFER_SIZE]; //开辟一片接收数据的缓存
  17. unsigned long Gu32ReceCnt=0;  //接收缓存数组的下标
  18. unsigned char Gu8ReceStep=0;  //接收中断函数里的步骤变量
  19. unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作变量。
  20. unsigned char Gu8ReceType=0; //接收的数据类型
  21. unsigned int Gu16ReceYY=0; //接收的动态密匙
  22. unsigned char Gu8ReceZZ=0; //接收的异或
  23. unsigned long Gu32ReceDataLength=0;  //接收的数据长度
  24. unsigned char Gu8FinishFlag=0;  //是否已接收完成一串数据的标志
  25. unsigned long *pu32Data; //用于数据转换的指针
  26. volatile unsigned char vGu8ReceTimeOutFlag=0;//通信过程中字节之间的超时定时器的开关
  27. volatile unsigned int vGu16ReceTimeOutCnt=0; //通信过程中字节之间的超时定时器,“喂狗”的对象

  28. void main()
  29. {
  30. SystemInitial();            
  31. Delay(10000);               
  32. PeripheralInitial();      
  33.     while(1)  
  34. {  
  35.    UsartTask();   //串口接收的任务函数
  36.     }
  37. }

  38. void usart(void) interrupt 4   //串口接发的中断函数,中断号为4         
  39. {        
  40.    if(1==RI)  //接收完一个字节后引起的中断
  41.    {
  42.         RI = 0; //及时清零,避免一直无缘无故的进入中断。

  43. /* 注释一:
  44. * 以下Gu8FinishFlag变量的用途。
  45. * 此变量一箭双雕,0代表正处于接收数据的状态,1代表已经接收完毕并且及时通知主函数中的处理函数
  46. * UsartTask()去处理新接收到的一串数据。除此之外,还起到一种“自锁自保护”的功能,在新数据还
  47. * 没有被主函数处理完毕的时候,禁止接收其它新的数据,避免新数据覆盖了尚未处理的数据。
  48. */
  49.            if(0==Gu8FinishFlag)  //1代表已经完成接收了一串新数据,并且禁止接收其它新的数据
  50.            {

  51. /* 注释二:
  52. * 以下Gu8ReceFeedDog变量的用途。
  53. * 此变量是用来检测并且识别通信过程中相邻的字节之间是否存在超时的情况。
  54. * 如果大家听说过单片机中的“看门狗”这个概念,那么每接收到一个数据此变量就“置1”一次,它的
  55. * 作用就是起到及时“喂狗”的作用。每接收到一个数据此变量就“置1”一次,在主函数里,相关
  56. * 的定时器就会被重新赋值,只要这个定时器能不断及时的被补充新的“能量”新的值,那么这个定时器
  57. * 就永远不会变成0,只要不变成0就不会超时。如果两个字节之间通信时间超过了固定的长度,就意味
  58. * 着此定时器变成了0,这时就需要把中断函数里的接收步骤Gu8Step及时切换到“接头暗号”的步骤。
  59. */
  60.                   Gu8ReceFeedDog=1; //每接收到一个字节的数据,此标志就置1及时更新定时器的值。
  61.                   switch(Gu8ReceStep)
  62.                   {
  63.                           case 0:     //接头暗号的步骤。判断数据头的步骤。
  64.                                    Gu8ReceBuffer[0]=SBUF; //直接读取刚接收完的一个字节的数据。
  65.                                    if(0xeb==Gu8ReceBuffer[0])  //等于数据头0xeb,接头暗号吻合。
  66.                                    {
  67.                                           Gu32ReceCnt=1; //接收缓存的下标
  68.                                           Gu8ReceStep=1;  //切换到下一个步骤,接收其它有效的数据
  69.                                    }
  70.                                    break;               
  71.                                        
  72.                           case 1:     //数据类型和长度
  73.                                    Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
  74.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备
  75.                                    if(Gu32ReceCnt>=6)  //前6个数据。接收完了“数据类型”和“数据长度”。
  76.                                    {
  77.                                             Gu8ReceType=Gu8ReceBuffer[1];  //提取“数据类型”
  78. //以下的数据转换,在第62节讲解过的指针法
  79.                                                 pu32Data=(unsigned long *)&Gu8ReceBuffer[2]; //数据转换
  80.                                                 Gu32ReceDataLength=*pu32Data; //提取“数据长度”
  81.                                             if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
  82.                                                 {
  83.                                                         Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  84.                                                         Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  85.                                                 }
  86.                                                 else   //如果还没结束,继续切换到下一个步骤,接收“其它数据”
  87.                                                 {
  88.                                                         Gu8ReceStep=2;   //切换到下一个步骤
  89.                                                 }                                                        
  90.                                    }
  91.                                    break;               
  92.                           case 2:     //其它数据
  93.                                    Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
  94.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备

  95. //靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
  96.                                    if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=REC_BUFFER_SIZE)
  97. {
  98.                                           Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  99.                                           Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  100.                                    }
  101.                                    break;        
  102.                   }
  103.        }
  104.    }
  105.    else  //发送数据引起的中断
  106.    {
  107.         TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  108.         //以下可以添加一个全局变量的标志位的相关代码,通知主函数已经发送完一个字节的数据了。
  109.    }                                                      
  110. }  


  111. void UsartTask(void)    //串口接收的任务函数,放在主函数内
  112. {
  113. static unsigned int *pSu16Data; //数据转换的指针
  114. static unsigned int Su16Data;  //转换后的数据
  115. static unsigned int i;
  116. static unsigned char Su8RecZZ=0;  //计算的“异或”



  117.     if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及时更新一次“超时检测的定时器”的初值
  118.         {
  119.                 Gu8ReceFeedDog=0;
  120.                                 
  121.                 vGu8ReceTimeOutFlag=0;
  122.         vGu16ReceTimeOutCnt=RECE_TIME_OUT;//更新一次“超时检测的定时器”的初值
  123.                 vGu8ReceTimeOutFlag=1;
  124.         }
  125.         else if(Gu8ReceStep>0&&0==vGu16ReceTimeOutCnt) //超时,并且步骤不在接头暗号的步骤
  126.         {
  127.             Gu8ReceStep=0; //串口接收数据的中断函数及时切换回接头暗号的步骤
  128.     }

  129.         
  130.         if(1==Gu8FinishFlag)  //1代表已经接收完毕一串新的数据,需要马上去处理
  131.         {
  132.                 switch(Gu8ReceType)  //接收到的数据类型
  133.                 {
  134.                         case 0x01:   //驱动蜂鸣器
  135. //以下的数据转换,在第62节讲解过的指针法

  136.                                  pSu16Data=(unsigned int *)&Gu8ReceBuffer[Gu32ReceDataLength-3]; //数据转换
  137. Gu16ReceYY=*pSu16Data; //提取“动态密匙”。本例子中暂时不做返回应答的处理

  138. Gu8ReceZZ=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取接收到的“异或”

  139. Su8RecZZ=Gu8ReceBuffer[0]; //提取数据串第“i=0”个数据作为异或的原始数据
  140. for(i=1;i<(Gu32ReceDataLength-1);i++) //注意,这里是从第“i=1”个数据开始
  141. {
  142. Su8RecZZ=Su8RecZZ^Gu8ReceBuffer[i];   //计算“异或”
  143. }

  144. if(Su8RecZZ==Gu8ReceZZ) //验证“异或”,“计算的”与“接收的”是否一致
  145. {
  146.                               pSu16Data=(unsigned int *)&Gu8ReceBuffer[6]; //数据转换。
  147.                                      Su16Data=*pSu16Data; //提取“蜂鸣器声音的长度”

  148. vGu8BeepTimerFlag=0;  
  149. vGu16BeepTimerCnt=Su16Data;   //让蜂鸣器鸣叫
  150. vGu8BeepTimerFlag=1;  
  151. }

  152.                
  153.                   break;
  154.         }

  155.         Gu8FinishFlag=0;  //上面处理完数据再清零标志,为下一次接收新的数据做准备
  156.     }
  157. }

  158. void T0_time() interrupt 1     
  159. {
  160. VoiceScan();  

  161. if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通信过程中字节之间的超时定时器
  162.         {
  163.                  vGu16ReceTimeOutCnt--;        
  164. }  

  165. TH0=0xfc;   
  166. TL0=0x66;   
  167. }


  168. void SystemInitial(void)
  169. {
  170. unsigned char u8_TMOD_Temp=0;

  171. //以下是定时器0的中断的配置
  172. TMOD=0x01;  
  173. TH0=0xfc;   
  174. TL0=0x66;   
  175. EA=1;      
  176. ET0=1;      
  177. TR0=1;   

  178. //以下是串口接收中断的配置
  179. //串口的波特率与内置的定时器1直接相关,因此配置此定时器1就等效于配置波特率。
  180. u8_TMOD_Temp=0x20; //即将把定时器1设置为:工作方式2,初值自动重装的8位定时器。
  181. TMOD=TMOD&0x0f; //此寄存器低4位是跟定时器0相关,高4位是跟定时器1相关。先清零定时器1。
  182. TMOD=TMOD|u8_TMOD_Temp;  //把高4位的定时器1填入0x2,低4位的定时器0保持不变。
  183. TH1=256-(11059200L/12/32/9600);  //波特率为9600。11059200代表晶振11.0592MHz,
  184. TL1=256-(11059200L/12/32/9600);  //L代表long的长类型数据。根据芯片手册提供的计算公式。
  185. TR1=1;  //开启定时器1

  186. SM0=0;  
  187. SM1=1;  //SM0与SM1的设置:选择10位异步通信,波特率根据定时器1可变  
  188. REN=1;  //允许串口接收数据

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

  192. ES=1;         //允许串口中断  
  193. EA=1;         //允许总中断
  194. }

  195. void Delay(unsigned long u32DelayTime)
  196. {
  197.     for(;u32DelayTime>0;u32DelayTime--);
  198. }

  199. void PeripheralInitial(void)
  200. {

  201. }

  202. void BeepOpen(void)
  203. {
  204. P3_4=0;  
  205. }

  206. void BeepClose(void)
  207. {
  208. P3_4=1;  
  209. }

  210. void VoiceScan(void)
  211. {

  212.           static unsigned char Su8Lock=0;  

  213. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  214.           {
  215.                   if(0==Su8Lock)
  216.                   {
  217.                    Su8Lock=1;  
  218. BeepOpen();
  219.      }
  220.     else  
  221. {     

  222.                        vGu16BeepTimerCnt--;         

  223.                    if(0==vGu16BeepTimerCnt)
  224.                    {
  225.                            Su8Lock=0;     
  226. BeepClose();  
  227.                    }

  228. }
  229.           }         
  230. }
复制代码

本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2018-10-4 12:36:37 | 显示全部楼层
是鸿哥的state简易状态机带我入门的,特来感谢。

出0入0汤圆

 楼主| 发表于 2018-10-14 10:17:42 | 显示全部楼层
第一百三十一节: 灵活切换各种不同大小“接收内存”的串口程序框架。

【131.1   切换各种不同大小“接收内存”。】

      很多32位的单片机,只要外挂SRAM或者SDRAM这类内存芯片,就可以轻松的把一个全局变量的数组开辟到几百K甚至几兆的容量。开辟这么大的数组,往往是用来处理一些文件类的大数据,比如串口接收一张480x272点阵大小的.BMP格式的图片文件,就需要开辟一个几百K的全局变量大数组。串口通信中,从接收内存的容量来划分,常用有两种数据类型,一种是常规控制类(容量小),一种是文件类(容量大),要能做到在这两种“接收内存”中灵活切换,关键是用到“指针的中转切换”技术。
      “常规控制类内存”负责两块事务,一块是接收“前部分的”[数据头,数据类型,数据长度],另一块是“后部分的”[常规控制类的专用数据]。
      “文件类内存”只负责“后部分的”[文件类的专用数据],而“前部分的”[数据头,数据类型,数据长度]是需要借助“常规控制类内存”来实现的。
      本节破题的关键在于,根据不同的数据类型,利用“指针的中转切换”实现不同接收内存的灵活切换。关键代码是串口中断函数这部分的处理,片段代码的讲解如下:


  1. unsigned char Gu8ReceBuffer[20]; //常规控制类的小内存
  2. unsigned char Gu8FileBuffer[40]; //文件类的大内存
  3. unsigned char *pGu8ReceBuffer;  //用来切换接收内存的“中转指针”

  4. void usart(void) interrupt 4           
  5. {        
  6.    if(1==RI)  
  7.    {
  8.         RI = 0;

  9.            if(0==Gu8FinishFlag)  
  10.            {
  11.                   Gu8ReceFeedDog=1;
  12.                   switch(Gu8ReceStep)
  13.                   {
  14.                           case 0:     //“前部分的”数据头。接头暗号的步骤
  15.                                    Gu8ReceBuffer[0]=SBUF;
  16.                                    if(0xeb==Gu8ReceBuffer[0])  
  17.                                    {
  18.                                           Gu32ReceCnt=1;
  19.                                           Gu8ReceStep=1;  
  20.                                    }
  21.                                    break;               
  22.                                        
  23.                           case 1:     //“前部分的”数据类型和长度
  24.                                    Gu8ReceBuffer[Gu32ReceCnt]=SBUF;
  25.                                    Gu32ReceCnt++;
  26.                                    if(Gu32ReceCnt>=6)  //前6个数据。接收完了“数据类型”和“数据长度”。
  27.                                    {
  28.                                             Gu8ReceType=Gu8ReceBuffer[1];  //提取“数据类型”
  29.                                                 pu32Data=(unsigned long *)&Gu8ReceBuffer[2];
  30.                                                 Gu32ReceDataLength=*pu32Data; //提取“数据长度”
  31.                                             if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
  32.                                                 {
  33.                                                         Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  34.                                                         Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  35.                                                 }
  36.                                                 else   //如果还没结束,继续切换到下一个步骤,接收“有效数据”
  37.                                                 {
  38.                              //以下几行代码是本节的破题关键!!!
  39.                              if(0x02==Gu8ReceType) //如果是文件类,把指针关联到Gu8FileBuffer
  40.                               {
  41.                                    pGu8ReceBuffer=(unsigned char *)&Gu8FileBuffer[0];//下标0
  42. }
  43. else //如果是常规类,继续把指针关联到Gu8ReceBuffer本身的数组
  44.                               {
  45.                                    pGu8ReceBuffer=(unsigned char *)&Gu8ReceBuffer[6];//下标6
  46. }

  47.                                                         Gu8ReceStep=2;   //切换到下一个步骤
  48.                                                 }                                                        
  49.                                    }
  50.                                    break;               
  51.                           case 2:     //“后部分的”数据
  52.                                    pGu8ReceBuffer[Gu32ReceCnt-6]=SBUF; //这里的指针就是各种不同内存的化身!!!
  53.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备

  54. //靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
  55.                                    if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=REC_BUFFER_SIZE)
  56. {
  57.                                           Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  58.                                           Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  59.                                    }
  60.                                    break;        
  61.                   }
  62.        }
  63.    }
  64.    else  //发送数据引起的中断
  65.    {
  66.         TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  67.         //以下可以添加一个全局变量的标志位的相关代码,通知主函数已经发送完一个字节的数据了。
  68.    }                                                      
  69. }  

复制代码

【131.2   通信协议。】

        数据头(EB):占1个字节,作为“起始字节”,起到“接头暗号”的作用,平时用来过滤无关的数据。
        数据类型(01):占用1个字节。数据类型是用来定义这串数据的用途。
        数据长度(00 00 00 0B):占4个字节。用来告诉通信的对方,这串数据一共有多少个字节。
        其它数据(03 E8):此数据根据不同的“数据类型”可以用来做不同的用途,根据具体的项目而定。
        动态密匙(00 01):这两个字节代表一个unsigned int类型的数据,数据范围是从0到65535,但是考虑到数据更加安全可靠,一般丢弃了首尾的0(十六进制的00 00)与65535(十六进制的FF FF),只保留从1到65534的变化。大部分的通信模型都是主机对从机的“一问一应答”模式,也就是,主机每发送一条指令给从机,从机才返回一条消息作为应答。如果主机发送了信息后,在规定的时间内,没有收到从机的应答指令,主机就继续发送信息给从机,但是此时,从机本来应该应答主机当前指令的,可能因为某种情况导致反馈的信息发生了延时,导致此时应答的数据是主机的上一条指令,从而造成“一问一应答”的数据帧发送了错位,这种情况加上“动态密匙”就能使问题得到有效的解决。主机每发送一条信息,信息里都携带了2个字节的“动态密匙”,从机每收到主机的一条信息,在应答此信息时都把收到的“动态密匙”原封不动的反馈给主机,主机再查看发送的“动态密匙”与接收到的“动态密匙”是否一致,以此来判断应答数据是否有效。“动态密匙”像流水号一样,每发送一次指令后都累加1,不断发生变化,从1到65534,依次循环。这是数据校验的一种方式。
       异或(0B)。“异或”放在数据串的最后一个字节,是前面所有字节的异或结果(不包括自己本身的字节)。比如:本例子中,数据串是:EB 01 00 00 00 0B 03 E8 00 01 0B。其中最后一个字节0B就是“异或”字节,前面所有字节相“异或”等于十六进制的0B。验证“异或”的方法,可以借用电脑“附件”自带的“计算器”软件来实现,打开“计算器”软件后,在“查看”的下拉菜单里,选择“程序员”,然后选择“十六进制”,该计算器软件的异或运算按键是“Xor”。不管是主机还是从机,每接收到一串数据后,都要自己计算一次“异或”,把自己计算得到的“异或”与接收到的最后一个字节的“异或”进行对比,来判断接收到的数据是否发生了丢失或者错误。

【131.3   程序例程。】


      
       上图131.3.1  有源蜂鸣器电路




      
       上图131.3.2  232串口电路

程序功能如下:
      (1)单片机模拟从机,上位机的串口助手模拟主机。在上位机的串口助手里,发送一串数据,控制蜂鸣器发出不同长度的声音。数据类型为01时,把“后部分的”数据发送给“常规控制类内存”;数据类型为02时,把“后部分的”数据发送给“文件类内存”。
      (2)本节因为还没有讲到数据发送的内容,因此应答“动态密匙”那部分的代码暂时不写,只写验证“异或”那部分的代码。
      (3)波特率9600,校验位NONE(无),数据位8,停止位1。
      (4)十六进制的数据格式:EB 01 00 00 00 0B XX XX YY YY ZZ 。其中:
        EB是数据头。
        01是代表数据类型。
        00 00 00 0B代表数据长度是11个(十进制)。
        XX XX代表一个unsigned int的数据,此数据的大小决定了蜂鸣器发出声音的长度。
        YY YY代表一个unsigned int的动态密匙,每收发一条指令,此数据累加一次1,范围从1到65534。
        ZZ 代表前面所有字节的异或结果。
比如:
        数据类型01,“后部分的”数据发给“常规控制类内存”,让蜂鸣器鸣叫1000毫秒,密匙为00 01,发送十六进制的:EB 01 00 00 00 0B 03 E8 00 01 0B
        数据类型02,“后部分的”数据发给“文件类内存”,让蜂鸣器鸣叫100毫秒, 密匙为00 02,发送十六进制的:EB 02 00 00 00 0B 00 64 00 02 84


  1. #include "REG52.H"

  2. #define RECE_TIME_OUT    2000  //通信过程中字节之间的超时时间2000ms
  3. #define REC_BUFFER_SIZE  20    //常规控制类数组的长度
  4. #define FILE_BUFFER_SIZE  40   //文件类数组的长度


  5. void usart(void);  //串口接收的中断函数
  6. void T0_time();    //定时器的中断函数

  7. void UsartTask(void);    //串口接收的任务函数,放在主函数内

  8. void SystemInitial(void) ;
  9. void Delay(unsigned long u32DelayTime) ;
  10. void PeripheralInitial(void) ;

  11. void BeepOpen(void);   
  12. void BeepClose(void);
  13. void VoiceScan(void);

  14. sbit P3_4=P3^4;  

  15. volatile unsigned char vGu8BeepTimerFlag=0;  
  16. volatile unsigned int vGu16BeepTimerCnt=0;  

  17. unsigned char Gu8ReceBuffer[REC_BUFFER_SIZE]; //常规控制类的小内存
  18. unsigned char Gu8FileBuffer[FILE_BUFFER_SIZE]; //文件类的大内存
  19. unsigned char *pGu8ReceBuffer;  //用来切换接收内存的“中转指针”

  20. unsigned long Gu32ReceCnt=0;  //接收缓存数组的下标
  21. unsigned char Gu8ReceStep=0;  //接收中断函数里的步骤变量
  22. unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作变量。
  23. unsigned char Gu8ReceType=0; //接收的数据类型
  24. unsigned int Gu16ReceYY=0; //接收的动态密匙
  25. unsigned char Gu8ReceZZ=0; //接收的异或
  26. unsigned long Gu32ReceDataLength=0;  //接收的数据长度
  27. unsigned char Gu8FinishFlag=0;  //是否已接收完成一串数据的标志
  28. unsigned long *pu32Data; //用于数据转换的指针
  29. volatile unsigned char vGu8ReceTimeOutFlag=0;//通信过程中字节之间的超时定时器的开关
  30. volatile unsigned int vGu16ReceTimeOutCnt=0; //通信过程中字节之间的超时定时器,“喂狗”的对象

  31. void main()
  32. {
  33. SystemInitial();            
  34. Delay(10000);               
  35. PeripheralInitial();      
  36.     while(1)  
  37. {  
  38.    UsartTask();   //串口接收的任务函数
  39.     }
  40. }

  41. void usart(void) interrupt 4   //串口接发的中断函数,中断号为4         
  42. {        
  43.    if(1==RI)  //接收完一个字节后引起的中断
  44.    {
  45.         RI = 0; //及时清零,避免一直无缘无故的进入中断。

  46. /* 注释一:
  47. * 以下Gu8FinishFlag变量的用途。
  48. * 此变量一箭双雕,0代表正处于接收数据的状态,1代表已经接收完毕并且及时通知主函数中的处理函数
  49. * UsartTask()去处理新接收到的一串数据。除此之外,还起到一种“自锁自保护”的功能,在新数据还
  50. * 没有被主函数处理完毕的时候,禁止接收其它新的数据,避免新数据覆盖了尚未处理的数据。
  51. */
  52.            if(0==Gu8FinishFlag)  //1代表已经完成接收了一串新数据,并且禁止接收其它新的数据
  53.            {

  54. /* 注释二:
  55. * 以下Gu8ReceFeedDog变量的用途。
  56. * 此变量是用来检测并且识别通信过程中相邻的字节之间是否存在超时的情况。
  57. * 如果大家听说过单片机中的“看门狗”这个概念,那么每接收到一个数据此变量就“置1”一次,它的
  58. * 作用就是起到及时“喂狗”的作用。每接收到一个数据此变量就“置1”一次,在主函数里,相关
  59. * 的定时器就会被重新赋值,只要这个定时器能不断及时的被补充新的“能量”新的值,那么这个定时器
  60. * 就永远不会变成0,只要不变成0就不会超时。如果两个字节之间通信时间超过了固定的长度,就意味
  61. * 着此定时器变成了0,这时就需要把中断函数里的接收步骤Gu8Step及时切换到“接头暗号”的步骤。
  62. */
  63.                   Gu8ReceFeedDog=1; //每接收到一个字节的数据,此标志就置1及时更新定时器的值。
  64.                   switch(Gu8ReceStep)
  65.                   {
  66.                           case 0:     //“前部分的”数据头。接头暗号的步骤。
  67.                                    Gu8ReceBuffer[0]=SBUF; //直接读取刚接收完的一个字节的数据。
  68.                                    if(0xeb==Gu8ReceBuffer[0])  //等于数据头0xeb,接头暗号吻合。
  69.                                    {
  70.                                           Gu32ReceCnt=1; //接收缓存的下标
  71.                                           Gu8ReceStep=1;  //切换到下一个步骤,接收其它有效的数据
  72.                                    }
  73.                                    break;               
  74.                                        
  75.                           case 1:     //“前部分的”数据类型和长度
  76.                                    Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
  77.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备
  78.                                    if(Gu32ReceCnt>=6)  //前6个数据。接收完了“数据类型”和“数据长度”。
  79.                                    {
  80.                                             Gu8ReceType=Gu8ReceBuffer[1];  //提取“数据类型”
  81. //以下的数据转换,在第62节讲解过的指针法
  82.                                                 pu32Data=(unsigned long *)&Gu8ReceBuffer[2]; //数据转换
  83.                                                 Gu32ReceDataLength=*pu32Data; //提取“数据长度”
  84.                                             if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
  85.                                                 {
  86.                                                         Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  87.                                                         Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  88.                                                 }
  89.                                                 else   //如果还没结束,继续切换到下一个步骤,接收“有效数据”
  90.                                                 {
  91.                              //以下几行代码是本节的破题关键!!!
  92.                              if(0x02==Gu8ReceType) //如果是文件类,把指针关联到Gu8FileBuffer
  93.                               {
  94.                                    pGu8ReceBuffer=(unsigned char *)&Gu8FileBuffer[0];//下标0
  95. }
  96. else //如果是常规类,继续把指针关联到Gu8ReceBuffer本身的数组
  97.                               {
  98.                                    pGu8ReceBuffer=(unsigned char *)&Gu8ReceBuffer[6];//下标6
  99. }

  100.                                                         Gu8ReceStep=2;   //切换到下一个步骤
  101.                                                 }                                                        
  102.                                    }
  103.                                    break;               
  104.                           case 2:     //“后部分的”数据
  105.                                    pGu8ReceBuffer[Gu32ReceCnt-6]=SBUF; //这里的指针就是各种不同内存的化身!!!
  106.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备

  107. //靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
  108.                                    if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=REC_BUFFER_SIZE)
  109. {
  110.                                           Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  111.                                           Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  112.                                    }
  113.                                    break;        
  114.                   }
  115.        }
  116.    }
  117.    else  //发送数据引起的中断
  118.    {
  119.         TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  120.         //以下可以添加一个全局变量的标志位的相关代码,通知主函数已经发送完一个字节的数据了。
  121.    }                                                      
  122. }  


  123. void UsartTask(void)    //串口接收的任务函数,放在主函数内
  124. {
  125. static unsigned int *pSu16Data; //数据转换的指针
  126. static unsigned int Su16Data;  //转换后的数据
  127. static unsigned int i;
  128. static unsigned char Su8RecZZ=0;  //计算的“异或”



  129.     if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及时更新一次“超时检测的定时器”的初值
  130.         {
  131.                 Gu8ReceFeedDog=0;
  132.                                 
  133.                 vGu8ReceTimeOutFlag=0;
  134.         vGu16ReceTimeOutCnt=RECE_TIME_OUT;//更新一次“超时检测的定时器”的初值
  135.                 vGu8ReceTimeOutFlag=1;
  136.         }
  137.         else if(Gu8ReceStep>0&&0==vGu16ReceTimeOutCnt) //超时,并且步骤不在接头暗号的步骤
  138.         {
  139.             Gu8ReceStep=0; //串口接收数据的中断函数及时切换回接头暗号的步骤
  140.     }

  141.         
  142.         if(1==Gu8FinishFlag)  //1代表已经接收完毕一串新的数据,需要马上去处理
  143.         {
  144.                 switch(Gu8ReceType)  //接收到的数据类型
  145.                 {
  146.                         case 0x01:   //常规控制类的小内存。驱动蜂鸣器
  147. //以下的数据转换,在第62节讲解过的指针法

  148.                                  pSu16Data=(unsigned int *)&Gu8ReceBuffer[Gu32ReceDataLength-3]; //数据转换
  149. Gu16ReceYY=*pSu16Data; //提取“动态密匙”。本例子中暂时不做返回应答的处理

  150. Gu8ReceZZ=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取接收到的“异或”

  151. Su8RecZZ=Gu8ReceBuffer[0]; //提取数据串第“i=0”个数据作为异或的原始数据
  152. for(i=1;i<(Gu32ReceDataLength-1);i++) //注意,这里是从第“i=1”个数据开始
  153. {
  154. Su8RecZZ=Su8RecZZ^Gu8ReceBuffer[i];   //计算“异或”
  155. }

  156. if(Su8RecZZ==Gu8ReceZZ) //验证“异或”,“计算的”与“接收的”是否一致
  157. {
  158.                               pSu16Data=(unsigned int *)&Gu8ReceBuffer[6]; //数据转换。
  159.                                      Su16Data=*pSu16Data; //提取“蜂鸣器声音的长度”

  160. vGu8BeepTimerFlag=0;  
  161. vGu16BeepTimerCnt=Su16Data;   //让蜂鸣器鸣叫
  162. vGu8BeepTimerFlag=1;  
  163. }
  164.         
  165.                   break;

  166.                         case 0x02:   //文件类的大内存。驱动蜂鸣器。
  167. //以下的数据转换,在第62节讲解过的指针法

  168.                                  pSu16Data=(unsigned int *)&Gu8ReceBuffer[Gu32ReceDataLength-3]; //数据转换
  169. Gu16ReceYY=*pSu16Data; //提取“动态密匙”。本例子中暂时不做返回应答的处理

  170. //注意,请留意以下代码文件类内存数组Gu8FileBuffer的下标位置
  171. Gu8ReceZZ=Gu8FileBuffer[Gu32ReceDataLength-1-6];  //提取接收到的“异或”

  172. //前面6个字节是“前部分的”[数据头,数据类型,数据长度]
  173. Su8RecZZ=Gu8ReceBuffer[0]; //提取数据串第“i=0”个数据作为异或的原始数据
  174. for(i=1;i<6;i++) //注意,这里是从第“i=1”个数据开始
  175. {
  176. Su8RecZZ=Su8RecZZ^Gu8ReceBuffer[i];   //计算“前部分的”“异或”
  177. }

  178. //6个字节之后是“后部分的”“文件类专用的数据”
  179. for(i=0;i<(Gu32ReceDataLength-1-6);i++)
  180. {
  181. Su8RecZZ=Su8RecZZ^Gu8FileBuffer[i];   //计算“后部分的”“异或”
  182. }

  183. if(Su8RecZZ==Gu8ReceZZ) //验证“异或”,“计算的”与“接收的”是否一致
  184. {
  185.                               pSu16Data=(unsigned int *)&Gu8FileBuffer[0]; //数据转换。此处下标0!
  186.                                      Su16Data=*pSu16Data; //提取“蜂鸣器声音的长度”

  187. vGu8BeepTimerFlag=0;  
  188. vGu16BeepTimerCnt=Su16Data;   //让蜂鸣器鸣叫
  189. vGu8BeepTimerFlag=1;  
  190. }
  191.                
  192.                   break;



  193.         }

  194.         Gu8FinishFlag=0;  //上面处理完数据再清零标志,为下一次接收新的数据做准备
  195.     }
  196. }

  197. void T0_time() interrupt 1     
  198. {
  199. VoiceScan();  

  200. if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通信过程中字节之间的超时定时器
  201.         {
  202.                  vGu16ReceTimeOutCnt--;        
  203. }  

  204. TH0=0xfc;   
  205. TL0=0x66;   
  206. }


  207. void SystemInitial(void)
  208. {
  209. unsigned char u8_TMOD_Temp=0;

  210. //以下是定时器0的中断的配置
  211. TMOD=0x01;  
  212. TH0=0xfc;   
  213. TL0=0x66;   
  214. EA=1;      
  215. ET0=1;      
  216. TR0=1;   

  217. //以下是串口接收中断的配置
  218. //串口的波特率与内置的定时器1直接相关,因此配置此定时器1就等效于配置波特率。
  219. u8_TMOD_Temp=0x20; //即将把定时器1设置为:工作方式2,初值自动重装的8位定时器。
  220. TMOD=TMOD&0x0f; //此寄存器低4位是跟定时器0相关,高4位是跟定时器1相关。先清零定时器1。
  221. TMOD=TMOD|u8_TMOD_Temp;  //把高4位的定时器1填入0x2,低4位的定时器0保持不变。
  222. TH1=256-(11059200L/12/32/9600);  //波特率为9600。11059200代表晶振11.0592MHz,
  223. TL1=256-(11059200L/12/32/9600);  //L代表long的长类型数据。根据芯片手册提供的计算公式。
  224. TR1=1;  //开启定时器1

  225. SM0=0;  
  226. SM1=1;  //SM0与SM1的设置:选择10位异步通信,波特率根据定时器1可变  
  227. REN=1;  //允许串口接收数据

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

  231. ES=1;         //允许串口中断  
  232. EA=1;         //允许总中断
  233. }

  234. void Delay(unsigned long u32DelayTime)
  235. {
  236.     for(;u32DelayTime>0;u32DelayTime--);
  237. }

  238. void PeripheralInitial(void)
  239. {

  240. }

  241. void BeepOpen(void)
  242. {
  243. P3_4=0;  
  244. }

  245. void BeepClose(void)
  246. {
  247. P3_4=1;  
  248. }

  249. void VoiceScan(void)
  250. {

  251.           static unsigned char Su8Lock=0;  

  252. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  253.           {
  254.                   if(0==Su8Lock)
  255.                   {
  256.                    Su8Lock=1;  
  257. BeepOpen();
  258.      }
  259.     else  
  260. {     

  261.                        vGu16BeepTimerCnt--;         

  262.                    if(0==vGu16BeepTimerCnt)
  263.                    {
  264.                            Su8Lock=0;     
  265. BeepClose();  
  266.                    }

  267. }
  268.           }         
  269. }


复制代码

本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2018-10-14 12:46:18 | 显示全部楼层
继续跟随鸿哥哥更新的脚步!

出0入0汤圆

发表于 2018-10-15 18:08:57 | 显示全部楼层
写的不错,一路跟过来了,,,希望能讲到一个综合程序,涉及串口通讯(通讯),按键,数码管,蜂鸣器(界面,设置串口通讯波特率、AD范围等),AD或者其他信号采集(信号),包含这三个要素

出0入0汤圆

发表于 2018-10-15 18:15:55 | 显示全部楼层
如果还能涉及到液晶屏界面设计,要是能把这些任务合理安排,,,出书了,那书就肯定值得买一本放床头了

出0入10汤圆

发表于 2018-10-23 16:28:56 | 显示全部楼层
先评论顶起,再慢慢细看,值得收藏的帖子。

出0入0汤圆

 楼主| 发表于 2018-10-30 13:03:20 | 显示全部楼层
第一百三十二节:“转发、透传、多种协议并存”的双缓存串口程序框架。

【132.1   字节间隔时间、双缓存切换、指针切换关联。】

       在一些通讯模块的项目中,常常涉及数据的转发,透传,提取关键字的处理,单片机接收到的数据不许随意丢失,必须全部暂存,然后提取关键字,再把整包数据原封不动地转发或者透传给“下家”。这类项目的特点是,通讯协议不是固定唯一的,因此,前面章节那种接头暗号(数据头)的程序框架不适合这里,本节跟大家分享另外一种程序框架。
       第一个要突破的技术难点是,既然通讯协议不是固定唯一的,那么,如何识别一串数据已经接收完毕?答案是靠接收每个字节之间的间隔时间来识别。当一串数据正在接收时,每个字节之间的间隔时间是“短暂的相对均匀的”。当一串数据已经接收完毕时,每个字节之间的间隔时间是“突然变长的”。代码的具体实现,是靠一个软件定时器,模拟单片机“看门狗”的“喂狗”原理。
       第二个要突破的技术难点是,既然通讯协议不是固定唯一的,数据内容带有随机性,甚至字节之间的间隔时间的长短也带有随机性和不确定性,那么,如何预防正在处理数据时突然“接收中断”又接收到的新数据覆盖了尚未来得及处理的旧数据,或者,如何预防正在处理旧数据时丢失了突然又新过来的本应该接收的新数据?答案是用双缓存轮流切换的机制。双缓存,一个用在处理刚刚接收到的旧数据,另一个用在时刻准备着接收新数据,轮流切换,两不误。
       第三个要突破的技术难点是,既然是用双缓存轮流切换的机制,那么,在主程序里如何统一便捷地处理两个缓存的数组?这里的“统一”是关键,要把两个数组“统一”成(看成是)一个数组,方法是,只需用“指针切换关联”的技术就可以了。

【132.2   程序例程。】


      
       上图132.2.1  有源蜂鸣器电路





       上图132.2.2  232串口电路

        程序功能如下:单片机接收任意长度(最大一次不超过30字节)的一串数据。如果发现连续有三个字节是0x02 0x03 0x04,蜂鸣器则“短叫”100ms提示;如果发现连续有四个字节是0x06 0x07 0x08 0x09,蜂鸣器则“长叫”2000ms提示。
       比如测试“短叫”100ms,发送十六进制的数据串:05 02 00 00 02 03 04 09
       比如测试“长叫”2000ms,发送十六进制的数据串:02 02 06 07 08 09 01 08 03 00 05
       代码如下:


  1. #include "REG52.H"

  2. #define DOG_TIME_OUT  20  //理论上,9600波特率的字节间隔时间大概0.8ms左右,因此取20ms足够
  3. #define RECE_BUFFER_SIZE  30   //接收缓存的数组大小

  4. void usart(void);  //串口接收的中断函数
  5. void T0_time();    //定时器的中断函数

  6. void UsartTask(void);    //串口接收的任务函数,放在主函数内

  7. void SystemInitial(void) ;
  8. void Delay(unsigned long u32DelayTime) ;
  9. void PeripheralInitial(void) ;

  10. void BeepOpen(void);   
  11. void BeepClose(void);
  12. void VoiceScan(void);

  13. sbit P3_4=P3^4;  

  14. unsigned char Gu8CurrentReceBuffer_Sec=0; //当前接收缓存的选择标志。0代表缓存A,1代表缓存B

  15. unsigned char Gu8ReceBuffer_A[RECE_BUFFER_SIZE]; //双缓存其中之一的缓存A
  16. unsigned long Gu32ReceCnt_A=0;    //缓存A的数组下标与计数器,必须初始化为0,做好接收准备

  17. unsigned char Gu8ReceBuffer_B[RECE_BUFFER_SIZE]; //双缓存其中之一的缓存B
  18. unsigned long Gu32ReceCnt_B=0;    //缓存B的数组下标与计数器,必须初始化为0,做好接收准备

  19. unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作变量。
  20. unsigned char Gu8FinishFlag=0; //接收完成标志。0代表还没有完成,1代表已经完成了一次接收

  21. volatile unsigned char vGu8ReceTimeOutFlag=0;//通信过程中字节之间的超时定时器的开关
  22. volatile unsigned int vGu16ReceTimeOutCnt=0; //通信过程中字节之间的超时定时器,“喂狗”的对象

  23. volatile unsigned char vGu8BeepTimerFlag=0;  
  24. volatile unsigned int vGu16BeepTimerCnt=0;  

  25. void main()
  26. {
  27. SystemInitial();            
  28. Delay(10000);               
  29. PeripheralInitial();      
  30.     while(1)  
  31. {  
  32.    UsartTask();   //串口接收的任务函数
  33.     }
  34. }

  35. void usart(void) interrupt 4           
  36. {        
  37.    if(1==RI)  
  38.    {
  39.         RI = 0;

  40. Gu8FinishFlag=0; //此处也清零,意味深长,当主函数正在处理数据时,可以兼容多次接收完成
  41.             Gu8ReceFeedDog=1; //看门狗的“喂狗”操作,给软件定时器继续“输血”
  42. if(0==Gu8CurrentReceBuffer_Sec)   //0代表选择缓存A
  43. {
  44.       if(Gu32ReceCnt_A<RECE_BUFFER_SIZE)
  45. {
  46. Gu8ReceBuffer_A[Gu32ReceCnt_A]=SBUF;
  47. Gu32ReceCnt_A++; //记录当前缓存A的接收字节数
  48. }
  49. }
  50. else     //1代表选择缓存B
  51. {
  52.       if(Gu32ReceCnt_B<RECE_BUFFER_SIZE)
  53. {
  54. Gu8ReceBuffer_B[Gu32ReceCnt_B]=SBUF;
  55. Gu32ReceCnt_B++;  //记录当前缓存B的接收字节数
  56. }

  57. }
  58.    }
  59.    else  //发送数据引起的中断
  60.    {
  61.         TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  62.         //以下可以添加一个全局变量的标志位的相关代码,通知主函数已经发送完一个字节的数据了。
  63.    }                                                      
  64. }  

  65. void UsartTask(void)    //串口接收的任务函数,放在主函数内
  66. {
  67. static unsigned char *pSu8ReceBuffer;  //“指针切换关联”中的指针,切换内存
  68. static unsigned char Su8Lock=0;  //用来避免一直更新的临时变量
  69. static unsigned long i;  //用在数据处理中的循环变量
  70. static unsigned long Su32ReceSize=0; //接收到的数据大小的临时变量


  71.     if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及时更新一次“超时检测的定时器”的初值
  72.         {
  73.                 Gu8ReceFeedDog=0;

  74. Su8Lock=0; //解锁。用来避免一直更新的临时变量
  75.                
  76.         //以下三行代码是看门狗中的“喂狗”操作。继续给软件定时器“输血”               
  77.                 vGu8ReceTimeOutFlag=0;
  78.         vGu16ReceTimeOutCnt=DOG_TIME_OUT;//正在通信时,两个字节间隔的最大时间,本节选用20ms
  79.                 vGu8ReceTimeOutFlag=1;
  80.         }
  81.         else if(0==Su8Lock&&0==vGu16ReceTimeOutCnt) //超时,代表一串数据已经接收完成
  82.         {
  83.             Su8Lock=1;  //避免一直进来更新
  84.         Gu8FinishFlag=1; //两个字节之间的时间超时,因此代表了一串数据已经接收完成
  85.     }

  86.         
  87.         if(1==Gu8FinishFlag)  //1代表已经接收完毕一串新的数据,需要马上去处理
  88.         {
  89. if(0==Gu8CurrentReceBuffer_Sec)  
  90. {
  91. Gu8CurrentReceBuffer_Sec=1; //以最快的速度先切换接收内存,避免丢失新发过来的数据
  92. //Gu32ReceCnt_B=0;//这里不能清零缓存B的计数器,意味深长,避免此处临界点发生中断
  93.             Gu8FinishFlag=0;  //尽可能以最快的速度清零本次完成的标志,为下一次新数据做准备
  94. pSu8ReceBuffer=(unsigned char *)&Gu8ReceBuffer_A[0]; //关联刚刚接收的数据缓存
  95. Su32ReceSize=Gu32ReceCnt_A; //记录当前缓存的有效字节数
  96. Gu32ReceCnt_A=0; //及时把当前缓存计数清零,为一次切换接收缓存做准备。意味深长。
  97. }
  98. else
  99. {
  100. Gu8CurrentReceBuffer_Sec=0; //以最快的速度先切换接收内存,避免丢失新发过来的数据
  101. //Gu32ReceCnt_A=0;//这里不能清零缓存A的计数器,意味深长,避免此处临界点发生中断
  102.             Gu8FinishFlag=0;  //尽可能以最快的速度清零本次完成的标志,为下一次新数据做准备
  103. pSu8ReceBuffer=(unsigned char *)&Gu8ReceBuffer_B[0]; //关联刚刚接收的数据缓存
  104. Su32ReceSize=Gu32ReceCnt_B; //记录当前缓存的有效字节数
  105. Gu32ReceCnt_B=0; //及时把当前缓存计数清零,为一次切换接收缓存做准备。意味深长。
  106. }

  107.         //Gu8FinishFlag=0; //之所以不选择在这里清零,是因为在上面清零更及时快速。意味深长。

  108.         //开始处理刚刚接收到的一串新数据,直接“统一”处理pSu8ReceBuffer指针为代表的数据即可
  109.         for(i=0;i<Su32ReceSize;i++)
  110. {
  111.              if(0x02==pSu8ReceBuffer[i]&&
  112. 0x03==pSu8ReceBuffer[i+1]&&
  113. 0x04==pSu8ReceBuffer[i+2]) //连续三个数是0x02 0x03 0x04
  114. {
  115. vGu8BeepTimerFlag=0;  
  116. vGu16BeepTimerCnt=100;   //让蜂鸣器“短叫”100ms
  117. vGu8BeepTimerFlag=1;  
  118.     return; //直接退出当前函数
  119. }

  120.              if(0x06==pSu8ReceBuffer[i]&&
  121. 0x07==pSu8ReceBuffer[i+1]&&
  122. 0x08==pSu8ReceBuffer[i+2]&&
  123. 0x09==pSu8ReceBuffer[i+3]) //连续四个数是0x06 0x07 0x08 0x09
  124. {
  125. vGu8BeepTimerFlag=0;  
  126. vGu16BeepTimerCnt=2000;   //让蜂鸣器“长叫”2000ms
  127. vGu8BeepTimerFlag=1;  
  128.     return; //直接退出当前函数
  129. }

  130. }

  131.     }
  132. }

  133. void T0_time() interrupt 1     
  134. {
  135. VoiceScan();  

  136. if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通信过程中字节之间的超时定时器
  137.         {
  138.                  vGu16ReceTimeOutCnt--;        
  139. }  

  140. TH0=0xfc;   
  141. TL0=0x66;   
  142. }


  143. void SystemInitial(void)
  144. {
  145. unsigned char u8_TMOD_Temp=0;

  146. //以下是定时器0的中断的配置
  147. TMOD=0x01;  
  148. TH0=0xfc;   
  149. TL0=0x66;   
  150. EA=1;      
  151. ET0=1;      
  152. TR0=1;   

  153. //以下是串口接收中断的配置
  154. //串口的波特率与内置的定时器1直接相关,因此配置此定时器1就等效于配置波特率。
  155. u8_TMOD_Temp=0x20; //即将把定时器1设置为:工作方式2,初值自动重装的8位定时器。
  156. TMOD=TMOD&0x0f; //此寄存器低4位是跟定时器0相关,高4位是跟定时器1相关。先清零定时器1。
  157. TMOD=TMOD|u8_TMOD_Temp;  //把高4位的定时器1填入0x2,低4位的定时器0保持不变。
  158. TH1=256-(11059200L/12/32/9600);  //波特率为9600。11059200代表晶振11.0592MHz,
  159. TL1=256-(11059200L/12/32/9600);  //L代表long的长类型数据。根据芯片手册提供的计算公式。
  160. TR1=1;  //开启定时器1

  161. SM0=0;  
  162. SM1=1;  //SM0与SM1的设置:选择10位异步通信,波特率根据定时器1可变  
  163. REN=1;  //允许串口接收数据

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

  167. ES=1;         //允许串口中断  
  168. EA=1;         //允许总中断
  169. }

  170. void Delay(unsigned long u32DelayTime)
  171. {
  172.     for(;u32DelayTime>0;u32DelayTime--);
  173. }

  174. void PeripheralInitial(void)
  175. {

  176. }

  177. void BeepOpen(void)
  178. {
  179. P3_4=0;  
  180. }

  181. void BeepClose(void)
  182. {
  183. P3_4=1;  
  184. }

  185. void VoiceScan(void)
  186. {

  187.           static unsigned char Su8Lock=0;  

  188. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  189.           {
  190.                   if(0==Su8Lock)
  191.                   {
  192.                    Su8Lock=1;  
  193. BeepOpen();
  194.      }
  195.     else  
  196. {     

  197.                        vGu16BeepTimerCnt--;         

  198.                    if(0==vGu16BeepTimerCnt)
  199.                    {
  200.                            Su8Lock=0;     
  201. BeepClose();  
  202.                    }

  203. }
  204.           }         
  205. }

复制代码

本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2018-10-30 14:57:11 | 显示全部楼层
吴坚鸿 发表于 2016-1-18 11:49
第一节:我是中国最幸福的单片机工程师。

好好学习一下楼主写的大作!

出0入0汤圆

发表于 2018-10-31 07:55:37 | 显示全部楼层
谢谢鸿哥更新!

出0入4汤圆

发表于 2018-11-2 09:25:43 | 显示全部楼层
还在更新,学习了!

出0入0汤圆

发表于 2018-11-3 11:40:13 | 显示全部楼层
对于初学者来说是一个不可多得的好文章~~~值得推广,看完这些,肯定可以玩转单片机了

出0入0汤圆

发表于 2018-11-10 21:02:59 | 显示全部楼层
跟上脚步,一定可以成功。谢谢LZ

出200入0汤圆

发表于 2018-11-10 21:36:10 | 显示全部楼层
感谢搂住的坚持,受益匪浅~~~

出0入0汤圆

发表于 2018-11-12 22:20:13 | 显示全部楼层
传播技术知识,为国贡献,功德无量!

出60入0汤圆

发表于 2018-11-13 13:53:58 | 显示全部楼层
非常感谢鸿哥,到这里所有的文档下载完了,从今天开始学单片机了,虽然自己年纪大了点,但看了你写的这些教程,感触非常磊,在此非常感谢你!!!

出0入0汤圆

发表于 2018-11-14 11:21:46 | 显示全部楼层
鸿哥出品,必属精品!

出0入0汤圆

 楼主| 发表于 2018-11-14 12:30:19 | 显示全部楼层
第一百三十三节:常用的三种串口发送函数。

【133.1   发送单字节的底层驱动函数。】
                  
      单片机内置的“独立硬件串口模块”能直接实现“发送一个字节数据”的基础功能,因此,发送单字节的函数是应用层与硬件层的最小单位的接口函数,也称为底层驱动函数。应用层再复杂的发送函数都基于此最小单位的接口函数来实现。单片机应用层与“独立硬件串口模块”之间的接口通信是靠寄存器SBUF作为中间载体的,要实现发送单字节的最小接口函数,有如下三个关键点。
      第一个,单片机应用层如何知道“硬件模块”已经发送完了一个字节,靠什么来识别?答:在初始化函数里,可以把“硬件模块”配置成,每发送完一个字节后都产生一次发送中断,在发送中断函数里让一个全局变量从0变成1,依此全局变量作为识别是否已经发送完一个字节的标志。
      第二个,发送一个字节数据的时候,如果“硬件模块”通讯异常,没有按预期产生发送中断,单片机就会一直处于死循环等待“完成标志”的状态,怎么办?答:在等待“完成标志”的时候,加入超时处理的机制。
      第三个,在连续发送一堆数据时,如果接收方(或者上位机)发现有丢失数据的时候,如何调节此发送函数?答:可以根据实际调试的结果,如果接收方发现丢失数据,可以尝试在每发送一个字节之后插入一个Delay延时,延时的时间长度根据实际调试为准。我个人的经验中,感觉stm32这类M3核或者M4核的单片机在发送一个字节的时候只需判断是否发送完成的标志位即可,不需要插入Delay延时。但是在其它某些个别厂家单片机的串口发送数据中,是需要插入Delay延时作为调节,否则在连续发送一堆数据时会丢失数据,这个,应该以实际调试项目为准。
      片段的讲解代码如下:

  1. unsigned char Gu8ReceData;
  2. unsigned char Gu8SendByteFinish=0; //发送一个字节完成的标志
  3. void usart(void) interrupt 4     //串口的中断函数      
  4. {        
  5.         if(1==RI)  
  6.         {
  7.             RI = 0;
  8. Gu8ReceData=SBUF;
  9. }
  10.         else  //发送数据引起的中断
  11.         {
  12.            TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  13.        Gu8SendByteFinish=1; //从0变成1通知主函数已经发送完一个字节的数据了。
  14.         }                                                      
  15. }  

  16. void UsartSendByteData(unsigned char u8SendData) //发送一个字节的底层驱动函数
  17. {
  18.     static unsigned int Su16TimeOutDelay;  //超时处理的延时计时器

  19.     Gu8SendByteFinish=0;  //在发送一个字节之前,必须先把此全局变量的标志清零。
  20. SBUF =u8SendData; //依靠寄存器SBUF作为载体发送一个字节的数据
  21. Su16TimeOutDelay=0xffff;  //超时处理的延时计时器装载一个相对合理的计时初始值
  22. while(Su16TimeOutDelay>0)  //超时处理
  23. {
  24.     if(1==Gu8SendByteFinish)  
  25. {
  26.     break;  //如果Gu8SendByteFinish为1,则发送一个字节完成,退出当前循环等待。
  27. }
  28. Su16TimeOutDelay--;  //超时计时器不断递减
  29. }

  30. //Delay();//在实际应用中,当连续发送一堆数据时如果发现丢失数据,可以尝试在此增加延时
  31. }
复制代码

【133.2   发送任意起始位置任意长度的函数。】
                  
      要连续发送一堆数据,必须先把这堆数据封装成一个数组,然后编写一个发送数组的函数。该函数内部是基于“发送单字节的最小接口函数”来实现的。该函数对外通常需要两个接口,一个是数组的任意起始位置,一个发送的数据长度。数组的任意起始位置只需靠指针即可实现。片段的讲解代码如下:

  1. //任意数组
  2. unsigned char Gu8SendBuffer[11]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A};

  3. //发送任意起始位置任意长度的函数
  4. void UsartSendBuffer(const unsigned char *pCu8SendBuffer,unsigned long u32SendSize)
  5. {
  6.     static unsigned long i;
  7.     for(i=0;i<u32SendSize;i++) //u32SendSize为发送的数据长度
  8. {
  9.        UsartSendByteData(pCu8SendBuffer[i]);  //基于“发送单字节的最小接口函数”来实现的
  10.     }
  11. }

  12. void main()
  13. {
  14.     UsartSendBuffer((const unsigned char *)&Gu8SendBuffer[0],5);//从第0位置发送5个数据
  15.     UsartSendBuffer((const unsigned char *)&Gu8SendBuffer[6],5);//从第6位置发送5个数据
  16.     while(1)  
  17. {  

  18.     }
  19. }
复制代码


【133.3   发送带协议的函数。】
                  
     前面章节中,我们讲过接收“带固定协议”的程序框架,这类“带固定协议”的数据串里本身就自带了“数据的长度”,因此,要编程一个发送带协议的函数,关键在于,在函数内部根据协议先提取整串数据的有效长度。该函数对外通常也需要两个接口,一个是数组的起始位置,一个发送数据的最大限制长度。最大限制长度的作用是用来防止数组越界,增强程序的安全性。片段的讲解代码如下:

  1. //“固定协议”十六进制的数据格式:EB 01 00 00 00 0B 03 E8 00 01 0B 。其中:
  2.     // EB是数据头。
  3. // 01是代表数据类型。
  4. // 00 00 00 0B代表数据长度是11个(十进制)。
  5. // 03 E8 00 01 0B代表其它数据

  6. //“带固定协议”的数组
  7. unsigned char Gu8SendMessage[11]={0xEB,0x01,0x00,0x00,0x00,0x0B,0x03,0xE8,0x00,0x01,0x0B};

  8. //发送带协议的函数
  9. void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize)
  10. {
  11.     static unsigned long i;
  12.     static unsigned long *pSu32;
  13.     static unsigned long u32SendSize;

  14.     pSu32=(const unsigned long *)&pCu8SendMessage[2];
  15. u32SendSize=*pSu32;  //从带协议的数组中提取整包数组的有效发送长度

  16. if(u32SendSize>u32SendMaxSize) //如果“有效发送长度”大于“最大限制的长度”,数据异常
  17. {
  18.    return;  //数据异常,直接退出当前函数,预防数组越界
  19. }

  20.     for(i=0;i<u32SendSize;i++) //u32SendSize为发送的数据长度
  21. {
  22.        UsartSendByteData(pCu8SendMessage[i]); //基于“发送单字节的最小接口函数”来实现的
  23.     }
  24. }

  25. void main()
  26. {
  27.     UsartSendMessage((const unsigned char *)&Gu8SendMessage[0],100); //必须从第0位置发送
  28.     while(1)  
  29. {  

  30.     }
  31. }
复制代码



【133.4   程序例程。】


上图133.4.1  232串口电路
            
程序功能如下:
单片机上电瞬间,直接发送三串数据。
第一串是十六进制的任意数据:00 01 02 03 04
第二串是十六进制的任意数据:06 07 08 09 0A
第三串是十六进制的“带协议”数据:EB 01 00 00 00 0B 03 E8 00 01 0B
波特率9600,校验位NONE(无),数据位8,停止位1。在电脑的串口助手软件里,设置接收显示的为“十六进制”(HEX模式),即可观察到发送的三串数据。
代码如下:

  1. #include "REG52.H"

  2. void UsartSendByteData(unsigned char u8SendData); //发送一个字节的底层驱动函数

  3. //发送任意起始位置任意长度的函数
  4. void UsartSendBuffer(const unsigned char *pCu8SendBuffer,unsigned long u32SendSize);

  5. //发送带协议的函数
  6. void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize);

  7. void usart(void);  //串口接收的中断函数
  8. void SystemInitial(void);
  9. void Delay(unsigned long u32DelayTime);
  10. void PeripheralInitial(void);

  11. unsigned char Gu8ReceData;
  12. unsigned char Gu8SendByteFinish=0; //发送一个字节完成的标志

  13. //任意数组
  14. unsigned char Gu8SendBuffer[11]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A};

  15. //“固定协议”十六进制的数据格式:EB 01 00 00 00 0B 03 E8 00 01 0B 。其中:
  16. // EB是数据头。
  17. // 01是代表数据类型。
  18. // 00 00 00 0B代表数据长度是11个(十进制)。
  19. // 03 E8 00 01 0B代表其它数据
  20. //“带固定协议”的数组
  21. unsigned char Gu8SendMessage[11]={0xEB,0x01,0x00,0x00,0x00,0x0B,0x03,0xE8,0x00,0x01,0x0B};

  22. void main()
  23. {
  24. SystemInitial();            
  25. Delay(10000);               
  26. PeripheralInitial();    //在此函数内部调用了发送的三串数据
  27.     while(1)  
  28. {  
  29.     }
  30. }

  31. void usart(void) interrupt 4     //串口的中断函数      
  32. {        
  33.         if(1==RI)  
  34.         {
  35.             RI = 0;
  36. Gu8ReceData=SBUF;
  37. }
  38.         else  //发送数据引起的中断
  39.         {
  40.            TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  41.        Gu8SendByteFinish=1; //从0变成1通知主函数已经发送完一个字节的数据了。
  42.         }                                                      
  43. }  

  44. void UsartSendByteData(unsigned char u8SendData) //发送一个字节的底层驱动函数
  45. {
  46.     static unsigned int Su16TimeOutDelay;  //超时处理的延时计时器

  47.     Gu8SendByteFinish=0;  //在发送以字节之前,必须先把此全局变量的标志清零。
  48. SBUF =u8SendData; //依靠寄存器SBUF作为载体发送一个字节的数据
  49. Su16TimeOutDelay=0xffff;  //超时处理的延时计时器装载一个相对合理的计时初始值
  50. while(Su16TimeOutDelay>0)  //超时处理
  51. {
  52.     if(1==Gu8SendByteFinish)  
  53. {
  54.     break;  //如果Gu8SendByteFinish为1,则发送一个字节完成,退出当前循环等待。
  55. }
  56. Su16TimeOutDelay--;  //超时计时器不断递减
  57. }

  58. //Delay();//在实际应用中,当连续发送一堆数据时如果发现丢失数据,可以尝试在此增加延时
  59. }

  60. //发送任意起始位置任意长度的函数
  61. void UsartSendBuffer(const unsigned char *pCu8SendBuffer,unsigned long u32SendSize)
  62. {
  63.     static unsigned long i;
  64.     for(i=0;i<u32SendSize;i++) //u32SendSize为发送的数据长度
  65. {
  66.        UsartSendByteData(pCu8SendBuffer[i]);  //基于“发送单字节的最小接口函数”来实现的
  67.     }
  68. }

  69. //发送带协议的函数
  70. void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize)
  71. {
  72.     static unsigned long i;
  73.     static unsigned long *pSu32;
  74.     static unsigned long u32SendSize;

  75.     pSu32=(const unsigned long *)&pCu8SendMessage[2];
  76. u32SendSize=*pSu32;  //从带协议的数组中提取整包数组的有效发送长度

  77. if(u32SendSize>u32SendMaxSize) //如果“有效发送长度”大于“最大限制的长度”,数据异常
  78. {
  79.    return;  //数据异常,直接退出当前函数,预防数组越界
  80. }

  81.     for(i=0;i<u32SendSize;i++) //u32SendSize为发送的数据长度
  82. {
  83.        UsartSendByteData(pCu8SendMessage[i]); //基于“发送单字节的最小接口函数”来实现的
  84.     }
  85. }

  86. void SystemInitial(void)
  87. {
  88. unsigned char u8_TMOD_Temp=0;

  89. //以下是定时器0的中断的配置
  90. TMOD=0x01;  
  91. TH0=0xfc;   
  92. TL0=0x66;   
  93. EA=1;      
  94. ET0=1;      
  95. TR0=1;   

  96. //以下是串口接收中断的配置
  97. //串口的波特率与内置的定时器1直接相关,因此配置此定时器1就等效于配置波特率。
  98. u8_TMOD_Temp=0x20; //即将把定时器1设置为:工作方式2,初值自动重装的8位定时器。
  99. TMOD=TMOD&0x0f; //此寄存器低4位是跟定时器0相关,高4位是跟定时器1相关。先清零定时器1。
  100. TMOD=TMOD|u8_TMOD_Temp;  //把高4位的定时器1填入0x2,低4位的定时器0保持不变。
  101. TH1=256-(11059200L/12/32/9600);  //波特率为9600。11059200代表晶振11.0592MHz,
  102. TL1=256-(11059200L/12/32/9600);  //L代表long的长类型数据。根据芯片手册提供的计算公式。
  103. TR1=1;  //开启定时器1

  104. SM0=0;  
  105. SM1=1;  //SM0与SM1的设置:选择10位异步通信,波特率根据定时器1可变  
  106. REN=1;  //允许串口接收数据

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

  110. ES=1;         //允许串口中断  
  111. EA=1;         //允许总中断
  112. }

  113. void Delay(unsigned long u32DelayTime)
  114. {
  115.     for(;u32DelayTime>0;u32DelayTime--);
  116. }

  117. void PeripheralInitial(void)
  118. {
  119.     //发送任意数组
  120.     UsartSendBuffer((const unsigned char *)&Gu8SendBuffer[0],5);//从第0位置发送5个数据
  121. UsartSendBuffer((const unsigned char *)&Gu8SendBuffer[6],5);//从第6位置发送5个数据

  122. //发送带协议的数组
  123.     UsartSendMessage((const unsigned char *)&Gu8SendMessage[0],100); //必须从第0位置发送
  124. }



复制代码

本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2018-11-15 00:44:00 | 显示全部楼层
后续希望也讲下MODBUS-RTU协议应用

出0入0汤圆

发表于 2018-11-15 07:51:42 | 显示全部楼层
谢谢鸿哥更新!

出0入0汤圆

发表于 2018-11-15 23:40:32 | 显示全部楼层
还坚持这么久 厉害

出0入0汤圆

发表于 2018-11-19 21:21:28 | 显示全部楼层

第一百三十三节 打不开

出0入0汤圆

发表于 2018-11-20 09:38:20 | 显示全部楼层
楼主太敬业了,三年过去了,这帖子居然还在更新,佩服佩服!

出0入0汤圆

发表于 2018-11-21 12:12:21 | 显示全部楼层
dtdzlujian 发表于 2018-11-19 21:21
第一百三十三节 打不开

把后缀名改为.PDF就可以了

出0入0汤圆

发表于 2018-11-22 09:16:48 | 显示全部楼层
谢谢! kkfy888

出0入0汤圆

发表于 2018-11-22 11:41:53 | 显示全部楼层

不用客气!

出0入0汤圆

发表于 2018-11-23 08:40:19 | 显示全部楼层
楼主有恒心,有情怀,加油!

出0入0汤圆

发表于 2018-12-16 13:22:56 | 显示全部楼层
顶一下 好东西

出0入0汤圆

发表于 2019-1-1 14:45:49 | 显示全部楼层
2019年了,元旦快乐,年后还更新吗

出0入0汤圆

发表于 2019-1-16 04:28:44 来自手机 | 显示全部楼层
刚好年底了,估计都是很忙的时候了。支持

出0入0汤圆

发表于 2019-1-16 08:25:01 | 显示全部楼层
浅显易懂,比较适合初学者,为楼主的开源共享和坚持精神点赞!

出0入0汤圆

发表于 2019-1-30 16:53:01 | 显示全部楼层
谢谢鸿哥更新

出0入0汤圆

 楼主| 发表于 2019-2-2 13:00:13 | 显示全部楼层
第一百三十四节:“应用层半双工”双机串口通讯的程序框架。

【134.1   应用层的“半双工”和“全双工”。】

       应用层的“半双工”。主机与从机在程序应用层采用“一问一答”的查询模式,主机是主动方,从机是被动方,主机问一句从机答一句,“聊天对话“的氛围很无趣很呆板。从机没有发言权,当从机想主动给主机发送一些数据时就“憋得慌”。半双工适用于大多数单向通讯的场合。
       应用层的“全双工”。主机与从机在程序应用层可以实现任意双向的通讯,这时从机也可变为主机,主机也可变为从机,就像两个人平时聊天,无所谓谁是从机谁是主机,也无所谓非要对方对我每句话都要应答附和(只要对方能听得清我讲什么就可以),“聊天对话”的氛围很生动很活泼。全双工适用于通讯更复杂的场合。
       本节从“半双工“开始讲,让初学者先熟悉双机通讯的基本程序框架,下一节再讲“全双工“。

【134.2   双机通讯的三类核心函数。】

       双机通讯在程序框架层面有三类核心的涵数,它们分别是:通讯过程的控制涵数,发送的队列驱动涵数,接收数据后的处理涵数。
       “通讯过程的控制涵数”的数量可以不止1个,每一个通讯事件都对应一个独立的“通讯过程的控制涵数”,根据通讯事件的数量,一个系统往往有N个“通讯过程的控制涵数”。顾名思义,它负责过程的控制,无论什么项目,凡是过程控制我都首选switch语句。此函数是属于上层应用的函数,它的基础底层是“发送的队列驱动涵数”和“接收数据后的处理涵数”这两个函数。
       “发送的队列驱动涵数”在系统中只有1个“发送的队列驱动涵数”,负责“通讯管道的占用”的分配,负责数据的具体发送。当同时存在很多“待发送”的请求指令时,此函数会根据“if ,else if...”的优先级,像队列一样安排各指令发送的先后顺序,确保各指令不会发生冲突。此函数属于底层的驱动函数。
       “接收数据后的处理涵数”在系统中只有1个,负责处理当前接收到的数据,它既属于“底层函数”也属于“应用层函数”,二者成分皆有。
       我们一旦深刻地领悟了这三类函数各自的分工与关联方式,将来应付再复杂的通讯系统都会脉络清析,游刃有余。

【134.3   例程的功能需求。】

       上位机与下位机都有一个一模一样的57个字节的大数组。在上位机端按下独立按键K1后,上位机开始与下位机建立通讯,上位机的目的是读取下位机的那57个字节的大数组,分批读取,每批读取10个字节,最后一批读取的是余下的7个字节。读取完毕后,上位机把读取到的大数组与自己的大数组进行对比:如果相等,表示通讯正确,蜂鸣器“长鸣”一声;如果不相等,表示通讯错误,蜂鸣器“短鸣”一声。在通讯过程中,如果出现通信异常(比如因为接收超时或者接收某批次数据错误而导致重发的次数超过最大限制的次数)也表示通讯错误,蜂鸣器也会发出“短鸣”一声的提示。

【134.4   例程的电路图。】

        两个单片机进行232串口通讯,一共需要3根线:1根作为共地线,其它2根是交叉的收发数据线(上位机的“接收线”连接下位机的“发送线”,上位机的“发送线”连接下位机的“接收线”),如下图所示:

       上图134.4.1  双机通讯的232串口接线图



       上图134.4.2  上位机的独立按键



       上图134.4.3 上位机的有源蜂鸣器

【134.5   例程的通讯协议。】

(一)通讯参数。波特率9600,校验位NONE(无),数据位8,停止位1。

(二)上位机读取下位机的数组容量的大小的指令。
        (1)上位机发送十六进制的数据:EB 01 00 00 00 07 ED。
         EB是数据头。
         01是指令类型,01代表请求下位机返回大数组的容量大小。
         00 00 00 07代表整个指令的数据长度。
         ED是前面所有字节数据的异或结果,用来作为校验数据。

       (2)下位机返回十六进制的数据:EB 01 00 00 00 0C XX XX XX XX ZZ。
         EB是数据头。
         01是指令类型,01代表返回大数组的容量大小。
         00 00 00 0B代表整个指令的数据长度
         XX XX XX XX代表大数组的容量大小
         ZZ是前面所有字节数据的异或结果,用来作为校验数据。

(三)上位机读取下位机的大数组的分段数据的指令。
       (1)上位机发送十六进制的数据:EB 02 00 00 00 0F RR RR RR RR YY YY YY YY ZZ
         EB是数据头
         02是指令类型,02代表请求下位机返回当前分段的数据。
         00 00 00 0F代表整个指令的数据长度
         RR RR RR RR代表请求下位机返回的数据的“请求起始地址”
         YY YY YY YY代表请求下位机从“请求起始地址”一次返回的数据长度
         ZZ是前面所有字节数据的异或结果,用来作为校验数据。

      (2)下位机返回十六进制的数据:EB 02 TT TT TT TT RR RR RR RR YY YY YY YY HH ...HH ZZ
        EB是数据头
        02是指令类型,02代表返回大数组当前分段的数据
        TT TT TT TT 代表整个指令的数据长度
        RR RR RR RR代表下位机返回数据时的“请求起始地址”
        YY YY YY YY代表下位机从“请求起始地址”一次返回的数据长度
        HH ...HH代表中间有效的数据内容
        ZZ是前面所有字节数据的异或结果,用来作为校验数据。

【134.6   解决本节例程编译不过去的方法。】

        因为本节用到的全局变量比较多,如果有初学者在编译的时候出现“error C249: 'DATA': SEGMENT TOO LARGE”的提示,请按下图的窗口提示来设置一下编译的环境。



       上图134.5.1 设置编译的环境

【134.7   例程的上位机程序。】
  1. #include "REG52.H"

  2. #define RECE_TIME_OUT    2000  //通讯过程中字节之间的超时时间2000ms
  3. #define REC_BUFFER_SIZE  30    //常规控制类数组的长度
  4. #define KEY_FILTER_TIME  25    //按键滤波的“稳定时间”

  5. void usart(void);  //串口接收的中断函数
  6. void T0_time();    //定时器的中断函数

  7. void BigBufferUsart(void);  //读取下位机大数组的“通讯过程的控制涵数”。三大核心函数之一
  8. void QueueSend(void);       //发送的队列驱动涵数。三大核心函数之一
  9. void ReceDataHandle(void);  //接收数据后的处理涵数。三大核心函数之一

  10. void UsartTask(void);    //串口收发的任务函数,放在主函数内

  11. unsigned char CalculateXor(const unsigned char *pCu8Buffer, //异或的算法函数
  12. unsigned long u32BufferSize);  

  13. //比较两个数组的是否相等。返回1代表相等,返回0代表不相等
  14. //u32BufferSize是参与对比的数组的大小
  15. unsigned char CmpTwoBufferIsSame(const unsigned char *pCu8Buffer_1,
  16. const unsigned char *pCu8Buffer_2,
  17. unsigned long u32BufferSize);

  18. void UsartSendByteData(unsigned char u8SendData); //发送一个字节的底层驱动函数

  19. //发送带协议的函数
  20. void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize);

  21. void SystemInitial(void) ;
  22. void Delay(unsigned long u32DelayTime) ;
  23. void PeripheralInitial(void) ;

  24. void BeepOpen(void);   
  25. void BeepClose(void);
  26. void VoiceScan(void);
  27. void KeyScan(void);   
  28. void KeyTask(void);   

  29. sbit P3_4=P3^4;        //蜂鸣器的驱动输出口
  30. sbit KEY_INPUT1=P2^2;  //K1按键识别的输入口。

  31. //下面表格数组的数据与下位机的表格数据一模一样,目的用来检测接收到的数据是否正确
  32. code unsigned char Cu8TestTable[]=  
  33. {
  34. 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,
  35. 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,
  36. 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,
  37. 0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,
  38. 0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,
  39. 0x51,0x52,0x53,0x54,0x55,0x56,0x57
  40. };

  41. unsigned char Gu8ReceTable[57]; //从下位机接收到的表格数据的数组

  42. //把一些针对某个特定事件的全局变量放在一个结构体内,可以让全局变量的分类更加清晰
  43. struct StructBigBufferUsart     //控制读取大数组的通讯过程的结构体
  44. {
  45. unsigned char u8Status; //通讯过程的状态 0为初始状态 1为通讯成功 2为通讯失败
  46. unsigned char u8ReSendCnt; //重发计数器
  47. unsigned char u8Step;   //通讯过程的步骤
  48. unsigned char u8Start;  //通讯过程的启动
  49. unsigned long u32NeedSendSize;    //一共需要发送的全部数据量
  50. unsigned long u32AlreadySendSize; //实际已经发送的数据量
  51. unsigned long u32CurrentAddr; //当前批次需要发送的起始地址
  52. unsigned long u32CurrentSize; //当前批次从起始地址开始发送的数据量
  53. unsigned char u8QueueSendTrig;//队列驱动函数的发送的启动
  54. unsigned char u8QueueSendBuffer[30]; //队列驱动函数的发送指令的数组
  55. unsigned char u8QueueStatus; //队列驱动函数的通讯状态 0为初始状态 1为通讯成功 2为通讯失败
  56. };

  57. unsigned char Gu8QueueReceUpdate=0;  //1代表“队列发送数据后,收到了新的数据”

  58. struct StructBigBufferUsart  GtBigBufferUsart;//此结构体变量专门用来控制读取大数组的通讯事件

  59. volatile unsigned char vGu8BigBufferUsartTimerFlag=0;  //过程控制的超时定时器
  60. volatile unsigned int vGu16BigBufferUsartTimerCnt=0;  

  61. volatile unsigned char vGu8QueueSendTimerFlag=0;  //队列发送的超时定时器
  62. volatile unsigned int vGu16QueueSendTimerCnt=0;  

  63. volatile unsigned char vGu8BeepTimerFlag=0;  
  64. volatile unsigned int vGu16BeepTimerCnt=0;  

  65. volatile unsigned char vGu8KeySec=0;

  66. unsigned char Gu8SendByteFinish=0; //发送一个字节完成的标志

  67. unsigned char Gu8ReceBuffer[REC_BUFFER_SIZE]; //常规控制类的小内存
  68. unsigned char *pGu8ReceBuffer;  //用来切换接收内存的“中转指针”

  69. unsigned long Gu32ReceCntMax=REC_BUFFER_SIZE;  //最大缓存
  70. unsigned long Gu32ReceCnt=0;  //接收缓存数组的下标
  71. unsigned char Gu8ReceStep=0;  //接收中断函数里的步骤变量
  72. unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作变量。
  73. unsigned char Gu8ReceType=0; //接收的数据类型
  74. unsigned char Gu8Rece_Xor=0; //接收的异或
  75. unsigned long Gu32ReceDataLength=0;  //接收的数据长度
  76. unsigned char Gu8FinishFlag=0;  //是否已接收完成一串数据的标志
  77. unsigned long *pu32Data; //用于数据转换的指针
  78. volatile unsigned char vGu8ReceTimeOutFlag=0;//通讯过程中字节之间的超时定时器的开关
  79. volatile unsigned int vGu16ReceTimeOutCnt=0; //通讯过程中字节之间的超时定时器,“喂狗”的对象

  80. void main()
  81. {
  82. SystemInitial();            
  83. Delay(10000);               
  84. PeripheralInitial();      
  85.     while(1)  
  86. {  
  87.     UsartTask();   //串口收发的任务函数
  88. KeyTask();   
  89.     }
  90. }

  91. void KeyTask(void)    //按键任务函数,放在主函数内
  92. {
  93. if(0==vGu8KeySec)
  94. {
  95. return; //按键的触发序号是0意味着无按键触发,直接退出当前函数,不执行此函数下面的代码
  96. }

  97. switch(vGu8KeySec) //根据不同的按键触发序号执行对应的代码
  98. {
  99.    case 1:     //1号按键。K1的独立按键
  100. //GtBigBufferUsart.u8Start在开机初始化函数里必须初始化为0!这一步很关键!
  101.             if(0==GtBigBufferUsart.u8Start) //只有在还没有启动的情况下,才能启动
  102. {
  103. GtBigBufferUsart.u8Status=0; //通讯过程的状态 0为初始状态
  104. GtBigBufferUsart.u8Step=0;   //通讯过程的步骤 0为从当前开始的步骤
  105. GtBigBufferUsart.u8Start=1;  //通讯过程的启动  
  106. }
  107. vGu8KeySec=0;  //响应按键服务处理程序后,按键编号必须清零,避免一致触发
  108. break;
  109. }
  110. }

  111. /* 注释一:
  112. *  每一个通讯事件都对应的一个独立的“通讯过程的控制涵数”,一个系统中有多少个通讯事件,就存在
  113. *  多少个“通讯过程的控制涵数”。该函数负责某个通讯事件从开始到结束的整个过程。比如本节项目,
  114. *  在通讯过程中,如果发现接收到的数据错误,则继续启动重发的机制。当发现接收到的累加字节数等于
  115. *  预期想要接收的数量时,则结束这个通讯的事件。
  116. */

  117. void BigBufferUsart(void)  //读取下位机大数组的“通讯过程的控制涵数”
  118. {
  119.     static const unsigned char SCu8ReSendCntMax=3; //重发的次数
  120. static unsigned long *pSu32Data; //用于数据与数组转换的指针

  121. switch(GtBigBufferUsart.u8Step)  //过程控制,我首选switch语句!
  122. {
  123.     case 0:
  124.          if(1==GtBigBufferUsart.u8Start) //通讯过程的启动
  125.          {
  126.                  //根据实际项目需要,在此第0步骤里可以添加一些初始化相关的数据
  127.                   GtBigBufferUsart.u8ReSendCnt=0; //重发计数器清零

  128.                   GtBigBufferUsart.u8Step=1;   //切换到下一步
  129. }
  130.          break;

  131. //-----------先发送“读取下位机的数组容量的大小的指令”---------------------
  132. //-----------EB 01 00 00 00 07 ED                      ---------------------
  133.     case 1:
  134.          GtBigBufferUsart.u8QueueSendBuffer[0]=0xeb; //数据头
  135.          GtBigBufferUsart.u8QueueSendBuffer[1]=0x01; //数据类型 读取数组容量大小
  136.          pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2];
  137.          *pSu32Data=7; //数据长度  本条指令的数据总长是7个字节
  138. //异或算法的函数
  139. GtBigBufferUsart.u8QueueSendBuffer[6]=CalculateXor(GtBigBufferUsart.u8QueueSendBuffer,
  140. 6);  //最后一个字节不纳入计算

  141.              //队列驱动函数的状态 0为初始状态 1为通讯成功 2为通讯失败
  142.              GtBigBufferUsart.u8QueueStatus=0; //队列驱动函数的通讯状态
  143.              GtBigBufferUsart.u8QueueSendTrig=1;//队列驱动函数的发送的启动


  144.              vGu8BigBufferUsartTimerFlag=0;
  145. vGu16BigBufferUsartTimerCnt=2000;   
  146.              vGu8BigBufferUsartTimerFlag=1;   //过程控制的超时定时器的启动

  147.              GtBigBufferUsart.u8Step=2;   //切换到下一步
  148.          break;

  149.     case 2:  //发送之后,等待下位机返回的数据的状态
  150.          if(1==GtBigBufferUsart.u8QueueStatus) //当前批次的接收到的数据成功
  151.          {
  152.                   GtBigBufferUsart.u8ReSendCnt=0; //重发计数器清零
  153.                   GtBigBufferUsart.u32AlreadySendSize=0; //实际已经发送的数据量清零
  154.                   GtBigBufferUsart.u32CurrentAddr=0;  //当前批次需要发送的起始地址
  155.                   GtBigBufferUsart.u32CurrentSize=10; //从当前批次起始地址开始发送的数据量
  156.                   GtBigBufferUsart.u8Step=3;   //切换到下一步
  157.          }
  158.          else if(2==GtBigBufferUsart.u8QueueStatus) //当前批次的接收到的数据失败
  159.          {
  160.               GtBigBufferUsart.u8ReSendCnt++;
  161.               if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重发次数
  162.               {
  163. GtBigBufferUsart.u8Step=0;
  164. GtBigBufferUsart.u8Start=0; //结束当前的过程通讯
  165. GtBigBufferUsart.u8Status=2; //对外宣布“通讯失败”
  166.                       vGu8BeepTimerFlag=0;  
  167. vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
  168. vGu8BeepTimerFlag=1;
  169. }
  170. else
  171. {
  172.                       GtBigBufferUsart.u8Step=1;   //返回上一步,重发当前段的数据
  173. }
  174.          }
  175.          else if(0==vGu16BigBufferUsartTimerCnt) //当前批次在等待接收返回数据时,超时
  176.          {
  177.               GtBigBufferUsart.u8ReSendCnt++;
  178.               if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重发次数
  179.               {
  180. GtBigBufferUsart.u8Step=0;
  181. GtBigBufferUsart.u8Start=0; //结束当前的过程通讯
  182. GtBigBufferUsart.u8Status=2; //对外宣布“通讯失败”

  183.                       vGu8BeepTimerFlag=0;  
  184. vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
  185. vGu8BeepTimerFlag=1;
  186. }
  187. else
  188. {
  189.                       GtBigBufferUsart.u8Step=1;   //返回上一步,重发当前段的数据
  190. }
  191.          }
  192.          break;

  193. //-----------接着发送“读取下位机的大数组的分段数据的指令”---------------------
  194. //-----------EB 02 00 00 00 0F RR RR RR RR YY YY YY YY ZZ  ---------------------
  195.     case 3:
  196.          GtBigBufferUsart.u8QueueSendBuffer[0]=0xeb; //数据头
  197.          GtBigBufferUsart.u8QueueSendBuffer[1]=0x02; //数据类型 读取分段数据
  198.          pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2];
  199.          *pSu32Data=15; //数据长度  本条指令的数据总长是15个字节

  200.          pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2+4];
  201.          *pSu32Data=GtBigBufferUsart.u32CurrentAddr; //当前批次需要发送的起始地址

  202.          pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2+4+4];
  203.          *pSu32Data=GtBigBufferUsart.u32CurrentSize; //从当前批次起始地址发送的数据量

  204. //异或算法的函数
  205. GtBigBufferUsart.u8QueueSendBuffer[14]=CalculateXor(GtBigBufferUsart.u8QueueSendBuffer,
  206. 14);  //最后一个字节不纳入计算

  207.              //队列驱动函数的状态 0为初始状态 1为通讯成功 2为通讯失败
  208.              GtBigBufferUsart.u8QueueStatus=0; //队列驱动函数的通讯状态
  209.              GtBigBufferUsart.u8QueueSendTrig=1;//队列驱动函数的发送的启动

  210.              vGu8BigBufferUsartTimerFlag=0;
  211. vGu16BigBufferUsartTimerCnt=2000;   
  212.              vGu8BigBufferUsartTimerFlag=1;   //过程控制的超时定时器的启动

  213.              GtBigBufferUsart.u8Step=4;   //切换到下一步
  214.          break;

  215.     case 4: //发送之后,等待下位机返回的数据的状态
  216.          if(1==GtBigBufferUsart.u8QueueStatus) //当前批次的接收到的数据成功
  217.          {
  218.               //更新累加当前实际已经发送的字节数
  219. GtBigBufferUsart.u32AlreadySendSize=GtBigBufferUsart.u32AlreadySendSize+
  220. GtBigBufferUsart.u32CurrentSize;

  221.               //更新下一步起始的发送地址
  222. GtBigBufferUsart.u32CurrentAddr=GtBigBufferUsart.u32CurrentAddr+
  223. GtBigBufferUsart.u32CurrentSize;

  224.               //更新下一步从起始地址开始发送的字节数
  225. if((GtBigBufferUsart.u32CurrentAddr+GtBigBufferUsart.u32CurrentSize)>
  226. GtBigBufferUsart.u32NeedSendSize) //最后一段数据的临界点的判断
  227. {
  228. GtBigBufferUsart.u32CurrentSize=GtBigBufferUsart.u32NeedSendSize-
  229. GtBigBufferUsart.u32CurrentAddr;
  230. }
  231. else
  232. {
  233. GtBigBufferUsart.u32CurrentSize=10;
  234. }

  235.                   //判断是否已经把整个大数组的57个字节都已经接收完毕。如果已经接收完毕,则
  236.                   //结束当前通信;如果还没结束,则继续请求下位机发送下一段新数据。
  237. if(GtBigBufferUsart.u32AlreadySendSize>=GtBigBufferUsart.u32NeedSendSize)
  238. {
  239. GtBigBufferUsart.u8Step=0;
  240. GtBigBufferUsart.u8Start=0; //结束当前的过程通讯

  241. if(1==CmpTwoBufferIsSame(Cu8TestTable,    //如果接收的数据与存储的相等
  242. Gu8ReceTable,
  243.                                                 57))
  244.                       {
  245.                           vGu8BeepTimerFlag=0;  
  246. vGu16BeepTimerCnt=1000;   //让蜂鸣器“长鸣”一声
  247. vGu8BeepTimerFlag=1;  
  248. GtBigBufferUsart.u8Status=1; //对外宣布“通讯成功”
  249. }
  250. else
  251. {
  252.                           vGu8BeepTimerFlag=0;  
  253. vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
  254. vGu8BeepTimerFlag=1;  
  255. GtBigBufferUsart.u8Status=2; //对外宣布“通讯失败”
  256. }

  257. }
  258. else
  259. {
  260.                       GtBigBufferUsart.u8ReSendCnt=0; //重发计数器清零
  261.                       GtBigBufferUsart.u8Step=3;   //返回上一步,继续发下一段的新数据
  262. }

  263.          }
  264.          else if(2==GtBigBufferUsart.u8QueueStatus) //当前批次的接收到的数据失败
  265.          {
  266.               GtBigBufferUsart.u8ReSendCnt++;
  267.               if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重发次数
  268.               {
  269. GtBigBufferUsart.u8Step=0;
  270. GtBigBufferUsart.u8Start=0; //结束当前的过程通讯
  271. GtBigBufferUsart.u8Status=2; //对外宣布“通讯失败”

  272.                       vGu8BeepTimerFlag=0;  
  273. vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
  274. vGu8BeepTimerFlag=1;
  275. }
  276. else
  277. {
  278.                       GtBigBufferUsart.u8Step=3;   //返回上一步,重发当前段的数据
  279. }
  280.          }
  281.          else if(0==vGu16BigBufferUsartTimerCnt) //当前批次在等待接收返回数据时,超时
  282.          {
  283.               GtBigBufferUsart.u8ReSendCnt++;
  284.               if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重发次数
  285.               {
  286. GtBigBufferUsart.u8Step=0;
  287. GtBigBufferUsart.u8Start=0; //结束当前的过程通讯
  288. GtBigBufferUsart.u8Status=2; //对外宣布“通讯失败”

  289.                       vGu8BeepTimerFlag=0;  
  290. vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
  291. vGu8BeepTimerFlag=1;
  292. }
  293. else
  294. {
  295.                       GtBigBufferUsart.u8Step=3;   //返回上一步,重发当前段的数据
  296. }
  297.          }
  298.          break;
  299. }

  300. }

  301. /* 注释二:
  302. *  整个项目中只有一个“发送的队列驱动涵数”,负责“通讯管道的占用”的分配,负责数据的具体发
  303. *  送。当同时存在很多“待发送”的请求指令时,此函数会根据“if ,else if...”的优先级,像队列一
  304. *  样安排各指令发送的先后顺序,确保各指令不会发生冲突。
  305. */

  306. void QueueSend(void)  //发送的队列驱动涵数
  307. {
  308. static unsigned char Su8Step=0;

  309. switch(Su8Step)
  310. {
  311.    case 0:  //分派即将要发送的任务
  312.         if(1==GtBigBufferUsart.u8QueueSendTrig)
  313.         {
  314.             GtBigBufferUsart.u8QueueSendTrig=0;  //及时清零。驱动层,不管结果,只发一次。

  315. Gu8QueueReceUpdate=0; //接收应答数据的状态恢复初始值

  316.             //发送带指令的数据
  317. UsartSendMessage((const unsigned char *)&GtBigBufferUsart.u8QueueSendBuffer[0],
  318. 30);

  319. vGu8QueueSendTimerFlag=0;
  320. vGu16QueueSendTimerCnt=2000;  
  321. vGu8QueueSendTimerFlag=1;  //队列发送的超时定时器
  322. Su8Step=1;
  323. }
  324. //      else if(...)  //当有其它发送的指令时,可以在此处继续添加判断,越往下优先级越低
  325. //      else if(...)  //当有其它发送的指令时,可以在此处继续添加判断,越往下优先级越低

  326.         break;

  327.    case 1: //发送之后,等待下位机的应答。驱动层,只管有没有应答,不管应答对不对。
  328.         if(1==Gu8QueueReceUpdate) //如果“接收数据后的处理涵数”接收到应答数据
  329.         {
  330. Su8Step=0;  //返回上一步继续处理其它“待发送的指令”
  331. }

  332. if(0==vGu16QueueSendTimerCnt) //发送指令之后,等待应答超时
  333.         {
  334. Su8Step=0;  //返回上一步继续处理其它“待发送的指令”
  335. }

  336.         break;
  337. }
  338. }


  339. /* 注释三:
  340. *  整个项目中只有一个“接收数据后的处理涵数”,负责即时处理当前接收到的数据。
  341. */

  342. void ReceDataHandle(void)  //接收数据后的处理涵数
  343. {
  344. static unsigned long *pSu32Data; //数据转换的指针
  345. static unsigned long i;
  346. static unsigned char Su8Rece_Xor=0;  //计算的“异或”

  347. static unsigned long Su32CurrentAddr; //读取的起始地址
  348. static unsigned long Su32CurrentSize; //读取的发送的数据量

  349.     if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及时更新一次“超时检测的定时器”的初值
  350.         {
  351.                 Gu8ReceFeedDog=0;
  352.                                
  353.                 vGu8ReceTimeOutFlag=0;
  354.         vGu16ReceTimeOutCnt=RECE_TIME_OUT;//更新一次“超时检测的定时器”的初值
  355.                 vGu8ReceTimeOutFlag=1;
  356.         }
  357.         else if(Gu8ReceStep>0&&0==vGu16ReceTimeOutCnt) //超时,并且步骤不在接头暗号的步骤
  358.         {
  359.             Gu8ReceStep=0; //串口接收数据的中断函数及时切换回接头暗号的步骤
  360.     }

  361.         if(1==Gu8FinishFlag)  //1代表已经接收完毕一串新的数据,需要马上去处理
  362.         {
  363.                 switch(Gu8ReceType)  //接收到的数据类型
  364.                 {
  365.                         case 0x01:   //读取下位机的数组容量的大小
  366. Gu8QueueReceUpdate=1;  //告诉“队列驱动函数”收到了新的应答数据

  367. Gu8Rece_Xor=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取接收到的“异或”
  368. Su8Rece_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //计算“异或”

  369. if(Gu32ReceDataLength>=11&& //接收到的数据长度必须大于或者等于11个字节
  370. Su8Rece_Xor==Gu8Rece_Xor) //验证“异或”,“计算的”与“接收的”是否一致
  371. {
  372.                              pSu32Data=(unsigned long *)&Gu8ReceBuffer[6]; //数据转换。
  373.                                      GtBigBufferUsart.u32NeedSendSize=*pSu32Data;  //提取将要接收数组的大小
  374. GtBigBufferUsart.u8QueueStatus=1; //告诉“过程控制函数”,当前通讯成功
  375. }
  376. else
  377. {
  378.                       GtBigBufferUsart.u8QueueStatus=2; //告诉“过程控制函数”,当前通讯失败
  379. }
  380.        
  381.                   break;

  382.                         case 0x02:   //读取下位机的分段数据
  383. Gu8QueueReceUpdate=1;  //告诉“队列驱动函数”收到了新的应答数据

  384. Gu8Rece_Xor=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取接收到的“异或”
  385. Su8Rece_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //计算“异或”

  386.                           pSu32Data=(unsigned long *)&Gu8ReceBuffer[6]; //数据转换。
  387.                                   Su32CurrentAddr=*pSu32Data;  //读取的起始地址

  388.                           pSu32Data=(unsigned long *)&Gu8ReceBuffer[6+4]; //数据转换。
  389.                                   Su32CurrentSize=*pSu32Data;  //读取的发送的数据量

  390. if(Gu32ReceDataLength>=11&& //接收到的数据长度必须大于或者等于11个字节
  391. Su8Rece_Xor==Gu8Rece_Xor&& //验证“异或”,“计算的”与“接收的”是否一致
  392. Su32CurrentAddr==GtBigBufferUsart.u32CurrentAddr&& //验证“地址”,相当于验证“动态密匙”
  393. Su32CurrentSize==GtBigBufferUsart.u32CurrentSize) //验证“地址”,相当于验证“动态密匙”
  394. {
  395.                       for(i=0;i<Su32CurrentSize;i++)
  396.                       {
  397. //及时把接收到的数据存储到Gu8ReceTable数组
  398.                            Gu8ReceTable[Su32CurrentAddr+i]=Gu8ReceBuffer[6+4+4+i];
  399. }

  400. GtBigBufferUsart.u8QueueStatus=1; //告诉“过程控制函数”,当前通讯成功
  401. }
  402. else
  403. {
  404.                       GtBigBufferUsart.u8QueueStatus=2; //告诉“过程控制函数”,当前通讯失败
  405. }
  406.        
  407.                   break;
  408.         }
  409.         
  410.         Gu8FinishFlag=0;  //上面处理完数据再清零标志,为下一次接收新的数据做准备
  411.     }
  412. }

  413. void UsartTask(void)    //串口收发的任务函数,放在主函数内
  414. {
  415. BigBufferUsart();  //读取下位机大数组的“通讯过程的控制涵数”
  416. QueueSend();       //发送的队列驱动涵数
  417. ReceDataHandle();  //接收数据后的处理涵数
  418. }

  419. void usart(void) interrupt 4   //串口接发的中断函数,中断号为4         
  420. {        
  421.    if(1==RI)  //接收完一个字节后引起的中断
  422.    {
  423.         RI = 0; //及时清零,避免一直无缘无故的进入中断。

  424.            if(0==Gu8FinishFlag)  //1代表已经完成接收了一串新数据,并且禁止接收其它新的数据
  425.            {
  426.                   Gu8ReceFeedDog=1; //每接收到一个字节的数据,此标志就置1及时更新定时器的值。
  427.                   switch(Gu8ReceStep)
  428.                   {
  429.                           case 0:     //“前部分的”数据头。接头暗号的步骤。
  430.                                    Gu8ReceBuffer[0]=SBUF; //直接读取刚接收完的一个字节的数据。
  431.                                    if(0xeb==Gu8ReceBuffer[0])  //等于数据头0xeb,接头暗号吻合。
  432.                                    {
  433.                                           Gu32ReceCnt=1; //接收缓存的下标
  434.                                           Gu8ReceStep=1;  //切换到下一个步骤,接收其它有效的数据
  435.                                    }
  436.                                    break;               
  437.                                        
  438.                           case 1:     //“前部分的”数据类型和长度
  439.                                    Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
  440.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备
  441.                                    if(Gu32ReceCnt>=6)  //前6个数据。接收完了“数据类型”和“数据长度”。
  442.                                    {
  443.                                             Gu8ReceType=Gu8ReceBuffer[1];  //提取“数据类型”
  444. //以下的数据转换,在第62节讲解过的指针法
  445.                                                 pu32Data=(unsigned long *)&Gu8ReceBuffer[2]; //数据转换
  446.                                                 Gu32ReceDataLength=*pu32Data; //提取“数据长度”
  447.                                             if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
  448.                                                 {
  449.                                                         Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  450.                                                         Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  451.                                                 }
  452.                                                 else   //如果还没结束,继续切换到下一个步骤,接收“有效数据”
  453.                                                 {
  454.                              //本节只用到一个接收数组,把指针关联到Gu8ReceBuffer本身的数组
  455.                               pGu8ReceBuffer=(unsigned char *)&Gu8ReceBuffer[6];
  456. Gu32ReceCntMax=REC_BUFFER_SIZE;  //最大缓存

  457.                                                          Gu8ReceStep=2;   //切换到下一个步骤
  458.                                                 }                                                       
  459.                                    }
  460.                                    break;               
  461.                           case 2:     //“后部分的”数据
  462.                                    pGu8ReceBuffer[Gu32ReceCnt-6]=SBUF; //这里的指针就是各种不同内存的化身!!!
  463.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备

  464. //靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
  465.                                    if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=Gu32ReceCntMax)
  466. {
  467.                                           Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  468.                                           Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  469.                                    }
  470.                                    break;       
  471.                   }
  472.        }
  473.    }
  474.    else  //发送数据引起的中断
  475.    {
  476.         TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  477.     Gu8SendByteFinish=1; //从0变成1通知主函数已经发送完一个字节的数据了。
  478.    }                                                      
  479. }  

  480. void UsartSendByteData(unsigned char u8SendData) //发送一个字节的底层驱动函数
  481. {
  482.     static unsigned int Su16TimeOutDelay;  //超时处理的延时计时器

  483.     Gu8SendByteFinish=0;  //在发送以字节之前,必须先把此全局变量的标志清零。
  484. SBUF =u8SendData; //依靠寄存器SBUF作为载体发送一个字节的数据
  485. Su16TimeOutDelay=0xffff;  //超时处理的延时计时器装载一个相对合理的计时初始值
  486. while(Su16TimeOutDelay>0)  //超时处理
  487. {
  488.     if(1==Gu8SendByteFinish)  
  489. {
  490.     break;  //如果Gu8SendByteFinish为1,则发送一个字节完成,退出当前循环等待。
  491. }
  492. Su16TimeOutDelay--;  //超时计时器不断递减
  493. }

  494. //Delay();//在实际应用中,当连续发送一堆数据时如果发现丢失数据,可以尝试在此增加延时
  495. }

  496. //发送带协议的函数
  497. void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize)
  498. {
  499.     static unsigned long i;
  500.     static unsigned long *pSu32;
  501.     static unsigned long u32SendSize;

  502.     pSu32=(const unsigned long *)&pCu8SendMessage[2];
  503. u32SendSize=*pSu32;  //从带协议的数组中提取整包数组的有效发送长度

  504. if(u32SendSize>u32SendMaxSize) //如果“有效发送长度”大于“最大限制的长度”,数据异常
  505. {
  506.    return;  //数据异常,直接退出当前函数,预防数组越界
  507. }

  508.     for(i=0;i<u32SendSize;i++) //u32SendSize为发送的数据长度
  509. {
  510.        UsartSendByteData(pCu8SendMessage[i]); //基于“发送单字节的最小接口函数”来实现的
  511.     }
  512. }

  513. unsigned char CalculateXor(const unsigned char *pCu8Buffer, //此处加const代表数组“只读”
  514. unsigned long u32BufferSize)  //参与计算的数组的大小
  515. {
  516. unsigned long i;
  517. unsigned char Su8Rece_Xor;
  518. Su8Rece_Xor=pCu8Buffer[0]; //提取数据串第“i=0”个数据作为异或的原始数据
  519. for(i=1;i<u32BufferSize;i++) //注意,这里是从第“i=1”个数据开始
  520. {
  521. Su8Rece_Xor=Su8Rece_Xor^pCu8Buffer[i];   //计算“异或”
  522. }
  523.     return Su8Rece_Xor;  //返回运算后的异或的计算结果
  524. }

  525. //比较两个数组的是否相等。返回1代表相等,返回0代表不相等
  526. unsigned char CmpTwoBufferIsSame(const unsigned char *pCu8Buffer_1,
  527. const unsigned char *pCu8Buffer_2,
  528. unsigned long u32BufferSize)  //参与对比的数组的大小
  529. {
  530. unsigned long i;
  531. for(i=0;i<u32BufferSize;i++)
  532. {
  533.    if(pCu8Buffer_1[i]!=pCu8Buffer_2[i])
  534.    {
  535.           return 0; //只要有一个不相等,则返回0并且退出当前函数
  536. }
  537. }
  538.     return 1; //相等
  539. }


  540. void KeyScan(void)  //此函数放在定时中断里每1ms扫描一次
  541. {
  542.    static unsigned char Su8KeyLock1; //1号按键的自锁
  543.    static unsigned int  Su16KeyCnt1; //1号按键的计时器

  544.    //1号按键
  545.    if(0!=KEY_INPUT1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  546.    {
  547.       Su8KeyLock1=0; //按键解锁
  548.       Su16KeyCnt1=0; //按键去抖动延时计数器清零,此行非常巧妙,是全场的亮点。      
  549.    }
  550.    else if(0==Su8KeyLock1)//有按键按下,且是第一次被按下。
  551.    {
  552.       Su16KeyCnt1++; //累加定时中断次数
  553.       if(Su16KeyCnt1>=KEY_FILTER_TIME) //滤波的“稳定时间”KEY_FILTER_TIME,长度是25ms。
  554.       {
  555.          Su8KeyLock1=1;  //按键的自锁,避免一直触发
  556.          vGu8KeySec=1;    //触发1号键
  557.       }
  558.    }
  559. }

  560. void T0_time() interrupt 1     
  561. {
  562. VoiceScan();  
  563. KeyScan();

  564. if(1==vGu8BigBufferUsartTimerFlag&&vGu16BigBufferUsartTimerCnt>0) //过程控制的超时定时器
  565.         {
  566.                  vGu16BigBufferUsartTimerCnt--;       
  567. }   

  568. if(1==vGu8QueueSendTimerFlag&&vGu16QueueSendTimerCnt>0) //队列发送的超时定时器
  569.         {
  570.                  vGu16QueueSendTimerCnt--;       
  571. }   

  572. if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通讯过程中字节之间的超时定时器
  573.         {
  574.                  vGu16ReceTimeOutCnt--;       
  575. }  

  576. TH0=0xfc;   
  577. TL0=0x66;   
  578. }


  579. void SystemInitial(void)
  580. {
  581. unsigned char u8_TMOD_Temp=0;

  582. //以下是定时器0的中断的配置
  583. TMOD=0x01;  
  584. TH0=0xfc;   
  585. TL0=0x66;   
  586. EA=1;      
  587. ET0=1;      
  588. TR0=1;   

  589. //以下是串口接收中断的配置
  590. //串口的波特率与内置的定时器1直接相关,因此配置此定时器1就等效于配置波特率。
  591. u8_TMOD_Temp=0x20; //即将把定时器1设置为:工作方式2,初值自动重装的8位定时器。
  592. TMOD=TMOD&0x0f; //此寄存器低4位是跟定时器0相关,高4位是跟定时器1相关。先清零定时器1。
  593. TMOD=TMOD|u8_TMOD_Temp;  //把高4位的定时器1填入0x2,低4位的定时器0保持不变。
  594. TH1=256-(11059200L/12/32/9600);  //波特率为9600。11059200代表晶振11.0592MHz,
  595. TL1=256-(11059200L/12/32/9600);  //L代表long的长类型数据。根据芯片手册提供的计算公式。
  596. TR1=1;  //开启定时器1

  597. SM0=0;  
  598. SM1=1;  //SM0与SM1的设置:选择10位异步通讯,波特率根据定时器1可变  
  599. REN=1;  //允许串口接收数据

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

  603. ES=1;         //允许串口中断  
  604. EA=1;         //允许总中断
  605. }

  606. void Delay(unsigned long u32DelayTime)
  607. {
  608.     for(;u32DelayTime>0;u32DelayTime--);
  609. }

  610. void PeripheralInitial(void)
  611. {
  612. GtBigBufferUsart.u8Start=0;  //通讯过程的启动变量必须初始化为0!这一步很关键!
  613. }

  614. void BeepOpen(void)
  615. {
  616. P3_4=0;  
  617. }

  618. void BeepClose(void)
  619. {
  620. P3_4=1;  
  621. }

  622. void VoiceScan(void)
  623. {

  624.           static unsigned char Su8Lock=0;  

  625. if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
  626.           {
  627.                   if(0==Su8Lock)
  628.                   {
  629.                    Su8Lock=1;  
  630. BeepOpen();
  631.      }
  632.     else  
  633. {     

  634.                        vGu16BeepTimerCnt--;         

  635.                    if(0==vGu16BeepTimerCnt)
  636.                    {
  637.                            Su8Lock=0;     
  638. BeepClose();  
  639.                    }

  640. }
  641.           }         
  642. }
复制代码

【134.8   例程的下位机程序。】

      下位机作为从机应答上位机的指令,程序相对简化了很多。不需要“通讯过程的控制涵数”,直接在“接收数据后的处理涵数”里启动“发送的队列驱动涵数”来发送应答的数据即可。发送应答数据后,也不用等待上位机的应答数据。
  1. #include "REG52.H"

  2. #define RECE_TIME_OUT    2000  //通讯过程中字节之间的超时时间2000ms
  3. #define REC_BUFFER_SIZE  30    //常规控制类数组的长度

  4. void usart(void);  //串口接收的中断函数
  5. void T0_time();    //定时器的中断函数

  6. void QueueSend(void);       //发送的队列驱动涵数
  7. void ReceDataHandle(void);  //接收数据后的处理涵数

  8. void UsartTask(void);    //串口收发的任务函数,放在主函数内

  9. unsigned char CalculateXor(const unsigned char *pCu8Buffer, //异或的算法的函数
  10. unsigned long u32BufferSize);  

  11. void UsartSendByteData(unsigned char u8SendData); //发送一个字节的底层驱动函数

  12. //发送带协议的函数
  13. void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize);

  14. void SystemInitial(void) ;
  15. void Delay(unsigned long u32DelayTime) ;
  16. void PeripheralInitial(void) ;

  17. //下面表格数组的数据与上位机的表格数据一模一样,目的用来让上位机检测接收到的数据是否正确
  18. code unsigned char Cu8TestTable[]=  
  19. {
  20. 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,
  21. 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,
  22. 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,
  23. 0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,
  24. 0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,
  25. 0x51,0x52,0x53,0x54,0x55,0x56,0x57
  26. };

  27. //把一些针对某个特定事件的全局变量放在一个结构体内,可以让全局变量的分类更加清晰
  28. struct StructBigBufferUsart     //应答读取大数组的通讯过程的结构体
  29. {
  30. unsigned char u8QueueSendTrig;//队列驱动函数的发送的启动
  31. unsigned char u8QueueSendBuffer[30]; //队列驱动函数的发送指令的数组
  32. unsigned char u8QueueStatus; //队列驱动函数的通讯状态 0为初始状态 1为通讯成功 2为通讯失败
  33. };

  34. unsigned char Gu8QueueReceUpdate=0;  //1代表“队列发送数据后,收到了新的数据”

  35. struct StructBigBufferUsart  GtBigBufferUsart;//此结构体变量专门用来应答读取大数组的通讯事件

  36. volatile unsigned char vGu8QueueSendTimerFlag=0;  //队列发送的超时定时器
  37. volatile unsigned int vGu16QueueSendTimerCnt=0;  

  38. unsigned char Gu8SendByteFinish=0; //发送一个字节完成的标志

  39. unsigned char Gu8ReceBuffer[REC_BUFFER_SIZE]; //常规控制类的小内存
  40. unsigned char *pGu8ReceBuffer;  //用来切换接收内存的“中转指针”

  41. unsigned long Gu32ReceCntMax=REC_BUFFER_SIZE;  //最大缓存
  42. unsigned long Gu32ReceCnt=0;  //接收缓存数组的下标
  43. unsigned char Gu8ReceStep=0;  //接收中断函数里的步骤变量
  44. unsigned char Gu8ReceFeedDog=1; //“喂狗”的操作变量。
  45. unsigned char Gu8ReceType=0; //接收的数据类型
  46. unsigned char Gu8Rece_Xor=0; //接收的异或
  47. unsigned long Gu32ReceDataLength=0;  //接收的数据长度
  48. unsigned char Gu8FinishFlag=0;  //是否已接收完成一串数据的标志
  49. unsigned long *pu32Data; //用于数据转换的指针
  50. volatile unsigned char vGu8ReceTimeOutFlag=0;//通讯过程中字节之间的超时定时器的开关
  51. volatile unsigned int vGu16ReceTimeOutCnt=0; //通讯过程中字节之间的超时定时器,“喂狗”的对象

  52. void main()
  53. {
  54. SystemInitial();            
  55. Delay(10000);               
  56. PeripheralInitial();      
  57.     while(1)  
  58. {  
  59.     UsartTask();   //串口收发的任务函数
  60.     }
  61. }

  62. /* 注释一:
  63. *  整个项目中只有一个“发送的队列驱动涵数”,负责“通讯管道的占用”的分配,负责数据的具体发
  64. *  送。当同时存在很多“待发送”的请求指令时,此函数会根据“if ,else if...”的优先级,像队列一
  65. *  样安排各指令发送的先后顺序,确保各指令不会发生冲突。
  66. */

  67. void QueueSend(void)  //发送的队列驱动涵数
  68. {
  69. static unsigned char Su8Step=0;

  70. switch(Su8Step)
  71. {
  72.    case 0:  //分派即将要发送的任务
  73.         if(1==GtBigBufferUsart.u8QueueSendTrig)
  74.         {
  75.             GtBigBufferUsart.u8QueueSendTrig=0;  //及时清零。驱动层,不管结果,只发一次。

  76. Gu8QueueReceUpdate=0; //接收应答数据的状态恢复初始值

  77.             //发送带指令的数据
  78. UsartSendMessage((const unsigned char *)&GtBigBufferUsart.u8QueueSendBuffer[0],
  79. 30);
  80.                 //注意,这里是从机应答主机的数据,不需要等待返回的数据,因此不需要切换Su8Step
  81. }
  82. //      else if(...)  //当有其它发送的指令时,可以在此处继续添加判断,越往下优先级越低
  83. //      else if(...)  //当有其它发送的指令时,可以在此处继续添加判断,越往下优先级越低

  84.         break;

  85.    case 1: //发送之后,等待下位机的应答。驱动层,只管有没有应答,不管应答对不对。
  86.         if(1==Gu8QueueReceUpdate) //如果“接收数据后的处理涵数”接收到应答数据
  87.         {
  88. Su8Step=0;  //返回上一步继续处理其它“待发送的指令”
  89. }

  90. if(0==vGu16QueueSendTimerCnt) //发送指令之后,等待应答超时
  91.         {
  92. Su8Step=0;  //返回上一步继续处理其它“待发送的指令”
  93. }

  94.         break;
  95. }
  96. }


  97. /* 注释二:
  98. *  整个项目中只有一个“接收数据后的处理涵数”,负责即时处理当前接收到的数据。
  99. */

  100. void ReceDataHandle(void)  //接收数据后的处理涵数
  101. {
  102. static unsigned long *pSu32Data; //数据转换的指针
  103. static unsigned long i;
  104. static unsigned char Su8Rece_Xor=0;  //计算的“异或”

  105. static unsigned long Su32CurrentAddr; //读取的起始地址
  106. static unsigned long Su32CurrentSize; //读取的发送的数据量

  107.     if(1==Gu8ReceFeedDog) //每被“喂一次狗”,就及时更新一次“超时检测的定时器”的初值
  108.         {
  109.                 Gu8ReceFeedDog=0;
  110.                                
  111.                 vGu8ReceTimeOutFlag=0;
  112.         vGu16ReceTimeOutCnt=RECE_TIME_OUT;//更新一次“超时检测的定时器”的初值
  113.                 vGu8ReceTimeOutFlag=1;
  114.         }
  115.         else if(Gu8ReceStep>0&&0==vGu16ReceTimeOutCnt) //超时,并且步骤不在接头暗号的步骤
  116.         {
  117.             Gu8ReceStep=0; //串口接收数据的中断函数及时切换回接头暗号的步骤
  118.     }

  119.         if(1==Gu8FinishFlag)  //1代表已经接收完毕一串新的数据,需要马上去处理
  120.         {
  121.                 switch(Gu8ReceType)  //接收到的数据类型
  122.                 {
  123.                         case 0x01:   //返回下位机的数组容量的大小

  124. Gu8Rece_Xor=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取接收到的“异或”
  125. Su8Rece_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //计算“异或”

  126. if(Su8Rece_Xor!=Gu8Rece_Xor) //验证“异或”,如果不相等,退出当前switch
  127. {
  128.     break;    //退出当前switch
  129. }

  130.               GtBigBufferUsart.u8QueueSendBuffer[0]=0xeb; //数据头
  131.               GtBigBufferUsart.u8QueueSendBuffer[1]=0x01; //数据类型 返回数组容量的大小
  132.               pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2];
  133.               *pSu32Data=11; //数据长度  本条指令的数据总长是11个字节

  134.               //提取数组容量的大小
  135.               pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2+4];
  136.               *pSu32Data=sizeof(Cu8TestTable);//相当于*pSu32Data=57;sizeof请参考第69节

  137. //异或算法的函数
  138. GtBigBufferUsart.u8QueueSendBuffer[10]=CalculateXor(GtBigBufferUsart.u8QueueSendBuffer,
  139. 10);  //最后一个字节不纳入计算

  140.                   //队列驱动函数的状态 0为初始状态 1为通讯成功 2为通讯失败
  141.                   GtBigBufferUsart.u8QueueStatus=0; //队列驱动函数的通讯状态
  142.                   GtBigBufferUsart.u8QueueSendTrig=1;//队列驱动函数的发送的启动

  143. Gu8QueueReceUpdate=1;  //告诉“队列驱动函数”此发送指令无需等待上位机的应答
  144.                   break;

  145.                         case 0x02:   //返回下位机的分段数据

  146. Gu8Rece_Xor=Gu8ReceBuffer[Gu32ReceDataLength-1];  //提取接收到的“异或”
  147. Su8Rece_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //计算“异或”

  148. if(Su8Rece_Xor!=Gu8Rece_Xor) //验证“异或”,如果不相等,退出当前switch
  149. {
  150.     break;    //退出当前switch
  151. }

  152.                           pSu32Data=(unsigned long *)&Gu8ReceBuffer[6]; //数据转换。
  153.                                   Su32CurrentAddr=*pSu32Data;  //读取的起始地址

  154.                           pSu32Data=(unsigned long *)&Gu8ReceBuffer[6+4]; //数据转换。
  155.                                   Su32CurrentSize=*pSu32Data;  //读取的发送的数据量

  156.               GtBigBufferUsart.u8QueueSendBuffer[0]=0xeb; //数据头
  157.               GtBigBufferUsart.u8QueueSendBuffer[1]=0x02; //数据类型 返回分段数据

  158.               pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2];
  159.               *pSu32Data=6+4+4+Su32CurrentSize+1; //数据总长度

  160.               pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2+4];
  161.               *pSu32Data=Su32CurrentAddr; //返回接收到的起始地址

  162.               pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer[2+4+4];
  163.               *pSu32Data=Su32CurrentSize; //返回接收到的当前批次的数据量

  164.                   for(i=0;i<Su32CurrentSize;i++)
  165.                   {
  166. //装载即将要发送的分段数据
  167.                  GtBigBufferUsart.u8QueueSendBuffer[6+4+4+i]=Cu8TestTable[Su32CurrentAddr+i];
  168. }

  169. //异或算法的函数
  170. GtBigBufferUsart.u8QueueSendBuffer[6+4+4+Su32CurrentSize]=
  171. CalculateXor(GtBigBufferUsart.u8QueueSendBuffer, 6+4+4+Su32CurrentSize);  

  172.                   //队列驱动函数的状态 0为初始状态 1为通讯成功 2为通讯失败
  173.                   GtBigBufferUsart.u8QueueStatus=0; //队列驱动函数的通讯状态
  174.                   GtBigBufferUsart.u8QueueSendTrig=1;//队列驱动函数的发送的启动

  175. Gu8QueueReceUpdate=1;  //告诉“队列驱动函数”此发送指令无需等待上位机的应答
  176.                   break;
  177.         }
  178.         
  179.         Gu8FinishFlag=0;  //上面处理完数据再清零标志,为下一次接收新的数据做准备
  180.     }
  181. }

  182. void UsartTask(void)    //串口收发的任务函数,放在主函数内
  183. {
  184. QueueSend();       //发送的队列驱动涵数
  185. ReceDataHandle();  //接收数据后的处理涵数
  186. }

  187. void usart(void) interrupt 4   //串口接发的中断函数,中断号为4         
  188. {        
  189.    if(1==RI)  //接收完一个字节后引起的中断
  190.    {
  191.         RI = 0; //及时清零,避免一直无缘无故的进入中断。

  192.            if(0==Gu8FinishFlag)  //1代表已经完成接收了一串新数据,并且禁止接收其它新的数据
  193.            {
  194.                   Gu8ReceFeedDog=1; //每接收到一个字节的数据,此标志就置1及时更新定时器的值。
  195.                   switch(Gu8ReceStep)
  196.                   {
  197.                           case 0:     //“前部分的”数据头。接头暗号的步骤。
  198.                                    Gu8ReceBuffer[0]=SBUF; //直接读取刚接收完的一个字节的数据。
  199.                                    if(0xeb==Gu8ReceBuffer[0])  //等于数据头0xeb,接头暗号吻合。
  200.                                    {
  201.                                           Gu32ReceCnt=1; //接收缓存的下标
  202.                                           Gu8ReceStep=1;  //切换到下一个步骤,接收其它有效的数据
  203.                                    }
  204.                                    break;               
  205.                                        
  206.                           case 1:     //“前部分的”数据类型和长度
  207.                                    Gu8ReceBuffer[Gu32ReceCnt]=SBUF; //直接读取刚接收完的一个字节的数据。
  208.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备
  209.                                    if(Gu32ReceCnt>=6)  //前6个数据。接收完了“数据类型”和“数据长度”。
  210.                                    {
  211.                                             Gu8ReceType=Gu8ReceBuffer[1];  //提取“数据类型”
  212. //以下的数据转换,在第62节讲解过的指针法
  213.                                                 pu32Data=(unsigned long *)&Gu8ReceBuffer[2]; //数据转换
  214.                                                 Gu32ReceDataLength=*pu32Data; //提取“数据长度”
  215.                                             if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
  216.                                                 {
  217.                                                         Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  218.                                                         Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  219.                                                 }
  220.                                                 else   //如果还没结束,继续切换到下一个步骤,接收“有效数据”
  221.                                                 {
  222.                              //本节只用到一个接收数组,把指针关联到Gu8ReceBuffer本身的数组
  223.                               pGu8ReceBuffer=(unsigned char *)&Gu8ReceBuffer[6];
  224. Gu32ReceCntMax=REC_BUFFER_SIZE;  //最大缓存

  225.                                                          Gu8ReceStep=2;   //切换到下一个步骤
  226.                                                 }                                                       
  227.                                    }
  228.                                    break;               
  229.                           case 2:     //“后部分的”数据
  230.                                    pGu8ReceBuffer[Gu32ReceCnt-6]=SBUF; //这里的指针就是各种不同内存的化身!!!
  231.                                    Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备

  232. //靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
  233.                                    if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=Gu32ReceCntMax)
  234. {
  235.                                           Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
  236.                                           Gu8ReceStep=0;   //及时切换回接头暗号的步骤
  237.                                    }
  238.                                    break;       
  239.                   }
  240.        }
  241.    }
  242.    else  //发送数据引起的中断
  243.    {
  244.         TI = 0;  //及时清除发送中断的标志,避免一直无缘无故的进入中断。
  245.     Gu8SendByteFinish=1; //从0变成1通知主函数已经发送完一个字节的数据了。
  246.    }                                                      
  247. }  

  248. void UsartSendByteData(unsigned char u8SendData) //发送一个字节的底层驱动函数
  249. {
  250.     static unsigned int Su16TimeOutDelay;  //超时处理的延时计时器

  251.     Gu8SendByteFinish=0;  //在发送以字节之前,必须先把此全局变量的标志清零。
  252. SBUF =u8SendData; //依靠寄存器SBUF作为载体发送一个字节的数据
  253. Su16TimeOutDelay=0xffff;  //超时处理的延时计时器装载一个相对合理的计时初始值
  254. while(Su16TimeOutDelay>0)  //超时处理
  255. {
  256.     if(1==Gu8SendByteFinish)  
  257. {
  258.     break;  //如果Gu8SendByteFinish为1,则发送一个字节完成,退出当前循环等待。
  259. }
  260. Su16TimeOutDelay--;  //超时计时器不断递减
  261. }

  262. //Delay();//在实际应用中,当连续发送一堆数据时如果发现丢失数据,可以尝试在此增加延时
  263. }

  264. //发送带协议的函数
  265. void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize)
  266. {
  267.     static unsigned long i;
  268.     static unsigned long *pSu32;
  269.     static unsigned long u32SendSize;

  270.     pSu32=(const unsigned long *)&pCu8SendMessage[2];
  271. u32SendSize=*pSu32;  //从带协议的数组中提取整包数组的有效发送长度

  272. if(u32SendSize>u32SendMaxSize) //如果“有效发送长度”大于“最大限制的长度”,数据异常
  273. {
  274.    return;  //数据异常,直接退出当前函数,预防数组越界
  275. }

  276.     for(i=0;i<u32SendSize;i++) //u32SendSize为发送的数据长度
  277. {
  278.        UsartSendByteData(pCu8SendMessage[i]); //基于“发送单字节的最小接口函数”来实现的
  279.     }
  280. }

  281. unsigned char CalculateXor(const unsigned char *pCu8Buffer, //此处加const代表数组“只读”
  282. unsigned long u32BufferSize)  //参与计算的数组的大小
  283. {
  284. unsigned long i;
  285. unsigned char Su8Rece_Xor;
  286. Su8Rece_Xor=pCu8Buffer[0]; //提取数据串第“i=0”个数据作为异或的原始数据
  287. for(i=1;i<u32BufferSize;i++) //注意,这里是从第“i=1”个数据开始
  288. {
  289. Su8Rece_Xor=Su8Rece_Xor^pCu8Buffer[i];   //计算“异或”
  290. }
  291.     return Su8Rece_Xor;  //返回运算后的异或的计算结果
  292. }

  293. void T0_time() interrupt 1     
  294. {

  295. if(1==vGu8QueueSendTimerFlag&&vGu16QueueSendTimerCnt>0) //队列发送的超时定时器
  296.         {
  297.                  vGu16QueueSendTimerCnt--;       
  298. }   

  299. if(1==vGu8ReceTimeOutFlag&&vGu16ReceTimeOutCnt>0) //通讯过程中字节之间的超时定时器
  300.         {
  301.                  vGu16ReceTimeOutCnt--;       
  302. }  

  303. TH0=0xfc;   
  304. TL0=0x66;   
  305. }


  306. void SystemInitial(void)
  307. {
  308. unsigned char u8_TMOD_Temp=0;

  309. //以下是定时器0的中断的配置
  310. TMOD=0x01;  
  311. TH0=0xfc;   
  312. TL0=0x66;   
  313. EA=1;      
  314. ET0=1;      
  315. TR0=1;   

  316. //以下是串口接收中断的配置
  317. //串口的波特率与内置的定时器1直接相关,因此配置此定时器1就等效于配置波特率。
  318. u8_TMOD_Temp=0x20; //即将把定时器1设置为:工作方式2,初值自动重装的8位定时器。
  319. TMOD=TMOD&0x0f; //此寄存器低4位是跟定时器0相关,高4位是跟定时器1相关。先清零定时器1。
  320. TMOD=TMOD|u8_TMOD_Temp;  //把高4位的定时器1填入0x2,低4位的定时器0保持不变。
  321. TH1=256-(11059200L/12/32/9600);  //波特率为9600。11059200代表晶振11.0592MHz,
  322. TL1=256-(11059200L/12/32/9600);  //L代表long的长类型数据。根据芯片手册提供的计算公式。
  323. TR1=1;  //开启定时器1

  324. SM0=0;  
  325. SM1=1;  //SM0与SM1的设置:选择10位异步通讯,波特率根据定时器1可变  
  326. REN=1;  //允许串口接收数据

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

  330. ES=1;         //允许串口中断  
  331. EA=1;         //允许总中断
  332. }

  333. void Delay(unsigned long u32DelayTime)
  334. {
  335.     for(;u32DelayTime>0;u32DelayTime--);
  336. }

  337. void PeripheralInitial(void)
  338. {

  339. }
复制代码

本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2019-2-2 17:10:34 | 显示全部楼层
谢谢鸿哥年前再次更新,祝您猪年吉祥如意,财源滚滚,心想事成,万事顺利,新年快乐!

出0入0汤圆

发表于 2019-2-14 22:00:44 | 显示全部楼层
鸿哥,新年好!

出0入0汤圆

发表于 2019-3-17 22:18:14 | 显示全部楼层
停更了?

出0入0汤圆

发表于 2019-3-24 11:16:40 | 显示全部楼层
可能工作比较忙吧,耐心等待吧!

出425入0汤圆

发表于 2019-3-24 18:31:18 | 显示全部楼层
做事的毅力非同一般。

出0入0汤圆

发表于 2019-6-6 16:28:19 | 显示全部楼层
很久没有更新了

出0入0汤圆

发表于 2019-6-7 22:30:04 | 显示全部楼层
看完之后,以前不太了解的东西现在认识清楚了.感谢楼主分享.

出0入0汤圆

发表于 2019-6-8 04:23:47 来自手机 | 显示全部楼层
请问有没有汇总或整理出书呢?谢谢。

出0入0汤圆

发表于 2019-6-13 16:15:28 | 显示全部楼层
等着呢,就是喜欢这种NBHH的大牛

出0入0汤圆

发表于 2019-6-17 16:29:09 | 显示全部楼层
为什么我点击 只看该作者 ,显示的还是全部楼层呢?

出0入0汤圆

发表于 2019-6-17 16:53:30 | 显示全部楼层
看了一部分,感觉写的很好,很多疑问和困惑都能得到解决!还有楼主对单片机的热爱,真的是让旁人都能感受到,这么长时间的坚持,实属不易!顶顶顶

出0入0汤圆

 楼主| 发表于 2019-10-23 14:33:57 | 显示全部楼层
第一百三十五节: 《从单片机基础到程序框架》(全集 2019版)整理完毕。





本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2019-10-23 15:49:20 | 显示全部楼层
本帖最后由 XTXB 于 2019-10-23 15:52 编辑
吴坚鸿 发表于 2019-10-23 14:33
第一百三十五节: 《从单片机基础到程序框架》(全集 2019版)整理完毕。


支持鸿哥的无私奉献!有书的话一定买本作纪念

出0入0汤圆

发表于 2019-10-23 16:24:22 | 显示全部楼层
出一版实体书吧,留个纪念。

出0入0汤圆

发表于 2019-10-23 17:16:40 | 显示全部楼层
本帖最后由 easier 于 2019-10-23 17:30 编辑

一開始就提倡可用就足夠,止步於可用……

出0入0汤圆

发表于 2019-10-30 13:27:45 | 显示全部楼层
如果!每个学校的老师有吴坚鸿--吴老师--吴总这么详细、全面、认真的教学态度!学校毕业的学生,工作就没那么难找了!
吴老师是那些刚毕业又想从事电子行业的一个引路人啊!

出0入0汤圆

发表于 2019-11-18 23:41:12 | 显示全部楼层
请问楼主有没有好的汇编学习方法推荐,因为是芯片公司要用到汇编所以想学习一下,但是感觉资料难找。

出0入0汤圆

发表于 2019-12-12 11:07:13 | 显示全部楼层
鸿哥完美收宫!

出0入0汤圆

发表于 2019-12-12 22:39:01 | 显示全部楼层
从单片机基础到程序框架,值得期待,认真整理

出0入0汤圆

发表于 2019-12-23 08:50:27 | 显示全部楼层
mjhjmh630 发表于 2019-10-30 13:27
如果!每个学校的老师有吴坚鸿--吴老师--吴总这么详细、全面、认真的教学态度!学校毕业的学生,工作就没那 ...

说的在理儿!

出0入0汤圆

发表于 2019-12-23 20:24:26 | 显示全部楼层
有始有终,赞一个!

出0入0汤圆

发表于 2020-4-14 09:05:42 | 显示全部楼层
拜读过其它的两个连载,收获巨大,这个连载要认真拜读,谢谢

出0入0汤圆

发表于 2020-7-7 21:44:56 | 显示全部楼层
哇,真的需要赞,以前看过楼主的文章,非常好,感谢

出0入0汤圆

发表于 2020-7-9 14:23:10 | 显示全部楼层
写的太精彩了,比安卓强100倍

出0入0汤圆

发表于 2020-7-9 16:10:14 | 显示全部楼层


加了一個目錄

本帖子中包含更多资源

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

x

出50入0汤圆

发表于 2020-9-5 22:02:48 | 显示全部楼层
历时四年,毅力惊人

出0入0汤圆

发表于 2020-9-6 08:10:39 | 显示全部楼层
感谢楼主的大爱!

出0入0汤圆

发表于 2020-9-19 08:28:21 | 显示全部楼层
谢谢楼主分享  学习一下  好东东

出0入0汤圆

发表于 2020-10-7 22:17:10 | 显示全部楼层
不知道现在写的怎么样了

出0入0汤圆

发表于 2020-12-1 18:22:01 | 显示全部楼层
写的非常详细,解决很多像我这样初学者的疑惑!

出0入0汤圆

发表于 2020-12-9 12:33:58 | 显示全部楼层

写的太精彩了

出0入0汤圆

发表于 2020-12-11 22:01:13 | 显示全部楼层
楼主,厉害,顶~

出0入0汤圆

发表于 2020-12-17 19:09:28 来自手机 | 显示全部楼层
谢谢楼主,我下载了!

出0入0汤圆

发表于 2021-1-27 14:05:12 | 显示全部楼层
顶一下,好资料

出0入8汤圆

发表于 2021-1-28 15:26:57 | 显示全部楼层
我是要感谢吴老师的,他的一部分框架我到现在都在用

出0入0汤圆

发表于 2021-2-3 09:20:24 | 显示全部楼层
好贴一定要顶

出0入0汤圆

发表于 2021-8-16 14:39:08 | 显示全部楼层
厉害了,我的神!

出0入0汤圆

发表于 2021-8-16 20:42:55 | 显示全部楼层
楼主有纸质版书了吗?

出0入0汤圆

发表于 2021-8-29 17:14:46 | 显示全部楼层
必须顶一个,厉害

出0入4汤圆

发表于 2021-8-31 14:56:29 | 显示全部楼层
该教程免费授权给所有的出版社和做单片机学习板的厂家和各
大培训机构以及全国各大院校,我本人不从中赢利也不收取任何版
权费用, 我本人也不卖书也不卖学习板也不搞线下培训。该教程的
版权无偿捐给全社会。
------

书可以出本 估计很多人需要

出0入0汤圆

发表于 2021-9-8 20:38:48 | 显示全部楼层
原来早就收官了,楼主幸苦了,顶顶!

出0入0汤圆

发表于 2021-12-15 13:46:59 | 显示全部楼层
教材写的很好,有实用性,点赞!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-4-25 06:57

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

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