串口接收上位机以字符串发送的数字(可以为多位小数,负数)

本文介绍了如何通过串口在单片机与上位机间传输浮点数和负数。通过发送字符串,单片机接收并解析字符,识别符号、小数点和数值位,最终将接收到的字符转换为浮点数。关键在于识别字符'.'(0x2E)作为小数点,字符'-'(0x2D)作为负号,并利用数组存储数值位,然后转换为实际数值。同时,帧头0x0d0x0a用于标记数据开始,0x0d0x0c标记ID号。

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

        当我想通过串口给单片机发一个数,这个数可能是正数,负数或者不定位小数时,我要怎么发和接收呢?这里给出一种解决方案:就是,上位机发送字符串,然后单片机通过串口接收!固件库的中断接收函数代码如下:

u8 cc;
void USART1_IRQHandler(void)                	                             
{

    static int chuank=0;				   // 接收记录
	static int j=0,c=0;                                          //记录数据个数标记那24个
	static int biaozhi=0;//小数标志
    static int fuhao=0;//负号记录
	static int jilu[10],k=0,s=1;
    static float num=0;	
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)  //接收中断(接收到的数据必须是0x0d 0x0a开头0x0a 0x0d结尾)
	{
		cc =USART_ReceiveData(USART1);	//读取接收到的数据
		
		switch(chuank)
		{
			case 0:
				if(cc == 0x0d) chuank=1;
				else chuank=0;    
				break;
			case 1:
				if(cc == 0x0a) chuank=2; //表示接受到的数据
				else if(cc==0x0c)chuank=3;//表示ID号 
				else chuank = 0;
				break;
			case 2 :
				  if(cc!=0x0d)
				 {  //12.12     1212   0.1
					//记录连带小数一个几位
					if(cc!=0x2D)   //判断负号
					{ 
					c++;
					if(cc!=0x2E)           
					{
					jilu[j]=cc-0x30;
					j++;//j记录一共几位数 
					}
					else biaozhi=c;//记录小数点的位置
				    }
					else fuhao=1;
				}
				else 
				{   for( k=0;k<j;k++)
					num=num*10+jilu[k];
					if(biaozhi!=0)
					for( s=1;s<=c-biaozhi;s++) //判断小数位数
		            num/=10.0;
					if(fuhao==1)
					{
					num=-num;
					fuhao=0;	
					}
					j=0;
					biaozhi=0;
					chuank=1;
					c=0;
					k=0;
				}					
				 break;
		}			
		
	}
		
		USART_ClearITPendingBit(USART1,USART_IT_TC|USART_IT_RXNE | USART_IT_LBD | USART_IT_CTS);
	} 
	

该项目使用的是串口1,配置如下:

