前言
这个学期有个嵌入式课程设计的任务,要求用战舰V3开发板整点活出来。毫无嵌入式底子但是想搞点花头的我思来想去,最终决定搞一台多方式控制的STM32自平衡小车。
整个设计可以简单的分为遥控器部分和小车部分。遥控器用的是正点原子的STM32F1战舰V3开发板;小车底盘、主控板用的是大鱼电子家的。目前已经实现:
用战舰V3的按键控制;
游戏手柄连接战舰V3控制;
MPU6050连接战舰V3实现体感控制;
键盘控制(电脑蓝牙连接小车然后键盘控制小车运动)。
演示视频链接:【疯狂试验】超越想象!我用STM32打造的自平衡小车竟然能飞!?_哔哩哔哩_bilibili
蓝牙通信篇
如何实现远程控制小车呢?其中一项较为简单的实现方式就是蓝牙。在控制器端串口连接主机蓝牙,在小车端串口连接从机蓝牙。在控制器端,将控制指令信息通过串口发送给主机蓝牙模块,主机蓝牙通过蓝牙通信协议将信息传输到从机蓝牙,小车端通过串口中断获取从机蓝牙接受的数据,进而控制小车。
主从蓝牙配对
首先我们需要一个能当主机的蓝牙和一个能当从机的蓝牙。注意:两个只能当从机的蓝牙不能进行配对!所以购买蓝牙模块时请注意其中一个蓝牙是否能当主机蓝牙。我用的主机蓝牙是HC05,从机蓝牙是BT06,两个加起来花我了¥24。
主从蓝牙配对需要更改蓝牙的初始化信息。修改可以采用串口调试工具例如XCOM进行可视化更改。当然更改前需要将蓝牙与电脑相连。一般是蓝牙接TTL转USB接口工具,接口工具USB口接电脑。蓝牙有4引脚或6引脚的,但是只需要用到其中4个引脚:VCC接5V,GND接地,RXD接TXD,TXD接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++);
}