对于串口通信部分来说,单片机给计算机发送字符串好说,有多大的数组就发送多少个字节即可,但是单片机接收数据,接收多少个才应该是一帧完整的数据呢?数据接收的起点在哪里,结束在哪里?
我们编程的思路就是这样:当需要发送一帧(多个字节)数据时,这些数据都是连续不断的发送的,即发送完一个字节后会紧接着发送下一个字节,期间没有间隔或间隔很短,而当这一帧数据都发送完毕后,就会间隔很长一段时间(相对于连续发送时的间隔来讲)不再发送数据,也就是通信总线上会空闲一段较长的时间。
于是我们就建立这样一种程序机制:设置一个软件的总线空闲定时器,这个定时器在有数据传输时(从单片机接收角度来说就是接收到数据时)清零,而在总线空闲时(也就是没有接收到数据时)时累加,当它累加到一定时间(例程里是 30ms)后,我们就可以认定一帧完整的数据已经传输完毕了,于是告诉其它程序可以来处理数据了,本次的数据处理完后就恢复到初始状态,再准备下一次的接收。
那么用于判定一帧结束的空闲时间取多少合适呢?
它取决于多个条件,并没有一个固定值,我们这里介绍几个需要考虑的原则:第一,这个时间必须大于波特率周期,很明显我们的单片机接收中断产生是在一个字节接收完毕后,也就是一个时刻点,而其接收过程我们的程序是无从知晓的,因此在至少一个波特率周期内你绝不能认为空闲已经时间达到了。第二,要考虑发送方的系统延时,因为不是所有的发送方都能让数据严格无间隔的发送,因为软件响应、关中断、系统临界区等等操作都会引起延时,所以还得再附加几个到十几个 ms 的时间。我们选取的 30ms 是一个折中的经验值,它能适应大部分的波特率(大于1200)和大部分的系统延时(PC 机或其它单片机系统)情况。
下面我将uart.c中的程序贴出来,可以参考一下思路。
/*******************************************************************************
* 文件名:uart.c
* 描 述:UART程序
* 作 者:小默haa
* 版本号:v1.0.0
* 日 期: 2019年2月18日
* 备 注:
*
*******************************************************************************/
#include "sys.h"
uint8 cntRxd = 0; //接收字节计数器
uint8 bufRxd[64]; //接收字节缓冲区
bit flagTxd = 0; //单字节发送完成标志,用来替代TXD中断标志位
bit flagFrame = 0; //帧接收完成标志,即接收道一帧新数据
/*******************************************************************************
* 函数名 :UartInit
* 输入值 :uint16 baud
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月18日
* 功能描述:设置波特率
* 备注 :
*******************************************************************************/
void UartInit(uint16 baud)
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为FOSC/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 256 - (FOSC / 12 / 32) / baud; //设定定时初值
TH1 = TL1; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
ES = 1; //使能串口中断
}
/*******************************************************************************
* 函数名 :UART_Interrupt
* 输入值 :none
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月18日
* 功能描述:串口中断服务程序
* 备注 :
*******************************************************************************/
void UART_Interrupt(void) interrupt 4
{
if(RI) //接收道新字节
{
RI = 0; //清零接收中断标志位
if(cntRxd < sizeof(bufRxd)) //接收缓冲区尚未用完时
{
bufRxd[cntRxd++] = SBUF; //保存接收字节,并递增计数器
}
}
if(TI)
{
TI = 0;
flagTxd = 1;
}
}
/*******************************************************************************
* 函数名 :UartWrite
* 输入值 :uint8 *buf, uint8 len
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月18日
* 功能描述:串口数据写入
* 备注 :buf为待发送数据的指针,len为指定的发送长度
*******************************************************************************/
void UartWrite(uint8 *buf, uint8 len)
{
while(len--) //循环发送所有字节
{
flagTxd = 0; //清零发送标志
SBUF = *buf++; //发送一个字节数据
while(!flagTxd); //等待该字节发送完毕
}
}
/*******************************************************************************
* 函数名 :UartRead
* 输入值 :uint8 *buf, uint8 len
* 返回值 :len
* 作者 :小默haa
* 时间 :2019年2月18日
* 功能描述:串口数据读取
* 备注 :buf为待发送数据的指针,len为指定的发送长度,返回值为实际读到的长度
*******************************************************************************/
uint8 UartRead(uint8 *buf, uint8 len)
{
uint8 i;
if(len > cntRxd) //指定读取长度大于实际接收到的数据长度时
len = cntRxd; //读取长度设置为实际接收到的数据长度
for(i = 0; i < len; i++) //复制接收到的数据到接收指针上
{
*buf++ = bufRxd[i];
}
cntRxd = 0; //接收计数器清零
return len; //返回值为实际读取长度
}
/*******************************************************************************
* 函数名 :UartDriver
* 输入值 :none
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月18日
* 功能描述:串口驱动函数,监测数据帧的接收,调度功能函数
* 备注 :在主循环中调用
*******************************************************************************/
void UartDriver(void)
{
uint8 len;
uint8 pdata buf[40];
if(flagFrame) //有命令到达时,读取处理该命令
{
flagFrame = 0;
len = UartRead(buf, sizeof(buf)); //将接收到的命令读取到缓冲区中
UartAction(buf, len); //传递数据帧,调用动作执行函数
}
}
/*******************************************************************************
* 函数名 :UartRxdMonitor
* 输入值 :uint8 ms
* 返回值 :none
* 作者 :小默haa
* 时间 :2019年2月18日
* 功能描述:串口接收监控,由空闲时间判定帧结束
* 备注 :在定时器中断中调用,ms为定时间隔
*******************************************************************************/
void UartRxdMonitor(uint8 ms)
{
static uint8 idletmr = 0;
static uint8 cntbkp = 0;
if(cntRxd > 0) //接收计数器大于0时,监控总线空闲时间
{
if(cntRxd != cntbkp) //接收计数器改变,即刚接收到数据时,清零空闲计时
{
cntbkp = cntRxd;
idletmr = 0;
}
else //接收计数器未改变,即总线空闲时,累积空闲时间
{
if(idletmr < 30) //空闲计时小于30ms时,持续增加
{
idletmr += ms;
if(idletmr >= 30) //空闲时间达到30ms时,即判定为一帧接收完毕
{
flagFrame = 1; //设置帧接收完成标志
}
}
}
}
else
{
cntbkp = 0;
}
}