#include "sys.h"
#include "usart.h"
#include "math.h"
#include "stdlib.h"
#include <string.h>
#include "stdio.h"
void uart1_init(u32 bound)
{
	  //GPIO端口设置
	  GPIO_InitTypeDef GPIO_InitStructure;
	  USART_InitTypeDef USART_InitStructure;
	   NVIC_InitTypeDef NVIC_InitStructure;
	 
	  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
           //USART1_TX   GPIOA.9
	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
	  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
	  //USART1_RX	  GPIOA.10初始化
	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;        //抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;		         //子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			  //IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
	//USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

	  USART_Init(USART1, &USART_InitStructure); //初始化串口1
	  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
	  USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

下面对核心代码进行分析:当我们上位机想要发送一个不定类型的数时,比如:0.8 1 198 -0.8 -198 那这几个数通过串口以字符串的形式发到我单片机上,它接收到的是什么呢?

其实很简单,单片机或者上位机都是以16进制发送数据的,我上位机发的是数字的字符串形式,就是每一位数字的字符,比如我发1 其实它发的是字符‘1’ 机器语言就是 0x31。我发198,它实际发的是“198” 机器语言就是 0x31 0x39 0x38 即1对应的字符形式的16进制,9对应的字符形式的16进制与8对应的字符形式的16进制。

我们通过串口调试助手来演示一下:

 

 我们在单条发送的输入框里面输入123,然后勾选16进制发送,看看输入框里的变化成了什么:

看和我们想的一样吧,同样我们发0.8呢:

 

它发送的就是0x30 0x2E 0x38,我们可以容易的发现小数点 '.' 字符对应的16进制是0x2E,这个很重要,作为我们判断小数的一个依据,那如果我们发-0.8呢: 

        可以很轻松地看出负号'-'对应字符的16进制是0x2D,这个也作为我们判断正负的标志,那么现在就清晰的,我们想要接收上位机发来的一个数,前提是一个数:

        那思路就是这样:由于一个数的符号为只可能出现一次且出现在开头,所以我们先判断符号,即第一个字节是不是0x2D,如果是,说明这个数是负数,我们就定义一个fuhao变量作为符号标记,标记为1,然后进行小数点判断,当然这里有个技巧就是小数点也只可能出现一次,我们需要定义一个变量来记录这个数总的位数j,然后当判断到其中一位位小数点时,我们记下这个位置biaozhi,那如果这个字节不是小数点它就是一个普通数字,我们就直接将他转换为相应的数就好,即它减去0x30然后我们用一个数组来保存所有的数字。

        然后我们需要将这个数组换算成我们想要的那个数,换算的思路就到过来,直接将数组里所有的数字进位:总的位数是k(包括了小数位),那么个数字直接加权相加即可:比如这个数组jilu[10]存的是:jilu[0]:1; jilu[1]:2;jilu[2]: 3;jilu[3]: 4;那么转换逻辑即为1*10^3+2*10^2+3*10+4,为1234;之后再判断小数标志,如果不为0,计算小数的位s数,然后直接除以s次10;最后判断fuhao,是不是负数是就取反。判断完成之后当然最重要的就是清楚标志位,全部清零!!!

        至于怎么判断小数位数请看代码:大概就是用1个变量计数,当读到小数字节时再用一个变量记下当前变量的值即位小数点的位置,然后用这个数总的位数减去它就是小数的位数~下面是核心代码:

if(cc!=0x0d)
				 {  //12.12     1212   0.1
					//记录连带小数一个几位
					if(cc!=0x2D)   //判断负号
					{ 
					c++;
					if(cc!=0x2E)           
					{
					jilu[j]=cc-0x30;
					j++;//j记录一共几位数 
					}
					else biaozhi=c;//记录小数点的位置
				    }
					else fuhao=1;
				}
				else 
				{   for( k=0;k<j;k++)
					num=num*10+jilu[k];
					if(biaozhi!=0)
					for( s=1;s<=c-biaozhi;s++) //判断小数位数
		            num/=10.0;
					if(fuhao==1)
					{
					num=-num;
					fuhao=0;	
					}
					j=0;
					biaozhi=0;
					chuank=1;
					c=0;
					k=0;
				}					

        我们每次都只能发送一个数,但是我们想要多次发送或者发多个意义不一样的数怎么做呢,我们就可以在发送数时先发送帧头,然后再发送数。我这里的0x0d 0x0a就是帧头,你还可以将0x0a换成其他的来代表这个数的不同意义。

        下面给出我在DevC++编译软件上调试仿真的代码,你可以去跑一跑,希望对你的理解有所帮助~:

#include<stdio.h>
#include<string.h>
void USART3_IRQHandler(unsigned char a[30]) ; 
typedef union
	{
		unsigned char Receive_Val[3];
		float Act_val;
	}u8toint;

typedef struct 
{
	float KP;
	float KI;
	float KD;
	float Error[3];
	float Output;
	float Input;
	float target;
	unsigned char   ID;
} PID_Struct;


 float yawD; 
PID_Struct Angle_yaw;
PID_Struct Distance_yaw;
PID_Struct Angle_roll_ingoHome;
PID_Struct Angle_yawX;
PID_Struct Distance_yawX; 	

int main()
{
//	0D 0A 31 32 2E 31 32 0D 0C 22 00 00 00   12.12
//0D 0A 31 30 30 0D 0C 22 00 00 00  100
unsigned char b[24];
b[0]=0x0D;
b[1]=0x0A;
b[2]=0X31;
b[3]=0X32;
b[4]=0X2e;
b[5]=0X31;
b[6]=0x32;
b[7]=0x0d;
b[8]=0x0c;
b[9]=0x22;
b[10]=0x00 ;
b[11]=0x00;
b[12]=0x00;//0D 0A 30 0D 0C 15 00 00 00 
//b[13]=0x0D;
//b[14]=0x0A;
//b[15]=0x30;
//b[16]=0x0D;
//b[17]=0x0C;
//b[18]=0x15;
//b[19]=0x00;
//b[20]=0x00;//1E E0 49 41
//b[21]=0x00;
//b[22]=0x49;
//b[23]=0x41;
USART3_IRQHandler( b) ;
	printf("%f\n",Angle_yaw.KP);
	printf("%f\n",Angle_yaw.KI);
	printf("%f\n",Angle_yaw.KD);
	//printf("%f\n",tempx);
}
unsigned char cc; 
 void USART3_IRQHandler(unsigned char a[20])                	                             
{

	static int chuank=0;				   // 接收记录
	static int j=0,c=0,i=0;                                          //记录数据个数标记那24个
	static unsigned char biaozhi=0;//小数标志
	//static  u8toint yaws,distances,dianyas,temps;
	static int jilu[10],k=0,s=1;
    static float num=0;	
	for(i=0;i<=24;i++)
	{
	cc =a[i];	//读取接收到的数据
	switch(chuank)
		{
			case 0:
				if(cc == 0x0D) chuank=1;
				else chuank=0;    
				break;
			case 1:
				if(cc == 0x0A) chuank=2; //表示接受到的数据
				else if(cc==0x0C)chuank=3;//表示ID号 
				else chuank = 0;
				break;
			case 2 :
				 if(cc!=0x0d)//0D 0A 31 32 2E 31 32 0D 0C 22 00 00 00 
				 {  //12.12     1212   0.1
					c++;//记录连带小数一个几位
					if(cc!=0x2E)           
					{
					jilu[j]=cc-0x30;
					j++;//j记录一共几位数 
					}
					else biaozhi=c;//记录小数点的位置
					
				 }
				else 
				{   for( k=0;k<j;k++)
					num=num*10+jilu[k];	
					if(biaozhi!=0)
					for( s=1;s<=c-biaozhi;s++) //判断小数位数
		            num/=10.0;
					j=0;
					biaozhi=0;
					chuank=1;
					c=0;
					k=0;
				}					
				 break;       //yp yi yd dp di dd       yp yi yd dp di dd  
			case 3://id号:大船:22 15 16 17 18 19 小船:21 06 07  0E 0F 10 
				switch(cc)
				{            /*extern PID_Struct Angle_yaw;
								extern PID_Struct Distance_yaw;
								extern PID_Struct Angle_roll_ingoHome;
								extern PID_Struct Angle_yawX;
								extern PID_Struct Distance_yawX; */
//0D 0A 31 32 2E 31 32 0D 0C 22 00 00 00 
					case 0x22:
						  Angle_yaw.KP=num;
						  break;
					case 0x15:
						  Angle_yaw.KI=num;
						  break;
					case 0x16:
						  Angle_yaw.KD=num;
						  break;
					case 0x17:
						  Distance_yaw.KP=num;
						  break;
					case 0x18:
						  Distance_yaw.KI=num;
						  break;
					case 0x19:	
						  Distance_yaw.KD=num;
						  break;
					case 0x21:
						  Angle_yawX.KP=num;
						  break;
					case 0x06:
						  Angle_yawX.KI=num;
						  break;
					case 0x07:
						  Angle_yawX.KD=num;
						  break;
					case 0x0E:
						  Distance_yawX.KP=num;
						  break;
					case 0x0F:
						  Distance_yawX.KI=num;
						  break;
					case 0x10:
						  Distance_yawX.KD=num;
						  break;
				}
				chuank=0;
				break;

		}
	}
 
}

这个仿真里呢用到了两种帧头:0x0d 0x0a 与0x0d 0x0c,然后测试的数是12.12 。主要是对核心代码的一个逻辑测试,你可以修改然后移植到单片机上。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只待花开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值