串口通信(7)判断数据帧头来接收一串数据

本文详细介绍了单片机串口通信中的码元、波特率概念,以及如何设计系统实现数据帧结构,包括起始位、停止位和帧头。还涉及硬件设计中的LED电路和软件设计中的定时器、中断处理。通过实例演示了如何发送字符串和控制LED灯响应数据帧内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 本文为博主 日月同辉,与我共生,csdn原创首发。希望看完后能对你有所帮助,不足之处请指正!一起交流学习,共同进步!

> 发布人:@日月同辉,与我共生_单片机-优快云博客

> 欢迎你为独创博主日月同辉,与我共生点赞❤❤❤+关注👍+收藏🌹+评论☺。

系列专栏: 优快云-单片机串口通信学习系列🎁

> 我的格言是:“尽最大努力,做最好的自己!💪

要转载,请提前告知!!!

版权声明:本文为优快云博主「日月同辉,与我共生」的原创文章,优快云独一份。

目录

一、码元、波特率

1.1码元

1.2波特率

二、数据帧

2.1起始位

2.2停止位

2.3数据-帧头

三、系统设计

3.1设计要求

3.2系统原理

3.3硬件设计

3.3.1串口设计

3.3.2LED电路

3.4软件设计

3.4.1发送数据

3.4.2串口初始化

3.4.3接收中断

3.4.4定时器初始化

3.4.5定时器中断模块

3.4.6处接收数据理模块

四、结果

4.1发送两个字符串

4.2LED灯亮

4.3LED灯灭

一、码元、波特率

1.1码元

用相同时间间隔的符号来表示一个二进制数,一般来说,码元≠比特(1个二进制数相当于1个比特,用1bit表示),例如,有4种状态(0、1、2、3),分别用二进制数表示,即为00、01、10、11,因此1码元=2比特,特殊地,如果是2种状态(0、1),可以用0、1两个二进制数分别表示两种状态,此时1码元=1比特。在串口通信中,每次发送数据都是一个一个地发送,因此串口通信码元=比特

1.2波特率

单位时间内发送的码元数,称为波特率,单位为b/s。

我们常用9600b/s,每发送1比特需要的时间为1s/9600=104us

二、数据帧

2.1起始位

1位,表示一次通信的开始,给接收器时钟一个同步,告知接收端开始接收数据

2.2停止位

1位,一次通信的结束

2.3数据-帧头

串口通信帧头(也称为帧起始标志)是用来标识一个串口通信帧的开始位置的特殊字符。它的作用是让接收端能够正确地识别数据帧的开始位置,以便接收端能够正确地解析整个数据帧。帧头通常是一个固定的特定字符或字符组合

三、系统设计

3.1设计要求

本次设计,最开始,单片机com1发送Wait for Serial Communication Tset Start.和Please Send a string of data:这两个字符串到虚拟串口com3,然后由虚拟串口com3发送数据给单片机com1,单片机接收数据后能够重新发回给com3。发送的数据由帧头+数据组成,帧头为AA 55 AA 55,数据有2种情况,当数据为01 02时,接P1^0的LED灯亮,当数据为02 01时,接P1^0的LED灯灭,当数据错误时,LED灯无反应(已近亮了无法灭,已近灭的无法亮)。另外,虚拟终端能接收到单片机发送的数据。

3.2系统原理

串口发送数据不是一次性发送,而是一个一个字符/字节发送。波特率为9600b/s,发送一个比特需要时间要104us,定时器可以定时1ms,若是定时时间超过5ms(规定值,一般是3ms-8ms),则说明接收数据完成,因此可以定义定时计数变量recv_timer_cnt,该变量每+1,定时累加1ms,如果该变量值超过5,则定时超过5ms,则接收完成,每接收一个数据,定时计数变量recv_timer_cnt清0,接收的数据存储到数组recv_buf。

接收完成后要判断数据是否正确并对正确数据进行解析,可以设定一个帧头变量recv_move_index,判断数组recv_buf每一个数据是否正确,有错误的,跳出该判断,将帧头变量+1,然后继续重头判断,直到判断到正确的一串数据或结束,判断到正确的一串数据,就开始对数据进行解析(处理LED)。

3.3硬件设计

3.3.1串口设计

3.3.2LED电路

LED灯采用共阳极接法,左端接电源,右端先接电阻再接到P1^0。

3.4软件设计

3.4.1发送数据

void sendByte(unsigned char dat) //发送一帧数据功能函数
{
	SBUF=dat;
	while(!TI);
	TI=0;
}

void sendString(unsigned char *dat)//发送字符串函数
{
	while(*dat != '\0')
	{
		sendByte(*dat++);
	}
}

3.4.2串口初始化

void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x20;		//设置定时器模式
	TL1 = 0xFD;			//设置定时初始值
	TH1 = 0xFD;			//设置定时重载值
	ET1 = 0;			  //禁止定时器中断
	ES=1;           //串口中断打开
	TR1 = 1;			  //定时器1开始计时
}

3.4.3接收中断

void ES_timers() interrupt 4 //接收中断
{
	if(RI)
	{ 
		RI=0; 
		start_timer=1;//1.开定时器标志位置1
    if(recv_cnt<MAX_REX_NUM)	//在规定字符长度范围内接收数据	
		{
			recv_buf[recv_cnt]=SBUF; //2.接收数据
			recv_cnt++; 
		}
		else
		{
			recv_cnt=MAX_REX_NUM;
		}
		recv_timer_cnt=0; //3.每接收一帧数据就计数清0
	}
}

