从单片机基础到程序框架(全集 2019pdf版).pdf - 第792页
上图 13 2.2.2 232 串口 电路 程序 功能如 下: 单片 机接收 任意 长度 (最大 一次 不超 过 30 字 节) 的一串 数据 。如 果发现 连续 有三 个字 节是 0x02 0x03 0x04,蜂 鸣器则 “短叫”10 0ms 提示;如 果发现连续有 四个字节是 0x06 0x07 0x 08 0x09, 蜂鸣器则“长叫 ”2000m s 提示。 比如测试 “短叫”10 0ms,发送十六 进制的数据串 :05 02 0…

第一百三十二节:“转发、透传、多种协议并存”的双缓存串口程序框架。
【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
代码如下:
#include "REG52.H"
#define DOG_TIME_OUT 20 //理论上,9600 波特率的字节间隔时间大概 0.8ms 左右,因此取 20ms 足够
#define RECE_BUFFER_SIZE 30 //接收缓存的数组大小
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[RECE_BUFFER_SIZE]; //双缓存其中之一的缓存 A
unsigned long Gu32ReceCnt_A=0; //缓存 A 的数组下标与计数器,必须初始化为 0,做好接收准备

unsigned char Gu8ReceBuffer_B[RECE_BUFFER_SIZE]; //双缓存其中之一的缓存 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[Gu32ReceCnt_A]=SBUF;
Gu32ReceCnt_A++; //记录当前缓存 A 的接收字节数
}
}
else //1 代表选择缓存 B
{
if(Gu32ReceCnt_B<RECE_BUFFER_SIZE)
{