使用STM32串口实现人机交互:解析数据包

串口收发解析数据包

学过stm32的同学都知道,利用串口与32进行通讯非常的方便,在正点原子的官方历程中我们就可以看到,在串口中断服务函数里面,对接受的数据用一个十六位的数据来判断是否接受完成(即是否在数据包的末尾接收到0x0D,0x0A,他们分别对应的是\r \n),利用高位处理状态,低位则将数据包数量储存下来,

u16 USART_RX_STA

请添加图片描述

那么既然可以判断是否接受到了数据,那么我们是不是可以将数据进行拆分处理,进行储存,然后对程序中的一些变量进行赋值,然后实现人机交互的功能,通过串口;

例如说,我们发送 X90 Y90 Z90那么就会对后面的数据进行接受处理;

在这里我们发送的数据包主要分为2中类型:

HEX数据包:

用于原始数据的接受,比如陀螺仪,以及温度传感器;

请添加图片描述

文本数据包:

常见的使用场景有CNC,以及3D打印使用的G代码;

请添加图片描述

然后在处理的时候,相信你也看到,需要用到状态机的知识;进行逐层判别,

例如正点原子用的是定义一个变量:

u16 USART_RX_STA

当然我们也可以通过将一个变量置0,1,2,3,,等等来表示不同的状态;

数据包为例的话:

请添加图片描述
请添加图片描述

你可能会有一个疑问就是,为什么HEX的包头包尾设置为0xFF与0xFE,这是因为,如果在数据包中存在一样的数值,可能会导致接受混乱的时候,这两个数据是影响比较小的,毕竟接受的数据不可能一直为很大的值;或者说出现为0xFF与0xFE的可能性比较小;通过这样的设置就极大程度的减小了数据包紊乱的问题;

针对文本数据包就比较好表示了,这了用到的是 @ 这个用的不是很多的字符作为包头,然后 \n 回车为包尾;

这里以文本数据包为例,简要讨论一下实现所需要的步骤:

1,串口的中断处理函数,在接受到数据中断以后,根据之前提到的状态机的知识判断数据的有效性;然后储存到char类型的数组中;

2,清空空格这种多余符号,使数据紧凑

3,数据包大体分为两大类:比如说,类似于这个"LED_ON"与"G 91"这么两种数据,处理起来肯定是不一样的;这里他们分为a,b两种类型:

​ a:字符串类型的话,调用strcmp(A,B)//if ture return 0把接受数据与事先定义好的字符串进行比较,然后类似于蓝牙的编程方式一样,进入相应的 if 语句,执行相关操作;

​ b:CNC,G代码类型的:在数组中寻找特定字母,然后标记位置,最后取出该字母与下一个字母之间的数据;注意一点的是,这些数据是 char 类型的单个数字,需要转化为 int 类型以后乘以每一位相应的位权,得到数据;

字符串类型的数据包,控制LED的开关:

int main(void)
{
	OLED_Init();
	LED_Init();
	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);
			
			if (strcmp(Serial_RxPacket, "LED_ON") == 0)
			{
				LED1_ON();
				Serial_SendString("LED_ON_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_ON_OK");
			}
			else if (strcmp(Serial_RxPacket, "LED_OFF") == 0)
			{
				LED1_OFF();
				Serial_SendString("LED_OFF_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_OFF_OK");
			}
			else
			{
				Serial_SendString("ERROR_COMMAND\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "ERROR_COMMAND");
			}
			
			Serial_RxFlag = 0;
		}
	}
}

然后这里放几个我写过的函数,适用于G代码,和CNC代码解析;

串口中断处理函数:

