基于AVR64DD32和ESP32-E的蓝牙小车控制系统
0 引言
本人曾于2022年3月Funpack第一季结束时,在电子森林发布了《基于Arduino Nano33 BLE和Wio Terminal的遥控小车设计》,当时使用的是Funpack第一季第8期和第12期的开发板。
此次Funpack第二季第4期也是机缘巧合,准备把原遥控小车的硬件更新一下,于是将车体控制板Arduino Nano33 BLE换成了AVR64DD32 Curiosity Nano,将遥控器控制板由Wio Terminal换成了FireBeetle ESP32-E及扩展板,同时又增加了一点新功能,将Funpack2-3和Funpack2-4的开发板物尽其用。
1 概述
本文使用Microchip公司AVR64DD32 Curiosity Nano开发板和DFRobot公司 FireBeetle ESP32-E开发板,设计开发了一套蓝牙小车控制系统。该系统主要包括遥控和车体两部分,通过操作遥控手柄,可实现四驱车的前进、后退、转向、停止等功能。
图1 遥控车体及遥控器
为便于测试,车体配备了一个手势传感器,可用手势操作,进行本地测试,验证电机驱动功能。
2 硬件设计
项目用到的主要硬件清单见表1
表1 项目关键硬件清单
序号 |
名称 |
型号 |
数量 |
供应商 |
1 |
车体控制板 |
AVR64DD32 Curiosity Nano |
1 |
Microchip |
2 |
电机驱动板 |
TB6612FNG |
1 |
TOSHIBA(东芝) |
3 |
手势传感器 |
PAJ7620U2 |
1 |
DFRobot |
4 |
蓝牙接收模块 |
HC-05 |
1 |
汇承 |
5 |
遥控控制板 |
FireBeetle ESP32-E |
1 |
DFRobot |
6 |
扩展板 |
|
1 |
硬禾学堂 |
车体以AVR64DD32开发板为核心,配合HC-05蓝牙模块、TB6612FNG电机驱动模块,共同完成蓝牙数据接收和电机控制功能。
车体部分的硬件连接图如图2所示。
图2 遥控车车体接线图
AVR64DD32开发板的PC2、PC3、PD1控制1组电机。其中PC2、PC3作为开关量输出,连接到电机驱动板TB6612FNG的AIN1和AIN2,控制小车左侧电机的方向;PD1作为PWM输出,连接到电机驱动板的PWMA,控制小车左侧电机的速度。PF2、PF3、PD2控制小车右侧另1组电机。为便于观察开发板的输出信号,在PC2、PC3、PF2、PF3都接了LED指示灯,用于显示小车的状态,如前进、后退、转弯等。
开发板的PF4脚接了一个开关,用来进行模式切换。当开关在本地模式时,可通过手势传感器对小车进行控制;当开关在远程模式时,通过蓝牙对小车进行控制。
手势传感器通过I2C接口与AVR64DD32开发板进行连接;蓝牙模块HC-05连接在开发板的UART1端口。
遥控器以ESP32-E开发板为核心,配合硬禾学堂的扩展板,完成遥控操作、蓝牙传输、状态显示等功能。遥控器的硬件连接图如图3所示.
图3 遥控器硬件接线图
遥控器的主控板为ESP32-E,它与硬禾学堂扩展板进行连接。通过SPI口连接彩色显示屏、通过开关量输入端口采集手柄的PWM信号,此外扩展板的三色LED也由主控板进行控制,用来显示车体状态。
3 软件设计
编写代码前,需要先对HC-05蓝牙模块进行配置。首先通过USB转TTL模块连接电脑与蓝牙模块,然后使用XCOM串口调试软件对HC-05模块进行配置。
本系统将HC-05配置成从站
AT+ORGL //恢复出厂设置,恢复后需重新连接
AT+NAME=LAOHU //设备名称
AT+ROLE=0 //从站模式
AT+CMODE=1 //任意蓝牙地址连接模式(不受绑定指令设置地址的约束)
AT+PSWD=1234 //密码
AT+UART=9600,0,0 //设置波特率
AT+ADDR? // 获取地址
然后打开Arduino软件,在首选项里配置开发板网址。
添加下面的网址
http://download.dfrobot.top/FireBeetle/package_DFRobot_index.json
http://drazzy.com/package_drazzy.com_index.json
然后在Arduino软件中安装开发板和库文件。
根据扩展板屏幕硬件参数信息,修改TFT_eSPI库配置文件
打开C:\Users\Administrator\Documents\Arduino\libraries\TFT_eSPI目录,修改
User_Setup.h 文件
#define ST7735_DRIVER //设置显示芯片型号
#define TFT_WIDTH 130 //设置屏幕大小,要设置的比128*128略大,否则会看到花屏的边线
#define TFT_HEIGHT 135
//设置TFT屏控制脚对应的引脚
#define TFT_CS D10
#define TFT_DC D11
#define TFT_RST -1 // -1表示使用硬件Reset脚
#define TOUCH_CS D9
下面是主要代码内容
车体代码
#include <Wire.h>
#include <SoftwareSerial.h>
#include "paj7620.h"
#define AVR_LED PIN_PF5
#define Drive_AIN1 PIN_PC2
#define Drive_AIN2 PIN_PC3
#define Drive_PWMA PIN_PD1
#define Drive_BIN1 PIN_PF2
#define Drive_BIN2 PIN_PF3
#define Drive_PWMB PIN_PD2
#define PIN_MODE PIN_PF4 //模式选择开关 0-本地模式 1-遥控模式
#define Speed_PWMA 100 //A通道速度 0-255
#define Speed_PWMB 100 //B通道速度 0-255
//SoftwareSerial UART0(PIN_PD5, PIN_PD4); //调试下载监控串口,不定义时用硬件串口,但硬件串口有时候不打印
SoftwareSerial UART1(PIN_PC1, PIN_PC0); //AVR接蓝牙模块串口
char carRcvData;//蓝牙接收的原始数据
char carState;//蓝牙数据过滤处理 1-前进 2-后退 3-左转 4-右转 5-停止
int WorkMode=1;//工作模式 0-遥控 1-本地
int iCarCmd; //小车控制命令
void setup()
{
Serial.begin(9600);// UART0.begin(9600)
UART1.begin(9600);
uint8_t error = 0;
error = paj7620Init(); // initialize Paj7620 registers
if (error)
{
Serial.print("INIT ERROR,CODE:");
Serial.println(error);
}
else
{
Serial.println("INIT OK");
}
Serial.println("Please input your gestures:\n");
pinMode(PIN_MODE, INPUT);
pinMode(AVR_LED, OUTPUT);
digitalWrite(AVR_LED, HIGH);////初始化,先把LED关掉
pinMode(Drive_AIN1, OUTPUT); //小车左侧两个电机控制,电机并联。AIN1=1=1,AIN2=0正转;AIN1=1=0,AIN2=1反转;AIN1=1=0,AIN2=0,停止
pinMode(Drive_AIN2, OUTPUT);
pinMode(Drive_PWMA, OUTPUT);//电机转速控制,0-255对应转速0-100%
pinMode(Drive_BIN1, OUTPUT); //小车右侧两个电机控制
pinMode(Drive_BIN2, OUTPUT);
pinMode(Drive_PWMB, OUTPUT);
digitalWrite(Drive_AIN1, LOW);//初始化
digitalWrite(Drive_AIN2, LOW);
analogWrite(Drive_PWMA,Speed_PWMA);
digitalWrite(Drive_BIN1, LOW);//初始化
digitalWrite(Drive_BIN2, LOW);
analogWrite(Drive_PWMB,Speed_PWMB);
}
void loop()
{
WorkMode=digitalRead(PIN_MODE);//获取工作模式
//接收手势指令
if(WorkMode==1) //本地模式,手势控制
{
digitalWrite(AVR_LED, HIGH);//LED灭
uint8_t data = 0, error;
error = paj7620ReadReg(0x43, 1, &data);
if (!error)
{
delay(50);
if (data==1) //手势向右,小车右转
{ iCarCmd=4; }
else if (data==2) //手势向左,小车左转
{ iCarCmd=3; }
else if (data==4) //手势向上,小车前进
{ iCarCmd=1; }
else if (data==8) //手势向下 小车后退
{ iCarCmd=2; }
else if ((data==16)||(data==32)) //手势靠近或远离传感器,小车停止
{ iCarCmd=5; }
}
delay(50);
}
else //WorkMode==0 远程遥控模式 接收蓝牙遥控指令
{
digitalWrite(AVR_LED, LOW);//LED亮
if (UART1.available())
{
carRcvData=UART1.read();
if ((carRcvData=='1') || (carRcvData=='2') || (carRcvData=='3') || (carRcvData=='4') || (carRcvData=='5'))
{carState=carRcvData;
Serial.println(carState);
}
if (carState=='1')
{iCarCmd=1; }
else if (carState=='2')
{ iCarCmd=2; }
else if (carState=='3')
{ iCarCmd=3;}
else if (carState=='4')
{iCarCmd=4;}
else if (carState=='5')
{iCarCmd=5; }
}
}
//控制小车运动
if (iCarCmd==1)
{
digitalWrite(Drive_AIN1, HIGH);
digitalWrite(Drive_AIN2, LOW);
analogWrite(Drive_PWMA,Speed_PWMA);
digitalWrite(Drive_BIN1, HIGH);
digitalWrite(Drive_BIN2, LOW);
analogWrite(Drive_PWMB,Speed_PWMB);
}
else if (iCarCmd==2)
{
digitalWrite(Drive_AIN1, LOW);
digitalWrite(Drive_AIN2, HIGH);
analogWrite(Drive_PWMA,Speed_PWMA);
digitalWrite(Drive_BIN1, LOW);
digitalWrite(Drive_BIN2, HIGH);
analogWrite(Drive_PWMB,Speed_PWMB);
}
else if (iCarCmd==3)
{
digitalWrite(Drive_AIN1, LOW);
digitalWrite(Drive_AIN2, HIGH);
analogWrite(Drive_PWMA,Speed_PWMA);
digitalWrite(Drive_BIN1, HIGH);
digitalWrite(Drive_BIN2, LOW);
analogWrite(Drive_PWMB,Speed_PWMB);
}
else if (iCarCmd==4)
{
digitalWrite(Drive_AIN1, HIGH);
digitalWrite(Drive_AIN2, LOW);
analogWrite(Drive_PWMA,Speed_PWMA);
digitalWrite(Drive_BIN1, LOW);
digitalWrite(Drive_BIN2, HIGH);
analogWrite(Drive_PWMB,Speed_PWMB);
}
else
{
digitalWrite(Drive_AIN1, LOW);
digitalWrite(Drive_AIN2, LOW);
analogWrite(Drive_PWMA,Speed_PWMA);
digitalWrite(Drive_BIN1, LOW);
digitalWrite(Drive_BIN2, LOW);
analogWrite(Drive_PWMB,Speed_PWMB);
}
}
遥控代码
#include "BluetoothSerial.h"
/*
#########################################################################
###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
C:\Users\Administrator\Documents\Arduino\libraries\TFT_eSPI
#########################################################################
*/
#include <TFT_eSPI.h> //TFT屏驱动
TFT_eSPI tft;
TFT_eSprite spr=TFT_eSprite(&tft);
int PIN_LED1 = D12;//蓝
int PIN_LED2 = D13;//绿
int PIN_LED3 = D2;//红
int LED_State;
int iAdd;
int PIN_Encoder = A0;//编码器引脚
int EncoderValue;
int PIN_PWM_IN = D3; //手柄引脚 也可以#define PIN_PWM_IN D3 //传感器的输出脚
int cycleTime,time_L,time_H;//PWM周期,低电平时长ms,高电平时长ms
float PWM_DutyCycle; //PWM占空比
float PWM_Frequency; //频率
float PWM_CycleTime; //周期
char CAR_State;//1-前进,2-后退,3-左转,4-右转,5-停止
BluetoothSerial SerialBT;
String MACadd = "00:21:07:00:0B:84";//00,21,07,00,0B,84 蓝牙MAC地址
uint8_t address[6] = {0x00, 0x21, 0x07, 0x00, 0x0B, 0x84};
String name = "LAOHU"; //蓝牙从站名称
const char *pin = "1234"; //<- 默认情况下将提供标准管脚
bool connected;
void setup() {
pinMode(PIN_LED1, OUTPUT);
pinMode(PIN_LED2, OUTPUT);
pinMode(PIN_LED3, OUTPUT);
pinMode(PIN_Encoder, INPUT);
pinMode(PIN_PWM_IN, INPUT);
tft.init();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
//spr.createSprite(TFT_HEIGHT,TFT_WIDTH);
spr.createSprite(128,128);
Serial.begin(9600);
SerialBT.begin("ESP32test", true);
Serial.println("The device started in master mode, make sure remote BT device is on!");
// 连接(address)速度快(最多10秒),连接(name)速度慢(最多30秒)
// 首先将名称解析为地址,但它允许连接到同名的不同设备。
// 将CoreDebugLevel设置为Info以查看设备蓝牙地址和设备名称
connected = SerialBT.connect(name);
if(connected) {
Serial.println("Connected Succesfully!");
} else {
while(!SerialBT.connected(10000)) {
Serial.println("Failed to connect. Make sure remote device is available and in range, then restart app.");
}
}
// disconnect() 最多可能需要10秒
if (SerialBT.disconnect()) {
Serial.println("Disconnected Succesfully!");
}
// 这将重新连接到名称(如果解析,将使用地址)或与connect一起使用的地址(名称/地址)。
SerialBT.connect();
}
void loop() {
spr.createSprite(128, 128);
spr.drawString("LED State",10,10); // spr.print("LED State:"); 带冒号会闪屏
spr.drawNumber(LED_State,80,10);
//处理编码器模拟量数值或按键按下后的模拟量电压值
EncoderValue = analogRead(PIN_Encoder);
spr.drawString("Encoder Value",10,30);
spr.drawNumber(EncoderValue,100,30);
//通过检测高低电平长度,计算周期
time_L = pulseIn(PIN_PWM_IN, LOW); //检测低电平的时间长度 ms
time_H = pulseIn(PIN_PWM_IN, HIGH);//检测高电平的时间长度 ms
cycleTime=time_L+time_H;
PWM_CycleTime=float(cycleTime);//周期
PWM_Frequency=10000.0/float(cycleTime);//频率
float f_timeL, f_timeH;
f_timeL=time_L/10.0;
f_timeH=time_H/10.0;
PWM_DutyCycle=f_timeH/(f_timeL+f_timeH); //占空比
spr.drawString("CycleTime",10,60);
spr.drawNumber(cycleTime,70,60);
spr.drawString("DutyCycle",10,90);
spr.drawFloat(PWM_DutyCycle,2,70,90);
spr.drawChar(CAR_State,110,110);
//动画显示 编码器模拟量
int EncoderRectWidth;
EncoderRectWidth= int(EncoderValue/40.95); //0-4095 转换成0-100
spr.fillRect(5, 45, EncoderRectWidth, 10, TFT_RED); //屏幕矩形显示电压 x坐标 y坐标 宽度 高度
//周期
int PwmT_RectWidth;
PwmT_RectWidth= int(222-PWM_CycleTime*0.047); //线性化 4700-2444 转换成0-100
spr.fillRect(5, 75, PwmT_RectWidth, 10, TFT_GREEN); //屏幕矩形显示电压 x坐标 y坐标 宽度 高度
//动画显示 PWM 占空比 Y轴
int PwmD_RectWidth;
PwmD_RectWidth= int((PWM_DutyCycle-0.31)*212.77); //线性化 0.33-0.80 转换成0-100
spr.fillRect(5, 105, PwmD_RectWidth, 10, TFT_BLUE); //屏幕矩形显示电压 x坐标 y坐标 宽度 高度
if ((PwmD_RectWidth>0) && (PwmD_RectWidth<40))
{CAR_State='1';//前进
LED_State=2; //绿灯
}
else if (PwmD_RectWidth>60)
{CAR_State='2';//后退
LED_State=3; //红灯
}
else if ((PwmT_RectWidth>0) && (PwmT_RectWidth<40))
{CAR_State='3';
LED_State=4; //黄灯
}
else if (PwmT_RectWidth>60)
{CAR_State='4';
LED_State=4; //黄灯
}
else
{CAR_State='5';
LED_State=1; //蓝灯
}
if (LED_State==1 )
{
digitalWrite(PIN_LED1, LOW);//低电平有效 蓝
digitalWrite(PIN_LED2, HIGH);
digitalWrite(PIN_LED3, HIGH);}
else if (LED_State==2 )
{
digitalWrite(PIN_LED1, HIGH);
digitalWrite(PIN_LED2, LOW);//绿
digitalWrite(PIN_LED3, HIGH);}
else if (LED_State==3 )
{
digitalWrite(PIN_LED1, HIGH);
digitalWrite(PIN_LED2, HIGH);
digitalWrite(PIN_LED3, LOW);}//红
else if (LED_State==4 )
{
digitalWrite(PIN_LED1, HIGH);
digitalWrite(PIN_LED2, LOW);//绿 红绿组合成黄灯
digitalWrite(PIN_LED3, LOW);//红
}
spr.pushSprite(0,0);
spr.deleteSprite();//刷新屏幕
SerialBT.write(CAR_State);
Serial.println(CAR_State);
delay(20);
}
4 演示效果
静止状态时,车体开发板两侧四个指示灯都熄灭,遥控器指示灯蓝色;
小车前进时,车体开发板两侧绿色指示灯点亮,遥控器指示灯绿色;
小车后退时,车体开发板两侧红色指示灯点亮,遥控器指示灯红色;
小车左转时,车体开发板左侧红色指示灯点亮,右侧绿色指示灯点亮,遥控器指示灯黄色;
小车右转时,车体开发板左侧绿色指示灯点亮,右侧红色指示灯点亮,遥控器指示灯黄色。
5 心得体会
蓝牙模块的波特率需设置一致,开始时HC-05使用的默认波特率38400,而手机蓝牙和ESP32-E都是9600,测试时出现收发数据不一致的情况,后来重新设置了HC-05的波特率为9600,解决了上述问题。
蓝牙数据过滤、数据格式。开发板接收蓝牙数据时,应进行过滤,只接收1、2、3、4、5等指定数据,并以字符形式发送,防止受到干扰。使用蓝牙透明传输和BLE还是有所区别,BLE相当于带协议,可以直接对应数据,如果用蓝牙传输多个数据时,还需要自己定义数据格式。
Arduino对AVR64DD32开发板编程时,建议使用版本1.8.19,使用高版本时会出现无法下载的问题。AVR64DD32使用硬件串口时,有时串口无输出,需改用softwareSerial再定义一次,具体原因不明。