hanhanlili 发表于 2018-9-19 18:03:26

牛,有没有整理出完整的文档呢?有书卖吗?

吴坚鸿 发表于 2018-10-4 11:14:10

第一百三十节: 接收带“动态密匙”与“异或”校验数据的串口程序框架。

【130.1   “异或”的校验。】

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

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

Gu8ReceZZ=Gu8ReceBuffer;//提取“累加和”

Su8RecZZ=0;
for(i=0;i<(Gu32ReceDataLength-1);i++)
{
Su8RecZZ=Su8RecZZ+Gu8ReceBuffer;   //计算“累加和”
}

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

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

Gu8ReceZZ=Gu8ReceBuffer;//提取接收到的“异或”

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

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

【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.2232串口电路

程序功能如下:
      (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


#include "REG52.H"

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


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

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

void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);
void VoiceScan(void);

sbit P3_4=P3^4;

volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;

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

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
   UsartTask();   //串口接收的任务函数
    }
}

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

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

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

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


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



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

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

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

Gu8ReceZZ=Gu8ReceBuffer;//提取接收到的“异或”

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

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

vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=Su16Data;   //让蜂鸣器鸣叫
vGu8BeepTimerFlag=1;
}

               
                  break;
      }

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

void T0_time() interrupt 1   
{
VoiceScan();

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

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
unsigned char u8_TMOD_Temp=0;

//以下是定时器0的中断的配置
TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;   

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

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

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

ES=1;         //允许串口中断
EA=1;         //允许总中断
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

void BeepOpen(void)
{
P3_4=0;
}

void BeepClose(void)
{
P3_4=1;
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;
BeepOpen();
   }
    else
{   

                     vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;   
BeepClose();
                   }

}
          }         
}

su33691 发表于 2018-10-4 12:36:37

是鸿哥的state简易状态机带我入门的,特来感谢。

吴坚鸿 发表于 2018-10-14 10:17:42

第一百三十一节: 灵活切换各种不同大小“接收内存”的串口程序框架。

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

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


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

void usart(void) interrupt 4         
{      
   if(1==RI)
   {
      RI = 0;

         if(0==Gu8FinishFlag)
         {
                  Gu8ReceFeedDog=1;
                  switch(Gu8ReceStep)
                  {
                        case 0:   //“前部分的”数据头。接头暗号的步骤
                                 Gu8ReceBuffer=SBUF;
                                 if(0xeb==Gu8ReceBuffer)
                                 {
                                          Gu32ReceCnt=1;
                                          Gu8ReceStep=1;
                                 }
                                 break;               
                                       
                        case 1:   //“前部分的”数据类型和长度
                                 Gu8ReceBuffer=SBUF;
                                 Gu32ReceCnt++;
                                 if(Gu32ReceCnt>=6)//前6个数据。接收完了“数据类型”和“数据长度”。
                                 {
                                          Gu8ReceType=Gu8ReceBuffer;//提取“数据类型”
                                                pu32Data=(unsigned long *)&Gu8ReceBuffer;
                                                Gu32ReceDataLength=*pu32Data; //提取“数据长度”
                                          if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
                                                {
                                                      Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
                                                      Gu8ReceStep=0;   //及时切换回接头暗号的步骤
                                                }
                                                else   //如果还没结束,继续切换到下一个步骤,接收“有效数据”
                                                {
                           //以下几行代码是本节的破题关键!!!
                           if(0x02==Gu8ReceType) //如果是文件类,把指针关联到Gu8FileBuffer
                              {
                                 pGu8ReceBuffer=(unsigned char *)&Gu8FileBuffer;//下标0
}
else //如果是常规类,继续把指针关联到Gu8ReceBuffer本身的数组
                              {
                                 pGu8ReceBuffer=(unsigned char *)&Gu8ReceBuffer;//下标6
}

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

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


【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.2232串口电路

程序功能如下:
      (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


#include "REG52.H"

#define RECE_TIME_OUT    2000//通信过程中字节之间的超时时间2000ms
#define REC_BUFFER_SIZE20    //常规控制类数组的长度
#define FILE_BUFFER_SIZE40   //文件类数组的长度


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

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

void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);
void VoiceScan(void);

sbit P3_4=P3^4;

volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;

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

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

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
   UsartTask();   //串口接收的任务函数
    }
}

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

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

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

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

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


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



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

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

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

Gu8ReceZZ=Gu8ReceBuffer;//提取接收到的“异或”

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

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

vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=Su16Data;   //让蜂鸣器鸣叫
vGu8BeepTimerFlag=1;
}
      
                  break;

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

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

//注意,请留意以下代码文件类内存数组Gu8FileBuffer的下标位置
Gu8ReceZZ=Gu8FileBuffer;//提取接收到的“异或”

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

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

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

vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=Su16Data;   //让蜂鸣器鸣叫
vGu8BeepTimerFlag=1;
}
               
                  break;



      }

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

void T0_time() interrupt 1   
{
VoiceScan();

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

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
unsigned char u8_TMOD_Temp=0;

//以下是定时器0的中断的配置
TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;   

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

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

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

ES=1;         //允许串口中断
EA=1;         //允许总中断
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

void BeepOpen(void)
{
P3_4=0;
}

void BeepClose(void)
{
P3_4=1;
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;
BeepOpen();
   }
    else
{   

                     vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;   
BeepClose();
                   }

}
          }         
}


