当前位置: 首页 > news >正文

【STM32】USART串口收发HEX数据包收发文本数据包

 有关串口知识参考:【STM32】USART串口协议&串口外设-学习笔记-CSDN博客

  • HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
  • 文本模式/字符模式:以原始数据编码后的形式显示 参考上面文章查看ASCII编码表

HEX数据包

包头包尾和载荷数据重复问题的解决方法:解决思路方法

文本数据包 

 文本模式有大量的字符可以作为包头包尾,可以有效避免载荷数据和包头包尾重复的问题

HEX数据包和文本数据包两者的优缺点:供参考

HEX数据包接收 

使用到了状态机的编程思想,关于状态机可以参考这个视频C语言之状态机编程_01_状态机基本工作原理

 文本数据包接收

同样用状态机思想理解 

​ 

 USART串口收发HEX数据包

 初始化代码参考【江协科技STM32】串口发送&串口发送+接收(学习笔记)-CSDN博客

 串口发送HEX数据包代码:

uint8_t Serial_TxPack[4];				//定义发送数据包数组,数据包格式:FF 01 02 03 04 FE



/**
  * 函    数:串口发送数据包
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去
  */
void Serial_SendPack(void)
{
	Serial_SendByte(0xFF);
	Serial_SendArray(Serial_TxPack,4);
	Serial_SendByte(0xFE);
}

 记得再头文件外部调用extern uint8_t Serial_TxPacket[];       

发送HEX数据包main函数: 

int main(void)
{
	OLED_Init();
	Serial_Init();
	
	Serial_TxPack[0] = 0x01;
	Serial_TxPack[1] = 0x02;
	Serial_TxPack[2] = 0x03;
	Serial_TxPack[3] = 0x04;
	
	Serial_SendPack();
	
	while(1)						
	{
		
	}
}

硬件复位串口收到数据: 

 中断函数:

uint8_t Serial_RxPacket[4];				//定义接收数据包数组
uint8_t Serial_RxFlag;					//定义接收数据包标志位


void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;		//定义表示当前状态机状态的静态变量
	static uint8_t pRxPacket = 0;   //定义表示当前接收数据位置的静态变量
	
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)	//判断是否是USART1的接收事件触发的中断
	{                                                   
		uint8_t RxData = USART_ReceiveData(USART1);     //读取数据寄存器,存放在接收的数据变量
		/*使用状态机的思路,依次处理数据包的不同部分*/
		/*当前状态为0,接收数据包包头*/
		if(RxState == 0)
		{
			if(RxData == 0xFF)	//如果数据确实是包头
			{                   
				RxState = 1;    //置下一个状态
				pRxPacket = 0;  //数据包的位置归零
			}
		}
		/*当前状态为1,接收数据包数据*/
		else if(RxState == 1)
		{
			Serial_RxPack[pRxPacket] = RxData;	//将数据存入数据包数组的指定位置
			pRxPacket++;			//数据包的位置自增
			if(pRxPacket >= 4)      //如果收够4个数据
			{                       
				RxState = 2;        //置下一个状态
			}
		}
		/*当前状态为2,接收数据包包尾*/
		else if(RxState  == 2)
		{
			if(RxData == 0xFE)		//如果数据确实是包尾部
			{                       
				RxState = 0;        //状态归0
				RX_Flag = 1;        //接收数据包标志位置1,成功接收一个数据包
			}
		}
		
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

记得再头文件外部调用extern uint8_t Serial_RxPacket[];和extern uint8_t Serial_RxFlag;       

 获取串口接收数据包标志位:

uint8_t Serial_GerRXFlag(void)
{
	if(RX_Flag == 1)
	{
		RX_Flag = 0;
		return 1;
	}
	return 0;
}

 发送HEX数据包和接收数据包main函数:

uint8_t KeyNum;

int main(void)
{
	OLED_Init();
	Serial_Init();
	Key_Init();
	
	OLED_ShowString(1,1,"TxData:");
	OLED_ShowString(3,1,"RxData:");
	
	Serial_TxPack[0] = 0x01;
	Serial_TxPack[1] = 0x02;
	Serial_TxPack[2] = 0x03;
	Serial_TxPack[3] = 0x04;
	
	while(1)						
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			Serial_TxPack[0]++;
			Serial_TxPack[1]++;
			Serial_TxPack[2]++;
			Serial_TxPack[3]++;
			
			Serial_SendPack();		//串口发送数据包Serial_TxPacket
			
			OLED_ShowHexNum(2,1,Serial_TxPack[0],2);
			OLED_ShowHexNum(2,4,Serial_TxPack[1],2);
			OLED_ShowHexNum(2,7,Serial_TxPack[2],2);
			OLED_ShowHexNum(2,10,Serial_TxPack[3],2);
		}
		if(Serial_GerRXFlag() == 1)		//如果接收到数据包
		{
			OLED_ShowHexNum(4,1,Serial_RxPack[0],2);
			OLED_ShowHexNum(4,4,Serial_RxPack[1],2);
			OLED_ShowHexNum(4,7,Serial_RxPack[2],2);
			OLED_ShowHexNum(4,10,Serial_RxPack[3],2);
		}
	}
}

 收发文本数据包

