从零开始速通一台多方式控制的STM32自平衡小车【2蓝牙通信篇】

 前言  

      这个学期有个嵌入式课程设计的任务,要求用战舰V3开发板整点活出来。毫无嵌入式底子但是想搞点花头的我思来想去,最终决定搞一台多方式控制的STM32自平衡小车。

        整个设计可以简单的分为遥控器部分和小车部分。遥控器用的是正点原子STM32F1战舰V3开发板;小车底盘、主控板用的是大鱼电子家的。目前已经实现:

        用战舰V3的按键控制;

        游戏手柄连接战舰V3控制;

        MPU6050连接战舰V3实现体感控制;

        键盘控制(电脑蓝牙连接小车然后键盘控制小车运动)。

        演示视频链接:【疯狂试验】超越想象!我用STM32打造的自平衡小车竟然能飞!?_哔哩哔哩_bilibili


蓝牙通信篇

        如何实现远程控制小车呢?其中一项较为简单的实现方式就是蓝牙。在控制器端串口连接主机蓝牙,在小车端串口连接从机蓝牙。在控制器端,将控制指令信息通过串口发送给主机蓝牙模块,主机蓝牙通过蓝牙通信协议将信息传输到从机蓝牙,小车端通过串口中断获取从机蓝牙接受的数据,进而控制小车。

主从蓝牙配对

        首先我们需要一个能当主机的蓝牙和一个能当从机的蓝牙。注意:两个只能当从机的蓝牙不能进行配对!所以购买蓝牙模块时请注意其中一个蓝牙是否能当主机蓝牙。我用的主机蓝牙是HC05,从机蓝牙是BT06,两个加起来花我了¥24。

        主从蓝牙配对需要更改蓝牙的初始化信息。修改可以采用串口调试工具例如XCOM进行可视化更改。当然更改前需要将蓝牙与电脑相连。一般是蓝牙接TTL转USB接口工具,接口工具USB口接电脑。蓝牙有4引脚或6引脚的,但是只需要用到其中4个引脚:VCC接5VGND接地RXD接TXDTXD接RXD

         连接好打开XCOM是如下界面,当你正确连接蓝牙、TTL转USB接口工具和电脑时,串口选择处会有跳出串口信息,点击打开串口,切换到多条发送,发送AT指令来修改蓝牙信息。注意:不同型号的蓝牙AT指令集不同(一般是一个文档),具体请参考你所采用的蓝牙型号

        HC05有点特殊,进入设置模式需要按住模块的小按键插电,看到红灯闪烁慢,那么说明工作进入设置模式。

        我们需要修改的AT信息可以参考这位佬的博客,他写的很详细。蓝牙的ROLE、PSWD、MODE、BIND等参数需要修改:(2条消息) HC05蓝牙主机配对BT06蓝牙从机教程_bt06蓝牙程序流程图_Diss_chan的博客-CSDN博客

       当主从蓝牙配置好后,可以给蓝牙通电连接。先启动从机蓝牙BT06再启动HC05,指示灯闪烁一致时即表示成功。

主机蓝牙与战舰V3开发板的连接和代码

        连接示意图如下。RXD接PB10(TX)TXD接PB9(RX),VCC接5v,GND接地。当然可以选择连接到其他引脚。

         以下是usart2.c代码。

#include "delay.h"
#include "usart2.h"
#include "stdarg.h"	 	 
#include "stdio.h"	 	 
#include "string.h"	 
#include "timer.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//串口3驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2015/3/29
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved									  
// 	   

//串口接收缓存区 	
u8 USART2_RX_BUF[USART2_MAX_RECV_LEN]; 				//接收缓冲,最大USART2_MAX_RECV_LEN个字节.
u8  USART2_TX_BUF[USART2_MAX_SEND_LEN]; 			//发送缓冲,最大USART2_MAX_SEND_LEN字节

//通过判断接收连续2个字符之间的时间差不大于10ms来决定是不是一次连续的数据.
//如果2个字符接收间隔超过10ms,则认为不是1次连续数据.也就是超过10ms没有接收到
//任何数据,则表示此次接收完毕.
//接收到的数据状态
//[15]:0,没有接收到数据;1,接收到了一批数据.
//[14:0]:接收到的数据长度
vu16 USART2_RX_STA=0;   	


void USART2_IRQHandler(void)
{
	u8 res;	      
	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)//接收到数据
	{	 
		res =USART_ReceiveData(USART2);		 
		if((USART2_RX_STA&(1<<15))==0)//接收完的一批数据,还没有被处理,则不再接收其他数据
		{ 
			if(USART2_RX_STA<USART2_MAX_RECV_LEN)	//还可以接收数据
			{
				TIM_SetCounter(TIM7,0);//计数器清空          				//计数器清空
				if(USART2_RX_STA==0) 				//使能定时器7的中断 
				{
					TIM_Cmd(TIM7,ENABLE);//使能定时器7
				}
				USART2_RX_BUF[USART2_RX_STA++]=res;	//记录接收到的值	 
			}else 
			{
				USART2_RX_STA|=1<<15;				//强制标记接收完成
			} 
		}
	}  				 											 
}   


