STM32串口多字节接收实现方法详解

如果不想看的可以直接使用git把我的代码下载出来,里面工程挺全的,后期会慢慢的补注释之类的

码云地址:stm32学习笔记: stm32学习笔记源码

如果不会使用git快速下载可以选择直接下载压缩包或者去看看git的使用

git的使用(下载及上传_gitcode怎么下载文件_是小刘不是刘的博客-CSDN博客

版权声明:本文为CSDN博主「是小刘不是刘」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_51426845/article/details/130949055

因为现在很多设备都要的是多字节发送,和接收,所以我们需要学习如何去接收一些设备返回的多字节参数然后去对他解析。

1、通过串口收发HEX格式数据包

2、通过串口收发字符格式数据包并且点灯

3、既能接收HEX又能接收字符格式数据包

目录

一 、数据包理论部分

1、hex数据包

 2 文本数据包

 3 hex的数据接收

 4、接收文本数据包

 二、代码部分

1、数据包的发送

2 数据包的接收

1 中断方式接收HEX数据包

 2 文本数据包

3 既实现接收字符串又能实现接收HEX


一 、数据包理论部分

1、hex数据包

数据包一般会包含包头和包尾,但是在一些设备的协议中,只有包头,包的结束由一个时间段内没有收到数据为结束信号。

在传输中怎么防止数据和包头包尾重复呢,这里两个方法就是:一个是让其固定包长,一个是加长帧头,让数据和包头包尾的重复率降低。

 2 文本数据包

这里也加入包长包尾,但是文本解析率比较低,因为一个字符他就是八位。

 3 hex的数据接收

这里可以写一个状态机,用这个状态机来判断是否接收到了帧头桢尾,以及是否接收数据完成。

首先判断接收到的数据是否位0xFF,如果不是就让s一直为0继续等待数据

若收到FF后将其置为1,后面检测其是否收够四个数据收够为2(这里是固定包长的想法

之后等待包尾,若收到包尾,就将s为0,重新接收包头,否则循环接收包尾

 4、接收文本数据包

这里就是可变包长的想法了

首先还是当s=0时,等待包头,若包头一直不为@则一直等待,为@后将s置为1,接收数据,这时候就等待包尾如果收到了\r就将s置为2,到下一个接收包尾的环节(因为这里是有两个包尾的,然后程序一次又只能判断一个字节,如果只有一个包尾,那就接收到包尾之后直接将s=0,就不需要最后的一步等待包尾了。

 二、代码部分

1、数据包的发送

上面没写数据包的发送,因为发送没什么限制,就是把接收到的数据,或者你自己写入的数据直接发送就好,不需要那么多判断帧头尾的判断。

这里也就不接收传感器数据再转发了,这里我们先直接写个发送,后面写完接收再写接收之后返回。往TXBUF里面写入四个16进制,要带上0x前缀哦不然代码会被默认为10进制,10进制是没有abcdef这些字母的,会直接报错。

//usart.c
u8 serial_TxPack[4]={0};

void Send_Pack(void)
{
	 Send_Byte(0xFF);
	 Send_Array(serial_RxPack,4);
	 Send_Byte(0xFE);
}

//main.c
int main(void)
{
		
	Usart_Config();
	
	serial_RxPack[0]=0xf1;
	serial_RxPack[1]=0x02;
	serial_RxPack[2]=0x03;
	serial_RxPack[3]=0x04;
	
	Send_Pack();
  while(1)
	{	

	}	
}

这样我们直接发送就能从串口测试有没有发送成功了

2 数据包的接收

1、中断方式接收HEX数据包

逻辑就是首先判断接收到的数据是不是包头,然后接收数据之后,判断数据个数,之后判断包尾,和上面的流程图是一样的。

首先写一个串口中断,这个在前面的单字节接收里面已经写过了,stm32f103系列USART串口收发(单字节_八月风贼冷的博客-CSDN博客

如果还不会配置串口中断的可以去看一下,然后就是写一个状态机了,这注释写的还是挺详细的,按照上面写的那个状态转移表的逻辑来写的代码,则例用了两个静态变量

1、基本概念
静态存储方式:指在程序运行时,给变量分配固定的存储空间的方式
2、 静态存储区存放以下变量:
全局变量:在程序开始执行时给全局变量分配存储区,程序运行完毕之后释放。在程序运行过程中它们占据固定的存储单元而不动态进行分配和释放。
静态变量:有时希望变量的值在函数调用结束后不消失而保留原值,这时就应该指定变量为“静态变量”,用关键字static进行命名

有时候,我们希望函数中局部变量的值在函数调用结束之后不会消失,而仍然保留其原值。即它所占用的存储单元不释放,在下一次调用该函数时,其局部变量的值仍然存在,也就是上一次函数调用结束时的值。这时候,我们就应该将该局部变量用关键字 static 声明为“静态局部变量“。

因为我们这里需要每一次都进串口并且让其他文件不能调用,所以我们这里可以使用静态变量。

uint8_t RxData; //数据转存
u8 serial_RxPack[4]={0}; //接收数据的数组
u8 Serial_RXFlag=0; //接收完成标志位

void USART1_IRQHandler(void)
{
			static u8 RxState=0;
      static u8 pRxPacket=0;
		  if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
			{
				 //接收串口发来的数据
				 RxData=USART_ReceiveData(USART1);
			   
				 //判断数据是否为包头
				 if(RxState==0)
				 {
					  if(RxData==0xFF)
						{
							 RxState=1;
						}
						else
						{
							  
						}
				 }
				 else if(RxState==1)
				 {
					  //接收到了包头,可以开始接收数据之后进行组包
					  serial_RxPack[pRxPacket]=RxData;
					  pRxPacket++;
					  //接收到数据为4个将状态转移为2
					  if(pRxPacket==4)
						{
							 RxState=2;
							 pRxPacket=0;
						}
				 }
				 else if(RxState==2)
				 {
					  //判断是否接收到包尾
					  if(RxData==0xFE)
						{
							 RxState=0;
							 Serial_RXFlag=1;
						}
						else
						{
							 
						}
				 }
			}
}

 之后我们写一个判断是否接收完成的标志位,通过这个标志位我们可以调用这个标志位来判断是否完成,这个标志位的定义在上面那段代码。

u8 Serial_GetRxFlag(void)
{
	 if(Serial_RXFlag == 1)
	 {
		 Serial_RXFlag=0;
		 return 1;
	 }
	 return 0;
}

之后就能写主函数了,判断接收完成标志位是否为1,因为前面状态机写的如果接收到了结束帧就会将其写为1。然后为1我们就将接收到的数据发送出去。并且将标志位重新写为0。

int main(void)
{
		
	Usart_Config();
	
  while(1)
	{	
		   //判断是否接收完成
       if(Serial_RXFlag==1)
			 {    
				   //将接收完成标志位置0
				   Serial_GetRxFlag();
				   //将接收的数据发送给串口显示出来
				   Send_Array(serial_RxPack,4);
			 }
	}	
}

运行效果,前面几次发送了11 22 33 44 后面为了测试数据不会和包头冲突发送了ff 22 33 44

 2 文本数据包

文本数据包和hex的写法基本差不多但是也有些要注意的点

首先我们还是写一个串口中断的状态机

这里我们就还是以上面的状态转移图来写代码了,首先起始帧为@我们判断是否为@,然后给数组赋值接着判断包尾,这里要注意要给字符串一个’0‘因为c语言中字符串要有一个\0的结束位,不然后面我们使用这个字符串的时候就无法判断是否结束,中断函数如下。

uint8_t RxData;

void USART1_IRQHandler(void)
{
			static u8 RxState=0;
      static u8 pRxPacket=0;
		  if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
			{
				 //接收串口发来的数据
				 RxData=USART_ReceiveData(USART1);
			   
				 //判断数据是否为包头
				 if(RxState==0)
				 {
					  if(RxData=='@')
						{
							 RxState=1;
						}
						else{  
						}
				 }
				 else if(RxState==1)
				 {

					   //判断数据是不是包尾,不是的话就接收数据
					   if(RxData=='\r')
						 {
							   RxState=2;
						 }
						 else
						 {
							  serial_RxPack[pRxPacket]=RxData;
							  pRxPacket++;
						 }

				 }
				 else if(RxState==2)
				 {
						 //判断数据是不是'\n'
					   if(RxData=='\n')
						 {  
							  //状态转移为0并且将
							  RxState=0;
							  serial_RxPack[pRxPacket]='\0'; //给字符串一个结束符
							  Serial_RXFlag=1;
							  pRxPacket=0;
						 }
						 else{
						 }
				 }
			}
}

之后我们在主函数调用,这样就能打印刚刚接收到的字符串了

  while(1)
	{	
		   //判断是否接收完成
       if(Serial_RXFlag==1)
			 {    
				   //将接收完成标志位置0
				   Serial_GetRxFlag();
				   //将接收的数据发送给串口显示出来
				   Send_String(serial_RxPack);
			 }
	}	

打印结果如下,这里发了三次所以打印的多了几个

 成功发送,之后我们测试发送命令控制板子上的LED灯的开关

这里我们先打开自己板子灯的GPIO这些操作,这个我说在PWM那一节的制作呼吸灯写过了可以去参考一下stm32f103配置PWM及实践_stm32pwm配置详解_是小刘不是刘的博客-CSDN博客

然后就是灯,这个可以自己直接配置和,调用set和rest两个点灯,也可以直接去调用有些公司写好的库,这里我们就自己写一下把

这里我还是使用了PB0的蓝色灯,可以根据自己的板子选择。

首先初始化我们的GPIOB和引脚0,记得先自己去创建一个led的板级支持包哦别都写在串口的支持包里面去了 

//led.c
void LED_Config(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
 
	//配置GPIO
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; 
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStruct);
}

开启GPIO之后我做一个led的封装这样等会好调用,这样我们就能在主函数很好的实现开灯了哈

//led.h
#define ON  0
#define OFF 1

#define LEDG(a)	if (a)	\
					GPIO_SetBits(GPIOB,GPIO_Pin_1);\
					else		\
					GPIO_ResetBits(GPIOB,GPIO_Pin_1)

然后写完在主函数测试一下能不能点亮,别等下写串口点灯写了半天发现灯本来就点不亮= =。

之后我们开始写接收字符串点灯,这里我们使用一个字符串比较函数strcmp,这个是c语言函数,不了解的可以百度一下。

我们还是在串口接收字符的基础上写,这里就写一些判断就好,很简单的逻辑,使用这个函数记得调用string的库,这个之前写串口单字节的时候也用过这个库来计算平方。

int main(void)
{
		
	Usart_Config();
	LED_Config();
	printf("串口打印测试");
  while(1)
	{	
		   //判断是否接收完成
       if(Serial_RXFlag==1)
			 {    
				   //将接收完成标志位置0
				   Serial_GetRxFlag();
                  //实现字符串比较
				  if(strcmp(serial_RxPack,"LED_ON")==0)
					{  
						LEDG(ON);
						printf("LED_ON\r\n");
					}
					else if(strcmp(serial_RxPack,"LED_OFF")==0)
					{
						 LEDG(OFF);
						 printf("LED_OFF\r\n");
					}
					else
					{
						 printf("command erro\r\n");
					}
			 }
	}	
}

然后附上两张硬件效果图

开灯

关灯

 

具体实现就这样了

3 既实现接收字符串又能实现接收HEX

思路还是写一个状态机,但是状态会多一点

首先在状态0时同时判断是@或者FF,若接收到得为@则进入状态1 这里接收字符,若字符为\r则进入状态3等待\n若为\n则返回状态0表示字符串接收完成

若接收到得为FF则跳到状态2接收HEX,但是现在是不定长16进制,所以数据为和包尾标志一点不可以重复,这时接收到FE则代表数据接收结束,回到状态0.

代码部分,两个数组分别存储字符串和HEX数据包

//main.c
int main(void)
{
	Usart_Config();
	LED_Config();
	printf("串口打印测试");
  while(1)
	{	
		   //判断是否接收完成
       if(Serial_RXFlag==1)
			 {    
				   //将接收完成标志位置0
				    Serial_GetRxFlag();
            Send_Array(modubus_RxPack,3);
				    Send_String(serial_RxPack);
			 }
	}	
}
//usart.c
char serial_RxPack[64]={0};
u8 modubus_RxPack[64]={0};
u8 Serial_RXFlag=0;
uint8_t RxData;
void USART1_IRQHandler(void)
{
			static u8 RxState=0;
      static u8 pRxPacket=0;
		  if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==SET)
			{
				 //接收串口发来的数据
				 RxData=USART_ReceiveData(USART1);
			   
				 //判断数据是否为包头
				 if(RxState==0)
				 {
					  if(RxData=='@')
						{
							 RxState=1; //状态1接收字符
						}
						else if(RxData==0xFF)
						{
							 RxState=2; //状态2接收到HEX
						}
				 }
				 else if(RxState==1)
				 {

					   //判断数据是不是包尾,不是的话就接收数据
					   if(RxData=='\r')
						 {
							   RxState=3;
						 }
						 else
						 {
							  serial_RxPack[pRxPacket]=RxData;
							  pRxPacket++;
						 }

				 }
				 else if(RxState==2)
				 {
					   if(RxData==0xFE)
						 {
							   Serial_RXFlag=1;
							   RxState=0; //重新接收包
							   pRxPacket=0;
						 }
						 else
						 { 
							  modubus_RxPack[pRxPacket]=RxData; 
							  pRxPacket++;
						 }
				 }
				 else if(RxState==3)
				 {
						 //判断数据是不是'\n'
					   if(RxData=='\n')
						 {  
							  //状态转移为0并且将
							  RxState=0;
							  serial_RxPack[pRxPacket]='\0'; //给字符串一个结束符
							  Serial_RXFlag=1;
							  pRxPacket=0;
						 }
						 else{
						 }
				 }
			}
}

 主函数还是只显示了3位数组,但是没关系的- -主要是为了回显,到时候处理数据的时候单片机直接接收到了之后处理就是了开不开回显都没关系,切换两个发送接收模式,测试是没有问题的。

物联沃分享整理
物联沃-IOTWORD物联网 » STM32串口多字节接收实现方法详解

发表评论