
ESP32小车演示视频
本教程将指导您如何使用ESP32单片机控制一个智能小车,通过手机APP实现两种控制方式:
| 组件 | 数量 | 说明 |
|---|---|---|
| ESP32开发板 | 1个 | 主控制器 |
| L298N电机驱动模块 | 1个 | 驱动直流电机 |
| 直流电机(带轮子) | 2个 | 小车动力 |
| 18650锂电池(带电池盒) | 2节 | 供电 |
| 万用板/小车底盘 | 1个 | 安装所有组件 |
| 杜邦线(公对公、公对母) | 若干 | 连接线路 |
| 微型开关 | 1个 | 电源开关(可选) |
| 电机 | L298N接口 |
|---|---|
| 左电机正极 | OUT1 |
| 左电机负极 | OUT2 |
| 右电机正极 | OUT3 |
| 右电机负极 | OUT4 |
| ESP32引脚 | L298N接口 | 功能 |
|---|---|---|
| GPIO26 | IN1 | 左电机控制1 |
| GPIO27 | IN2 | 左电机控制2 |
| GPIO12 | IN3 | 右电机控制1 |
| GPIO13 | IN4 | 右电机控制2 |
| 3.3V | +5V | 逻辑电源 |
| GND | GND | 接地 |
| 组件 | 电源连接 |
|---|---|
| 电池正极 | L298N +12V |
| 电池负极 | L298N GND |
| ESP32 VIN | L298N +5V |
| ESP32 GND | L298N GND |
[电池] --> [L298N电源接口]
|
+---> [电机接口]
+---> [ESP32电源]
+---> [ESP32控制引脚]
打开Arduino IDE
点击 文件 > 首选项
在"附加开发板管理器网址"中添加:
https://dl.espressif.com/dl/package_esp32_index.json
点击 工具 > 开发板 > 开发板管理器
搜索"esp32",安装"ESP32 by Espressif Systems"
// 引入必要的库文件
#include <Arduino.h> // Arduino核心库,包含基本函数和数据类型
#include <BluetoothSerial.h> // ESP32蓝牙串口库,用于蓝牙通信
// 创建蓝牙串口对象,用于处理蓝牙通信
BluetoothSerial SerialBT;
// 电机控制引脚定义
// 左电机的两个控制引脚
const int MOTOR_LEFT_1 = 26; // 左电机控制引脚1
const int MOTOR_LEFT_2 = 27; // 左电机控制引脚2
// 右电机的两个控制引脚
const int MOTOR_RIGHT_1 = 12; // 右电机控制引脚1
const int MOTOR_RIGHT_2 = 13; // 右电机控制引脚2
// 状态LED引脚定义(ESP32板载LED)
const int STATUS_LED = 2; // 状态指示灯引脚
// 系统状态变量
bool systemEnabled = true; // 系统总开关,true表示系统已启动
bool emergencyStop = false; // 急停状态,true表示急停激活
bool bluetoothConnected = false; // 蓝牙连接状态
unsigned long lastCommandTime = 0; // 记录最后一次收到命令的时间
const unsigned long SAFETY_TIMEOUT = 5000; // 安全超时时间(5秒),超过此时间无指令则停止电机
// 数据接收相关变量
uint8_t gyroDataBuffer[4]; // 陀螺仪数据缓冲区,用于存储4字节的陀螺仪数据
int gyroBufferIndex = 0; // 陀螺仪缓冲区索引,记录当前存储位置
bool expectingGyroData = false; // 标志位,表示是否正在接收陀螺仪数据
unsigned long lastDataTime = 0; // 记录最后一次收到数据的时间
// 控制模式变量
bool gyroMode = true; // 陀螺仪模式开关,true表示启用陀螺仪控制
bool buttonMode = true; // 按钮模式开关,true表示启用按钮控制
// 电机控制状态
bool motorsActive = false; // 电机活动状态,true表示电机正在运转
// 函数声明部分(告诉编译器这些函数的存在)
void stopMotors(); // 停止所有电机
void testMotors(); // 测试电机功能
void moveForward(); // 前进
void moveBackward(); // 后退
void turnLeft(); // 左转
void turnRight(); // 右转
void spinLeft(); // 原地左转
void spinRight(); // 原地右转
void controlByJoystick(int x, int y); // 根据摇杆值控制电机
void parseBluetoothData(); // 解析蓝牙数据
void checkSafetyTimeout(); // 检查安全超时
void updateStatusLED(); // 更新状态指示灯
void processGyroData(uint8_t* data, int length); // 处理陀螺仪数据
void processButtonCommand(uint8_t cmd); // 处理按钮命令
// 停止所有电机函数
void stopMotors() {
// 将左电机的两个控制引脚都设为低电平,停止左电机
digitalWrite(MOTOR_LEFT_1, LOW);
digitalWrite(MOTOR_LEFT_2, LOW);
// 将右电机的两个控制引脚都设为低电平,停止右电机
digitalWrite(MOTOR_RIGHT_1, LOW);
digitalWrite(MOTOR_RIGHT_2, LOW);
motorsActive = false; // 更新电机状态为停止
}
// 电机测试函数
void testMotors() {
Serial.println("开始电机测试..."); // 在串口输出测试开始信息
// 测试左电机正转
Serial.println("测试左电机正转");
digitalWrite(MOTOR_LEFT_1, HIGH); // 设置左电机引脚1为高电平
digitalWrite(MOTOR_LEFT_2, LOW); // 设置左电机引脚2为低电平,电机正转
delay(1000); // 持续1秒
stopMotors(); // 停止电机
delay(500); // 等待0.5秒
// 测试左电机反转
Serial.println("测试左电机反转");
digitalWrite(MOTOR_LEFT_1, LOW); // 设置左电机引脚1为低电平
digitalWrite(MOTOR_LEFT_2, HIGH); // 设置左电机引脚2为高电平,电机反转
delay(1000); // 持续1秒
stopMotors(); // 停止电机
delay(500); // 等待0.5秒
// 测试右电机正转
Serial.println("测试右电机正转");
digitalWrite(MOTOR_RIGHT_1, HIGH); // 设置右电机引脚1为高电平
digitalWrite(MOTOR_RIGHT_2, LOW); // 设置右电机引脚2为低电平,电机正转
delay(1000); // 持续1秒
stopMotors(); // 停止电机
delay(500); // 等待0.5秒
// 测试右电机反转
Serial.println("测试右电机反转");
digitalWrite(MOTOR_RIGHT_1, LOW); // 设置右电机引脚1为低电平
digitalWrite(MOTOR_RIGHT_2, HIGH); // 设置右电机引脚2为高电平,电机反转
delay(1000); // 持续1秒
stopMotors(); // 停止电机
Serial.println("电机测试完成"); // 在串口输出测试完成信息
}
// 前进函数
void moveForward() {
Serial.println("执行前进"); // 在串口输出前进信息
// 控制左电机正转
digitalWrite(MOTOR_LEFT_1, LOW);
digitalWrite(MOTOR_LEFT_2, HIGH);
// 控制右电机正转
digitalWrite(MOTOR_RIGHT_1, LOW);
digitalWrite(MOTOR_RIGHT_2, HIGH);
motorsActive = true; // 更新电机状态为活动
}
// 后退函数
void moveBackward() {
Serial.println("执行后退"); // 在串口输出后退信息
// 控制左电机反转
digitalWrite(MOTOR_LEFT_1, HIGH);
digitalWrite(MOTOR_LEFT_2, LOW);
// 控制右电机反转
digitalWrite(MOTOR_RIGHT_1, HIGH);
digitalWrite(MOTOR_RIGHT_2, LOW);
motorsActive = true; // 更新电机状态为活动
}
// 左转函数
void turnLeft() {
Serial.println("执行左转"); // 在串口输出左转信息
// 控制左电机反转(或停止)
digitalWrite(MOTOR_LEFT_1, LOW);
digitalWrite(MOTOR_LEFT_2, HIGH);
// 控制右电机正转
digitalWrite(MOTOR_RIGHT_1, HIGH);
digitalWrite(MOTOR_RIGHT_2, LOW);
motorsActive = true; // 更新电机状态为活动
}
// 右转函数
void turnRight() {
Serial.println("执行右转"); // 在串口输出右转信息
// 控制左电机正转
digitalWrite(MOTOR_LEFT_1, HIGH);
digitalWrite(MOTOR_LEFT_2, LOW);
// 控制右电机反转(或停止)
digitalWrite(MOTOR_RIGHT_1, LOW);
digitalWrite(MOTOR_RIGHT_2, HIGH);
motorsActive = true; // 更新电机状态为活动
}
// 原地左转函数
void spinLeft() {
Serial.println("执行原地左转"); // 在串口输出原地左转信息
// 控制左电机反转
digitalWrite(MOTOR_LEFT_1, LOW);
digitalWrite(MOTOR_LEFT_2, HIGH);
// 控制右电机正转
digitalWrite(MOTOR_RIGHT_1, HIGH);
digitalWrite(MOTOR_RIGHT_2, LOW);
motorsActive = true; // 更新电机状态为活动
}
// 原地右转函数
void spinRight() {
Serial.println("执行原地右转"); // 在串口输出原地右转信息
// 控制左电机正转
digitalWrite(MOTOR_LEFT_1, HIGH);
digitalWrite(MOTOR_LEFT_2, LOW);
// 控制右电机反转
digitalWrite(MOTOR_RIGHT_1, LOW);
digitalWrite(MOTOR_RIGHT_2, HIGH);
motorsActive = true; // 更新电机状态为活动
}
// 根据摇杆值控制电机函数
void controlByJoystick(int x, int y) {
// 在串口输出陀螺仪数据
Serial.print("陀螺仪控制 - X:");
Serial.print(x);
Serial.print(" Y:");
Serial.println(y);
// 摇杆中心值(假设摇杆数据范围是0-200,中心是100)
int centerX = 100;
int centerY = 100;
// 计算相对于中心的偏移量
int offsetX = x - centerX;
int offsetY = y - centerY;
// 死区范围,避免微小抖动和延迟
int deadZone = 20;
// 如果在死区内,停止电机
if (abs(offsetX) < deadZone && abs(offsetY) < deadZone) {
Serial.println("在死区内,停止电机");
stopMotors();
return; // 退出函数,不执行后续控制逻辑
}
// 简单方向控制 - 基于象限判断
if (abs(offsetY) > abs(offsetX)) {
// 主要上下移动(Y轴偏移大于X轴偏移)
if (offsetY > 0) {
Serial.println("前进");
moveForward(); // 调用前进函数
} else {
Serial.println("后退");
moveBackward(); // 调用后退函数
}
} else {
// 主要左右移动(X轴偏移大于Y轴偏移)
if (offsetX > 0) {
Serial.println("右转");
turnRight(); // 调用右转函数
} else {
Serial.println("左转");
turnLeft(); // 调用左转函数
}
}
}
// 初始化函数,只在设备启动时执行一次
void setup() {
Serial.begin(115200); // 初始化串口通信,波特率为115200
// 初始化电机控制引脚为输出模式
pinMode(MOTOR_LEFT_1, OUTPUT);
pinMode(MOTOR_LEFT_2, OUTPUT);
pinMode(MOTOR_RIGHT_1, OUTPUT);
pinMode(MOTOR_RIGHT_2, OUTPUT);
// 初始停止状态,确保电机不会意外启动
stopMotors();
// 初始化状态LED引脚为输出模式
pinMode(STATUS_LED, OUTPUT);
digitalWrite(STATUS_LED, LOW); // 初始状态为关闭
// 初始化蓝牙,设置设备名称为"ESP32_Robot_Car"
if (!SerialBT.begin("ESP32_Car")) {
Serial.println("蓝牙初始化失败!");
while(1); // 如果初始化失败,则无限循环停止程序
}
// 在串口输出系统信息
Serial.println("=== ESP32机器人小车 - 双控制模式 ===");
Serial.println("系统默认启动");
Serial.println("左摇杆: 陀螺仪控制");
Serial.println("右摇杆: 方向键控制");
Serial.println("等待蓝牙连接...");
// 执行电机测试
testMotors();
}
// 处理按钮命令函数
void processButtonCommand(uint8_t cmd) {
lastCommandTime = millis(); // 更新最后命令时间
// 在串口输出按钮命令信息
Serial.print("处理按钮命令: 0x");
if(cmd < 0x10) Serial.print("0"); // 如果小于16,补零显示
Serial.println(cmd, HEX); // 以十六进制格式输出命令
// 根据按钮命令执行相应操作
switch(cmd) {
// 按钮A - 急停
case 0xA1:
emergencyStop = true; // 激活急停状态
stopMotors(); // 立即停止所有电机
Serial.println("!!! 急停激活 !!!");
break;
case 0xA0:
// 按钮A释放,不做任何操作
break;
// 按钮B - 系统启动/停止
case 0xB1:
systemEnabled = !systemEnabled; // 切换系统状态
if (systemEnabled) {
emergencyStop = false; // 启动时解除急停
Serial.println("系统启动");
} else {
stopMotors();
Serial.println("系统停止");
}
break;
case 0xB0:
// 按钮B释放,不做任何操作
break;
// 右摇杆按钮控制
case 0x51: // 右上 - 前进
if (systemEnabled && !emergencyStop && buttonMode) {
moveForward(); // 只有在系统启用、非急停状态且按钮模式启用时才执行
}
break;
case 0x50: // 右上释放
if (systemEnabled && !emergencyStop && buttonMode) {
stopMotors(); // 释放按钮时停止电机
}
break;
case 0x61: // 右下 - 后退
if (systemEnabled && !emergencyStop && buttonMode) {
moveBackward();
}
break;
case 0x60: // 右下释放
if (systemEnabled && !emergencyStop && buttonMode) {
stopMotors();
}
break;
case 0x71: // 右左 - 左转
if (systemEnabled && !emergencyStop && buttonMode) {
turnLeft();
}
break;
case 0x70: // 右左释放
if (systemEnabled && !emergencyStop && buttonMode) {
stopMotors();
}
break;
case 0x81: // 右右 - 右转
if (systemEnabled && !emergencyStop && buttonMode) {
turnRight();
}
break;
case 0x80: // 右右释放
if (systemEnabled && !emergencyStop && buttonMode) {
stopMotors();
}
break;
// 功能按钮C - 原地左转
case 0xC1:
if (systemEnabled && !emergencyStop && buttonMode) {
spinLeft();
}
break;
case 0xC0:
// 按钮C释放
if (systemEnabled && !emergencyStop && buttonMode) {
stopMotors();
}
break;
// 功能按钮D - 原地右转
case 0xD1:
if (systemEnabled && !emergencyStop && buttonMode) {
spinRight();
}
break;
case 0xD0:
// 按钮D释放
if (systemEnabled && !emergencyStop && buttonMode) {
stopMotors();
}
break;
// 开关E - 陀螺仪模式
case 0xE1:
gyroMode = true; // 开启陀螺仪模式
Serial.println("陀螺仪模式 - 开启");
break;
case 0xE0:
gyroMode = false; // 关闭陀螺仪模式
Serial.println("陀螺仪模式 - 关闭");
break;
// 开关F - 按钮模式
case 0xF1:
buttonMode = true; // 开启按钮模式
Serial.println("按钮模式 - 开启");
break;
case 0xF0:
buttonMode = false; // 关闭按钮模式
Serial.println("按钮模式 - 关闭");
break;
}
}
// 处理陀螺仪数据函数
void processGyroData(uint8_t* data, int length) {
// 检查数据长度是否正确
if (length != 4) {
Serial.println("错误: 陀螺仪数据长度不正确");
return; // 长度不正确,退出函数
}
// 根据文档格式解析陀螺仪数据:1X/16 2X%16 3Y/16 4Y%16
// 将高4位和低4位组合成完整的8位数据
int gyroX = ((data[0] & 0x0F) << 4) | (data[1] & 0x0F);
int gyroY = ((data[2] & 0x0F) << 4) | (data[3] & 0x0F);
// 在串口输出解析后的陀螺仪数据
Serial.print("解析陀螺仪数据 - X:");
Serial.print(gyroX);
Serial.print(" Y:");
Serial.println(gyroY);
// 检查系统状态,只有满足条件才执行陀螺仪控制
if (systemEnabled && !emergencyStop && gyroMode) {
controlByJoystick(gyroX, gyroY); // 调用摇杆控制函数
}
}
// 解析蓝牙数据函数
void parseBluetoothData() {
// 循环读取所有可用的蓝牙数据
while (SerialBT.available()) {
uint8_t byteRead = SerialBT.read(); // 读取一个字节
lastDataTime = millis(); // 更新最后数据接收时间
lastCommandTime = millis(); // 更新最后命令时间
// 检查是否是按钮命令
bool isButtonCommand = false;
// 按钮命令列表,检查当前字节是否在预定义的按钮命令中
switch(byteRead) {
case 0xA0: case 0xA1: case 0xB0: case 0xB1:
case 0x50: case 0x51: case 0x60: case 0x61:
case 0x70: case 0x71: case 0x80: case 0x81:
case 0xC0: case 0xC1: case 0xD0: case 0xD1:
case 0xE0: case 0xE1: case 0xF0: case 0xF1:
isButtonCommand = true; // 如果是预定义的按钮命令,设置标志为true
break;
}
if (isButtonCommand) {
// 如果是按钮命令,立即处理
processButtonCommand(byteRead);
// 重置陀螺仪数据接收状态
expectingGyroData = false;
gyroBufferIndex = 0;
} else {
// 如果不是按钮命令,则作为陀螺仪数据处理
if (!expectingGyroData) {
// 开始接收陀螺仪数据
expectingGyroData = true;
gyroBufferIndex = 0;
gyroDataBuffer[gyroBufferIndex++] = byteRead; // 存储第一个字节
} else {
// 继续收集数据
if (gyroBufferIndex < 4) {
gyroDataBuffer[gyroBufferIndex++] = byteRead; // 存储后续字节
}
// 如果收集到4个字节,处理数据
if (gyroBufferIndex == 4) {
processGyroData(gyroDataBuffer, 4); // 处理完整的陀螺仪数据
expectingGyroData = false; // 重置接收状态
gyroBufferIndex = 0; // 重置缓冲区索引
}
}
}
}
// 检查数据超时,如果超过100ms没有收到新数据,重置接收状态
if (expectingGyroData && (millis() - lastDataTime > 100)) {
expectingGyroData = false;
gyroBufferIndex = 0;
}
}
// 检查安全超时函数
void checkSafetyTimeout() {
// 如果系统启用且超过安全超时时间没有收到命令
if (systemEnabled && (millis() - lastCommandTime > SAFETY_TIMEOUT)) {
stopMotors(); // 停止所有电机
// 限制超时信息的输出频率,避免频繁输出
static unsigned long lastTimeoutMessage = 0;
if (millis() - lastTimeoutMessage > 10000) { // 每10秒输出一次
Serial.println("安全超时: 5秒无指令,电机已停止");
lastTimeoutMessage = millis();
}
}
}
// 更新状态指示灯函数
void updateStatusLED() {
static unsigned long lastBlink = 0; // 记录上次闪烁时间
static bool ledState = false; // LED当前状态
if (emergencyStop) {
// 紧急停止状态 - 快闪(200ms间隔)
if (millis() - lastBlink > 200) {
ledState = !ledState; // 切换LED状态
digitalWrite(STATUS_LED, ledState);
lastBlink = millis();
}
} else if (systemEnabled) {
if (bluetoothConnected) {
// 系统启用且蓝牙连接 - 常亮
digitalWrite(STATUS_LED, HIGH);
} else {
// 系统启用但蓝牙断开 - 慢闪(1000ms间隔)
if (millis() - lastBlink > 1000) {
ledState = !ledState;
digitalWrite(STATUS_LED, ledState);
lastBlink = millis();
}
}
} else {
// 系统停止 - 慢闪(1000ms间隔)
if (millis() - lastBlink > 1000) {
ledState = !ledState;
digitalWrite(STATUS_LED, ledState);
lastBlink = millis();
}
}
}
// 主循环函数,不断重复执行
void loop() {
// 检查蓝牙连接状态
static bool lastBTState = false; // 上次蓝牙状态
bool currentBTState = SerialBT.hasClient(); // 当前蓝牙状态
// 如果蓝牙状态发生变化
if (currentBTState != lastBTState) {
if (currentBTState) {
Serial.println("蓝牙已连接");
bluetoothConnected = true;
} else {
Serial.println("蓝牙已断开");
bluetoothConnected = false;
}
lastBTState = currentBTState; // 更新上次状态
}
// 处理蓝牙数据(只有在蓝牙连接时)
if (bluetoothConnected) {
parseBluetoothData();
}
// 检查安全超时
checkSafetyTimeout();
// 更新状态指示灯
updateStatusLED();
delay(10); // 短暂延迟,减少CPU负载
}

根据学会助手APP,配置以下按钮映射:
| 按钮功能 | 发送数据 |
|---|---|
| 急停 | A1 |
| 系统启动/停止 | B1 |
| 前进(右上) | 51 |
| 后退(右下) | 61 |
| 左转(右左) | 71 |
| 右转(右右) | 81 |
| 原地左转 | C1 |
| 原地右转 | D1 |
| 陀螺仪模式开关 | E1/E0 |
| 按钮模式开关 | F1/F0 |
完成基础功能后,您可以考虑添加以下扩展功能:
通过本教程,您已经成功搭建了一个基于ESP32的智能小车,并实现了双控制模式。这个项目不仅让您学习了硬件连接和编程知识,还为您后续的物联网和机器人项目打下了坚实基础。
Article Navigation System © 2025
欢迎来到我的留言板,留下你的足迹,与我分享你的想法和感受。
点击文本框会有惊喜哦`(。•̀ᴗ-)✧