程序只写接收部分,因为发送的话,不方便像HEX数组一样一个个更改, 所以发送直接在主函数调用Send_String函数或者printf

 中断函数:

//定义接收数据包数组,数据包格式"@MSG\r\n"
//定义接收数据包标志位 

void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;		//定义表示当前状态机状态的静态变量
	static uint8_t pRxPacket = 0;   //定义表示当前接收数据位置的静态变量
	
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)	//判断是否是USART1的接收事件触发的中断
	{                                                   
		uint8_t RxData = USART_ReceiveData(USART1);     //读取数据寄存器,存放在接收的数据变量
		/*使用状态机的思路,依次处理数据包的不同部分*/
		/*当前状态为0,接收数据包包头*/
		if(RxState == 0)
		{
			if(RxData == '@')	//如果数据确实是包头
			{                   
				RxState = 1;    //置下一个状态
				pRxPacket = 0;  //数据包的位置归零
			}
		}
		/*当前状态为1,接收数据包数据*/
		else if(RxState == 1)
		{
			if(RxData == '\r')	//如果收到第一个包尾
			{
				RxState = 2;
			}
			else
			{
				Serial_RxPack[pRxPacket] = RxData;	//将数据存入数据包数组的指定位置
				pRxPacket++;						//数据包的位置自增
			}
		}
		/*当前状态为2,接收数据包包尾*/
		else if(RxState  == 2)
		{
			if(RxData == '\n')		//如果收到第二个包尾
			{                       
				RxState = 0;        //状态归0
				Serial_RxPack[pRxPacket] = '\0';	//将收到的字符数据包添加一个字符串结束标志
				RX_Flag = 1;        //接收数据包标志位置1,成功接收一个数据包
			}
		}
		
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}

自己复现编写代码时出现的错误:

 char Serial_RxPacket[100]; 数组没改为100,导致单片机接收溢出,接收不到长字符串

main函数:

int main(void)
{
	OLED_Init();
	LED_Init();
	Serial_Init();

	
	OLED_ShowString(1,1,"TxData:");
	OLED_ShowString(3,1,"RxData:");
	
	
	while(1)						
	{
		if(Serial_GerRXFlag() == 1)		//如果接收到数据包
		{
			OLED_ShowString(4,1,"                ");//其实就是OLED上面会累积旧数据,你得清屏,把旧数据清空,
													//再显示新的,要不旧的多余部分不会被覆盖
			OLED_ShowString(4,1,Serial_RxPack);
			
			if(strcmp(Serial_RxPack,"LED_ON") == 0)
			{
				LED1_ON();									//点亮LED
				Serial_SendString("LED_ON_OK\r\n");			//串口回传一个字符串LED_ON_OK
				OLED_ShowString(2,1,"                ");		
				OLED_ShowString(2,1,"LED_ON_OK");			//OLED清除指定位置,并显示LED_ON_OK
			}
			else if(strcmp(Serial_RxPack,"LED_OFF") == 0)
			{
				LED1_OFF();									//关闭LED
				Serial_SendString("LED_OFF_OK\r\n");
				OLED_ShowString(2,1,"                ");
				OLED_ShowString(2,1,"LED_OFF_OK");
			}
			else 		//上述所有条件均不满足,即收到了未知指令
			{
				Serial_SendString("ERROR_COMAND\r\n");		//串口回传一个字符串ERROR_COMMAND
				OLED_ShowString(2,1,"                ");
				OLED_ShowString(2,1,"ERROR_COMAND");
			}
		}
		
	}
}

自己复现编写代码时出现的错误:

①将操作LED灯部分写到判断标志位外面去了,就是每个if独立开来,导致单片机连续接收数据

②串口回传完一个字符串后知识一个简单的OLED显示

 需要注意的问题:

如果连续发送数据包,程序处理不及时,可能导致数据包错位,所以要修改一下程序,等每次处理完成之后再接收下一个数据包。怎么修改呢?这里利用Serial_GerRxFlag,主函数就不使用读取Flag标志位就立刻清除的策略了,而是:

①修改中断函数,在中断这里只有Serial_RxFlag ==0了,才会继续接收下一个数据包,这样写数据和读数据完全严格分开,不会同时进行

②读取标志位清零函数直接不要了

③把uint8_t Serial_RxFlag ;  声明为外部可调用,不封装了

④直接判断 Serial_RxFlag ==1,然后处理完再清除标志位

