网络上写51单片机串口接收定长数据的例子有很多,但是没有一个写的通用性好。恰好最近做了一个利用51单片机的串口进行接收和发送数据的项目。通过学习,掌握了很多编程的细节,在此做一个总结分享。相信通过本文章的学习,你会对串口接收数据处理有一个更深刻的学习,以及对C编程有一个更加深刻的认识。向优秀的人学习可以提升自己的不足,自己去总结才能将别人的知识转变为自己的理解。
1. 实验目标
目标是接收一个长度为23字节的字符串,包括有头,帧尾。同时还要具备字符串定长发送的功能。
2.实验思路
串口初始化都不必说了,这里配置115200波特率,主要这里讲串口是采用接收中断的方法,通过串口接收中断将接收到的数据存入一个事先预定好的数组。然后再将里面的数组值放到While循环中进行处理。
3.传统串口接收框架
首先是串口初始化:
void Uart0Init(void)
{
PCON |= 0x80; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T --单片机编程写为6T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFF; //设定定时初值
TH1 = 0xFF; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
ES = 1; //开串口中断,ES =1;
TI = 0;
EA = 0;
RI = 0;
}
开启了串口中断已有数据就会进入中断。
中断处理函数模板可以这么写,先定义一个数组存放接收数据。
uint8 idata Line[26] = {0};
#define BufLen 23
这里有一个很好的方法来定义变量类型。
#define recvbuf Line
通过这样的方法,可以将定义的数组以recvbuf替代Line,这样的好处就是,通过这里的修改可以将接收的数据存放到任意指定的数组,例如Line1,Line2.....
void Uart0_ISR(void) interrupt 4
{
if (RI)
{
RI=0;
if(i>=recvLen) recvLen=0;
recvbuf[i++] = res;
}
if(TI)
{
TI = 0;
}
return;
}
我们再定义一个数组,将接收到的数据进行预处理,处理的目的主要是进行帧头校验,校验和,以及跳转到后续的数据处理函数。
static uint8 state = 0;
static uint8 count = 0;
uint8 i,res,checksum;
void Uart0RecHandle(uint8 idata *line)
{
for(i = 0; i < BufLen; i++)
{
res = SerialRecBUF[i];
res = res;
switch(state)
{
case 0:
if(res == 0x07)
{
count = 0;
line[count] = res;
count++;
state = 1;
}
else
{
count = 0;
state = 0;
}
break;
case 1:
if(res == 0x17)
{
line[count] = res;
count++;
state = 2;
}
else
{
count = 0;
state = 0;
}
break;
case 2:
line[count] = res;
count++;
if(count >= (BufLen-1))state = 3;
break;
case 3:
line[count] = res;
checksum = 0;
for(i=0;i<count;i++)
{
checksum+=line[i];
}
if(checksum == res)
{
Uart0FrameHandle(line);
}
state = 0;
count = 0;
break;
default:break;
}
}
}
接收完毕校验和通过,就可以进行数据处理。
然后再主函数中定义一个数组RecBuf[]
#define MAX_LENGTH 75
uint8 idata RecBuf[MAX_LENGTH] = {0};
在主循环中调用上述函数:
while(1)
{
Uart0RecHandle(RecBuf);
}
传统数据处理方式,有一个弊端就是需要定义两个长数组。对于idata比较小的单片机,额外的数组空间对于后续编程存在隐患。同时这种接收方式不能对中断接收的数组进行判断,这就导致,后续判断无法确定数据是否接收完毕,好容易导致数据错乱。因此这种方式并不推荐。
4. 优化后的接收处理框架
针对上述问题,这个接收框架主要针对上述缺点进行优化,首先节约接收数组空间,因此我们只用一个数组用于接收和数据处理。其次,在串口接收中断中就开始进行数据判断,判断完校验和之后利用一个标志位进行判断是否接收完毕全部数组。
#define BufLen 23
uint8 idata Line[80] = {0};
我们就用这一个80个字节空间的数组。
#define recvbuf Line
记得用上刚才说的好方法。
static uint8 Recflag = 0;
static uint8 recvLen = 0;
static uint8 state = 0;
static uint8 count = 0;
static uint8 i = 0;
static uint8 res = 0;
static uint8 checksum = 0;
void Uart0_ISR(void) interrupt 4
{
if (RI)
{
RI=0;
res = SBUF;
recvbuf[recvLen++] = res;
switch(state)
{
case 0:
if(res == 0x07)
{
state = 1;
}
else
{
state = 0;
recvLen = 0;
}
break;
case 1:
if(res == 0x17)
{
state = 2;
}
else
{
state = 0;
recvLen = 0;
}
break;
case 2:
if(recvLen >= (BufLen-1))state = 3;
break;
case 3:
checksum = 0;
for(i=0;i<(BufLen-1);i++)
{
checksum += recvbuf[i];
}
if(checksum == res)
{
Recflag = 1;
}
state = 0;
recvLen = 0;
break;
default:break;
}
}
if(TI)
{
TI = 0;
}
return;
}
校验和函数如下:
uint8 CheckSum(uint8 idata *pbuf, uint8 len)
{
uint8 i;
uint8 sum = 0;
for(i=0; i<len; i++)
{
sum += pbuf[i];
}
return sum;
}
而在接收完毕之后我们直接拿来这个数组进行取数组,进行数据处理。后面的内容更加精彩不要错过!