tt98 发表于 2018-10-14 12:46:18

继续跟随鸿哥哥更新的脚步!

m8684589 发表于 2018-10-15 18:08:57

{:smile:} 写的不错,一路跟过来了,,,希望能讲到一个综合程序,涉及串口通讯(通讯),按键,数码管,蜂鸣器(界面,设置串口通讯波特率、AD范围等),AD或者其他信号采集(信号),包含这三个要素

m8684589 发表于 2018-10-15 18:15:55

{:smile:} 如果还能涉及到液晶屏界面设计,要是能把这些任务合理安排,,,出书了,那书就肯定值得买一本放床头了

zzage 发表于 2018-10-23 16:28:56

先评论顶起,再慢慢细看,值得收藏的帖子。

吴坚鸿 发表于 2018-10-30 13:03:20

第一百三十二节:“转发、透传、多种协议并存”的双缓存串口程序框架。

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

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

【132.2   程序例程。】


      
       上图132.2.1有源蜂鸣器电路





       上图132.2.2232串口电路

      程序功能如下:单片机接收任意长度(最大一次不超过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
       代码如下:


#include "REG52.H"

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

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

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

void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);
void VoiceScan(void);

sbit P3_4=P3^4;

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

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

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

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

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

volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
   UsartTask();   //串口接收的任务函数
    }
}

void usart(void) interrupt 4         
{      
   if(1==RI)
   {
      RI = 0;

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

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

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


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

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

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

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

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

             if(0x06==pSu8ReceBuffer&&
0x07==pSu8ReceBuffer&&
0x08==pSu8ReceBuffer&&
0x09==pSu8ReceBuffer) //连续四个数是0x06 0x07 0x08 0x09
{
vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=2000;   //让蜂鸣器“长叫”2000ms
vGu8BeepTimerFlag=1;
    return; //直接退出当前函数
}

}

    }
}

void T0_time() interrupt 1   
{
VoiceScan();

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

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
unsigned char u8_TMOD_Temp=0;

//以下是定时器0的中断的配置
TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;   

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

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

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

ES=1;         //允许串口中断
EA=1;         //允许总中断
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

void BeepOpen(void)
{
P3_4=0;
}

void BeepClose(void)
{
P3_4=1;
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;
BeepOpen();
   }
    else
{   

                     vGu16BeepTimerCnt--;         

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;   
BeepClose();
                   }

}
          }         
}

gaobao_1 发表于 2018-10-30 14:57:11

吴坚鸿 发表于 2016-1-18 11:49
第一节:我是中国最幸福的单片机工程师。




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

tt98 发表于 2018-10-31 07:55:37

谢谢鸿哥更新!

dadian 发表于 2018-11-2 09:25:43

还在更新,学习了!

focus_hai 发表于 2018-11-3 11:40:13

对于初学者来说是一个不可多得的好文章~~~值得推广,看完这些,肯定可以玩转单片机了

dtdzlujian 发表于 2018-11-10 21:02:59

跟上脚步,一定可以成功。谢谢LZ

wahaha 发表于 2018-11-10 21:36:10

感谢搂住的坚持,受益匪浅~~~

kkfy888 发表于 2018-11-12 22:20:13

传播技术知识,为国贡献,功德无量!