//初始化IO 串口2
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率	  
void USART2_init(u32 bound)
{  

	NVIC_InitTypeDef NVIC_InitStructure;
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);	// GPIOB时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //串口2时钟使能

 	USART_DeInit(USART2);  //复位串口2
		 //USART2_TX   PA2
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PB10
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA2
   
    //USART2_RX	  PA3
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PA3
	
	USART_InitStructure.USART_BaudRate = bound;//波特率一般设置为9600;
	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); //初始化串口	3
  

	USART_Cmd(USART2, ENABLE);                    //使能串口 
	
	//使能接收中断
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断   
	
	//设置中断优先级
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
	
	
	TIM7_Int_Init(99,7199);		//10ms中断
	USART2_RX_STA=0;		//清零
	TIM_Cmd(TIM7,DISABLE);			//关闭定时器7

}

//串口2,printf 函数
//确保一次发送数据不超过USART2_MAX_SEND_LEN字节
void u2_printf(char* fmt,...)  
{  
	u16 i,j; 
	va_list ap; 
	va_start(ap,fmt);
	vsprintf((char*)USART2_TX_BUF,fmt,ap);
	va_end(ap);
	i=strlen((const char*)USART2_TX_BUF);		//此次发送数据的长度
	for(j=0;j<i;j++)							//循环发送数据
	{
	  while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕   
		USART_SendData(USART2,USART2_TX_BUF[j]); 
	} 
}

以下是usart2.h代码。

#ifndef __USART2_H
#define __USART2_H	 
#include "sys.h"   

#define USART2_MAX_RECV_LEN		600					//�����ջ����ֽ���
#define USART2_MAX_SEND_LEN		600					//����ͻ����ֽ���
#define USART2_RX_EN 			1					//0,������;1,����.

extern u8  USART2_RX_BUF[USART2_MAX_RECV_LEN]; 		
extern u8  USART2_TX_BUF[USART2_MAX_SEND_LEN]; 		
extern vu16 USART2_RX_STA;   						

void usart2_init(u32 bound);				
void u2_printf(char* fmt,...);
#endif

 main.c的代码根据需求创作了,我就简单贴一份按键&游戏手柄控制的main代码作为参考。

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "usart2.h"			 	 
#include "string.h"	  
#include "key.h"
#include "joypad.h"

const u8*JOYPAD_SYMBOL_TBL[8]=
{"Right","Left","Down","Up","Start","Select","B","A"};
const char JOYPAD_KEY[8]={'C','G','E','A','Z','Z','F','D'};

 int main(void)
 {	 
	u8 t;
	u8 i=0;
	u8 key;
	u8 temp;	//i-4
	u8 padKey; //joypad key
	u8 sendmask=0;
	u8 sendcnt=0;
	u8 sendbuf[20];	  
	u8 reclen=0;  	
	//char d='A';
	 int te=0;
	delay_init();	    	 //延时函数初始化	  
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为9600
	 USART2_init(9600);
	LED_Init();				//初始化与LED连接的硬件接口
	KEY_Init();				//初始化按键
  LCD_Init();				//初始化LCD
	JOYPAD_Init();
  //usmart_dev.init(72); 	//初始化USMART		
	 
	POINT_COLOR=RED;
	LCD_ShowString(30,30,200,16,16,"ALIENTEK STM32F1 ^_^");	
	LCD_ShowString(30,50,200,16,16,"HC05 BLUETOOTH COM TEST");	
	LCD_ShowString(30,70,200,16,16,"ATOM@ALIENTEK");
	delay_ms(1000);			//等待蓝牙模块上电稳定
 	
 	while(1) 
	{		
		if(t==0){
			sprintf((char*)sendbuf,"A/0");
			u2_printf("A/0");		//发送到蓝牙模块
			t=1;
		}
		
		
		//sprintf((char*)sendbuf,"%c",d);
		//u2_printf("%c",d);		//发送到蓝牙模?
		
		
		//joypad
		padKey = JOYPAD_Read();
		if(padKey)
		{
			LCD_ShowNum(116,130,padKey,3,16);
			for(i=0;i<8;i++)
			{
				if(padKey&(0X80>>i))
				{
					LCD_Fill(30+50,150,30+56+48,150+16,WHITE);
					LCD_ShowString(30+56,150,200,16,16,(u8*)JOYPAD_SYMBOL_TBL[i]);//????
					//transfer to hc05
					
					temp = JOYPAD_KEY[i];
					sprintf((char*)sendbuf,"%c",temp);
					u2_printf("%c",temp);		//发送到蓝牙模块
					LED0=!LED0;
				}
			}
		}
		delay_ms(10);
		
		
		//hc05
		key=KEY_Scan(0);
		if(key==KEY1_PRES)						//切换模块主从设置
		{
   		sendmask=!sendmask;				//发送/停止发送  	 
			if(sendmask==0)LCD_Fill(30+40,160,240,160+16,WHITE);//清除显示
			sendcnt ='Z';
		}else if(key==KEY0_PRES)
		{
			sendmask=!sendmask;				//发送/停止发送  	 
			if(sendmask==0)LCD_Fill(30+40,160,240,160+16,WHITE);//清除显示
			sendcnt = 'C';
		}else if(key==KEY2_PRES)
		{
			sendmask=!sendmask;				//发送/停止发送  	 
			if(sendmask==0)LCD_Fill(30+40,160,240,160+16,WHITE);//清除显示
			sendcnt = 'G';
		}
		else if(key==WKUP_PRES)
		{
			sendmask=!sendmask;				//发送/停止发送  	 
			if(sendmask==0)LCD_Fill(30+40,160,240,160+16,WHITE);//清除显示
			sendcnt = 'A';
		}
		else delay_ms(10);	   
		
		if(t==10)
		{
			if(sendmask)					//定时发送
			{
				sprintf((char*)sendbuf,"%c",sendcnt);
	  		LCD_ShowString(30+40,160,200,16,16,sendbuf);	//显示发送数据	
				u2_printf("%c",sendcnt);		//发送到蓝牙模块
				//sendcnt++;
				//if(sendcnt>99)sendcnt=0;
			}
			t=0;
			LED0=!LED0; 	     
		}	  
		
		if(USART2_RX_STA&0X8000)			//接收到一次数据了
		{
			LCD_Fill(30,200,240,320,WHITE);	//清除显示
 			reclen=USART2_RX_STA&0X7FFF;	//得到数据长度
		  	USART2_RX_BUF[reclen]=0;	 	//加入结束符
			if(reclen==9||reclen==8) 		//控制DS1检测
			{
				if(strcmp((const char*)USART2_RX_BUF,"+LED1 ON")==0)LED1=0;	//打开LED1
				if(strcmp((const char*)USART2_RX_BUF,"+LED1 OFF")==0)LED1=1;//关闭LED1
			}
 			LCD_ShowString(30,200,209,119,16,USART2_RX_BUF);//显示接收到的数据
 			USART2_RX_STA=0;	 
		}	 															     				   
		t++;	
	}
}

