循迹蓝牙小车的第三篇终于来了,这篇开篇先来介绍一下整个开发过程中得构思思路。本来这应该放在第一篇,但是实际思路会因为开发过程中遇到的问题而改变,到今天为止小车的三个目标功能都已经实现。所以在介绍app控制小车之前先来理清一下思路。
整个设计软件方面分为两大块:
一、STM32作为主控制器,有三种工作模式,三种模式通过开发板上的物理按键或者App虚拟按键来进行切换:
1、红外遥控——通过接受解码红外头接收到的遥控信息来控制小车
2、循线跟踪——通过循环扫描光电循迹模块检测黑线并且跟踪黑线行驶
3、手机蓝牙控制——HC-05蓝牙模块接收App通过蓝牙发送的信息控制小车行驶
二、Android端APP:
App功能分为两大块:蓝牙连接、控制小车,两大功能模块通过两个Activity来实现。
蓝牙连接包括:
1、判断当前设备是否支持蓝牙
2、判断当前设备蓝牙功能是否打开
3、读取已经配对的蓝牙信息
4、扫描周围蓝牙信息
5、与指定蓝牙发起配对
6、与指定蓝牙发起连接
7、断开连接、取消配对
控制小车:
控制小车其实就是和小车通讯,只需要通过已经连接蓝牙的Socket通道,向小车端发送约定的信息,
小车端STM32接收到信息,解码之后根据约定的含义控制小车。
先从App开始:
App开发软件平台AndroidStudio2.2.2
硬件平台:Android手机,目标API 28,最低API 24。检测平台OPPO A7X
APP工程文件构架:
蓝牙连接过程详细说明请访问Android 官服档,有详细说明:
Android 官服文档 ——蓝牙概览
循迹蓝牙小车.mp4
STM32蓝牙接收端源码:
因为TC-05蓝牙模块数据通过串口输出,所以使用的时候不需要涉及到任何蓝牙协议。直接把模块当成串口来使用就行,代码相对简单:
串口初始化程序:
void LanYa_Init(void){
GPIO_InitTypeDef GPIO_InitStrue;
USART_InitTypeDef USART_InitStrue;
NVIC_InitTypeDef NVIC_InitStrue;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//① 使能GPIOA时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//① 使能USART2时钟
GPIO_InitStrue.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStrue.GPIO_Pin=GPIO_Pin_2;
GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStrue);//②设置发送端IO
GPIO_InitStrue.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStrue.GPIO_Pin=GPIO_Pin_3;
GPIO_InitStrue.GPIO_Speed=GPIO_Speed_10MHz;
GPIO_Init(GPIOA,&GPIO_InitStrue);//②设置接收端IO
USART_InitStrue.USART_BaudRate=9600;
USART_InitStrue.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
USART_InitStrue.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;
USART_InitStrue.USART_Parity=USART_Parity_No;
USART_InitStrue.USART_StopBits=USART_StopBits_1;
USART_InitStrue.USART_WordLength=USART_WordLength_8b;
USART_Init(USART2,&USART_InitStrue);//③设置串口参数,波特率9600,发送接收双工模式,无奇偶校验,数据位8位,停止位1位,无流控制
USART_Cmd(USART2,ENABLE);//使能串口1
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//开启接收中断
NVIC_InitStrue.NVIC_IRQChannel=USART2_IRQn;
NVIC_InitStrue.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStrue.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStrue.NVIC_IRQChannelSubPriority=1;//中断优先级设置
NVIC_Init(&NVIC_InitStrue);
}
//串口2中断服务程序
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2,USART_IT_RXNE))
{
LanYaRes = USART_ReceiveData(USART2); //蓝牙接收到的数据
LCD_ShowNum(30+8*16,70,1,5,16);
flag = 0;
}
}
int GetLanYaRes(void){
if(flag == 0){
flag = 1;
return LanYaRes;
}else return -1;
}
数据接收处理代码:
LCD_Clear(WHITE);
LanYa_Init(); //蓝牙接收初始化
// USART_Cmd(USART2,ENABLE);//使能串口2
// USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);//开启串口2接收中断
pwm_sudu = 10000;
flagSudu = 0;
while(mode1 == 2){ //模式3,App蓝牙模式
mode1 = GetMode();
if(flagSudu == 0){
PGout(13) = 0;
PGout(14) = 0;
flagSudu = 1;
}
Show_Str(30,30,200,16,"循迹小车蓝牙调试界面",16,0);
POINT_COLOR=RED;
LanYaRes1 = GetLanYaRes();
Show_Str(30,70,200,16,"蓝牙接收数据:",16,0);
switch(LanYaRes1){
case 48:{
TIM_SetCompare4(TIM3,pwm_sudu); //前进
PGout(13) = 1;
PGout(14) = 0;
LanYaRes1 = -1;
}break;
case 50:{
TIM_SetCompare4(TIM3,pwm_sudu); //后退
PGout(13) = 0;
PGout(14) = 1;
LanYaRes1 = -1;
}break;
case 49:{
TIM_SetCompare4(TIM3,0); //停止
PGout(13) = 0;
PGout(14) = 0;
LanYaRes1 = -1;
}break;
case 51:{ //加速
if(pwm_sudu <= 14000)pwm_sudu+=500;
else pwm_sudu=8000;
TIM_SetCompare4(TIM3,pwm_sudu);
LanYaRes1 = -1;
}break;
case 52:{ //减速
if(pwm_sudu >=1000)pwm_sudu-=500;
else pwm_sudu=0;
TIM_SetCompare4(TIM3,pwm_sudu);
LanYaRes1 = -1;
}break;
case 54:{ //左转
TIM_SetCompare2(TIM3,MIN_zou);
LanYaRes1 = -1;
}break;
case 53:{ //右转
TIM_SetCompare2(TIM3,MAX_you);
LanYaRes1 = -1;
}break;
case 56:{ //右转
TIM_SetCompare2(TIM3,Z_qian);
LanYaRes1 = -1;
}break;
default:break;
}
}
代码中设计一条LanYaRes1 = -1; 的代码,为什么要在每次数据处理之后将LanYaRes1 = -1置为-1?
因为蓝牙接收到的信息不是按键,如果不在每使用一次数据信息之后将数据清理,那么同一条数据就会重复直行很多次,就像按键处理里面的连续按键一样。可以看到,在上面一个程序片段中,也有相似的代码:flag = 0; return -1; 如下,也是同样的原因。
if(USART_GetITStatus(USART2,USART_IT_RXNE))
{
LanYaRes = USART_ReceiveData(USART2); //蓝牙接收到的数据
LCD_ShowNum(30+8*16,70,1,5,16);
flag = 0;
}
}
int GetLanYaRes(void){
if(flag == 0){
flag = 1;
return LanYaRes;
}else return -1;
}
App端源码:
public void onClick(View v) {
switch (v.getId()){
case R.id.BluetoothConnectivity:{ //连接蓝牙
startActivityForResult(new Intent(MainActivity.this,Main2Activity.class),1);
// Toast.makeText(MainActivity.this,"按“搜索”开始扫描附近蓝牙设备",Toast.LENGTH_LONG).show();
}break;
case R.id.Forward_Button:{ //前进
if(writeTask != null)
writeTask.doInBackground(""+0);
}break;
case R.id.Stop_Button:{ //停止
if(writeTask != null)
writeTask.doInBackground(""+1);
}break;
case R.id.Back_Button:{ //后退
if(writeTask != null)
writeTask.doInBackground(""+2);
}break;
case R.id.Accelerate_Button:{ //加速
if(writeTask != null)
writeTask.doInBackground(""+3);
}break;
case R.id.SlowDown_Button:{ //减速
if(writeTask != null)
writeTask.doInBackground(""+4);
}break;
case R.id.Right_Button:{ //右转
if(writeTask != null)
writeTask.doInBackground(""+5);
}break;
case R.id.Left_Button:{ //左转
if(writeTask != null)
writeTask.doInBackground(""+6);
}break;
case R.id.Mode_Button:{ //模式切换
if(writeTask != null)
writeTask.doInBackground(""+7);
}break;
case R.id.StraightTravel_Button:{ //直行
if(writeTask != null)
writeTask.doInBackground(""+8);
}break;
default:writeTask.cancel(); //关闭蓝牙通道
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
// super.onActivityResult(requestCode, resultCode, data);
switch (requestCode){
case 1:{
if(resultCode == RESULT_OK){
if(data.getParcelableExtra("connectBluetooth") != null)
currentBluetoothDevice = data.getParcelableExtra("connectBluetooth"); //接收反馈回来的当前连接蓝牙设备
LanYa_TextView.setText("蓝牙“" + currentBluetoothDevice.getName() + "”已连接");
if(((GlobalBlueSocket)getApplication()).getGlobalBlueSocket() != null)
mBluetoothSocket = ((GlobalBlueSocket)getApplication()).getGlobalBlueSocket(); //通过全局变量获取蓝牙套接字
writeTask = new WriteTask(mBluetoothSocket);
}
}break;
default:break;
}
}
关于蓝牙连接以及相关的逻辑代码,因为涉及功能多逻辑复杂,将在下篇进行介绍。
STM32循迹小车/Android蓝牙控制小车(一)
STM32循迹小车/Android蓝牙控制小车(二)
STM32循迹小车/Android蓝牙控制小车(三)
STM32循迹小车/Android蓝牙控制小车(四)完结篇——Android经典蓝牙开发