liangying520 发表于 2018-11-13 13:53:58

非常感谢鸿哥,到这里所有的文档下载完了,从今天开始学单片机了,虽然自己年纪大了点,但看了你写的这些教程,感触非常磊,在此非常感谢你!!!{:handshake:}{:handshake:}{:handshake:}

XTXB 发表于 2018-11-14 11:21:46

鸿哥出品,必属精品!

吴坚鸿 发表于 2018-11-14 12:30:19

第一百三十三节:常用的三种串口发送函数。

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

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

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

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

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

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

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

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

void main()
{
    UsartSendBuffer((const unsigned char *)&Gu8SendBuffer,5);//从第0位置发送5个数据
    UsartSendBuffer((const unsigned char *)&Gu8SendBuffer,5);//从第6位置发送5个数据
    while(1)
{

    }
}

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

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

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

//发送带协议的函数
void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize)
{
    static unsigned long i;
    static unsigned long *pSu32;
    static unsigned long u32SendSize;

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

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

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

void main()
{
    UsartSendMessage((const unsigned char *)&Gu8SendMessage,100); //必须从第0位置发送
    while(1)
{

    }
}



【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模式),即可观察到发送的三串数据。
代码如下:

#include "REG52.H"

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

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

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

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

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

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

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

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();    //在此函数内部调用了发送的三串数据
    while(1)
{
    }
}

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

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

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

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

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