主从蓝牙发送数据测试

        测试思路是先将主机蓝牙连接到控制器即战舰V3上,从机蓝牙通过TTL-SUB接到电脑上,然后再打开XCOM上,待蓝牙连接后查看数据接收情况。当XCOM成功显示传输的数据,基本就成功了。

        注意:设置好的主机蓝牙和从机蓝牙上电后会自动匹配!XCOM记得打开串口!接收到的字符和数字信息是不同的,例如A和65!

小车端串口代码

       小车端的蓝牙模块连接主控板上的usart2,同样只需要连4条线,VCC接5V、GND、RXD接TX,TXD接RX。中断函数内容在void USART2_IRQHandler(void)中。串口接收到数据会改变标志位,通过这个条件判断从机蓝牙是否接收到主机蓝牙的数据,可以写个if,写接下来的动作。

#include "usart2.h"
	
u8 USART2_RX_BUF[USART2_MAX_RECV_LEN]; 				//????,??USART2_MAX_RECV_LEN???.
u8  USART2_TX_BUF[USART2_MAX_SEND_LEN]; 			//????,??USART2_MAX_SEND_LEN??
vu16 USART2_RX_STA=0;   


void uart2_init(u32 bound)
{  	 
	  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//使能UGPIOB时钟
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);	//使能USART2时钟
	//USART2_TX  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);
   
  //USART2_RX	  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA3
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);

   //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 
}

/**************************************************************************
函数功能:串口2接收中断
入口参数:无
返回  值:无
**************************************************************************/
u8 Fore,Back,Left,Right;
void USART2_IRQHandler(void)
{
	int Uart_Receive;
	if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)//接收中断标志位拉高
	{
		Uart_Receive=USART_ReceiveData(USART2);//保存接收的数据
		BlueData=Uart_Receive;
		BluetoothCMD(Uart_Receive);		
		
	}
}

void BluetoothCMD(int Uart_Receive)
{
	switch(Uart_Receive)
		{
			case 65://前进
				Fore=1,Back=0,Left=0,Right=0;
				break;
			default://停止
				Fore=0,Back=0,Left=0,Right=0;
				break;
		}
}

void Uart2SendByte(char byte)   //串口发送一个字节
{
		USART_SendData(USART2, byte);        //通过库函数  发送数据
		while( USART_GetFlagStatus(USART2,USART_FLAG_TC)!= SET);  
		//等待发送完成。   检测 USART_FLAG_TC 是否置1;    //见库函数 P359 介绍
}

void Uart2SendBuf(char *buf, u16 len)
{
	u16 i;
	for(i=0; i<len; i++)Uart2SendByte(*buf++);
}
void Uart2SendStr(char *str)
{
	u16 i,len;
	len = strlen(str);
	for(i=0; i<len; i++)Uart2SendByte(*str++);
}

        多方式控制小车的关键之一就在于成功建立蓝牙通信!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值