#C51串口通讯2-#一串数据#定时中断实现超时接收(推荐)

系列文章目录-串口通讯

/C51串口通讯调试目录:/
第5章:#C51串口通讯5-#一串数据#中断定时+超时接收+接收应答+CRC校验

第4章:#C51串口通讯4-#一串数据#中断即时解析用户自定义协议(握手接收应答)

第3章:#C51串口通讯3-#一串数据#中断即时解析用户自定义协议

第2章:#C51串口通讯2-#一串数据#定时中断实现超时接收(推荐)

第1章:#C51串口通讯1-#一串数据#接收与发送(基础概念)


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


一.场景

实现一串非固定长度的数据接收,并返回对应数据(数据解析的基础框架)

二.编程实现

1.设计思想

  1. 借助T0定时器,不断的计数+1
  2. 接收到一帧数据(1Byte)后,串口中断服务函数将定时器T0计数清0(类似喂狗),并创建一个计数标志
  3. 一帧数据(1Byte)长度约为1.04ms[9600bps,1,0,1]。当串口中断数据接收完毕后,短时间无有效数据接收并进入中断服务函数,此时T0计数器不被清0,不断累加
  4. 持续检测到大于固定时间时,认为此刻一串数据已传输完毕。
  5. 固定时间间隔一般设置3-5倍的一帧数据长度(1.04ms)

2.代码设计

  1. 主函数暂时处理为返回接收到的字符串
  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;
			
		}
	}
}
  1. 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)的代码块。

  1. 若直接调用函数sendString(),即时超出MAX_LENGTH,此字符串依旧可以完整的返回至串口。
  2. 若是将数组范围上限赋值给cRealLen,执行主函数后返回至串口的数据只有开辟区域的内容
    在这里插入图片描述
    分析:
    1.缓冲寄存器SBUF传递完整的字符串,写入数组时,超出数组空间的字符以连续的内存地址存储。
    2.函数sendString()是形参是指针变量。指针数组作形参时,函数名代入的其实是数组首地址,即元素0
    3.超出的字符连续存储。
void sendString(unsigned char *dat)

总结

1.“定时中断+超时接收”,通用性较强,推荐使用。
2.慎重对待惯性思维,局部变量及全局变量处理的功能模块位置很重要
2.后续一章一步步介绍如何使用帧头检测–>用户自定义协议的接收与解析,由简至深。

上一章:#C51串口通讯1-#一串数据#接收与发送(基础概念)
下一章:#C51串口通讯3-#一串数据#中断即时解析用户自定义协议-状态机思想

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值