一、概述
本文设计了一个蓝牙遥控小车控制系统,系统主要包含车身和遥控器两部分。
车体的控制板为Arduino Nano33 BLE,作为BLE Server,用来接收BLE信号和驱动电机;遥控采用的Wio Terminal,作为BLE Client,主要是采集手柄的模拟量信号,进行显示并通过BLE发送。
系统实现了小车的前进、后退、转向、加减速等控制功能,结构简单,具有一定的趣味性。
二、硬件设计
系统硬件包括车身和遥控器两部分。
2.1车身硬件设计
车身硬件主要元件如下表所示
序号 |
名称 |
数量 |
1 |
Arduino Nano33 BLE开发板 |
1 |
2 |
TB6612FNG电机驱动板 |
1 |
3 |
直流电机 |
4 |
车身的硬件连接图如下图所示:
车身的电源为DC5V,由充电宝供电。Arduino Nano33 BLE接收到遥控器的控制信号,通过IO端口输出,分成两路到电机驱动板的输入。
电机驱动板的AIN1,AIN2,PWMA为第1路输入;BIN1,BIN2,PWMB为第2路输入。AO1,AO2为第1路输出;BO1,BO2为第2路输出。
第1路输出接左侧两个电机,第2路输出接右侧两个电机。同侧的两个电机并联在一起。
以第1路为例,电机方向由AIN1和AIN2控制。当AIN1=1,AIN2=0,电机正转;当AIN1=0,AIN2=1,电机反转;当AIN1=0,AIN2=0,电机停止。通过PWMA端口接收的数值,可以控制电机转速。
因此,当左侧和右侧电机方向和速度都相同时,小车前进或后退;当速度不同时,通过差速可以实现车身转向控制。
2.2遥控硬件设计
遥控主要包括Wio Terminal和双轴手柄各1个。硬件连接图如下:
双轴手柄的GND、VCC、X、Y分别连接Wio Terminal的GND、3.3V、A0、A1。
Wio Terminal对手柄模拟量信号进行AD转换,将0-3.3V电压信号,转换成0-1024。经过程序线性处理后,由BLE发送给车身的控制器。
三、软件设计
车身主要代码
#include <ArduinoBLE.h>
#include <math.h>
BLEService MyService("181A");
BLEUnsignedIntCharacteristic Character_X("2A88", BLEWrite | BLENotify);
BLEUnsignedIntCharacteristic Character_Y("2A89", BLEWrite | BLENotify);
//byte iCarState=0;//小车状态 0-停止 1-前进 2-后退 3-左 4-右 BLE通信接口由 接受一个变量 前后左右,改成接受两个变量 X和Y手柄值,0-255变化
int16_t iBle_X=120;//零位
int16_t iBle_Y=120;
int Speed;
void setup() {
Serial.begin(9600);
//while (!Serial);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
if (!BLE.begin()) {
Serial.println("BLE failed to Initiate");
delay(500);
while (1);
}
BLE.setLocalName("Arduino Nano 33 BLE Sensors");
BLE.setAdvertisedService(MyService);
//MyService.addCharacteristic(temperatureChar);
MyService.addCharacteristic(Character_X);
MyService.addCharacteristic(Character_Y);
BLE.addService(MyService);
Character_X.readValue(iBle_X);
Character_Y.readValue(iBle_Y);
BLE.advertise();
Serial.println("Bluetooth device is now active, waiting for connections...");
}
void loop() {
BLEDevice central = BLE.central();
if (central) {
Serial.print("Connected to central: ");
Serial.println(central.address());
digitalWrite(LED_BUILTIN, HIGH);
while (central.connected()) {
delay(10);
Character_X.readValue(iBle_X);
Character_Y.readValue(iBle_Y);
Serial.println("X: ");
Serial.println(iBle_X);
Serial.println("Y: ");
Serial.println(iBle_Y);
//中间死区 静止
if ( (iBle_Y>=100) && (iBle_Y<=130) && (iBle_X>=100) && (iBle_X<=130)) {
Speed=0;
digitalWrite(2, LOW);
digitalWrite(3, LOW);
analogWrite(4, Speed);//Speed值0-255对应占空比 0-100
digitalWrite(5, LOW);
digitalWrite(6, LOW);
analogWrite(7, Speed);
}
//向前直行 比例控制
else if ((iBle_Y>0) && (iBle_Y<100) && (iBle_X>=100) && (iBle_X<=130)) {
Speed=230-iBle_Y;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,Speed);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, Speed);
}
//向后直行 比例控制
else if ((iBle_Y>=130) && (iBle_X>=100) && (iBle_X<=130)) {
Speed=iBle_Y;
digitalWrite(2, LOW);
digitalWrite(3, HIGH);
analogWrite(4,Speed);
digitalWrite(5, LOW);
digitalWrite(6, HIGH);
analogWrite(7, Speed);
}
//向左原地打转
else if ((iBle_Y>=100) && (iBle_Y<=130) && (iBle_X>0) && (iBle_X<100)) {
//Speed=230-iBle_X; 可以不用比例
digitalWrite(2, LOW);
digitalWrite(3, HIGH);
analogWrite(4,120);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 120);
}
//向右原地打转 比例控制
else if ((iBle_Y>=100) && (iBle_Y<=130) && (iBle_X>130)) {
// Speed=iBle_X;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,120);
digitalWrite(5, LOW);
digitalWrite(6, HIGH);
analogWrite(7, 120);
}
//前进中左拐 低速 向左慢拐
else if ((iBle_Y>=50) && (iBle_Y<100) && (iBle_X>=50) && (iBle_X<100)) {
Speed=0;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,150);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 180);
}
//前进中左拐 低速 向左快拐
else if ((iBle_Y>=50) && (iBle_Y<100) && (iBle_X>=0) && (iBle_X<50)) {
Speed=0;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,120);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 180);
}
//前进中左拐 高速 向左慢拐
else if ((iBle_Y>0) && (iBle_Y<50) && (iBle_X>=50) && (iBle_X<100)) {
Speed=0;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,150);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 200);
}
//前进中左拐 高速 向左快拐
else if ((iBle_Y>0) && (iBle_Y<50) && (iBle_X>0) && (iBle_X<50) ) {
Speed=0;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,120);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 200);
}
//前进中右拐 低速 慢拐
else if ((iBle_Y>=50) && (iBle_Y<100) && (iBle_X>130) && (iBle_X<180)) {
Speed=0;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,180);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 150);
}
//前进中右拐 低速 快拐
else if ((iBle_Y>=50) && (iBle_Y<100) && (iBle_X>=180) ) {
Speed=0;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,180);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 120);
}
//前进中右拐 高速 慢拐
else if ((iBle_Y>0) && (iBle_Y<50) && (iBle_X>130) && (iBle_X<180)) {
Speed=0;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,200);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 150);
}
//前进中右拐 高速 快拐
else if ((iBle_Y>0) && (iBle_Y<50) && (iBle_X>=180)) {
Speed=0;
digitalWrite(2, HIGH);
digitalWrite(3, LOW);
analogWrite(4,200);
digitalWrite(5, HIGH);
digitalWrite(6, LOW);
analogWrite(7, 120);
}
else {
Speed=0;
digitalWrite(2, LOW);
digitalWrite(3, LOW);
analogWrite(4, Speed);
digitalWrite(5, LOW);
digitalWrite(6, LOW);
analogWrite(7, Speed);
}
}
}
digitalWrite(LED_BUILTIN, LOW);
Serial.print("Disconnected from central: ");
Serial.println(central.address());
}
遥控器主要代码
#include "rpcBLEDevice.h" //BLE库
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#include"TFT_eSPI.h" //LCD库
TFT_eSPI tft;
TFT_eSprite spr=TFT_eSprite(&tft);
#define IWIDTH 320
#define IHEIGHT 180
uint8_t ble_x;
uint8_t ble_y;
static BLEUUID serviceUUID(0x181A); //CAR service UUID
static BLEUUID characteristicUUID_x(0x2A88);//x characteristic UUID 手柄输出值(0-255)
static BLEUUID characteristicUUID_y(0x2A89);//y characteristic UUID
static boolean doConnect = false;//执行连接
static boolean isConnected = false;//已连接
static boolean doScan = false;//执行搜索Server
static BLERemoteService* pRemoteService;
static BLERemoteCharacteristic *pRemoteCharacteristic1;
static BLERemoteCharacteristic *pRemoteCharacteristic2;
static BLEClient* pClient = BLEDevice::createClient();
static BLEAdvertisedDevice* myDevice;
uint8_t bd_addr[6] = {0xB2, 0xAA, 0x9B, 0xDF, 0xD7, 0x60};//BLE Server的MAC地址,注意Aruduino里和手机APP搜到的MAC顺序是反的(60:D7:DF:9B:AA:98)
BLEAddress BattServer(bd_addr);
static void notifyCallback(
BLERemoteCharacteristic* pBLERemoteCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
Serial.print("Notify callback for characteristic ");
Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
Serial.print(" of data length ");
Serial.println(length);
Serial.print("data: ");
Serial.print(*(uint8_t *)pData);
}
class MyClientCallback : public BLEClientCallbacks {
void onConnect(BLEClient* pclient) {
}
void onDisconnect(BLEClient* pclient) {
isConnected = false;
Serial.println("onDisconnect");
}
};
bool connectToServer() {
Serial.print("Forming a connection to ");
Serial.println(myDevice->getAddress().toString().c_str());
pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - Connected to server");
isConnected = true;
return true;
}
bool disconnectToServer() {
pClient->disconnect(); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
Serial.println(" - DisConnected to server");
isConnected = false;
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice advertisedDevice) {
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if (memcmp(advertisedDevice.getAddress().getNative(),BattServer.getNative(), 6) == 0) {
Serial.print("BATT Device found: ");
Serial.println(advertisedDevice.toString().c_str());
BLEDevice::getScan()->stop();
Serial.println("new BLEAdvertisedDevice");
myDevice = new BLEAdvertisedDevice(advertisedDevice);
Serial.println("new BLEAdvertisedDevice done");
doConnect = true;
doScan = true;
} // onResult
}
}; // MyAdvertisedDeviceCallbacks
void setup() {
Serial.begin(115200);
pinMode(A0, INPUT);
pinMode(A1, INPUT);
tft.begin();
tft.setRotation(3);//设置屏幕方向
//绘制欢迎页
tft.fillScreen(TFT_WHITE);
tft.setTextColor(TFT_BLUE, TFT_WHITE);
tft.setTextSize(2);
tft.drawString("Connecting to",50,65);
tft.drawString("BLE Server...",50,95);
spr.createSprite(IWIDTH,IHEIGHT);//不知为何画布大小无法设置成 TFT_WIDTH,TFT_HEIGHT 即320,240
// while(!Serial){}; //不用串口监视时可注释掉
delay(2000);
Serial.println("Starting Arduino BLE Client application...");
BLEDevice::init("");
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setInterval(1349);
pBLEScan->setWindow(449);
pBLEScan->setActiveScan(true);
pBLEScan->start(5, false);
} // End of setup.
// This is the Arduino main loop function.
void loop() {
int x = analogRead(A0);//0-1024对应0-3.3V
int y = analogRead(A1);
float volt_x = x * (3.3 / 1024.0); //将返回值换算成电压 只表示换算关系,实际不用
float volt_y = y * (3.3 / 1024.0); //将返回值换算成电压
ble_x=x/4+1;//缩小BLE发送数据的长度,不超出unit8_t范围,控制在0-255之间
ble_y=y/4+1;
Serial.println("X = ");
Serial.println(ble_x);
Serial.println("Y = ");
Serial.println(ble_y);
//显示界面
//绘制标题栏
spr.fillSprite(TFT_WHITE);
spr.fillRect(0,0,320,50,TFT_BLUE);
spr.setTextColor(TFT_WHITE);
spr.setTextSize(3);
spr.drawString("BLE CAR",100,15);
//X
spr.setTextColor(TFT_BLACK);
spr.setTextSize(2);
spr.drawString("X",70,65);
spr.setTextSize(3);
spr.drawNumber(ble_x,50,95);
//Y
spr.setTextColor(TFT_BLACK);
spr.setTextSize(2);
spr.drawString("Y",220,65);
spr.setTextSize(3);
spr.drawNumber(ble_y,200,95);
if (doConnect == true) {
connectToServer();
doConnect = false;
}
if (isConnected ==true)
{
pRemoteCharacteristic1=pClient->getService(serviceUUID)->getCharacteristic(characteristicUUID_x);
pRemoteCharacteristic1->writeValue(ble_x);
pRemoteCharacteristic2=pClient->getService(serviceUUID)->getCharacteristic(characteristicUUID_y);
pRemoteCharacteristic2->writeValue(ble_y);
spr.setTextSize(2);
spr.drawString("ONLINE",200,150);
}
else
{
spr.setTextSize(2);
spr.drawString("OFFLINE",200,150);
}
spr.pushSprite(0,0);
} // End of loop
四、心得体会
1.使用Wio Terminal进行AD转换时,需要注意AD采样的电压范围,只能0-3.3V,使用5V时会超限。
2.合理确定BLE通讯接口。系统设计时尝试使用4个characterUUID传输手柄上、下、左、右数据时,发现延时较大,控制实时性严重下降;后改为2个characterUUID传输手柄X轴和Y轴数据,控制效果有明显提升。因此,要考虑“带宽”对通讯和控制的影响。
3.因程序分为车体程序和遥控程序两部分,需注意程序的版本对应关系。