【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根是交叉的收发数据线(上位机的“接收线”连接下位机的“发送线”,上位机的“发送线”连接下位机的“接收线”),如下图所示:
![](https://img.kancloud.cn/07/c7/07c71ef5d2261aa2417c7dc4e2a24fe1_811x339.png)
上图134.4.1 双机通讯的232串口接线图
![](https://img.kancloud.cn/a2/3d/a23df87ac21f61d2182864f67461b009_359x103.png)
上图134.4.2 上位机的独立按键
![](https://img.kancloud.cn/89/70/8970513a066fe0726b2997dcb0329ce0_194x190.png)
上图134.4.3 上位机的有源蜂鸣器
【134.5 例程的通讯协议。】
(1) 通讯参数。波特率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”的提示,请按下图的窗口提示来设置一下编译的环境。
![](https://img.kancloud.cn/0b/4c/0b4cf94661c684369773a2110577eb4b_415x279.png)
上图134.5.1 设置编译的环境
【134.7 例程的上位机程序。】
\#include "REG52.H"
\#define RECE\_TIME\_OUT 2000 //通讯过程中字节之间的超时时间2000ms
\#define REC\_BUFFER\_SIZE 30 //常规控制类数组的长度
\#define KEY\_FILTER\_TIME 25 //按键滤波的“稳定时间”
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\[57\]; //从下位机接收到的表格数据的数组
//把一些针对某个特定事件的全局变量放在一个结构体内,可以让全局变量的分类更加清晰
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\[30\]; //队列驱动函数的发送指令的数组
unsigned char u8QueueStatus; //队列驱动函数的通讯状态 0为初始状态 1为通讯成功 2为通讯失败
};
unsigned char Gu8QueueReceUpdate=0; //1代表“队列发送数据后,收到了新的数据”
struct StructBigBufferUsart GtBigBufferUsart;//此结构体变量专门用来控制读取大数组的通讯事件
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\[REC\_BUFFER\_SIZE\]; //常规控制类的小内存
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\[0\]=0xeb; //数据头
GtBigBufferUsart.u8QueueSendBuffer\[1\]=0x01; //数据类型 读取数组容量大小
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2\];
\*pSu32Data=7; //数据长度 本条指令的数据总长是7个字节
//异或算法的函数
GtBigBufferUsart.u8QueueSendBuffer\[6\]=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\[0\]=0xeb; //数据头
GtBigBufferUsart.u8QueueSendBuffer\[1\]=0x02; //数据类型 读取分段数据
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2\];
\*pSu32Data=15; //数据长度 本条指令的数据总长是15个字节
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4\];
\*pSu32Data=GtBigBufferUsart.u32CurrentAddr; //当前批次需要发送的起始地址
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4+4\];
\*pSu32Data=GtBigBufferUsart.u32CurrentSize; //从当前批次起始地址发送的数据量
//异或算法的函数
GtBigBufferUsart.u8QueueSendBuffer\[14\]=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\[0\],
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\[Gu32ReceDataLength-1\]; //提取接收到的“异或”
Su8Rece\_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //计算“异或”
if(Gu32ReceDataLength>=11&& //接收到的数据长度必须大于或者等于11个字节
Su8Rece\_Xor==Gu8Rece\_Xor) //验证“异或”,“计算的”与“接收的”是否一致
{
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6\]; //数据转换。
GtBigBufferUsart.u32NeedSendSize=\*pSu32Data; //提取将要接收数组的大小
GtBigBufferUsart.u8QueueStatus=1; //告诉“过程控制函数”,当前通讯成功
}
else
{
GtBigBufferUsart.u8QueueStatus=2; //告诉“过程控制函数”,当前通讯失败
}
break;
case 0x02: //读取下位机的分段数据
Gu8QueueReceUpdate=1; //告诉“队列驱动函数”收到了新的应答数据
Gu8Rece\_Xor=Gu8ReceBuffer\[Gu32ReceDataLength-1\]; //提取接收到的“异或”
Su8Rece\_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //计算“异或”
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6\]; //数据转换。
Su32CurrentAddr=\*pSu32Data; //读取的起始地址
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6+4\]; //数据转换。
Su32CurrentSize=\*pSu32Data; //读取的发送的数据量
if(Gu32ReceDataLength>=11&& //接收到的数据长度必须大于或者等于11个字节
Su8Rece\_Xor==Gu8Rece\_Xor&& //验证“异或”,“计算的”与“接收的”是否一致
Su32CurrentAddr==GtBigBufferUsart.u32CurrentAddr&& //验证“地址”,相当于验证“动态密匙”
Su32CurrentSize==GtBigBufferUsart.u32CurrentSize) //验证“地址”,相当于验证“动态密匙”
{
for(i=0;i<Su32CurrentSize;i++)
{
//及时把接收到的数据存储到Gu8ReceTable数组
Gu8ReceTable\[Su32CurrentAddr+i\]=Gu8ReceBuffer\[6+4+4+i\];
}
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\[0\]=SBUF; //直接读取刚接收完的一个字节的数据。
if(0xeb==Gu8ReceBuffer\[0\]) //等于数据头0xeb,接头暗号吻合。
{
Gu32ReceCnt=1; //接收缓存的下标
Gu8ReceStep=1; //切换到下一个步骤,接收其它有效的数据
}
break;
case 1: //“前部分的”数据类型和长度
Gu8ReceBuffer\[Gu32ReceCnt\]=SBUF; //直接读取刚接收完的一个字节的数据。
Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备
if(Gu32ReceCnt>=6) //前6个数据。接收完了“数据类型”和“数据长度”。
{
Gu8ReceType=Gu8ReceBuffer\[1\]; //提取“数据类型”
//以下的数据转换,在第62节讲解过的指针法
pu32Data=(unsigned long \*)&Gu8ReceBuffer\[2\]; //数据转换
Gu32ReceDataLength=\*pu32Data; //提取“数据长度”
if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
{
Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
Gu8ReceStep=0; //及时切换回接头暗号的步骤
}
else //如果还没结束,继续切换到下一个步骤,接收“有效数据”
{
//本节只用到一个接收数组,把指针关联到Gu8ReceBuffer本身的数组
pGu8ReceBuffer=(unsigned char \*)&Gu8ReceBuffer\[6\];
Gu32ReceCntMax=REC\_BUFFER\_SIZE; //最大缓存
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; //及时清除发送中断的标志,避免一直无缘无故的进入中断。
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\[2\];
u32SendSize=\*pSu32; //从带协议的数组中提取整包数组的有效发送长度
if(u32SendSize>u32SendMaxSize) //如果“有效发送长度”大于“最大限制的长度”,数据异常
{
return; //数据异常,直接退出当前函数,预防数组越界
}
for(i=0;i<u32SendSize;i++) //u32SendSize为发送的数据长度
{
UsartSendByteData(pCu8SendMessage\[i\]); //基于“发送单字节的最小接口函数”来实现的
}
}
unsigned char CalculateXor(const unsigned char \*pCu8Buffer, //此处加const代表数组“只读”
unsigned long u32BufferSize) //参与计算的数组的大小
{
unsigned long i;
unsigned char Su8Rece\_Xor;
Su8Rece\_Xor=pCu8Buffer\[0\]; //提取数据串第“i=0”个数据作为异或的原始数据
for(i=1;i<u32BufferSize;i++) //注意,这里是从第“i=1”个数据开始
{
Su8Rece\_Xor=Su8Rece\_Xor^pCu8Buffer\[i\]; //计算“异或”
}
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\[i\]!=pCu8Buffer\_2\[i\])
{
return 0; //只要有一个不相等,则返回0并且退出当前函数
}
}
return 1; //相等
}
void KeyScan(void) //此函数放在定时中断里每1ms扫描一次
{
static unsigned char Su8KeyLock1; //1号按键的自锁
static unsigned int Su16KeyCnt1; //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\_SIZE 30 //常规控制类数组的长度
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\[30\]; //队列驱动函数的发送指令的数组
unsigned char u8QueueStatus; //队列驱动函数的通讯状态 0为初始状态 1为通讯成功 2为通讯失败
};
unsigned char Gu8QueueReceUpdate=0; //1代表“队列发送数据后,收到了新的数据”
struct StructBigBufferUsart GtBigBufferUsart;//此结构体变量专门用来应答读取大数组的通讯事件
volatile unsigned char vGu8QueueSendTimerFlag=0; //队列发送的超时定时器
volatile unsigned int vGu16QueueSendTimerCnt=0;
unsigned char Gu8SendByteFinish=0; //发送一个字节完成的标志
unsigned char Gu8ReceBuffer\[REC\_BUFFER\_SIZE\]; //常规控制类的小内存
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\[0\],
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\[Gu32ReceDataLength-1\]; //提取接收到的“异或”
Su8Rece\_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //计算“异或”
if(Su8Rece\_Xor!=Gu8Rece\_Xor) //验证“异或”,如果不相等,退出当前switch
{
break; //退出当前switch
}
GtBigBufferUsart.u8QueueSendBuffer\[0\]=0xeb; //数据头
GtBigBufferUsart.u8QueueSendBuffer\[1\]=0x01; //数据类型 返回数组容量的大小
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2\];
\*pSu32Data=11; //数据长度 本条指令的数据总长是11个字节
//提取数组容量的大小
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4\];
\*pSu32Data=sizeof(Cu8TestTable);//相当于\*pSu32Data=57;sizeof请参考第69节
//异或算法的函数
GtBigBufferUsart.u8QueueSendBuffer\[10\]=CalculateXor(GtBigBufferUsart.u8QueueSendBuffer,
10); //最后一个字节不纳入计算
//队列驱动函数的状态 0为初始状态 1为通讯成功 2为通讯失败
GtBigBufferUsart.u8QueueStatus=0; //队列驱动函数的通讯状态
GtBigBufferUsart.u8QueueSendTrig=1;//队列驱动函数的发送的启动
Gu8QueueReceUpdate=1; //告诉“队列驱动函数”此发送指令无需等待上位机的应答
break;
case 0x02: //返回下位机的分段数据
Gu8Rece\_Xor=Gu8ReceBuffer\[Gu32ReceDataLength-1\]; //提取接收到的“异或”
Su8Rece\_Xor=CalculateXor(Gu8ReceBuffer,Gu32ReceDataLength-1); //计算“异或”
if(Su8Rece\_Xor!=Gu8Rece\_Xor) //验证“异或”,如果不相等,退出当前switch
{
break; //退出当前switch
}
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6\]; //数据转换。
Su32CurrentAddr=\*pSu32Data; //读取的起始地址
pSu32Data=(unsigned long \*)&Gu8ReceBuffer\[6+4\]; //数据转换。
Su32CurrentSize=\*pSu32Data; //读取的发送的数据量
GtBigBufferUsart.u8QueueSendBuffer\[0\]=0xeb; //数据头
GtBigBufferUsart.u8QueueSendBuffer\[1\]=0x02; //数据类型 返回分段数据
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2\];
\*pSu32Data=6+4+4+Su32CurrentSize+1; //数据总长度
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4\];
\*pSu32Data=Su32CurrentAddr; //返回接收到的起始地址
pSu32Data=(unsigned long \*)&GtBigBufferUsart.u8QueueSendBuffer\[2+4+4\];
\*pSu32Data=Su32CurrentSize; //返回接收到的当前批次的数据量
for(i=0;i<Su32CurrentSize;i++)
{
//装载即将要发送的分段数据
GtBigBufferUsart.u8QueueSendBuffer\[6+4+4+i\]=Cu8TestTable\[Su32CurrentAddr+i\];
}
//异或算法的函数
GtBigBufferUsart.u8QueueSendBuffer\[6+4+4+Su32CurrentSize\]=
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\[0\]=SBUF; //直接读取刚接收完的一个字节的数据。
if(0xeb==Gu8ReceBuffer\[0\]) //等于数据头0xeb,接头暗号吻合。
{
Gu32ReceCnt=1; //接收缓存的下标
Gu8ReceStep=1; //切换到下一个步骤,接收其它有效的数据
}
break;
case 1: //“前部分的”数据类型和长度
Gu8ReceBuffer\[Gu32ReceCnt\]=SBUF; //直接读取刚接收完的一个字节的数据。
Gu32ReceCnt++; //每接收一个字节,数组下标都自加1,为接收下一个数据做准备
if(Gu32ReceCnt>=6) //前6个数据。接收完了“数据类型”和“数据长度”。
{
Gu8ReceType=Gu8ReceBuffer\[1\]; //提取“数据类型”
//以下的数据转换,在第62节讲解过的指针法
pu32Data=(unsigned long \*)&Gu8ReceBuffer\[2\]; //数据转换
Gu32ReceDataLength=\*pu32Data; //提取“数据长度”
if(Gu32ReceCnt>=Gu32ReceDataLength) //靠“数据长度”来判断是否完成
{
Gu8FinishFlag=1; //接收完成标志“置1”,通知主函数处理。
Gu8ReceStep=0; //及时切换回接头暗号的步骤
}
else //如果还没结束,继续切换到下一个步骤,接收“有效数据”
{
//本节只用到一个接收数组,把指针关联到Gu8ReceBuffer本身的数组
pGu8ReceBuffer=(unsigned char \*)&Gu8ReceBuffer\[6\];
Gu32ReceCntMax=REC\_BUFFER\_SIZE; //最大缓存
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; //及时清除发送中断的标志,避免一直无缘无故的进入中断。
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\[2\];
u32SendSize=\*pSu32; //从带协议的数组中提取整包数组的有效发送长度
if(u32SendSize>u32SendMaxSize) //如果“有效发送长度”大于“最大限制的长度”,数据异常
{
return; //数据异常,直接退出当前函数,预防数组越界
}
for(i=0;i<u32SendSize;i++) //u32SendSize为发送的数据长度
{
UsartSendByteData(pCu8SendMessage\[i\]); //基于“发送单字节的最小接口函数”来实现的
}
}
unsigned char CalculateXor(const unsigned char \*pCu8Buffer, //此处加const代表数组“只读”
unsigned long u32BufferSize) //参与计算的数组的大小
{
unsigned long i;
unsigned char Su8Rece\_Xor;
Su8Rece\_Xor=pCu8Buffer\[0\]; //提取数据串第“i=0”个数据作为异或的原始数据
for(i=1;i<u32BufferSize;i++) //注意,这里是从第“i=1”个数据开始
{
Su8Rece\_Xor=Su8Rece\_Xor^pCu8Buffer\[i\]; //计算“异或”
}
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)
{
}
- 首页
- 第一节:我的价值观
- 第二节:初学者的疑惑
- 第三节:单片机最重要的一个特性
- 第四节:平台软件和编译器软件的简介
- 第五节:用Keil2软件关闭,新建,打开一个工程的操作流程
- 第六节:把.c源代码编译成.hex机器码的操作流程
- 第七节:本节预留
- 第八节:把.hex机器码程序烧录到单片机的操作流程
- 第九节:本节预留
- 第十节:程序从哪里开始,要到哪里去?
- 第十一节:一个在单片机上练习C语言的模板程序
- 第十二节:变量的定义和赋值
- 【TODO】第十三节:赋值语句的覆盖性
- 【TODO】第十四节:二进制与字节单位,以及常用三种变量的取值范围
- 【TODO】第十五节:二进制与十六进制
- 【TODO】第十六节:十进制与十六进制
- 【TODO】第十七节:加法运算的5种常用组合
- 【TODO】第十八节:连加、自加、自加简写、自加1
- 【TODO】第十九节:加法运算的溢出
- 【TODO】第二十节:隐藏中间变量为何物?
- 【TODO】第二十一节:减法运算的5种常用组合。
- 【TODO】第二十二节:连减、自减、自减简写、自减1
- 【TODO】第二十三节:减法溢出与假想借位
- 【TODO】第二十四节:借用unsigned long类型的中间变量可以减少溢出现象
- 【TODO】第二十五节:乘法运算中的5种常用组合
- 【TODO】第二十六节:连乘、自乘、自乘简写,溢出
- 【TODO】第二十七节:整除求商
- 【TODO】第二十八节:整除求余
- 【TODO】第二十九节:“先余后商”和“先商后余”提取数据某位,哪家强?
- 【TODO】第三十节:逻辑运算符的“与”运算
- 【TODO】第三十一节:逻辑运算符的“或”运算
- 【TODO】第三十二节:逻辑运算符的“异或”运算
- 【TODO】第三十三节:逻辑运算符的“按位取反”和“非”运算
- 【TODO】第三十四节:移位运算的左移
- 【TODO】第三十五节:移位运算的右移
- 【TODO】第三十六节:括号的强制功能---改变运算优先级
- 【TODO】第三十七节:单字节变量赋值给多字节变量的疑惑
- 【TODO】第三十八节:第二种解决“运算过程中意外溢出”的便捷方法
- 【TODO】第三十九节:if判断语句以及常量变量的真假判断
- 【TODO】第四十节:关系符的等于“==”和不等于“!=”
- 【TODO】第四十一节:关系符的大于“>”和大于等于“>=”
- 【TODO】第四十二节:关系符的小于“<”和小于等于“<=”
- 【TODO】第四十三节:关系符中的关系符:与“&&”,或“||”
- 【TODO】第四十四节:小括号改变判断优先级
- 【TODO】第四十五节: 组合判断if...else if...else
- 【TODO】第四十六节: 一维数组
- 【TODO】第四十七节: 二维数组
- 【TODO】第四十八节: while循环语句
- 【TODO】第四十九节: 循环语句do while和for
- 【TODO】第五十节: 循环体内的continue和break语句
- 【TODO】第五十一节: for和while的循环嵌套
- 【TODO】第五十二节: 支撑程序框架的switch语句
- 【TODO】第五十三节: 使用函数的三要素和执行顺序
- 【TODO】第五十四节: 从全局变量和局部变量中感悟“栈”为何物
- 【TODO】第五十五节: 函数的作用和四种常见书写类型
- 【TODO】第五十六节: return在函数中的作用以及四个容易被忽略的功能
- 【TODO】第五十七节: static的重要作用
- 【TODO】第五十八节: const(./book/或code)在定义数据时的作用
- 【TODO】第五十九节: 全局“一键替换”功能的#define
- 【TODO】第六十节: 指针在变量(./book/或常量)中的基础知识
- 【TODO】第六十一节: 指针的中转站作用,地址自加法,地址偏移法
- 【TODO】第六十二节: 指针,大小端,化整为零,化零为整
- 【TODO】第六十三节: 指针“化整为零”和“化零为整”的“灵活”应用
- 【TODO】第六十四节: 指针让函数具备了多个相当于return的输出口
- 【TODO】第六十五节: 指针作为数组在函数中的入口作用
- 【TODO】第六十六节: 指针作为数组在函数中的出口作用
- 【TODO】第六十七节: 指针作为数组在函数中既“入口”又“出口”的作用
- 【TODO】第六十八节: 为函数接口指针“定向”的const关键词
- 【TODO】第六十九节: 宏函数sizeof(./book/)
- 【TODO】第七十节: “万能数组”的结构体
- 【TODO】第七十一节: 结构体的内存和赋值
- 【TODO】第七十二节: 结构体的指针
- 【TODO】第七十三节: 结构体数据的传输存储和还原
- 【TODO】第七十四节: 结构体指针在函数接口处的频繁应用
- 【TODO】第七十五节: 指针的名义(例:一维指针操作二维数组)
- 【TODO】第七十六节: 二维数组的指针
- 【TODO】第七十七节: 指针唯一的“单向输出”通道return
- 【TODO】第七十八节: typedef和#define和enum
- 【TODO】第七十九节: 各种变量常量的命名规范
- 【TODO】第八十节: 单片机IO口驱动LED
- 【TODO】第八十一节: 时间和速度的起源(指令周期和晶振频率)
- 【TODO】第八十二节: Delay“阻塞”延时控制LED闪烁
- 【TODO】第八十三节: 累计主循环的“非阻塞”延时控制LED闪烁
- 【TODO】第八十四节: 中断与中断函数
- 【TODO】第八十五节: 定时中断的寄存器配置
- 【TODO】第八十六节: 定时中断的“非阻塞”延时控制LED闪烁
- 【TODO】第八十七节: 一个定时中断产生N个软件定时器
- 【TODO】第八十八节: 两大核心框架理论(四区一线,switch外加定时中断)
- 【TODO】第八十九节: 跑马灯的三种境界
- 【TODO】第九十节: 多任务并行处理两路跑马灯
- 【TODO】第九十一节: 蜂鸣器的“非阻塞”驱动
- 【TODO】第九十二节: 独立按键的四大要素(自锁,消抖,非阻塞,清零式滤波)
- 【TODO】第九十三节: 独立按键鼠标式的单击与双击
- 【TODO】第九十四节: 两个独立按键构成的组合按键
- 【TODO】第九十五节: 两个独立按键的“电脑键盘式”组合按键
- 【TODO】第九十六节: 独立按键“一键两用”的短按与长按
- 【TODO】第九十七节: 独立按键按住不松手的连续均匀触发
- 【TODO】第九十八节: 独立按键按住不松手的“先加速后匀速”的触发
- 【TODO】第九十九节: “行列扫描式”矩阵按键的单个触发(原始版)
- 【TODO】第一百节: “行列扫描式”矩阵按键的单个触发(优化版)
- 【TODO】第一百零一节: 矩阵按键鼠标式的单击与双击
- 【TODO】第一百零二节: 两个“任意行输入”矩阵按键的“有序”组合触发
- 【TODO】第一百零三节: 两个“任意行输入”矩阵按键的“无序”组合触发
- 【TODO】第一百零四节: 矩阵按键“一键两用”的短按与长按
- 【TODO】第一百零五节: 矩阵按键按住不松手的连续均匀触发
- 【TODO】第一百零六节: 矩阵按键按住不松手的“先加速后匀速”触发
- 【TODO】第一百零七节: 开关感应器的识别与软件滤波
- 【TODO】第一百零八节: 按键控制跑马灯的启动和暂停和停止
- 【TODO】第一百零九节: 按键控制跑马灯的方向
- 【TODO】第一百一十节: 按键控制跑马灯的速度
- 第一百一十一节: 工业自动化设备的开关信号的运动控制
- 【TODO】第一百一十二节: 数码管显示的基础知识
- 【TODO】第一百一十三节: 动态扫描的数码管显示数字
- 【TODO】第一百一十四节: 动态扫描的数码管显示小数点
- 【TODO】第一百一十五节: 按键控制数码管的秒表
- 【TODO】第一百一十六节: 按键控制数码管的倒计时
- 【TODO】第一百一十七节: 按键切换数码管窗口来设置参数
- 【TODO】第一百一十八节: 按键让某位数码管闪烁跳动来设置参数
- 【TODO】第一百一十九节: 一个完整的人机界面的程序框架的脉络
- 【TODO】第一百二十节: 按键切换窗口切换局部来设置参数
- 【TODO】第一百二十一节: 可调参数的数码管倒计时
- 【TODO】第一百二十二节: 利用定时中断做的“时分秒”数显时钟
- 【TODO】第一百二十三节: 一种能省去一个lock自锁变量的按键驱动程序
- 【TODO】第一百二十四节: 数显仪表盘显示“速度、方向、计数器”的跑马灯
- 【TODO】第一百二十五节: “双线”的肢体接触通信
- 【TODO】第一百二十六节: “单线”的肢体接触通信
- 【TODO】第一百二十七节: 单片机串口接收数据的机制
- 【TODO】第一百二十八节: 接收“固定协议”的串口程序框架
- 【TODO】第一百二十九节: 接收带“动态密匙”与“累加和”校验数据的串口程序框架
- 【TODO】第一百三十节: 接收带“动态密匙”与“异或”校验数据的串口程序框架
- 【TODO】第一百三十一节: 灵活切换各种不同大小“接收内存”的串口程序框架
- 【TODO】第一百三十二节:“转发、透传、多种协议并存”的双缓存串口程序框架
- 【TODO】第一百三十三节:常用的三种串口发送函数
- 【TODO】第一百三十四节:“应用层半双工”双机串口通讯的程序框架