3.4.4定时器初始化

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;			//设置定时器模式
	TMOD |= 0x01;			//设置定时器模式
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	ET0=1;          //定时器0中断打开
	TR0 = 1;				//定时器0开始计时
}

3.4.5定时器中断模块

void T0_timer() interrupt 1 //利用1ms计数,判断是否接收完成
{
	TR0=0;
	if(start_timer == 1)//软件定时器打开
	{
		recv_timer_cnt++;//计数加1
		if(recv_timer_cnt>MAX_timer_cnt) //计数值超过规定范围说明接收完成
	  {
		  recv_timer_cnt=0;//计数重新置0 
		  
		  recv_flag=1;//接收完成标志位置1
	  }
	}
	TL0 = 0x66;				//设置定时初始值
	TH0 = 0xFC;				//设置定时初始值
	TR0=1;
}

3.4.6处接收数据理模块

void uart_service(unsigned char *buf) //完成对接收正确性的判断及相应解析
{
	unsigned char recv_move_index;//帧头变量的定义
	if(recv_flag)//接收完成开始解析
		{
			start_timer=0;//接收完成要关闭软件定时器
			recv_flag=0;//接收完成标志位置0,以便下次接收
			sendString(buf);//发送接收缓冲区的数据
			while((recv_cnt>=6)&&(recv_move_index<=recv_cnt))//数据字节大于等于6,帧头不能超过数据字节
			{
				if((buf[recv_move_index+0]==0xAA)&&(buf[recv_move_index+1]==0x55)&&(buf[recv_move_index+2]==0xAA)&&(buf[recv_move_index+3]==0x55))
					    //满足帧头 AA 55 AA 55
				{
						if((buf[recv_move_index+4]==0x01)&&(buf[recv_move_index+5]==0x02))
							//帧头后数据为01 02时LED亮
						{
							LED=0;
						}
						if((buf[recv_move_index+4]==0x02)&&(buf[recv_move_index+5]==0x01))
							//帧头后数据为02 01时LED灭
						{
							LED=1;
						}
						break;//满足帧头和数据退出while循环
				}
				recv_move_index++;//帧头移动1位
			}
			recv_cnt=0;
            clr_recvbuffer(buf);//清除缓冲函数
		}
}

四、结果

4.1发送两个字符串

4.2LED灯亮

4.3LED灯灭

下一文将着重串口中断即时解析数据帧头的通信程序,亲爱的读者敬请期待,下一文更精彩!!!

一日不读书,胸臆无佳想。我叫不白吃,喜欢我的,可以支持我,博主名叫@日月同辉,与我共生

@日月同辉,与我共生_单片机基础,单片机串口通信-优快云博客@日月同辉,与我共生擅长单片机基础,单片机串口通信,等方面的知识,@日月同辉,与我共生关注stm32,c语言,51单片机,proteus,单片机领域.https://blog.youkuaiyun.com/LIN___IT?spm=1000.2115.3001.5343

### STM32 HAL 库中串口数据帧分离方法 在嵌入式开发过程中,尤其是使用STM32微控制器时,正确处理接收到的数据对于应用程序至关重要。当涉及到串行通信协议下的数据传输时,确保能够有效地识别和分割完整的消息帧成为了一个重要的课题。 为了实现这一目标,在基于DMA的接收模式下,可以利用IDLE线状态检测来判断一帧数据是否结束。这种方法允许系统在不依赖特定字符作为终止符的情况下工作,从而提高了灵活性和可靠性[^1]。 #### IDLE事件触发机制 每当UART接口处于空闲状态一段时间之后(即没有新的位被采样),就会产生一个IDLE线事件。这个特性非常适合用来指示上一次有效数据流已经结束,并且新一帧即将开始。通过配置相应的中断服务例程(ISR),可以在每次发生这样的情况时采取行动: ```c void USARTx_IRQHandler(void) { /* USER CODE BEGIN USARTx_IRQn 0 */ /* USER CODE END USARTx_IRQn 0 */ HAL_UART_IRQHandler(&huart1); /* USER CODE BEGIN USARTx_IRQn 1 */ if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET){ __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除标志位 // 处理已接收到的数据... } /* USER CODE END USARTx_IRQn 1 */ } ``` 上述代码展示了如何捕获到IDLE事件并清除其对应的硬件标志位。一旦确认发生了IDLE条件,则意味着当前的数据包已经被完整接收下来,此时就可以对其进行进一步分析或存储操作了。 #### 数据缓存管理策略 考虑到实际应用场景可能会有连续不断的多帧数据到来,因此还需要考虑有效的内存管理和缓冲区溢出保护措施。一种常见的做法是在ISR内部维护两个指针分别指向读写位置,并采用环形队列的方式来进行数据交换: ```c #define BUFFER_SIZE 64 uint8_t buffer[BUFFER_SIZE]; volatile uint16_t readIndex = 0; volatile uint16_t writeIndex = 0; // 在 ISR 中更新写索引 writeIndex = (writeIndex + lengthReceived) % BUFFER_SIZE; // 用户层读取数据时同步读索引 while ((readIndex != writeIndex)){ process_data(buffer[readIndex]); readIndex = (readIndex + 1) % BUFFER_SIZE; } ``` 这种设计方案不仅简单易懂而且效率较高,特别适合资源受限的小型MCU平台。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值