//发送带协议的函数
void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize)
{
    static unsigned long i;
    static unsigned long *pSu32;
    static unsigned long u32SendSize;

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

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

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

void SystemInitial(void)
{
unsigned char u8_TMOD_Temp=0;

//以下是定时器0的中断的配置
TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;   

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

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

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

ES=1;         //允许串口中断
EA=1;         //允许总中断
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{
    //发送任意数组
    UsartSendBuffer((const unsigned char *)&Gu8SendBuffer,5);//从第0位置发送5个数据
UsartSendBuffer((const unsigned char *)&Gu8SendBuffer,5);//从第6位置发送5个数据

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



kkfy888 发表于 2018-11-15 00:44:00

后续希望也讲下MODBUS-RTU协议应用

tt98 发表于 2018-11-15 07:51:42

谢谢鸿哥更新!{:lol:}

wuminglx 发表于 2018-11-15 23:40:32

还坚持这么久 厉害

dtdzlujian 发表于 2018-11-19 21:21:28


第一百三十三节 打不开

xczxwy 发表于 2018-11-20 09:38:20

楼主太敬业了,三年过去了,这帖子居然还在更新,佩服佩服!

kkfy888 发表于 2018-11-21 12:12:21

dtdzlujian 发表于 2018-11-19 21:21
第一百三十三节 打不开

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

dtdzlujian 发表于 2018-11-22 09:16:48

谢谢! kkfy888

kkfy888 发表于 2018-11-22 11:41:53

dtdzlujian 发表于 2018-11-22 09:16
谢谢! kkfy888

{:smile:} 不用客气!

XTXB 发表于 2018-11-23 08:40:19

楼主有恒心,有情怀,加油!

TKZXJ 发表于 2018-12-16 13:22:56

顶一下 好东西

kkfy888 发表于 2019-1-1 14:45:49

2019年了,元旦快乐,年后还更新吗

dtdzlujian 发表于 2019-1-16 04:28:44

刚好年底了,估计都是很忙的时候了。支持

jacktau 发表于 2019-1-16 08:25:01

浅显易懂,比较适合初学者,为楼主的开源共享和坚持精神点赞!

anyanggcmy 发表于 2019-1-30 16:53:01

谢谢鸿哥更新

吴坚鸿 发表于 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   例程的上位机程序。】
#include "REG52.H"

#define RECE_TIME_OUT    2000//通讯过程中字节之间的超时时间2000ms
#define REC_BUFFER_SIZE30    //常规控制类数组的长度
#define KEY_FILTER_TIME25    //按键滤波的“稳定时间”

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

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

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

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

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

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

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

void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

void BeepOpen(void);   
void BeepClose(void);
void VoiceScan(void);
void KeyScan(void);   
void KeyTask(void);   

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

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

unsigned char Gu8ReceTable; //从下位机接收到的表格数据的数组

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

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

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

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

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

volatile unsigned char vGu8BeepTimerFlag=0;
volatile unsigned int vGu16BeepTimerCnt=0;

volatile unsigned char vGu8KeySec=0;

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

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

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

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
    UsartTask();   //串口收发的任务函数
KeyTask();   
    }
}

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

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

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

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

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

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

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

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


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

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

    case 2://发送之后,等待下位机返回的数据的状态
         if(1==GtBigBufferUsart.u8QueueStatus) //当前批次的接收到的数据成功
         {
                  GtBigBufferUsart.u8ReSendCnt=0; //重发计数器清零
                  GtBigBufferUsart.u32AlreadySendSize=0; //实际已经发送的数据量清零
                  GtBigBufferUsart.u32CurrentAddr=0;//当前批次需要发送的起始地址
                  GtBigBufferUsart.u32CurrentSize=10; //从当前批次起始地址开始发送的数据量
                  GtBigBufferUsart.u8Step=3;   //切换到下一步
         }
         else if(2==GtBigBufferUsart.u8QueueStatus) //当前批次的接收到的数据失败
         {
            GtBigBufferUsart.u8ReSendCnt++;
            if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重发次数
            {
GtBigBufferUsart.u8Step=0;
GtBigBufferUsart.u8Start=0; //结束当前的过程通讯
GtBigBufferUsart.u8Status=2; //对外宣布“通讯失败”
                      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
vGu8BeepTimerFlag=1;
}
else
{
                      GtBigBufferUsart.u8Step=1;   //返回上一步,重发当前段的数据
}
         }
         else if(0==vGu16BigBufferUsartTimerCnt) //当前批次在等待接收返回数据时,超时
         {
            GtBigBufferUsart.u8ReSendCnt++;
            if(GtBigBufferUsart.u8ReSendCnt>=SCu8ReSendCntMax) //大于最大的重发次数
            {
GtBigBufferUsart.u8Step=0;
GtBigBufferUsart.u8Start=0; //结束当前的过程通讯
GtBigBufferUsart.u8Status=2; //对外宣布“通讯失败”

                      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
vGu8BeepTimerFlag=1;
}
else
{
                      GtBigBufferUsart.u8Step=1;   //返回上一步,重发当前段的数据
}
         }
         break;

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

         pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer;
         *pSu32Data=GtBigBufferUsart.u32CurrentAddr; //当前批次需要发送的起始地址

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

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

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

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

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

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

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

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

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

if(1==CmpTwoBufferIsSame(Cu8TestTable,    //如果接收的数据与存储的相等
Gu8ReceTable,
                                                57))
                      {
                        vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=1000;   //让蜂鸣器“长鸣”一声
vGu8BeepTimerFlag=1;
GtBigBufferUsart.u8Status=1; //对外宣布“通讯成功”
}
else
{
                        vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
vGu8BeepTimerFlag=1;
GtBigBufferUsart.u8Status=2; //对外宣布“通讯失败”
}

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

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

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

                      vGu8BeepTimerFlag=0;
vGu16BeepTimerCnt=30;   //让蜂鸣器“短鸣”一声
vGu8BeepTimerFlag=1;
}
else
{
                      GtBigBufferUsart.u8Step=3;   //返回上一步,重发当前段的数据
}
         }
         break;
}

}

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

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

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

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

            //发送带指令的数据
UsartSendMessage((const unsigned char *)&GtBigBufferUsart.u8QueueSendBuffer,
30);

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

      break;

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

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

      break;
}
}


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

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

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

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

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

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

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

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

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

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

                        pSu32Data=(unsigned long *)&Gu8ReceBuffer; //数据转换。
                                  Su32CurrentSize=*pSu32Data;//读取的发送的数据量

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

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

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

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

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

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

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

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

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

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

