M-Design设计竞赛-基于ESP32-S3系列开发板实现的BLE遥控车
该项目使用了乐鑫科技的ESP32-S3系列开发板,实现了BLE遥控车的设计,它的主要功能为:用户通过操作遥控手柄,可以控制小车前进、后退、转向和停止等动作,并且小车会进行语音播报。
标签
BLE
蓝牙
ESP32-S3
PWM控制
遥控车
语音播报
M-Design设计竞赛
chinaking
更新2025-04-02
48

一、  项目概述

本项目为参加2025贸泽电子M-Design创意设计竞赛而设计,任务为方向三:无线通信、物联网。项目主要涉及ESP32平台下的蓝牙通讯应用。

本项目展示了一个典型的遥控车控制系统,该系统主要包括遥控和车体两部分。用户通过操作遥控手柄,可以控制小车前进、后退、转向和停止等动作,同时小车会进行语音播报。

本项目使用乐鑫科技的ESP32-S3-DevKitC-1ESP32-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单色屏。通过PWM1PWM2接口连接电机驱动板,可实现调速控制。通过UART接口连接语音模块。语音模块连接扬声器,用于语音播放。

OLED 单色屏通过I2C接口与ESP32-S3-DevKitRC-1 开发板连接,用于显示接收到的手柄指令信息。

电机驱动板有两路输出,分别连接左边两个电机和右边两个电机,通过PWM信号控制电机的速度和方向。

语音模块通过UART接口与ESP32-S3-DevKitRC-1 开发板连接,用于语音播放遥控指令。

遥控部分

 ESP32-S3-BOX-Lite 开发板作为蓝牙服务器(BLE Server),负责发送控制指令到车体。通过ADC1ADC2接口连接双轴手柄,用于检测手柄位置,控制小车速度和方向。

元件清单

序号

型号

名称

数量

生产单位

备注

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

支持ESP32XIAO

的扩展板

1

自制

此前自制PCB

7

小车底盘

小车底盘

1

国产(淘宝)

 

8

双轴手柄

双轴手柄

1

国产(淘宝)

 

 

主要元件介绍

ESP32-S3-DevKitC-1

ESP32-S3-DevKitC-1 是乐鑫科技推出的一款入门级开发板,搭载 Wi-Fi + Bluetooth® LE 模组 ESP32-S3-WROOM-1ESP32-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-LiteGPI011GPI012.

 

三、 软件设计

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.     全局变量定义:

定义了一些全局变量,如电机速度、方向、小车状态、蓝牙接收到的遥控手柄的XY值等。

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() 

根据蓝牙接收到的XY值,确定小车的状态(前进、后退、左转、右转或停止)。

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显示屏上显示小车的状态和蓝牙接收到的XY值。

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; 

  定义开发板模拟量输入引脚XY,使用GPIO11GPIO12.

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"

显示XY的值,使用不同的颜色和字体大小。

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-1ESP32-S3-WROOM-1U ESP32-S3-WROOM-2等不同模组。不同模组内部的FLASH芯片和主控连接接口不同,需要参考模组手册,再在Arduino中选择不同设置。

image.pngimage.png

模组ESP32-S3-WROOM-1U外挂的是QSPI Flash,所以选QIO

模组ESP32-S3-WROOM-2外挂的是OSPI Flash,所以选OPI

2.通讯延时处理

  通讯机制:以前做的BLE遥控车,车体做BLE Server,遥控做BLE Client,需要遥控一直对车体写数据,影响实时性;

这次项目,车体做BLE Client ,遥控做BLE ServerBLE Server的UUID具有Notify属性支持订阅,车体订阅Server的通知,数据变化时,可以快速响应。

  数据处理过程中,浮点运算、串口打印等都会影响到实时性,需要运用数据类型转换,并且最后注释掉串口调试的代码。

  1. 调试过程中,借助手机测试。BLE Server编程完成后,先使用手机App nRF Connect搜索BLE Server并连接,确保连接和通讯正常,然后再用客户端测试。

五、 演示效果

遥控器显示

遥控器上电时,显示正在连接提示。

BLE连接成功时,TFT屏幕上显示BLE连接状态和手柄XY轴值

BLE连接失败时,显示"BLE Connect Failed"

车体显示

具体控制功能演示,详见视频。

六、 心得体会

1. 这次项目使用的ESP32-S3-DevKitC-1ESP32-S3-BOX-Lite开发板,都是乐鑫科技官方的开发板,都采用ESP32-S3芯片或模组。程序可维护性强,两款ESP32开发板都支持Arduino BLE库,库文件统一,测试中也可以进行互换。

以前做的BLE遥控项目中,服务器和客户端硬件处理器不统一,库文件不统一,调试中会遇到很多问题。

这次,开始使用BLE 订阅功能,并对通讯实时性做了优化。

2. 这个项目既注重实用性,也考虑到了学习价值,使用了此前自制的EXEB-F1扩展板,但其体积相对较大。如果希望设计得更加紧凑,可以考虑使用XIAO ESP32S3系列开发板来实现电机控制和遥控器的功能,这样更节省空间。

3.本项目功能丰富,综合性强,包括蓝牙通讯、ADC采集、PWM控制、I2C接口OLED驱动、SPI接口彩色屏显示、UART串口放音等功能开发,适用于各种智能车项目和教育应用。

软硬件
电路图
附件下载
BLE_Server_ESP32-S3-BOX1.0.3.ino
遥控器程序
BLE_Client_Notify_ESP32-S3-DevkitC1.0.4.ino
车体程序
团队介绍
老胡,自动化工程师,电子爱好者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号