void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;		//定义表示当前状态机状态的静态变量
	static uint8_t pRxPacket = 0;	//定义表示当前接收数据位置的静态变量
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)	//判断是否是USART1的接收事件触发的中断
	{
		uint8_t RxData = USART_ReceiveData(USART1);			//读取数据寄存器,存放在接收的数据变量
		
		/*使用状态机的思路,依次处理数据包的不同部分*/
		
		/*当前状态为0,接收数据包包头*/
		if (RxState == 0)
		{
			if (RxData == '@' && Serial_RxFlag == 0)		//如果数据确实是包头,并且上一个数据包已处理完毕
			{
				RxState = 1;			//置下一个状态
				pRxPacket = 0;			//数据包的位置归零
			}
		}
		/*当前状态为1,接收数据包数据,同时判断是否接收到了第一个包尾*/
		else if (RxState == 1)
		{
			if (RxData == '\r')			//如果收到第一个包尾
			{
				RxState = 2;			//置下一个状态
			}
			else						//接收到了正常的数据
			{
				Serial_RxPacket[pRxPacket] = RxData;		//将数据存入数据包数组的指定位置
				pRxPacket ++;			//数据包的位置自增
			}
		}
		/*当前状态为2,接收数据包第二个包尾*/
		else if (RxState == 2)
		{
			if (RxData == '\n')			//如果收到第二个包尾
			{
				RxState = 0;			//状态归0
				Serial_RxPacket[pRxPacket] = '\0';			//将收到的字符数据包添加一个字符串结束标志
				Serial_RxFlag = 1;		//接收数据包标志位置1,成功接收一个数据包
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
	}
}

 main:

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	LED_Init();			//LED初始化
	Serial_Init();		//串口初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "TxPacket");
	OLED_ShowString(3, 1, "RxPacket");
	
	while (1)
	{
		if (Serial_RxFlag == 1)		//如果接收到数据包
		{
			OLED_ShowString(4, 1, "                ");
			OLED_ShowString(4, 1, Serial_RxPacket);				//OLED清除指定位置,并显示接收到的数据包
			
			/*将收到的数据包与预设的指令对比,以此决定将要执行的操作*/
			if (strcmp(Serial_RxPacket, "LED_ON") == 0)			//如果收到LED_ON指令
			{
				LED1_ON();										//点亮LED
				Serial_SendString("LED_ON_OK\r\n");				//串口回传一个字符串LED_ON_OK
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_ON_OK");				//OLED清除指定位置,并显示LED_ON_OK
			}
			else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)	//如果收到LED_OFF指令
			{
				LED1_OFF();										//熄灭LED
				Serial_SendString("LED_OFF_OK\r\n");			//串口回传一个字符串LED_OFF_OK
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_OFF_OK");			//OLED清除指定位置,并显示LED_OFF_OK
			}
			else						//上述所有条件均不满足,即收到了未知指令
			{
				Serial_SendString("ERROR_COMMAND\r\n");			//串口回传一个字符串ERROR_COMMAND
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "ERROR_COMMAND");			//OLED清除指定位置,并显示ERROR_COMMAND
			}
			
			Serial_RxFlag = 0;			//处理完成后,需要将接收数据包标志位清零,否则将无法接收后续数据包
		}
	}
}

有关数据包的收发,其实还是有着非常多的问题需要考虑的。实际应用要多想想。 

相关文章:

  • 2024年12月CCF-GESP编程能力等级认证C++编程三级真题解析
  • Vue前端项目部署到宝塔面板的详细过程
  • VSCode通过SSH免密远程登录Windows服务器
  • 搭建Spring Boot Admin监控系统
  • 【SpringBoot】最佳实践——JWT结合Redis实现双Token无感刷新
  • 写时拷贝技术
  • 第三周日志-周末看书(3)
  • 计算机组成原理常用的计数单位(性能指标的计算)持续更新
  • 02自动化测试常用函数
  • 多页pdf转长图
  • 《重新审视深度部分标签学习中的一致性正则化》2022年ICML论文精读
  • 寄生虫仿生算法:基于寄生虫特征的算法设计
  • c语言整理
  • 重塑音乐未来:张漾斌引领漫寻音乐的颠覆之旅
  • STL标准库
  • 外星人入侵-Python-三
  • 结构型模式之组合模式:让对象构成树形结构
  • AtCoder Beginner Contest 004(A - 流行、B - 回転、C - 入れ替え、D - マーブル)题目翻译
  • Keepalived高可用架构实战:从安装配置到高级应用详解
  • 打包当前Ubuntu镜像 制作Ubuntu togo系统
  • 1块钱解锁2万部微短剧还能日更,侵权盗版难题怎么破?
  • 人民日报评论员:党政机关要带头过紧日子
  • 读懂城市|成都高新区:打造“人尽其才”的“理想之城”
  • 持续降雨存在落石风险,贵州黄果树景区水帘洞将封闭至6月初
  • 《五行令》《攻守占》,2个月后国博见
  • 广西百色“致富果”:高品质芒果直供香港,带动近五千户增收