一、 项目概述
本项目为参加2025贸泽电子M-Design创意设计竞赛而设计,任务为方向三:无线通信、物联网。项目主要涉及ESP32平台下的蓝牙通讯应用。
本项目展示了一个典型的遥控车控制系统,该系统主要包括遥控和车体两部分。用户通过操作遥控手柄,可以控制小车前进、后退、转向和停止等动作,同时小车会进行语音播报。
本项目使用乐鑫科技的ESP32-S3-DevKitC-1和ESP32-S3-BOX-Lite开发板, 分别作为车体和遥控的控制板,通过蓝牙低功耗(BLE)技术实现无线控制,结合了电机控制、语音播放和屏幕显示等功能。
实物图
二、 硬件设计
下图是基于ESP32-S3系列开发板设计的遥控车控制系统的架构图。
系统原理框图
系统硬件包括车体和遥控两部分,二者通过蓝牙低功耗(BLE)进行通信。车体部分的ESP32-S3-DevKitRC-1 开发板作为BLE Client,遥控部分的ESP32-S3-BOX-Lite 开发板作为BLE Server。 遥控发送控制指令到车体,车体根据指令控制电机动作和语音播放。
车体部分
ESP32-S3-DevKitRC-1 开发板作为蓝牙客户端(BLE Client),负责接收来自遥控部分的指令。
开发板通过I2C接口连接OLED单色屏。通过PWM1和PWM2接口连接电机驱动板,可实现调速控制。通过UART接口连接语音模块。语音模块连接扬声器,用于语音播放。
OLED 单色屏通过I2C接口与ESP32-S3-DevKitRC-1 开发板连接,用于显示接收到的手柄指令信息。
电机驱动板有两路输出,分别连接左边两个电机和右边两个电机,通过PWM信号控制电机的速度和方向。
语音模块通过UART接口与ESP32-S3-DevKitRC-1 开发板连接,用于语音播放遥控指令。
遥控部分
ESP32-S3-BOX-Lite 开发板作为蓝牙服务器(BLE Server),负责发送控制指令到车体。通过ADC1和ADC2接口连接双轴手柄,用于检测手柄位置,控制小车速度和方向。
元件清单
序号 | 型号 | 名称 | 数量 | 生产单位 | 备注 |
1 | ESP32-S3-DevKitC-1 | ESP32-S3开发板 | 1 | Espressif(乐鑫) |
|
2 | ESP32-S3-BOX-Lite | ESP32-S3-BOX开发板 | 1 | Espressif(乐鑫) |
|
3 | DRI0044 | 微型双路直流电机 驱动模块 | 1 | DFRobot | 芯片TB6612FNG |
4 | JQ8900-16P | MP3模块 | 1 | 国产(淘宝) |
|
5 | 0.96寸单色屏 128*64 | OLED单色屏 (I2C接口) | 1 | 国产(淘宝) | 驱动芯片SSD1306 |
6 | EXEB-F1 | 支持ESP32和XIAO 的扩展板 | 1 | 自制 | 此前自制PCB |
7 | 小车底盘 | 小车底盘 | 1 | 国产(淘宝) |
|
8 | 双轴手柄 | 双轴手柄 | 1 | 国产(淘宝) |
|
主要元件介绍
ESP32-S3-DevKitC-1
ESP32-S3-DevKitC-1 是乐鑫科技推出的一款入门级开发板,搭载 Wi-Fi + Bluetooth® LE 模组 ESP32-S3-WROOM-1、ESP32-S3-WROOM-1U 或 ESP32-S3-WROOM-2。本项目使用的模组通过贸泽电子采购,搭载的ESP32-S3-WROOM-2模组。
板上模组的大部分管脚均已引出至开发板两侧排针,开发人员可根据实际需求,轻松通过跳线连接多种外围设备,也可将开发板插在面包板上使用。
ESP32-S3-BOX-Lite
ESP32-S3-BOX-Lite 是 ESP32-S3 的 BOX 系列 AIoT 应用开发板,搭载了 ESP32-S3 Wi-Fi + Bluetooth 5 (LE)模块,是一款轻量级 AI 语音开发套件。 ESP32-S3-BOX-Lite 开发套件配备了一块 2.4 寸 LCD 显示屏、双麦克风、一个扬声器、两个用于硬件拓展的 Pmod™ 兼容接口和3个独立按键,可构建多样的 HMI 人机交互应用。
这款开发板特别适合用于开发智能家居设备,它不仅支持智能语音识别,还具备按键控制、传感器、红外控制和智能Wi-Fi网关等多种功能,能够作为全屋设备的控制中心。
DRI0044
DFRobot微型双向直流电机驱动器是基于TB6612FNG电机驱动IC设计的。它继承了DFRobot L298N电机控制逻辑,只需要四个引脚就可以驱动两个电机。与IC分线板相比,它节省了两个宝贵的GPIO资源。
TB6612FNG是一款双通道全桥驱动芯片。单通道的最大连续驱动电流可以达到1.2A,峰值2A/3.2A(连续脉冲/单脉冲),可以驱动一些微型直流电机。控制逻辑与L298N相似,代码可以直接兼容DFROBOT L298N电机驱动器。标准的XH2.54引脚可以直接插入面包板。它是DIY项目或产品开发的一个好选择。
本项目使用的电机驱动模块也是通过贸泽电子采购。
JQ8900-16P
本模块主要功能是基于单片机利用串口给语音模块发送命令,并且能够用按键来实现语音播放功能。此模块具有USB口,可通过数据线连接到电脑,通过U盘方式直接更换语音文件,使用非常方便。
EXEB-F1扩展板
扩展板是本人以前自制的PCB,本项目中取消了原板子的电源和工业通讯电路,仅保留接插件。扩展板类似一个专用的“面包板”,可以快速将ESP32-S3-DevKitC-1开发板和DRI0044电机驱动板、JQ8900-16P语音模块、OLED单色显示屏等进行快速绑定,省去传统面包板飞线的烦恼。
硬件实物
车体控制板
遥控板
遥控板只有ESP32-S3-BOX-Lite和手柄,使用面包板简单焊接即可。注意手柄的供电电压为3.3V,手柄由开发板供电。手柄的两路ADC电压信号输入到ESP32-S3-BOX-Lite的GPI011和GPI012.
三、 软件设计
3.1 程序简介
软件开发使用Arduino编程。程序包括车体程序和遥控程序两部分。
3.2 车体程序设计
车体程序主要包括蓝牙接收、状态显示、电机控制、音乐播放等四部分。通过蓝牙接收指令来控制小车的运动,并在OLED显示屏上显示当前状态,MP3播放控制指令,PWM控制电机速度。
下面是对程序结构的介绍
1. 包含头文件和定义宏:
• 包含了用于蓝牙通信和OLED显示屏的库文件。
#include <ArduinoBLE.h>
#include <U8g2lib.h>
• 定义了一些宏,如I2C引脚、LED通道、定时器精度、基础频率等。
#define LEDC_CHANNEL_0 0
#define LEDC_CHANNEL_1 1
// use 12 bit precission for LEDC timer
#define LEDC_TIMER_12_BIT 12
// use 5000 Hz as a LEDC base frequency
#define LEDC_BASE_FREQ 5000
// fade LED PIN (replace with LED_BUILTIN constant for built-in LED)
#define LEDA_PIN 2 //A电机PWM脚
#define Motor_AIN2 1
#define LEDB_PIN 41 //B电机PWM脚
#define Motor_BIN2 42
2. OLED显示屏初始化:
• 使用U8G2_SSD1306_128X64_NONAME_F_HW_I2C类初始化OLED显示屏。
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/13, /* data=*/14); // 单色I2C屏引脚定义
3. LED PWM通道设置:
• 设置了两个LED通道,用于控制电机的速度。
4. 电机控制引脚定义:
• 定义了电机的PWM控制引脚和方向控制引脚。
5. 全局变量定义:
• 定义了一些全局变量,如电机速度、方向、小车状态、蓝牙接收到的遥控手柄的X和Y值等。
6. MP3播放控制数据:
• 定义了一些用于控制MP3播放的十六进制数据数组。
7. setup() 函数:
• 初始化串口通信。
• 初始化MP3播放模块。
Serial1.begin(9600, SERIAL_8N1, 18, 17);//uart1口播放MP3
while (!Serial1);
delay(1000);
Serial1.write(hexdata_sound30,5);//设置音量等级,数值越大,音量越大
• 初始化LED PWM通道和电机方向控制引脚。
• 初始化OLED显示屏。
u8g2.begin();
u8g2.enableUTF8Print();
u8g2.setFont(u8g2_font_wqy16_t_gb2312);
• 开始蓝牙扫描。
if (!BLE.begin()) {
Serial.println("starting Bluetooth® Low Energy module failed!");
while (1);
}
Serial.println("Bluetooth® Low Energy Central - SensorTag button");
Serial.println("Make sure to turn on the device.");
// start scanning for peripheral
BLE.scan();
8. loop()函数:
• 检查是否有蓝牙设备被发现。如果发现名为"ESP32-S3-BOX"的设备,停止扫描并尝试连接。
if (peripheral.localName() == "ESP32-S3-BOX") {
// stop scanning
BLE.stopScan();
connectToBleServer(peripheral);
// peripheral disconnected, start scanning again
BLE.scan();
}
9. connectToBleServer() 函数:
• 连接到蓝牙设备并发现服务。
• 订阅蓝牙设备的特性,以便接收数据更新。
• 在连接期间,不断检查特性值的更新,并根据更新调用getCarCmd() 、 u8g2Display() 和 ControlCar() 子程序。
10. getCarCmd() :
根据蓝牙接收到的X和Y值,确定小车的状态(前进、后退、左转、右转或停止)。
void getCarCmd()
{
if ((iBle_Y<115) && (iBle_X>=115) && (iBle_X<=140))
{ iCarState=1;//前进
}
else if ((iBle_Y>140) && (iBle_X>=115) && (iBle_X<=140))
{iCarState=2;//后退
}
else if ((iBle_Y>=115) && (iBle_Y<=140) && (iBle_X<115))
{ iCarState=3;//向左原地打转
}
else if ((iBle_Y>=115) && (iBle_Y<=140) && (iBle_X>140))
{iCarState=4;//向右原地打转
}
else
{
iCarState=0;
}
}
11. u8g2Display() :
在OLED显示屏上显示小车的状态和蓝牙接收到的X和Y值。
void u8g2Display()
{
u8g2.enableUTF8Print(); //中文字体需要有此行,否则只能显示字母
u8g2.setFont(u8g2_font_wqy16_t_gb2312);//设置中文字体16
u8g2.setFontDirection(0);
u8g2.firstPage();
do {
if (iCarState==1)
{
u8g2.drawUTF8(50,40,"前进");
}
else if(iCarState==2)
{
u8g2.drawUTF8(50,40,"后退");
}
else if(iCarState==3)
{
u8g2.drawUTF8(50,40,"左转");
}
else if(iCarState==4)
{
u8g2.drawUTF8(50,40,"右转");
}
else
{
u8g2.drawUTF8(50,40,"停止");
}
u8g2.setFont(u8g2_font_wqy12_t_gb2312);//设置中文字体12
u8g2.drawUTF8(10,10,"X=");
u8g2.setCursor(25, 10);//设置X,Y起始坐标
u8g2.print(iBle_X);
u8g2.drawUTF8(50,10,"Y=");
u8g2.setCursor(65, 10);//设置X,Y起始坐标
u8g2.print(iBle_Y);
} while ( u8g2.nextPage() );
}
12. ledcAnalogWrite() :
用于设置PWM通道的占空比,从而控制电机速度。
void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255) {
// calculate duty, 4095 from 2 ^ 12 - 1
uint32_t duty = (4095 / valueMax) * min(value, valueMax);
// write duty to LEDC
ledcWrite(channel, duty);
}
13. MP3Control() :
根据小车的状态,控制MP3播放不同的音乐。
void MP3Control()
{
if (iCarState==1)
{ if(playMp3ID[0])
{Serial1.write(hexdata_music7,6); //播放 前进
playMp3ID[0]=false;
playMp3ID[1]=true;
playMp3ID[2]=true;
playMp3ID[3]=true;
}
}
else if (iCarState==2)
{ if(playMp3ID[1])
{Serial1.write(hexdata_music8,6);//播放 后退
playMp3ID[0]=true;
playMp3ID[1]=false;
playMp3ID[2]=true;
playMp3ID[3]=true;
}
}
else if (iCarState==3)
{ if(playMp3ID[2])
{Serial1.write(hexdata_music9,6);//播放 左转
playMp3ID[0]=true;
playMp3ID[1]=true;
playMp3ID[2]=false;
playMp3ID[3]=true;
}
}
else if (iCarState==4)
{ if(playMp3ID[3])
{Serial1.write(hexdata_music10,6);//播放 右转
playMp3ID[0]=true;
playMp3ID[1]=true;
playMp3ID[2]=true;
playMp3ID[3]=false;
}
}
else
{
playMp3ID[0]=true;
playMp3ID[1]=true;
playMp3ID[2]=true;
playMp3ID[3]=true;
}
}
14. ControlCar() :
根据小车的状态,设置电机的方向和速度。
void ControlCar()
{
//向前直行 比例控制
if (iCarState==1)
{
Dir_MotorA=LOW;
Dir_MotorB=LOW;
Speed_MotorA=(int)((255-iBle_Y)*0.6);
Speed_MotorB=(int)((255-iBle_Y)*0.6);
}
//后退
else if (iCarState==2)
{
Dir_MotorA=HIGH;
Dir_MotorB=HIGH;
Speed_MotorA=100;
Speed_MotorB=100;
}
//左转
else if (iCarState==3)
{
Dir_MotorA=HIGH;
Dir_MotorB=LOW;
Speed_MotorA=100;
Speed_MotorB=100;
}
//右转
else if (iCarState==4)
{
Dir_MotorA=LOW;
Dir_MotorB=HIGH;
Speed_MotorA=100;
Speed_MotorB=100;
}
else
{
Dir_MotorA=LOW;
Dir_MotorB=LOW;
Speed_MotorA=0;
Speed_MotorB=0;
}
digitalWrite(Motor_AIN2, Dir_MotorA);
ledcAnalogWrite(LEDC_CHANNEL_0, Speed_MotorA);
digitalWrite(Motor_BIN2, Dir_MotorB);
ledcAnalogWrite(LEDC_CHANNEL_1, Speed_MotorB);
}
整体来看,这个程序通过蓝牙接收指令来控制小车的运动,并通过OLED显示屏显示当前状态。同时,程序还包含了MP3播放控制功能,可以根据小车的状态播放不同的语音。程序结构清晰,功能模块化,易于理解和维护。
3.3遥控程序设计
遥控器使用ESP32-S3-BOX开发板,开发板外接1个双轴手柄,采集X轴和Y轴的电压信号。遥控器作为蓝牙BLE Server,主要功能是通过蓝牙低功耗(BLE)与车体通信,将手柄指令发送到车体,并在遥控器TFT屏幕上显示手柄的模拟信号值及通讯状态。
下面是代码的结构和功能的详细分析:
包含的库和定义
#include <ArduinoBLE.h>
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
包含Arduino BLE库,用于蓝牙通信。
包含TFT屏幕驱动库。
创建TFT屏幕对象。
创建TFT精灵对象,用于在屏幕上绘制图形。
定义引脚和变量
const int analogPin_X = 11;
const int analogPin_Y = 12;
定义开发板模拟量输入引脚X和Y,使用GPIO11和GPIO12.
int handleX, handleY;
uint8_t BleSend_X, BleSend_Y;
定义变量分别存储手柄的XY电压信号和要发送的蓝牙数据,范围0-255。
蓝牙服务和特性
BLEService MyService("181A");
BLEUnsignedIntCharacteristic Character_X("2A88", BLERead | BLEWrite | BLENotify);
BLEUnsignedIntCharacteristic Character_Y("2A89", BLERead | BLEWrite | BLENotify);
定义BLE服务,UUID为"181A"。
定义两个BLE特性,支持读取和写入数据,并支持发送通知。
读取手柄值函数 readHandleValue()
使用 analogRead 函数读取手柄的模拟信号值。
将读取的值除以16,得到0-255范围内的值,存储在 BleSend_X 和 BleSend_Y 中。
void readHandleValue()
{
handleX = analogRead(analogPin_X);//0-4095
handleY = analogRead(analogPin_Y);//0-4095
BleSend_X=handleX/16;
BleSend_Y=handleY/16;
// 将读取的值打印到串行监视器 实际应用时,不要打印,不用串口监控时,会影响实时性
/*
Serial.print("X is "); // 打印字符串
Serial.println(BleSend_X);
Serial.print("y is "); // 打印字符串
Serial.println(BleSend_Y);
*/
}
显示TFT屏幕函数 TFT_DisplayValue()
建立通讯时,清屏并绘制绿色矩形,显示"BLE Connect OK"。
显示X和Y的值,使用不同的颜色和字体大小。
void TFT_DisplayValue()
{
spr.fillSprite(TFT_WHITE);//清屏
spr.fillRect(20, 10, 280, 40, TFT_GREEN); // 绘制一个绿色实心矩形
spr.setTextColor(TFT_BLACK);
spr.setTextSize(2);
spr.drawString("BLE Connect OK",30,20);
//显示X
spr.setTextColor(TFT_BLACK);
spr.setTextSize(2);
spr.drawString("X=",30,100);
spr.setTextColor(TFT_BLUE);
spr.setTextSize(2);
spr.drawNumber(BleSend_X,30,120);
//显示Y
spr.setTextColor(TFT_BLACK);
spr.setTextSize(2);
spr.drawString("Y=",200,100);
spr.setTextColor(TFT_BLUE);
spr.setTextSize(2);
spr.drawNumber(BleSend_Y,200,120);
spr.pushSprite(0,0);
//spr.deleteSprite();//不用反复创建和删除,否则会影响实时性。fillSprite()即可刷屏
}
显示TFT屏幕错误函数 TFT_DisplayError()
清屏并绘制红色矩形,当通讯断开时,显示"BLE Connect Failed"。
void TFT_DisplayError()
{
//通讯断开时,界面提醒
spr.fillSprite(TFT_WHITE);
spr.fillRect(20, 10, 280, 40, TFT_RED); // 绘制一个红色矩形
spr.setTextColor(TFT_BLACK);
spr.setTextSize(2);
spr.drawString("BLE Connect Failed",30,20);
spr.pushSprite(0,0);
}
启动函数setup()
初始化串口通信。
初始化TFT屏幕和精灵。
配置BLE设备,设置本地名称、广告服务和特性。
开始广播,等待连接。
void setup() {
Serial.begin(9600);
tft.init();
tft.setRotation(3); //设置屏幕方向
spr.createSprite(320,240);
spr.fillSprite(TFT_WHITE);
spr.setTextColor(TFT_BLUE);
spr.setTextSize(2);
spr.drawString("Wait BLE Connect...",30,50);
spr.pushSprite(0,0);
if (!BLE.begin())
{Serial.println("BLE failed to Initiate");
delay(500);
while (1);
}
BLE.setLocalName("ESP32-S3-BOX"); //蓝牙设备名称
BLE.setAdvertisedService(MyService);
MyService.addCharacteristic(Character_X);
MyService.addCharacteristic(Character_Y);
BLE.addService(MyService);
BLE.advertise();
Serial.println("Bluetooth device is now active, waiting for connections...");
}
循环函数 loop()
检查是否有中央设备连接。
如果连接成功,读取手柄值,将数据写入BLE特性,并在TFT屏幕上显示。
如果断开连接,显示错误信息。
void loop()
{
BLEDevice central = BLE.central();
if (central)
{
Serial.print("Connected to central: ");
Serial.println(central.address());
while (central.connected())
{
readHandleValue(); //读取手柄数值
Character_X.writeValue(BleSend_X); //将数据写入Character中
Character_Y.writeValue(BleSend_Y);
TFT_DisplayValue();//界面显示数值
}
TFT_DisplayError();
}
}
总结
这段代码实现了一个基于ESP32-S3的蓝牙低功耗通信系统,通过TFT屏幕显示手柄的模拟信号值。它包括了BLE服务和特性的配置、手柄值的读取和处理、以及TFT屏幕的显示功能。代码结构清晰,功能明确,适用于需要蓝牙通信和图形显示的项目。
四、 遇到的问题及处理
1. ESP32-S3-DevkitC-1开发板,Arduino下载程序后不运行问题
ESP32-S3-DevkitC-1开发板,出厂可能焊接 ESP32-S3-WROOM-1、ESP32-S3-WROOM-1U 或 ESP32-S3-WROOM-2等不同模组。不同模组内部的FLASH芯片和主控连接接口不同,需要参考模组手册,再在Arduino中选择不同设置。
模组ESP32-S3-WROOM-1U外挂的是QSPI Flash,所以选QIO
模组ESP32-S3-WROOM-2外挂的是OSPI Flash,所以选OPI
2.通讯延时处理
通讯机制:以前做的BLE遥控车,车体做BLE Server,遥控做BLE Client,需要遥控一直对车体写数据,影响实时性;
这次项目,车体做BLE Client ,遥控做BLE Server,BLE Server的UUID具有Notify属性支持订阅,车体订阅Server的通知,数据变化时,可以快速响应。
数据处理过程中,浮点运算、串口打印等都会影响到实时性,需要运用数据类型转换,并且最后注释掉串口调试的代码。
- 调试过程中,借助手机测试。BLE Server编程完成后,先使用手机App nRF Connect搜索BLE Server并连接,确保连接和通讯正常,然后再用客户端测试。
五、 演示效果
遥控器显示
遥控器上电时,显示正在连接提示。
当BLE连接成功时,在TFT屏幕上显示BLE连接状态和手柄的X、Y轴值。
当BLE连接失败时,显示"BLE Connect Failed"。
车体显示
具体控制功能演示,详见视频。
六、 心得体会
1. 这次项目使用的ESP32-S3-DevKitC-1和ESP32-S3-BOX-Lite开发板,都是乐鑫科技官方的开发板,都采用ESP32-S3芯片或模组。程序可维护性强,两款ESP32开发板都支持Arduino BLE库,库文件统一,测试中也可以进行互换。
以前做的BLE遥控项目中,服务器和客户端硬件处理器不统一,库文件不统一,调试中会遇到很多问题。
这次,开始使用BLE 订阅功能,并对通讯实时性做了优化。
2. 这个项目既注重实用性,也考虑到了学习价值,使用了此前自制的EXEB-F1扩展板,但其体积相对较大。如果希望设计得更加紧凑,可以考虑使用XIAO ESP32S3系列开发板来实现电机控制和遥控器的功能,这样更节省空间。
3.本项目功能丰富,综合性强,包括蓝牙通讯、ADC采集、PWM控制、I2C接口OLED驱动、SPI接口彩色屏显示、UART串口放音等功能开发,适用于各种智能车项目和教育应用。