系列文章目录-串口通讯
/C51串口通讯调试目录:/
第5章:#C51串口通讯5-#一串数据#中断定时+超时接收+接收应答+CRC校验
第4章:#C51串口通讯4-#一串数据#中断即时解析用户自定义协议(握手接收应答)
第3章:#C51串口通讯3-#一串数据#中断即时解析用户自定义协议
第2章:#C51串口通讯2-#一串数据#定时中断实现超时接收(推荐)
第1章:#C51串口通讯1-#一串数据#接收与发送(基础概念)
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
一.场景
实现一串非固定长度的数据接收,并返回对应数据(数据解析的基础框架)
二.编程实现
1.设计思想
- 借助T0定时器,不断的计数+1
- 接收到一帧数据(1Byte)后,串口中断服务函数将定时器T0计数清0(类似喂狗),并创建一个计数标志
- 一帧数据(1Byte)长度约为1.04ms[9600bps,1,0,1]。当串口中断数据接收完毕后,短时间无有效数据接收并进入中断服务函数,此时T0计数器不被清0,不断累加
- 持续检测到大于固定时间时,认为此刻一串数据已传输完毕。
- 固定时间间隔一般设置3-5倍的一帧数据长度(1.04ms)
2.代码设计
- 主函数暂时处理为返回接收到的字符串
- UART中断服务函数处理:
接收到一个字节,打开T0计数软件标志,清一次计数器(计数器在T0定时器中一直在+1)
void uart_ISR() interrupt 4
{
static unsigned int recv_Cnt = 0;
if(RI)
{
RI = 0;
timer_start = 1; //1. 接收到一帧数据时,打开T0软件计数器开始计数
recv_buf[recv_Cnt] = SBUF;
recv_Cnt++;
if(recv_Cnt >= MAX_LENGTH) //超出开辟数组的保护处理,剩余掐断
{
recv_Cnt = MAX_LENGTH;
}
ctimer_Cnt = 0; //3. 一帧数据接收完成后,将T0计数单元清0,类似喂狗。但T0一直累加
if(recv_done) //5. 一串数据传输完毕后,将实际长度输出,生成数组recv_buf[],由主函数处理解析
{
recv_done = 0;
cRealLen = recv_Cnt;
recv_flag = 1;
recv_Cnt = 0;
}
}
}
- T0定时中断服务函数处理:
接收到串口一个字节数据到达标志,开始计数+1。判断计数累计循环次数,确定一串数据已发送完成
void Timer0_ISR() interrupt 1
{
TR0 = 0; //进T0中断首先关闭定时器
if(timer_start)
{
ctimer_Cnt++; //2. 接收到一帧数据后,定时时间计数单元开始计数
// sendByte(ctimer_Cnt);
if(ctimer_Cnt > MAX_RECV_TIME) //4. 一串数据接收完成后,下一帧无数据,此时ctimer_Cnt一直被累加,
{ // 当计时大于3ms,认为一串数据已接收完成,将完成标志置1
ctimer_Cnt = 0;
recv_done = 1;
}
}
TL0 = 0x66; //装初值
TH0 = 0xFC;
TR0 = 1; //打开定时器T0
}
3.调试验证
按照以上代码去ma,会发现一个问题:串口发送数据出错!
没错,以上代码有问题!
1.逻辑分析
a. 异常现象是:每发一串数据,返回多一帧,多的数据刚好是第一个字节
b.定位到UART中断服务函数内的处理,看现象是结束处理有异常,数据不完整。
c.问题定位:最后一个字节传输完成后,退出UART中断服务函数。超时的判断是在T0中断服务函数中将标志置1,表明下一帧无数据。但是此刻并不能进入UART中断服务函数。
d.实际借用串口助手操作时,是第二次发送才有效!通过printf中间数据,发现缓冲区recv_buf[]被不断刷新。
2.代码出错原因
个人习惯性动作–将函数内计数变量作局部变量,条件判断结束时建立标志位。习惯性将T0中判断超时时建立一个提示标志,又回传至UART服务函数中处理,然而实际并不能进入。
4.更新代码
void uart_ISR() interrupt 4
{
if(RI)
{
RI = 0;
timer_start = 1; //1. 接收到一帧数据时,打开T0软件计数器开始计数
recv_buf[recv_Cnt] = SBUF;
recv_Cnt++;
if(recv_Cnt >= MAX_LENGTH) //超出开辟数组的保护处理,剩余掐断
{
recv_Cnt = MAX_LENGTH;
}
ctimer_Cnt = 0; //3. 一帧数据接收完成后,将T0计数单元清0,类似喂狗。但T0一直累加
}
}
void Timer0_ISR() interrupt 1
{
TR0 = 0; //进T0中断首先关闭定时器
if(timer_start)
{
ctimer_Cnt++; //2. 接收到一帧数据后,定时时间计数单元开始计数
if(ctimer_Cnt > MAX_RECV_TIME) //4. 一串数据接收完成后,下一帧无数据,此时ctimer_Cnt一直被累加,
{ // 当计时大于3ms,认为一串数据已接收完成,将完成标志置1
ctimer_Cnt = 0;
recv_flag = 1;
cRealLen = recv_Cnt; //5. 一串数据传输完毕后,将实际长度输出,生成数组recv_buf[],由主函数处理解析
recv_Cnt = 0;
}
}
TL0 = 0x66; //装初值
TH0 = 0xFC;
TR0 = 1; //打开定时器T0
}
@@@@这里引申一个问题:关于写数据超出给定的内存区域:
if(recv_flag)
{
recv_flag = 0;
timer_start = 0; //关掉定时器,防止T0一直在执行
for(i = 0; i < cRealLen; i++)
{
P1 = 0xFF;
recv_buf[i] += 1;
sendByte(recv_buf[i]);
cLedDis = _crol_(0xFE,i);
P1 &= cLedDis;
set_buzz(BUZZ_TYPE1);
Delay_xms(1000);
}
// sendString(recv_buf); //全部返回
clr_recvbuffer(recv_buf);
比如上图我的主函数while(1)的代码块。
- 若直接调用函数sendString(),即时超出MAX_LENGTH,此字符串依旧可以完整的返回至串口。
- 若是将数组范围上限赋值给cRealLen,执行主函数后返回至串口的数据只有开辟区域的内容
分析:
1.缓冲寄存器SBUF传递完整的字符串,写入数组时,超出数组空间的字符以连续的内存地址存储。
2.函数sendString()是形参是指针变量。指针数组作形参时,函数名代入的其实是数组首地址,即元素0
3.超出的字符连续存储。
void sendString(unsigned char *dat)
总结
1.“定时中断+超时接收”,通用性较强,推荐使用。
2.慎重对待惯性思维,局部变量及全局变量处理的功能模块位置很重要
2.后续一章一步步介绍如何使用帧头检测–>用户自定义协议的接收与解析,由简至深。
上一章:#C51串口通讯1-#一串数据#接收与发送(基础概念)
下一章:#C51串口通讯3-#一串数据#中断即时解析用户自定义协议-状态机思想