#include
#define uchar unsigned char//byte
#define uint unsigned int//word
sbit led1=P0^0;
sbit fir=P2^4; //fir=0;作业
sbit sec=P2^5; //sec=0;作业
sbit thi=P2^6; //thi=0;作业
sbit fot=P2^7; //fot=0;作业
uchar table[]={0x28,0xeb,0x32,0xa2,0xe1,0xa4,0x24,0xea,0x20,0xa0};
//P1=table[i];/* 0123456789 */
static uchar dispbuf[5];
//动态显现数字的函数
void scandisp(void)
{
unsigned int i;
fir=0;
P1=table[ dispbuf[0] ];
for(i=0;i<200;i++);
fir=1;
sec=0;
P1=table[ dispbuf[1] ];
for(i=0;i<200;i++);
sec=1;
thi=0;
P1=table[ dispbuf[2] ];
for(i=0;i<200;i++);
thi=1;
fot=0;
P1=table[ dispbuf[3] ];
for(i=0;i<200;i++);
fot=1;
}
//十六进制转十进制存储
void HEX_TO_BCD(unsigned int n)
{
dispbuf[3]=n/1000;
dispbuf[2]=(n/100)%10;
dispbuf[1]=(n/10)%10;
dispbuf[0]=n%10;
}
void main(void)
{
uchar a;
uint mydata;
mydata=0x00;
TMOD=0x20;
PCON=0x00;
SCON=0x50;
TL1=0xfd;
TH1=0xfd;
TR1=1;
while(1)//动态现实是接纳的数据
{//假如没有接纳到数据,RI=0,一向循环显现原值
//假如有接纳到数据,RI=1,跳出循环从头核算并再次进入循环
HEX_TO_BCD(mydata);
while(RI==0)scandisp();
RI=0;//从头置0
a=SBUF;//从缓冲区获取数据
mydata=a;
//HEX_TO_BCD(mydata);
//scandisp();
//SBUF=a;
//while(TI==0)
//TI=0;
}
}
1. 自定义数据通讯协议
这儿所说的数据协议是建立在物理层之上的通讯数据包格局。所谓通讯的物理层就是指咱们一般所用到的RS232、RS485、红外、光纤、无线等等通讯办法。在这个层面上,底层软件供给两个根本的操作函数:发送一个字节数据、接纳一个字节数据。一切的数据协议悉数建立在这两个操作办法之上。
通讯中的数据往往以数据包的办法进行传送的,咱们把这样的一个数据包称作为一帧数据。相似于网络通讯中的TCPIP协议一般,比较牢靠的通讯协议往往包括有以下几个组成部分:帧头、地址信息、数据类型、数据长度、数据块、校验码、帧尾。
帧头和帧尾用于数据包完整性的判别,一般挑选必定长度的固定字节组成,要求是在整个数据链中判别数据包的误码率越低越好。减小固定字节数据的匹配时机,也就是说使帧头和帧尾的特征字节在整个数据链中能够匹配的时机最小。一般有两种做法,一、减小特征字节的匹配几率。二、添加特征字节的长度。一般选取榜首种办法的状况是整个数据链路中的数据不具有随即性,数据可猜测,能够经过人为挑选帧头和帧尾的特征字来避开,然后减小特征字节的匹配几率。运用第二种办法的状况愈加通用,适合于数据随即的场合。经过添加特征字节的长度减小匹配几率,尽管不能够彻底的防止匹配的状况,但能够使匹配几率大大减小,假如碰到匹配的状况也能够由校验码来进行检测,因而这种状况在绝大多说状况下比较牢靠。
地址信息首要用于多机通讯中,经过地址信息的不同来辨认不同的通讯终端。在一对多的通讯体系中,能够只包括意图地址信息。一起包括源地址和意图地址则适用于多对多的通讯体系。
数据类型、数据长度和数据块是首要的数据部分。数据类型能够标识后边紧接着的是指令仍是数据。数据长度用于指示有用数据的个数。
校验码则用来查验数据的完整性和正确性。一般对数据类型、数据长度和数据块三个部分进行相关的运算得到。最简略的做法可是对数据段作累加和,杂乱的也能够对数据进行CRC运算等等,能够依据运算速度、容错度等要求来选取。
2. 上位机和下位机中的数据发送
物理通讯层中供给了两个根本的操作函数,发送一个字节数据则为数据发送的根底。数据包的发送即把数据包中的左右字节依照次序一个一个的发送数据罢了。当然发送的办法也有不同。
在单片机体系中,比较常用的办法是直接调用串口发送单个字节数据的函数。这种办法的缺陷是需求处理器在发送进程中全程参加,长处是所要发送的数据能够当即的呈现在通讯线路上,能够当即被接纳端接纳到。别的一种办法是选用中止发送的办法,一切需求发送的数据被送入一个缓冲区,运用发送中止将缓冲区中的数据发送出去。这种办法的长处是占用处理器资源小,可是或许呈现需求发送的数据不能当即被发送的状况,不过这种时延适当的小。关于51系列单片机,比较倾向于选用直接发送的办法,选用中止发送的办法比较占用RAM资源,而且比照直接发送来说也没有太多的长处。以下是51系列单片机中发送单个字节的函数。
void SendByte(unsigned char ch)
{
SBUF = ch;
while(TI == 0);
TI = 0;
}
上位机中关于串口通讯的办法也有多种,这种办法不是指数据有没有缓冲的问题,而是操作串口的办法不同,由于PC上数据发送根本上都会被缓冲后再发送。关于编程来说操作串口有三种办法,一、运用windows体系中自带的串口通讯控件,这种办法运用起来比较简略,需求留意的是接纳时的堵塞处理和线程机制。二、运用体系的API直接进行串口数据的读取,在windows和linux体系中,设备被虚拟为文件,只需求运用体系供给的API函数即可进行串口数据的发送和读取。三、运用串口类进行串口操作。在此只介绍windows环境下运用串口类编程的办法。
CSerialPort是比较好用的串口类。它供给如下的串口操作办法:
void WriteToPort(char* string, int len);
串口初始化成功后,调用此函数即可向串口发送数据。为了防止串口缓冲所带来的延时,能够敞开串口的冲刷机制。
3. 下位机中的数据接纳和协议解析
下位机接纳数据也有两种办法,一、等候接纳,处理器一向查询串口状况,来判别是否接纳到数据。二、中止接纳。两种办法的优缺陷在此前的一篇关于串口通讯的文章中详细讨论过。得出的结论是选用中止接纳的办法比较好。
数据包的解析进程能够设置到不同的方位。假如协议比较简略,整个体系仅仅处理一些简略的指令,那么能够直接把数据包的解析进程放入到中止处理函数中,当收到正确的数据包的时分,置位相应的标志,在主程序中再对指令进行处理。假如协议略微杂乱,比较好的办法是将接纳的数据寄存于缓冲区中,主程序读取数据后进行解析。也有两种办法穿插运用的,比方一对多的体系中,首要在接纳中止中解析“衔接”指令,衔接指令接纳到后主程序进入设置状况,选用查询的办法来解析其他的协议。
以下给出详细的实例。在这个体系中,串口的指令十分简略。一切的协议悉数在串口中止中进行。数据包的格局如下:
0x55, 0xAA, 0x7E, 0x12, 0xF0, 0x02, 0x23, 0x45, SUM, XOR, 0x0D
其间0x55, 0xAA, 0x7E为数据帧的帧头,0x0D为帧尾,0x12为设备的意图地址,0xF0为源地址,0x02为数据长度,后边接着两个数据0x23, 0x45,从意图地址开端结算累加、异或校验和,到数据的最终一位结束。
协议解析的意图,首要判别数据包的完整性,正确性,然后提取数据类型,数据等数据,寄存起来用于主程序处理。代码如下:
if(state_machine == 0) //协议解析状况机
{
if(rcvdat == 0x55) //接纳到帧头榜首个数据
state_machine = 1;
else
state_machine = 0; //状况机复位
}
else if(state_machine == 1)
{
if(rcvdat == 0xAA) //接纳到帧头第二个数据
state_machine = 2;
else
state_machine = 0; //状况机复位
}
else if(state_machine == 2)
{
if(rcvdat == 0x7E) //接纳到帧头第三个数据
state_machine = 3;
else
state_machine = 0; //状况机复位
}
else if(state_machine == 3)
{
sumchkm = rcvdat; //开端核算累加、异或校验和
xorchkm = rcvdat;
if(rcvdat == m_SrcAdr) //判别意图地址是否正确
state_machine = 4;
else
state_machine = 0;
}
else if(state_machine == 4)
{
sumchkm += rcvdat;
xorchkm ^= rcvdat;
if(rcvdat == m_DstAdr) //判别源地址是否正确
state_machine = 5;
else
state_machine = 0;
}
else if(state_machine == 5)
{
lencnt = 0; //接纳数据计数器
rcvcount = rcvdat; //接纳数据长度
sumchkm += rcvdat;
xorchkm ^= rcvdat;
state_machine = 6;
}
else if(state _machine == 6 || state _machine == 7)
{
m_ucData[lencnt++] = rcvdat; //数据保存
sumchkm += rcvdat;
xorchkm ^= rcvdat;
if(lencnt == rcvcount) //判别数据是否接纳结束
state_machine = 8;
else
state_machine = 7;
}
else if(state_machine == 8)
{
if(sumchkm == rcvdat) //判别累加和是否持平
state_machine = 9;
else
state_machine = 0;
}
else if(state_machine == 9)
{
if(xorchkm == rcvdat) //判别异或校验和是否持平
state_machine = 10;
else
state_machine = 0;
}
else if(state_machine == 10)
{
if(0x0D == rcvdat) //判别是否接纳到帧尾结束符
{
retval = 0xaa; //置标志,表明一个数据包接纳到
}
state_machine = 0; //复位状况机
}
此进程中,运用了一个变量state_machine作为协议状况机的转化状况,用于确认当时字节处于一帧数据中的那个部位,一起在接纳进程中主动对接纳数据进行校验和处理,在数据包接纳完的一起也进行了校验的比较。因而当帧尾结束符接纳到的时分,则表明一帧数据现已接纳结束,而且经过了校验,要害数据也保存到了缓冲去中。主程序即可经过retval的标志位来进行协议的解析处理。
接纳进程中,只需哪一步收到的数据不是预期值,则直接将状况机复位,用于下一帧数据的判别,因而体系呈现状况死锁的状况十分少,体系比较稳定,假如呈现丢掉数据包的状况也可由上位机进行指令的补发,不过这种状况笔者还没有碰到。
关于主程序中进行协议处理的进程与此相似,主程序循环中不断的读取串口缓冲区的数据,此数据即参加到主循环中的协议处理进程中,代码与上面所述彻底相同。