串口通讯接收数据的几种代码写法

串口通讯代码

补充一下串口知识:

1、接收采用中断是为了能及时迅速接收。及时响应。
在这里插入图片描述

2、中断标志位写法
在这里插入图片描述

3.标志位使用方法:先判断标志位是否为1,出去之前清空标志位

——————————————————————main.py——————————————————————————————————
while(1)//反复检查标志位
{
	if(rxd_flag=1)//标志位,说明一个数据包接收完毕
	{
		process_data();
		rxd_flag=0;//标志位=0,是在处理数据之后,表明rxd_flag=0时,数据处理完毕
	}
}
——————————————————————————————————————————————————————————————————
_______________________usart.py__________________________________
uint8_t rx_buffer[100];//不固定保持,数据缓冲去设置大些
uint8_t rxd_flag;
void USART2_IRQHandler(void)
{
	uint8_t Recv_data;		
	static uint8_t recv_state = 0;
	static uint8_t rx_buffer_index = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断
	{
		uint8_t Recv_data = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		
		/*使用状态机的思路,依次处理数据包的不同部分*/
		
		switch(recv_state)
		{
			case 0: 
			{
				//# !rxd_flag 用于判断是否缓存区数据全部处理完,处理完之后才能处理新的数据。#
				if(Recv_data=='$' && !rxd_flag)  //判断包头,是包头就进入下一个状态
				{
						rx_buffer_index = 0; 
						recv_state=1;
				}
				else
				{
					recv_state=0;   //不是包头,就保持当前状态,一直等待包头
				}
			}
				break;
			case 1:   
			{
				if(Recv_data=='\r')//如果是\r,进入下一个阶段,不是就保存数据到rx_buffer中
				{
					recv_state=2;
				}
				else
				{
					rx_buffer[rx_buffer_index++]=Recv_data;
				}
			}	
				break;
			case 2:
			{
					if(Recv_data=='\n')
					{
						rxd_flag= 1;////标志位=1,是在接收完一包数据状态,表明rxd_flag=1时,一包数据接收完毕
						recv_state=0;
					}
			}	
			break;
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}

_________________________________________________________

1 发送数据

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
	USART_SendData(USART1, Byte);		//将字节数据写入数据寄存器,写入后USART自动生成时序波形
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);	//等待发送完成
	/*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
	Serial_SendByte(ch);			//将printf的底层重定向到自己的发送字节函数
	return ch;
}
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
	uint32_t Result = 1;	//设置结果初值为1
	while (Y --)			//执行Y次
	{
		Result *= X;		//将X累乘到结果
	}
	return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
	uint8_t i;
	for (i = 0; i < Length; i ++)		//根据数字长度遍历数字的每一位
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');	//依次调用Serial_SendByte发送每位数字
	}
}

2 接收数据

2.1 如果说你的数据包协议比较简单,我们可以在数据接收的时候,在串口中断里面实时解析。

在这里插入图片描述

2.1.1接收固定包长 :

数据格式:[ 0XFF 0X01 0X02 0X03 0X04 0XFE ]
在接收中断接收一个字节后,用状态机来接收该字节(会多次反复进入状态机,每来一个数据,进入一次状态机)

uint8_t rx_buffer[4];
uint8_t rx_buffer_RxFlag;
void USART2_IRQHandler(void)
{
	uint8_t Recv_data;		
	static uint8_t recv_state = 0;
	static uint8_t rx_buffer_index = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断
	{
		uint8_t Recv_data = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		
		/*使用状态机的思路,依次处理数据包的不同部分*/
		
		switch(recv_state)
		{
			case 0: 
			{
				if(Recv_data==0xFF)  //判断包头,是包头就进入下一个状态
				{
						rx_buffer_index=0;
						recv_state=1;
				}
				else
				{
					recv_state=0;   //不是包头,就保持当前状态,一直等待包头
				}
			}
			break;
			case 1:   
			{
				rx_buffer[rx_buffer_index++]=Recv_data;
				if(rx_buffer_index>=4)
				{
					recv_state=2;
				}
			}	
			break;
			case 2:
			{
				if(Recv_data==0xFE)
				{
					recv_state=0;
					rx_buffer_RxFlag=1;
				}
			}
			break;
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}

2.1.2接收可变包长 :

在这里插入图片描述

uint8_t rx_buffer[100];//不固定保持,数据缓冲去设置大些
uint8_t rx_buffer_RxFlag;
void USART2_IRQHandler(void)
{
	uint8_t Recv_data;		
	static uint8_t recv_state = 0;
	static uint8_t rx_buffer_index = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断
	{
		uint8_t Recv_data = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		
		/*使用状态机的思路,依次处理数据包的不同部分*/
		
		switch(recv_state)
		{
			case 0: 
			{
				if(Recv_data=='$')  //判断包头,是包头就进入下一个状态
				{
						rx_buffer_index = 0; 
						recv_state=1;
				}
				else
				{
					recv_state=0;   //不是包头,就保持当前状态,一直等待包头
				}
			}
				break;
			case 1:   
			{
				if(Recv_data=='\r')//如果是\r,进入下一个阶段,不是就保存数据到rx_buffer中
				{
					recv_state=2;
				}
				else
				{
					rx_buffer[rx_buffer_index++]=Recv_data;
				}
			}	
				break;
			case 2:
			{
					if(Recv_data=='\n')
					{
						rx_buffer_RxFlag = 1;
						recv_state=0;
					}
			}	
			break;
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}
补充:发送方发送数据过快,接收来不及处理

一般而言,单片机发送方,发送一个数据包到下一个发送新的数据包,有一定的间隔,这段时间足够,单片机接收方,解析并处理了该数据。

但是如果单片机发送方发送数据过快,接收方来不及处理怎么办?
思路1:双缓冲区,
:思路2:接收方接收的数据过快,新的数据会覆盖缓冲区,怎么办?老的数据如果没有处理完,新的数据就不保存到缓冲区。

但是这种方法其实不好,部分新的数据会丢失。

mian.py
这种标志位的用法,进去之前判断标志位==1,出去之前标志位清零。

gpio_init():
usart_init();
led_init();
while(1)
{
	if(rxd_flag=1)//标志位,说明一个数据包接收完毕
	{
		process_data();
		rxd_flag=0;//标志位,说明一个数据包处理完毕,可以再次接受新的数据
	}
}

usart.py

uint8_t rx_buffer[100];//不固定保持,数据缓冲去设置大些
uint8_t rx_buffer_RxFlag;
void USART2_IRQHandler(void)
{
	uint8_t Recv_data;		
	static uint8_t recv_state = 0;
	static uint8_t rx_buffer_index = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断
	{
		uint8_t Recv_data = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		
		/*使用状态机的思路,依次处理数据包的不同部分*/
		
		switch(recv_state)
		{
			case 0: 
			{
				//# !rxd_flag 用于判断是否缓存区数据全部处理完,处理完之后才能处理新的数据。#
				if(Recv_data=='$' && !rxd_flag)  //判断包头,是包头就进入下一个状态
				{
						rx_buffer_index = 0; 
						recv_state=1;
				}
				else
				{
					recv_state=0;   //不是包头,就保持当前状态,一直等待包头
				}
			}
				break;
			case 1:   
			{
				if(Recv_data=='\r')//如果是\r,进入下一个阶段,不是就保存数据到rx_buffer中
				{
					recv_state=2;
				}
				else
				{
					rx_buffer[rx_buffer_index++]=Recv_data;
				}
			}	
				break;
			case 2:
			{
					if(Recv_data=='\n')
					{
						rx_buffer_RxFlag = 1;
						recv_state=0;
					}
			}	
			break;
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}
接收一个字节数据,马上处理该数据(暂时保留)
/**
***********************************************************************
包格式:帧头0  帧头1  数据长度高8位  数据长度低8位                 功能字           和校验高8位   和校验低8位
        0x42   0x4d	        0x00	      0x1C	     ...		...		...		...	SumH	      SumL	    总计32字节
***********************************************************************
*/
#define FRAME_HEAD_0        0x42  
#define FRAME_HEAD_1        0x4D
#define PACKET_DATA_LEN     32  //包长度
void USART0_IRQHandler(void)
{
	if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_RBNE) != RESET)
	{
		usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_RBNE);
		uint8_t uData = (uint8_t)usart_data_receive(g_uartHwInfo.uartNo);
		//接收完数据,马上处理
		ProcUartData(uData);
	}
}
//实现来了一个数据,是包头和包尾剔除,保存载荷数据,当接收一个完整的数据包,标志位置1
//标志位置1,用于通知处理该数据包。
static void ProcUartData(uint8_t data)//这个函数会自动反复执行,因为中断中调用该函数
{
	static uint8_t s_index = 0;//静态变量,只要第一次进入赋值为0,以后进入不在初始化。
	g_rcvDataBuf[s_index++] = data;
	
	switch (s_index)//S_index 是数组的索引。
	{
		case 1:
			if (g_rcvDataBuf[0] != FRAME_HEAD_0)//如果不是包头1,索引设置为0
			{
				s_index = 0;
			}
			break;
		case 2:
			if (g_rcvDataBuf[1] != FRAME_HEAD_1)//包头1成立,但包头2不成立,索引设置为0
			{
				s_index = 0;
			}
			break;
		case PACKET_DATA_LEN://当数组长度存够32个字节(一个数据包为32个字节)
			g_pktRcvd = true;//标志位1,标志着一个完整数据包接收完毕
			s_index = 0;
			break;
		default:
			break;
	}
}


uint8_t Serial_RxData[100];		//定义串口接收的数据变量
uint8_t Serial_RxFlag;		//定义串口接收的标志位变量
/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)		//判断是否是USART1的接收事件触发的中断,防止其他中断进入
	{
		while(USART_GetITStatus(USART1, USART_IT_RXNE) == RESET);//等待当前字节接收结束
		//如果USART_GetITStatus(USART1, USART_IT_RXNE) == RESET,就反复执行空语句,一直等待、
		//直到USART_GetITStatus(USART1, USART_IT_RXNE) == SET,跳出循环
		
		Serial_RxData[res1] = USART_ReceiveData(USART1);				//读取数据寄存器,存放在接收的数据变量
		res1++;
		Serial_RxFlag = 1;										//置接收标志位变量为1
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);			//清除USART1的RXNE标志位
																//读取数据寄存器会自动清除此标志位
																//如果已经读取了数据寄存器,也可以不执行此代码
	}
}
2.1.3 不带针头和帧尾的数据包:串口中断+定时中断超时接收数据包的编程方法