//发送带协议的函数
void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize)
{
    static unsigned long i;
    static unsigned long *pSu32;
    static unsigned long u32SendSize;

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

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

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

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

//比较两个数组的是否相等。返回1代表相等,返回0代表不相等
unsigned char CmpTwoBufferIsSame(const unsigned char *pCu8Buffer_1,
const unsigned char *pCu8Buffer_2,
unsigned long u32BufferSize)//参与对比的数组的大小
{
unsigned long i;
for(i=0;i<u32BufferSize;i++)
{
   if(pCu8Buffer_1!=pCu8Buffer_2)
   {
          return 0; //只要有一个不相等,则返回0并且退出当前函数
}
}
    return 1; //相等
}


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

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

void T0_time() interrupt 1   
{
VoiceScan();
KeyScan();

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

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

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

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
unsigned char u8_TMOD_Temp=0;

//以下是定时器0的中断的配置
TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;   

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

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

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

ES=1;         //允许串口中断
EA=1;         //允许总中断
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

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

void BeepOpen(void)
{
P3_4=0;
}

void BeepClose(void)
{
P3_4=1;
}

void VoiceScan(void)
{

          static unsigned char Su8Lock=0;

if(1==vGu8BeepTimerFlag&&vGu16BeepTimerCnt>0)
          {
                  if(0==Su8Lock)
                  {
                   Su8Lock=1;
BeepOpen();
   }
    else
{   

                     vGu16BeepTimerCnt--;       

                   if(0==vGu16BeepTimerCnt)
                   {
                           Su8Lock=0;   
BeepClose();
                   }

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

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

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

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

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

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

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

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

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

void SystemInitial(void) ;
void Delay(unsigned long u32DelayTime) ;
void PeripheralInitial(void) ;

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

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

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

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

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

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

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

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

void main()
{
SystemInitial();            
Delay(10000);               
PeripheralInitial();      
    while(1)
{
    UsartTask();   //串口收发的任务函数
    }
}

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

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

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

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

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

      break;

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

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

      break;
}
}


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                        pSu32Data=(unsigned long *)&Gu8ReceBuffer; //数据转换。
                                  Su32CurrentSize=*pSu32Data;//读取的发送的数据量

            GtBigBufferUsart.u8QueueSendBuffer=0xeb; //数据头
            GtBigBufferUsart.u8QueueSendBuffer=0x02; //数据类型 返回分段数据

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

            pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer;
            *pSu32Data=Su32CurrentAddr; //返回接收到的起始地址

            pSu32Data=(unsigned long *)&GtBigBufferUsart.u8QueueSendBuffer;
            *pSu32Data=Su32CurrentSize; //返回接收到的当前批次的数据量

                  for(i=0;i<Su32CurrentSize;i++)
                  {
//装载即将要发送的分段数据
               GtBigBufferUsart.u8QueueSendBuffer=Cu8TestTable;
}

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

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

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

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

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

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

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

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

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

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

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

//发送带协议的函数
void UsartSendMessage(const unsigned char *pCu8SendMessage,unsigned long u32SendMaxSize)
{
    static unsigned long i;
    static unsigned long *pSu32;
    static unsigned long u32SendSize;

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

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

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

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

void T0_time() interrupt 1   
{

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

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

TH0=0xfc;   
TL0=0x66;   
}


void SystemInitial(void)
{
unsigned char u8_TMOD_Temp=0;

//以下是定时器0的中断的配置
TMOD=0x01;
TH0=0xfc;   
TL0=0x66;   
EA=1;      
ET0=1;      
TR0=1;   

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

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

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

ES=1;         //允许串口中断
EA=1;         //允许总中断
}

void Delay(unsigned long u32DelayTime)
{
    for(;u32DelayTime>0;u32DelayTime--);
}

void PeripheralInitial(void)
{

}

kkfy888 发表于 2019-2-2 17:10:34

谢谢鸿哥年前再次更新,祝您猪年吉祥如意,财源滚滚,心想事成,万事顺利,新年快乐!

XTXB 发表于 2019-2-14 22:00:44

鸿哥,新年好!

mikeliujia 发表于 2019-3-17 22:18:14

停更了?

kkfy888 发表于 2019-3-24 11:16:40

可能工作比较忙吧,耐心等待吧!

guolun 发表于 2019-3-24 18:31:18

做事的毅力非同一般。

anyanggcmy 发表于 2019-6-6 16:28:19

很久没有更新了

大浪淘沙 发表于 2019-6-7 22:30:04

看完之后,以前不太了解的东西现在认识清楚了.感谢楼主分享.

darkness27 发表于 2019-6-8 04:23:47

请问有没有汇总或整理出书呢?谢谢。

z4p7q2 发表于 2019-6-13 16:15:28

等着呢,就是喜欢这种NBHH的大牛

hzyzs 发表于 2019-6-17 16:29:09

为什么我点击 只看该作者 ,显示的还是全部楼层呢?

迅得电子 发表于 2019-6-17 16:53:30

看了一部分,感觉写的很好,很多疑问和困惑都能得到解决!还有楼主对单片机的热爱,真的是让旁人都能感受到,这么长时间的坚持,实属不易!顶顶顶{:biggrin:}

吴坚鸿 发表于 2019-10-23 14:33:57

第一百三十五节: 《从单片机基础到程序框架》(全集 2019版)整理完毕。





XTXB 发表于 2019-10-23 15:49:20

本帖最后由 XTXB 于 2019-10-23 15:52 编辑

吴坚鸿 发表于 2019-10-23 14:33
第一百三十五节: 《从单片机基础到程序框架》(全集 2019版)整理完毕。




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

zhuyuye 发表于 2019-10-23 16:24:22

出一版实体书吧,留个纪念。

easier 发表于 2019-10-23 17:16:40

本帖最后由 easier 于 2019-10-23 17:30 编辑

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

mjhjmh630 发表于 2019-10-30 13:27:45

如果!每个学校的老师有吴坚鸿--吴老师--吴总这么详细、全面、认真的教学态度!学校毕业的学生,工作就没那么难找了!
吴老师是那些刚毕业又想从事电子行业的一个引路人啊!

裸奔的蜗牛菌 发表于 2019-11-18 23:41:12

请问楼主有没有好的汇编学习方法推荐,因为是芯片公司要用到汇编所以想学习一下,但是感觉资料难找。

tt98 发表于 2019-12-12 11:07:13

鸿哥完美收宫!

jesse2012 发表于 2019-12-12 22:39:01

从单片机基础到程序框架,值得期待,认真整理

liu615 发表于 2019-12-23 08:50:27

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

说的在理儿!

lylm123 发表于 2019-12-23 20:24:26

有始有终,赞一个!

lijianxing 发表于 2020-4-14 09:05:42

拜读过其它的两个连载,收获巨大,这个连载要认真拜读,谢谢

leonmsi 发表于 2020-7-7 21:44:56

哇,真的需要赞,以前看过楼主的文章,非常好,感谢

kingboy10000 发表于 2020-7-9 14:23:10

写的太精彩了,比安卓强100倍

gsq19920418 发表于 2020-7-9 16:10:14



加了一個目錄

261854681 发表于 2020-9-5 22:02:48

历时四年,毅力惊人

Wwdzaefd 发表于 2020-9-6 08:10:39

感谢楼主的大爱!

wangdanq 发表于 2020-9-19 08:28:21

谢谢楼主分享学习一下好东东

fxue231 发表于 2020-10-7 22:17:10

不知道现在写的怎么样了{:smile:}

hong1025 发表于 2020-12-1 18:22:01

写的非常详细,解决很多像我这样初学者的疑惑!

blankly503 发表于 2020-12-9 12:33:58


写的太精彩了

fsj5098@hotmail 发表于 2020-12-11 22:01:13

楼主,厉害,顶~

catwill 发表于 2020-12-17 19:09:28

谢谢楼主,我下载了!

吴刚童 发表于 2021-1-27 14:05:12

顶一下,好资料

sandoz1cn 发表于 2021-1-28 15:26:57

我是要感谢吴老师的,他的一部分框架我到现在都在用

maozetian 发表于 2021-2-3 09:20:24

好贴一定要顶

龍黍 发表于 2021-8-16 14:39:08

厉害了,我的神!

elevator2021 发表于 2021-8-16 20:42:55

楼主有纸质版书了吗?

ppgu 发表于 2021-8-29 17:14:46

必须顶一个,厉害 {:victory:}

Fireone 发表于 2021-8-31 14:56:29

该教程免费授权给所有的出版社和做单片机学习板的厂家和各
大培训机构以及全国各大院校,我本人不从中赢利也不收取任何版
权费用, 我本人也不卖书也不卖学习板也不搞线下培训。该教程的
版权无偿捐给全社会。
------

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

小水瓶 发表于 2021-9-8 20:38:48

原来早就收官了,楼主幸苦了,顶顶!

fuze2009 发表于 2021-12-15 13:46:59

教材写的很好,有实用性,点赞!{:smile:}
页: 1 2 3 4 [5]
查看完整版本: 从单片机基础到程序框架(连载)