java单链表:51串口通讯错误的实例分析(数码管显示串口数据)
来源:百度文库 编辑:偶看新闻 时间:2024/04/28 02:37:43
今天有个朋友Hi我让我看一个他写的51程序,程序要求是:
§以16进制发送一个0-65536之间的任一数,当单片机收到后在数码管上动态显示出来,波特率自定。按照他的意思:PC串口发送0x65536然后单片机板子上的五个数码管显示6,5,5,3,6。
他给我的程序是这样的(注了我的分析):
#include
#define uchar unsigned char
#define uint unsigned int
uchar sel;
unsigned long temp;
uchar wan,qian,bai,shi,ge;
uchar code table[]={0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void delay(uint z) //ker:不清楚他这里的延时函数具体延时多少
{
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
void init()
{
TMOD=0x20;
TH1=0xfd;
TL1=0xfd;
TR1=1;
PCON=0;
SM0=0;
SM1=1;
REN=1;
}
void display(uchar a,uchar b,uchar c,uchar d,uchar e) //ker:显示程序,应该是动态扫描的。没什么大问题
{
sel=0;
P2=sel;
P0=table[a];
delay(1); //ker:还是不知道具体的延时时间是多少,这个问题很严重,后面会分析。
sel=1;
P2=sel;
P0=table[b];
delay(1);
sel=2;
P2=sel;
P0=table[c];
delay(1);
sel=3;
P2=sel;
P0=table[d];
delay(1);
sel=4;
P2=sel;
P0=table[e];
delay(1);
}
void main()
{
init();
while(1)
{
if(RI) //ker:用的是查询的方式
{
RI=0;
temp=SBUF; //ker,注意SBUF是一个寄存器,长度一个字节,char型。而temp是long型
}
//ker:实际上这个时候收到串口数据的一个字节。
wan=temp/65536;
qian=temp/4096%16;
bai=temp/256%16;
shi=temp/16%16;
ge=temp%16;
//即使能实现功能,要知道在单片机中只有累加器,乘除法运算是非常消耗资源。
//实现同样的功能可以在算法上进行优化。
display(wan,qian,bai,shi,ge);
}
}
问题:两位的可以。现在只可以显示100以内的,超过100就不行。
分析:
关于串口通讯的发送机制问题。如果他想用PC软件发0x12854这个数据,在串口是按照字节Byte一个一个发送的,也就是说正常的PC端程序在编写的时候(如VC)一定是将这个数据拆成0x01 0x28 0x54三个字节发送。即使是发送0x112也需要将数据拆成0x00 0x01 0x22三个字节发送。不过他说他的PC端发送软件不是自己写的。那首先分析这个发送软件是怎样的发送机制。
关于串口通讯的接收机制问题。如上所说,0x12854是按照0x01 0x28 0x54三个字节连续发送的。那单片机的接收也需要收到这三个字节才能正确还原出0x12854这个数据。因此需要查RI三次,收SUBF三次。如下
if(RI)
{
RI=0;
temp=SBUF;
for(char i=0; i<2; i++) //收后面两个数据
{
while(RI)
{
RI=0;
temp=temp<<8; //将数据按位右移8位
temp=temp+SBUF; //将接收区数据整合到数据
}
}
}
注意上面的先右移操作后加法的数据整合算法,在不开辟新变量的前提下,实现了三个字节数据还原成一个数据的功能。
关于接收的时间配合问题。接收双方之间在没有完整的握手协议的前提下,发送方三个数据是连续发送的,即不管接收方是否成功接收了第一个字节,发送方都是马上发送下一个字节的。因此,如这个朋友写的那样,通过查询的方式进行数据接收,接收到一个数据之后进行数据整理的运算,再进行了一次送显函数,可能第二个字节收在SUBF里来不及查收,而第三个字节已经发送完毕了。或者更严重的说,如果恰巧数据发送发生在查询RI之后,在数据整理数据和送显的过程中。那么很有可能程序轮询到下一次if(RI)操作的时候第三个数据已经发送完毕了。这样就只能接收到第一个数据。
能不能正常收到的关键在于数据整理的运算和送显函数中延时的长短。因为这个延时时间影响到轮询查询if(RI)操作的执行间隔。所以通过查询方式接收就必须保证查询操作的执行间隔足够短。也就是其他程序任务足够简单简短。
关于程序的算法优化问题。为了保证轮询查询时间间隔足够短,那就必须其他程序任务足够简单简短。从这个角度出发,我们必须优化下面的代码。
wan=temp/65536;
qian=temp/4096%16;
bai=temp/256%16;
shi=temp/16%16;
ge=temp%16;
我们知道在51单片机中只有累加器,乘除法运算的执行指令周期是很长的。所以我们需要另外一种算法来取代除法运算,但实现同样的功能。
wan=(temp>>16)&0x0f;
qian=(temp>>12)&0x0f;
bai=(temp>>8)&0x0f;
shi=(temp>>4)&0x0f;
ge=temp&0x0f;
通过分析数据在存储器上的存储的数据结构上来分析,用移位操作就能取代除法运算实现同样的运算结果,同时大大的提高了速度,缩短了运行代码所消耗的时间,而且这个算法的优化是非常明显的。
同时我们也发现了一个问题,就是这样一来我们的程序作了无用功:接收到三个字节的数据之后,将他们运算整理成一个数据temp,然后再送显之前又将数据再次拆散。为什么我们不直接将收到的字节数据直接送显示,即第一个字节送入wan,第二个字节送入qian和bai,第三个字节送入shi和ge。这样可以从程序上省下不少运行的时间而增加单位时间内轮询RI的频率。
结论:
为了尽可能少的改动程序,我保留了原来的变量和实现方式。供参考。
uchar sel;
uchar i; //新添加的变量
uchar temp; //原为unsigned long temp;
uchar wan,qian,bai,shi,ge;
uchar code table[]={0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,
0x39,0x5e,0x79,0x71};
void delay(uint z);//建议先对函数进行声明
void init();//建议先对函数进行声明
void display(uchar a,uchar b,uchar c,uchar d,uchar e);//建议先对函数进行声明
void delay(uint z) //保持不变
{…… }
void init()//保持不变
{…… }
void display(uchar a,uchar b,uchar c,uchar d,uchar e) //保持不变
{…… }
void main()
{
init();
while(1)
{
if(RI)
{
RI=0;
temp=SBUF;
wan=temp&0x0f;
for(i=0; i<2; i++) //收后面两个数据
{
while(RI)
{
RI=0;
temp=SBUF;
if(i)
{//第三个数据
ge=temp&0x0f;
shi=(temp&0xf0)>>4;
}
else
{//第二个数据
bai=temp&0x0f;
qian=(temp&0xf0)>>4;
}
}
}
}
display(wan,qian,bai,shi,ge);
}
}
*注:如果接收和显示的过程经常不正常,建议缩短delay函数的执行时间,保证轮询RI查询的时间间隔足够短。如果main函数中还会添其他任务轮询时间不能保证,建议还是采用中断方式接收。