在这里插入图片描述
这种数据包没有涵盖帧头和帧尾,无法通过帧头和帧尾去判断数据包的完整性,只能通过时间间隔来判别我们数据包的完整性,我们标准的modus数据协议就是这种,数据包和数据包之间有一定的间隔,

补充:串口中断需要中断优先级比定时器优先级设置高一下,防止漏数据。

在这里插入图片描述

数据包间隔一般设置为4~10个数据间隔。
9600波特率 9600个bit/1 s
1个bit 1/9600s
1个字节(1个数据帧10位) 1/960010
10个字节(1个数据帧10位) 1/9600
10*10 S = 10ms

-------------------------time.py----------------------------
uint8_t rxd_cnt;
uint8_t rxd_flag =0;
uint8_t rxd_buf[100];
uint8_t rxd_index = 0;
void TIM2_IRQHandler(void)
{
		if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
		{
				rxd_cnt++;//每过1ms 计数变量+1
				if(rxd_cnt>=10)//当没有接收数据超过10ms。
				{
					rxd_cnt = 0;
					rxd_flag = 1;//标识数据包接收完成
				}
				TIM_ClearITPendingBit(TIM2 , TIM_IT_Update);
		}
}
--------------------------------------------------------------
--------------------------usart.py-----------------------------
void USART3_IRQHandler(void)
{
	if(USART_GetITStatus(USART1 , USART_IT_RXNE == SET ))
	{
		rxd_buf[rxd_index++] = USART_ReceiveData(USART1);
		rxd_cnt = 0;//只要接收到数据就rxd_cnt = 0,清零,
		//每接收一次数据,都会清零,因此接收数据时无法累加,只要在没有接收数据时,才计数累加。
		USART_ClearITPendingBit(USART1 , USART_IT_RXNE);
	}
}
---------------------------------------------------------------------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值