基于stm32的双蓝牙主从通信—双蓝牙主从配置、串口配置、串口接收处理浮点数(附测试代码)

前言

        此次做的内容是使用 mpu6050 无线控制小车的运动。在做的过程中发现需要用到双蓝牙进行两个板子之间的通信,将主板mpu6050检测的数据传输至从板上从而控制车的移动。

1、配置双蓝牙主从通信

        以下是转载博主 不怨天,不尤人 的主从配置方法,简洁明了:

        首先让蓝牙进入AT模式
        先按住蓝牙上的微动开关,然后给蓝牙上电。蓝牙上的红灯慢闪表示进入AT模式。
        进行蓝牙AT指令配置
        1、打开两个串口调试助手,选好COM口、波特率选38400,数据位为8,停止位为1。
        2、恢复两个蓝牙的默认设置(最好选择文本模式发送AT命令):AT+ORGL\r\n
(\r\n代表一个回车,在每一条AT指令之后都要加一个回车)。
        3、【(A)主机配置】蓝牙名字配置:AT+NAME=YI(名字任意)
        4、【(A)主机配置】蓝牙模式配置:AT+ROLE=1(主机模式)
        5、【(A)主机配置】蓝牙密码配置:AT+PSWD=1234(密码任意)
        6、【(B)从机配置】蓝牙名字配置:AT+NAME=YI(名字要一致)
        7、【(B)从机配置】蓝牙模式配置:AT+ROLE=0(从机模式)
        8、【(B)从机配置】蓝牙密码配置:AT+PSWD=1234(密码要一致)
        9、蓝牙地址的绑定,通过串口助手查询B蓝牙的地址:AT+ADDR?(很多查询都是指令后面加问号、回车,但是有一些东西是不能查询的,比如名字等)
        10蓝牙A绑定蓝牙B的地址,给蓝牙A(主蓝牙)发送指令:AT+BIND=(B的地址) ,注意在绑定地址的时候要把查询到的地址中的冒号换成逗号,例如98d3:51:fd8103,应该换成98d3,51,fd8103。
        11、按照相同的方式,查询A的地址,让B绑定A的地址。
        12、蓝牙的连接模式配置:AT+CMODE=0(0是指定蓝牙地址连接模式,设置为0才能自动的连接绑定的地址)
        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。        
        原文链接:https://blog.csdn.net/weixin_42994525/article/details/82622405

配置好之后,当蓝牙上电的时候,红灯快闪表示没匹配到地址。过几秒后出现搁几秒红灯快闪两下,就表示匹配成功了。测试方法也非常简单:

使用 ttl 将主从蓝牙连接在电脑不同的端口上,使用上位机在一个端口发送数据,另一个端口便能接收到数据。

2、串口配置

        我使用的是stm32c8t6,由于要使用ttl在串口1进行烧录代码,所以主从板都使用串口2进行收发数据。主板串口2的配置如下,重定义了 printf() 函数:

#include "sys.h"
#include "usart2.h"	  

//
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
_sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART2->SR&0X40)==0);//循环发送,直到发送完毕   
    USART2->DR = (u8) ch;      
	return ch;
}
#endif 

	

u8 USART_RX_BUF2[USART_REC_LEN];
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA2=0;       //接收状态标记	  
  
void uart2_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);	//使能USART2时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);	//使能GPIOA时钟

	USART_DeInit(USART2);  //复位串口2
	
	//USART1_TX   GPIOA.2
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.2
   
  //USART1_RX	  GPIOA.3初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.3 

  //Usart2 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;  //抢占优先级2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级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(USART2, &USART_InitStructure); //初始化串口2
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART2, ENABLE);                    //使能串口2 

}

void USART2_IRQHandler(void)                	//串口2中断服务程序
{
	u8 Res;

	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		Res =USART_ReceiveData(USART2);	//读取接收到的数据
		
		if((USART_RX_STA2&0x8000)==0)//接收未完成
		{
			if(USART_RX_STA2&0x4000)//接收到了0x0d
			{
				if(Res!=0x0a)USART_RX_STA2=0;//接收错误,重新开始
				else USART_RX_STA2|=0x8000;	//接收完成了 
			}else //还没收到0X0D
			{	
				if(Res==0x0d)USART_RX_STA2|=0x4000;
				else
				{
					USART_RX_BUF2[USART_RX_STA2&0X3FFF]=Res ;
					USART_RX_STA2++;
					if(USART_RX_STA2>(USART_REC_LEN-1))USART_RX_STA2=0;//接收数据错误,重新开始接收	  
				}		 
			}
		}   		 
     } 

}

        从板的串口配置如下,编写了 u2_printf() 函数:

#include "usart2.h"
#include "sys.h"
#include <stdarg.h>
 
 
char  USART2_RX_BUF[USART2_REC_LEN]; //?óê??o3?,×?′óUSART_REC_LEN??×??ú.??×??ú?a??DD·? 
u16 USART2_RX_STA;         		//?óê?×′ì?±ê??	
 