void USART1_IRQHandler(void)//Êý¾Ý°üÒÔ@¿ªÍ·\0(»Ø³µ)½áβ
{
	static uint8_t RxState = 0;
	static uint8_t pRxPacket = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		uint8_t RxData = USART_ReceiveData(USART1);
		
		if (RxState == 0)
		{
			if (RxData == '@' && Serial_RxFlag == 0)//ûÓÐÓöÁÈ¡±ê־λÁ¢¿ÌÇåÁãµÄº¯Êý£¬Ä¿µÄÊDZÜÃâÁ¬Ðø·¢Ë͵Äʱºò£¬±¾´ÎûÓд¦ÀíÍê¾Í¿ªÊ¼ÏÂÒ»´ÎÊý¾ÝµÄ´¦Àí£¬µ¼Ö½ÓÊÜ´íÎó
			{                                       //Ö¤Ã÷ÉÏÒ»´Î½ÓÊÜÒѾ­ÔÚmainº¯ÊýÖд¦ÀíÍê±ÏÁË
				RxState = 1;
				pRxPacket = 0;//¾²Ì¬¾Ö²¿±äÁ¿£¬ËùÒÔÿ´Îµ÷ÓõÄʱºò£¬²»Ò»¶¨Îª0
			}
		}
		else if (RxState == 1)
		{
			if (RxData == '\r')
			{
				RxState = 2;
			}
			else
			{
				Serial_RxPacket[pRxPacket] = RxData;
				pRxPacket ++;
			}
		}
		else if (RxState == 2)
		{
			if (RxData == '\n')
			{
				RxState = 0;
				Serial_RxPacket[pRxPacket] = '\0';//×¢ÒâCÓïÑÔµÄÊý×é½áβΪ\0
				Serial_RxFlag = 1;    //Êý¾Ý½ÓÊÜÍê³É£¬¿ÉÒÔ½øÈëmainº¯ÊýÖÐ
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

清除空格:

void clear_blank(char *a)
{
	int i=0, j=0;
	for (i = 0; i < strlen(a); i++) {
		if (a[i] != ' ') {
			a[j++] = a[i];
		}
		else if(a[i]==' '){
			continue;
		}
	}
	a[j] = '\0';  //ÔÚÊý×éµÄĩβÌí¼ÓÒ»¸ö'\0'//ÕâÀïºÍ֮ǰµÄÖжϴ¦Àíº¯Êý²»³åÍ»£¬
				  //Ò»¸öÊǽӽÓÊÕÍêÒÔºó¼Ó£¬Ò»¸öÊÇÈ¥³ý¿Õ°×¼Ó
	
}

查找(搜索)特殊字符在数组中的位置:

int search_array( char *a, int n, char x )
{
  int i,x_Tab_dowm;
  int flag=-1;
	
  x_Tab_dowm=x+32;//ÒÑÖªÔÚASCLL´úÂëÖУ¬´óд×ÖĸºÍСд×ÖĸÏà²î32
	
  for(i=0;i<n;i++)
  {
    if(a[i]==x)
	{
		flag=1;
		break;//Ò»µ©¼ì²âµ½×ÖĸµÄ»°£¬¾Í²»ÐèÒª½øÐÐforÑ­»·ÁË£¬ËùÒÔÕâÀïÓÃbreakÀ´Ð´;
	}
	
	else if(a[i]==x_Tab_dowm)
	{
		flag=1;
		break;
	}

  }
  if(flag==1)
  	return i;
  else
  	return flag;
}

读取特定字符后面的数据,然后转换成 int 类型:

int get_array_number( char *a, int start , int place)//Êý×éa,ÆðʼλÖÃ(×ÖĸXYZËùÔÚµÄλÖÃ),ÐèÒªÌáÈ¡Êý×ֵĴóС
{

	int mi = 0, di = 0;
	int temp = 0, result = 0, real_place = 0, i = 0, j = 0;//ÉùÃ÷±äÁ¿ºóÒ»¶¨Òª¼°Ê±³õʼ»¯±äÁ¿


	for (i = 0; i < place; i++)
	{
		if ( ('0' <= a[start + i + 1]) && (a[start + i + 1] <= '9') ) { real_place++; }//´ÓÐèÒªÅжϵÄ×Öĸ¿ªÊ¼£¬µ½ÏÂÒ»¸ö×Öĸ½áÊø
		if ( ('A' <= a[start + i + 1]) && (a[start + i + 1]<= 'z') ) { break; }//ÀýÈçÄãÏë¶ÔKp,Ki,Kd½øÐд¦Àí£¬ÄÇôֻÐèҪѰÕÒp£¬i£¬dÈý¸ö×Öĸ£¬µ÷ÓÃsearch_arrayº¯Êý
		if (a[start + i + 1] == '\0') { break; }

	}
	
	
	for (j = 0; j < real_place; j++)
	{
		mi = pow(10, real_place - j - 1);
		di = (a[start + j + 1] - '0');

		temp = di * mi;
		result = temp+result;
	}
	
	return result;
}

数据处理函数:

int x_place,y_place,z_place;
int x_num,y_num,z_num;

void Data_Handler(void)
{

if (Serial_RxFlag == 1)
		{
	
			clear_blank(Serial_RxPacket);
			
			x_place=search_array(Serial_RxPacket,Serial_RxPacket_Size,'X');
			y_place=search_array(Serial_RxPacket,Serial_RxPacket_Size,'Y');
			z_place=search_array(Serial_RxPacket,Serial_RxPacket_Size,'Z');
			
			x_num = get_array_number(Serial_RxPacket, x_place, Serial_RxPacket_Size);
			y_num = get_array_number(Serial_RxPacket, y_place, Serial_RxPacket_Size);
			z_num = get_array_number(Serial_RxPacket, z_place, Serial_RxPacket_Size);
			
			
			Serial_RxFlag = 0;
		}


}

主函数:

int main(void)
{
	
	OLED_Init();
	LED_Init();
	Serial_Init();
	
//	OLED_ShowString(1, 1, "TxPacket");
	OLED_ShowString(1, 1, "RxPacket");
	
	while (1)
	{
	        Data_Handler();
		    OLED_ShowString(2, 1, "X_num:");
			OLED_ShowString(3, 1, "Y_num:");
			OLED_ShowString(4, 1, "Z_num:");
			
			OLED_ShowNum(2,7,x_num,5);
			OLED_ShowNum(3,7,y_num,5);
			OLED_ShowNum(4,7,z_num,5);
	}
}

Serial.h:

Serial.c:

物联沃分享整理
物联沃-IOTWORD物联网 » 使用STM32串口实现人机交互:解析数据包

发表评论