从单片机基础到程序框架(全集 2019pdf版).pdf - 第780页
Gu32ReceCn t++; if(Gu32Rec eCnt>=6) //前 6 个数 据。接收完了 “数据类 型”和“数据 长度” 。 { Gu8ReceT ype=Gu8Rece Buffer[1]; / /提取“数据 类型” pu32Data=( unsigned lo ng *)&Gu8R eceBuffer [2]; Gu32ReceDa taLength= *pu32Data; / /提取“数据 长度” if…

第一百三十一节: 灵活切换各种不同大小“接收内存”的串口程序框架。
【131.1 切换各种不同大小“接收内存”。】
很多 32 位的单片机,只要外挂 SRAM 或者 SDRAM 这类内存芯片,就可以轻松的把一个全局变量的数组开
辟到几百 K 甚至几兆的容量。开辟这么大的数组,往往是用来处理一些文件类的大数据,比如串口接收一张
480x272 点阵大小的.BMP 格式的图片文件,就需要开辟一个几百 K 的全局变量大数组。串口通信中,从接收
内存的容量来划分,常用有两种数据类型,一种是常规控制类(容量小),一种是文件类(容量大),要能做
到在这两种“接收内存”中灵活切换,关键是用到“指针的中转切换”技术。
“常规控制类内存”负责两块事务,一块是接收“前部分的”[数据头,数据类型,数据长度],另一块
是“后部分的”[常规控制类的专用数据]。
“文件类内存”只负责“后部分的”[文件类的专用数据],而“前部分的”[数据头,数据类型,数据
长度]是需要借助“常规控制类内存”来实现的。
本节破题的关键在于,根据不同的数据类型,利用“指针的中转切换”实现不同接收内存的灵活切换。
关键代码是串口中断函数这部分的处理,片段代码的讲解如下:
unsigned char Gu8ReceBuffer[20]; //常规控制类的小内存
unsigned char Gu8FileBuffer[40]; //文件类的大内存
unsigned char *pGu8ReceBuffer; //用来切换接收内存的“中转指针”
unsigned long Gu32ReceCntMax=20; //最大缓存(初始值 20 或者 40 都没关系,因为后续会动态改变)
void usart(void) interrupt 4
{
if(1==RI)
{
RI = 0;
if(0==Gu8FinishFlag)
{
Gu8ReceFeedDog=1;
switch(Gu8ReceStep)
{
case 0: //“前部分的”数据头。接头暗号的步骤
Gu8ReceBuffer[0]=SBUF;
if(0xeb==Gu8ReceBuffer[0])
{
Gu32ReceCnt=1;
Gu8ReceStep=1;
}
break;
case 1: //“前部分的”数据类型和长度
Gu8ReceBuffer[Gu32ReceCnt]=SBUF;

Gu32ReceCnt++;
if(Gu32ReceCnt>=6) //前 6 个数据。接收完了“数据类型”和“数据长度”。
{
Gu8ReceType=Gu8ReceBuffer[1]; //提取“数据类型”
pu32Data=(unsigned long *)&Gu8ReceBuffer[2];
Gu32ReceDataLength=*pu32Data; //提取“数据长度”
if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
{
Gu8FinishFlag=1; //接收完成标志“置 1”,通知主函数处理。
Gu8ReceStep=0; //及时切换回接头暗号的步骤
}
else //如果还没结束,继续切换到下一个步骤,接收“有效数据”
{
//以下几行代码是本节的破题关键!!!
if(0x02==Gu8ReceType) //如果是文件类,把指针关联到 Gu8FileBuffer
{
pGu8ReceBuffer=(unsigned char *)&Gu8FileBuffer[0];//下标 0
Gu32ReceCntMax=40+6; //最大缓存
}
else //如果是常规类,继续把指针关联到 Gu8ReceBuffer 本身的数组
{
pGu8ReceBuffer=(unsigned char *)&Gu8ReceBuffer[6];//下标 6
Gu32ReceCntMax=20; //最大缓存
}
Gu8ReceStep=2; //切换到下一个步骤
}
}
break;
case 2: //“后部分的”数据
pGu8ReceBuffer[Gu32ReceCnt-6]=SBUF; //这里的指针就是各种不同内存的化身!!!
Gu32ReceCnt++; //每接收一个字节,数组下标都自加 1,为接收下一个数据做准备
//靠“数据长度”来判断是否完成。也不允许超过数组的最大缓存的长度
if(Gu32ReceCnt>=Gu32ReceDataLength||Gu32ReceCnt>=Gu32ReceCntMax)
{
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 程序例程。】