void uart2_init(u32 baudrate)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//ê1?üUSART2£?GPIOAê±?ó
 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//?′ó?í?íìê?3?
    GPIO_Init(GPIOA, &GPIO_InitStructure);//3?ê??ˉGPIOA.2
 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//????ê?è?
    GPIO_Init(GPIOA, &GPIO_InitStructure);//3?ê??ˉGPIOA.3  
 
    //Usart1 NVIC ????
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//?à??ó??è??3
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//×óó??è??3
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQí¨μàê1?ü
    NVIC_Init(&NVIC_InitStructure);	//?ù?Y???¨μ?2?êy3?ê??ˉVIC??′??÷
 
    //USART 3?ê??ˉéè??
 
    USART_InitStructure.USART_BaudRate = baudrate;//′??ú2¨ì??ê
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//×?3¤?a8??êy?Y??ê?
    USART_InitStructure.USART_StopBits = USART_StopBits_1;//ò???í£?1??
    USART_InitStructure.USART_Parity = USART_Parity_No;//?T????D£?é??
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//?Tó2?têy?Yá÷????
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//ê?·¢?£ê?
 
    USART_Init(USART2, &USART_InitStructure); //3?ê??ˉ′??ú2
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//?a??′??ú?óêü?D??
    USART_Cmd(USART2, ENABLE);                    //ê1?ü′??ú2
}
 
void USART2_IRQHandler(void)                	//′??ú2?D??·t??3ìDò
{
	u8 Res;
 
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  //?óê??D??(?óê?μ?μ?êy?Y±?D?ê?0x0d 0x0a?á?2)
    {
        Res =USART_ReceiveData(USART2);	//?áè??óê?μ?μ?êy?Y
 
        if((USART2_RX_STA&0x8000)==0)//?óê??′íê3é
        {
            if(USART2_RX_STA&0x4000)//?óê?μ?á?0x0d
            {
                if(Res!=0x0a)USART2_RX_STA=0;//?óê?′í?ó,??D??aê?
                else USART2_RX_STA|=0x8000;	//?óê?íê3éá? 
            }
            else //?1??ê?μ?0X0D
            {	
                if(Res==0x0d)USART2_RX_STA|=0x4000;
                else
                {
                    USART2_RX_BUF[USART2_RX_STA&0X3FFF]=Res ;
                    USART2_RX_STA++;
                    if(USART2_RX_STA>(USART2_REC_LEN-1))USART2_RX_STA=0;//?óê?êy?Y′í?ó,??D??aê??óê?	  
                }		 
            }
        }   		 
    } 
 
}

//自定义串口2 的printf 函数
char UART2_TX_BUF[200];
void u2_printf(char* fmt, ...)    //无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
{
    u16 i, j;
    va_list ap;          //va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
    va_start(ap, fmt);   //va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束
    vsprintf((char*)UART2_TX_BUF, fmt, ap);	// 把生成的格式化的字符串存放在这里
    va_end(ap);
    i = strlen((const char*)UART2_TX_BUF);              //此次发送数据的长度
    for(j = 0; j < i; j++)                                                    //循环发送数据
    {
        while((USART2->SR & 0X40) == 0);                    //循环发送,直到发送完毕
        USART2->DR = UART2_TX_BUF[j];
    }
}

 3、浮点数的收发

发送数据

        主板先检测到mpu的数据后将数据转为带两位小数的浮点数,并在每个数据前加上了类似包头的东西来分辨数据,通过 printf() 函数,由串口2发送至从设备。

	while(1)
	{
		mpu_dmp_get_data(&pitch,&roll,&yaw);			//得到姿态角即欧拉角
		temp=MPU_Get_Temperature();								//得到温度值
		MPU_Get_Accelerometer(&aacx,&aacy,&aacz);	//得到加速度传感器数据
		MPU_Get_Gyroscope(&gyrox,&gyroy,&gyroz);	//得到陀螺仪数据
		
		printf("P%.2fR%.2fY%.2f\r\n", pitch, roll, yaw);
		extract_data_from_buffer();
		
		oled_show();
		
		delay_ms(200);
	}	

接收数据

        从板通过串口2的中断服务函数接收完数据后,通过 extract_data_from_buffer() 函数处理接收到的数据。

        sscanf() 会对存储接收到数据的数组 USART2_RX_BUF 进行处理。将字符串转化为浮点数。

   "P%fR%fY%f" 这个格式字符串指定了要在输入字符串中匹配一个 P,然后是一个浮点数,再匹配一个 R,然后是一个浮点数,再匹配一个 Y,然后是一个浮点数。

   pitch, roll, yaw:这些是指向 float 类型变量的指针,sscanf 会将解析出的浮点数存储到这些变量中。

工作流程

  1. sscanf 函数将 USART2_RX_BUF 中的字符串与 "P%fR%fY%f" 进行匹配;
  2. sscanf 查找字符串中的 P,并将其后的浮点数读取到 pitch 指向的变量中;
  3. sscanf 查找字符串中的 R,并将其后的浮点数读取到 roll 指向的变量中;
  4. sscanf 查找字符串中的 Y,并将其后的浮点数读取到 yaw 指向的变量中;
  5. 最后对接收到的数据进行校验,返回的数字是接收到数据的个数,判断是否为3。
u8 extract_data_from_buffer(float *pitch, float *roll, float *yaw)
{
	u8 sta=0;
	if(USART2_RX_STA&0x8000)//串口1接收到数据
	{
		if (sscanf(USART2_RX_BUF, "P%fR%fY%f", pitch, roll, yaw) == 3)
		{
			USART2_RX_STA=0;
			sta=1;
		}
	}
	return sta;
}

  • 33
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值