基于ESP32的桌面机器人:低成本入门PWM控制与Wi-Fi遥控实践
如果你对机器人开发感兴趣,但又觉得ROS太复杂、树莓派太贵,或者只是想快速体验一下让小车动起来的乐趣,那么这篇文章就是为你准备的。
今天要介绍的这个开源项目——桌面ESP32双轮机器人,它可能不是功能最强大的,但绝对是门槛最低、成本最可控的入门选择之一。它绕开了复杂的操作系统和重型框架,直指机器人开发的核心:用最少的代码,理解最本质的运动控制逻辑。很多人以为机器人开发必须从ROS学起,结果卡在环境配置和概念理解上,最终不了了之。而这个项目恰恰提供了一个“捷径”:它用一块几十元的ESP32开发板,加上两个电机和一个电池,就构建了一个完整的、可编程的移动机器人平台。
本文将带你从零开始,手把手复现这个项目。你不仅能得到一个可以遥控、避障甚至巡线的小车,更重要的是,你将彻底理解PWM电机控制、传感器数据融合、以及嵌入式无线通信(如Wi-Fi或蓝牙)在机器人中的应用。这不仅仅是“做个小车”,而是一次从单片机思维到机器人系统思维的微型实践。对于学生、电子爱好者或想切入物联网/机器人领域的开发者来说,这是一个极佳的起点。
1. 为什么选择ESP32作为机器人主控?
在开始动手之前,我们需要先回答一个根本问题:为什么是ESP32?市面上有Arduino Uno、树莓派、STM32等多种选择。
核心优势在于“性价比”与“集成度”的完美平衡。传统的Arduino Uno虽然简单,但性能有限且缺乏无线功能,需要额外扩展。树莓派功能强大,但功耗高、实时性弱,且需要运行完整的Linux系统,对于简单的运动控制来说有些“杀鸡用牛刀”。STM32性能强悍,但开发环境相对复杂,无线功能同样需要外挂模块。
ESP32则不同,它原生集成了双核处理器、Wi-Fi、蓝牙、丰富的GPIO和硬件PWM,价格却与一块Arduino Nano相当。这意味着:
- 无线控制零成本:无需额外模块,即可通过手机App或电脑进行Wi-Fi/蓝牙遥控。
- 强大的PWM支持:硬件PWM可以精确控制电机的速度和方向,这是实现平稳运动的基础。
- 双核潜力:一个核心可以专用于电机控制和传感器读取(保证实时性),另一个核心处理无线通信或上层逻辑。
- 庞大的社区与库支持:无论是Arduino IDE还是PlatformIO,都有成熟的开发环境和丰富的第三方库。
对于我们的桌面双轮机器人来说,ESP32提供了“刚好够用”的性能和“开箱即用”的无线能力,将硬件成本和开发复杂度都降到了最低。
2. 项目核心:差速转向与PWM控制
在深入代码之前,必须理解这个机器人是如何运动的。它采用双轮差速驱动,这是最简单也是最经典的移动机器人底盘形式。
差速转向原理:机器人没有方向盘。它的转向完全依靠左右两个轮子的速度差。
- 当左轮速度 = 右轮速度时,机器人直线前进或后退。
- 当左轮速度 > 右轮速度时,机器人向右转弯。
- 当左轮速度 < 右轮速度时,机器人向左转弯。
- 当左轮速度与右轮速度大小相等、方向相反时,机器人实现原地旋转。
那么,如何精确控制每个轮子的速度呢?答案就是PWM(脉冲宽度调制)。我们通过电机驱动模块(如L298N、TB6612FNG)来控制电机。给驱动模块的输入信号并不是模拟电压,而是数字PWM波。PWM波的占空比(高电平时间占整个周期的比例)决定了电机的平均电压,从而控制了电机的转速。
关键点:ESP32的LEDC(LED PWM控制器)硬件外设可以生成非常稳定且频率可调的PWM信号,远比用analogWrite(软件模拟)要精确和高效。这是我们实现平滑运动控制的硬件保障。
3. 物料清单与电路连接
在开始编程前,请准备好以下硬件。总成本可以控制在百元以内。
| 组件 | 型号/规格 | 数量 | 说明 |
|---|---|---|---|
| 主控板 | ESP32开发板(如ESP32 DevKitC, NodeMCU-32S) | 1 | 核心处理器 |
| 电机驱动模块 | TB6612FNG 或 L298N | 1 | 推荐TB6612,效率高、发热小 |
| 直流减速电机 | 3-6V,带编码器(可选) | 2 | 决定机器人的动力和速度 |
| 轮子 | 与电机配套 | 2 | |
| 机器人底盘 | 亚克力或3D打印件 | 1套 | 包括底盘、电机支架、万向轮 |
| 电源 | 18650锂电池两节及电池盒 | 1套 | 提供3.7V*2=7.4V动力电源 |
| 电压转换模块 | AMS1117-3.3V 或 MP1584EN | 1 | 将7.4V降压为3.3V给ESP32供电 |
| 超声波传感器 | HC-SR04 | 1 | 用于避障功能(可选) |
| 杜邦线 | 公对公、公对母 | 若干 | 用于连接 |
电路连接示意图(以TB6612FNG和ESP32为例):
ESP32 GPIO TB6612FNG引脚 电机 --------- ------------- ---- GPIO16 -------> PWMA | GPIO17 -------> AIN1 |---> 连接至 左电机 GPIO5 -------> AIN2 | | GPIO18 -------> PWMB | GPIO19 -------> BIN1 |---> 连接至 右电机 GPIO21 -------> BIN2 | | GND -------> GND | VM -------> 连接至电池正极 (7.4V) VCC -------> 连接至3.3V (为逻辑部分供电) STBY引脚 -------> 连接至3.3V (高电平使能驱动芯片)电源连接注意事项:
- 绝对不要将7.4V锂电池直接接到ESP32的VIN或5V引脚,会烧毁芯片!
- 必须使用降压模块(如MP1584EN)将7.4V降至5V或3.3V,再给ESP32供电。通常ESP32的VIN引脚可接受5V输入。
- 电机驱动模块的VM(电机电源)直接接7.4V电池。其逻辑电源VCC接ESP32的3.3V或5V。
- 确保所有GND(电池、ESP32、驱动模块、传感器)共地。
4. 开发环境搭建 (Arduino IDE)
我们将使用最普及的Arduino IDE进行开发,它对初学者最友好。
4.1 安装Arduino IDE与ESP32开发板支持
- 从Arduino官网下载并安装最新版Arduino IDE。
- 打开IDE,点击
文件->首选项。在“附加开发板管理器网址”中,填入:
如果有多个URL,用逗号隔开。https://espressif.github.io/arduino-esp32/package_esp32_index.json - 点击
工具->开发板->开发板管理器。搜索“esp32”,找到由“Espressif Systems”提供的“ESP32”开发板包,点击安装。
4.2 安装必要的库
本项目需要以下库,均可通过库管理器安装:
- ESP32Servo: 用于ESP32的硬件PWM控制(即使我们不控制舵机,它也提供了优秀的PWM API)。
- WebServer和WiFi: 用于实现Wi-Fi遥控网页服务器(内置库,无需额外安装)。
- BluetoothSerial: 用于实现蓝牙串口遥控(内置库,无需额外安装)。
安装方法:工具->管理库...,搜索库名并安装。
5. 核心代码实现:电机驱动与基础运动
让我们从最核心的电机驱动开始。我们将创建一个Motor类来封装对单个电机的控制。
// 文件:Motor.h #ifndef MOTOR_H #define MOTOR_H #include <Arduino.h> #include <ESP32Servo.h> // 利用其硬件PWM功能 class Motor { private: int pinPWM; // PWM控制速度引脚 int pinIN1; // 方向控制引脚1 int pinIN2; // 方向控制引脚2 int channel; // LEDC通道 (0-15) int freq; // PWM频率 (Hz) int resolution;// PWM分辨率 (位) public: // 构造函数 Motor(int pwmPin, int in1Pin, int in2Pin, int ledcChannel, int pwmFreq = 5000, int pwmRes = 8); // 初始化电机引脚和PWM void begin(); // 驱动电机:速度范围 -255 到 +255 void drive(int speed); }; #endif// 文件:Motor.cpp #include "Motor.h" Motor::Motor(int pwmPin, int in1Pin, int in2Pin, int ledcChannel, int pwmFreq, int pwmRes) { pinPWM = pwmPin; pinIN1 = in1Pin; pinIN2 = in2Pin; channel = ledcChannel; freq = pwmFreq; resolution = pwmRes; } void Motor::begin() { pinMode(pinIN1, OUTPUT); pinMode(pinIN2, OUTPUT); // 使用ESP32Servo库来简化LEDC配置 ledcSetup(channel, freq, resolution); // 设置PWM通道 ledcAttachPin(pinPWM, channel); // 将引脚绑定到通道 this->drive(0); // 初始化为停止状态 } void Motor::drive(int speed) { // 限制速度范围 speed = constrain(speed, -255, 255); // 设置方向 if (speed > 0) { // 正转 digitalWrite(pinIN1, HIGH); digitalWrite(pinIN2, LOW); } else if (speed < 0) { // 反转 digitalWrite(pinIN1, LOW); digitalWrite(pinIN2, HIGH); } else { // 停止 digitalWrite(pinIN1, LOW); digitalWrite(pinIN2, LOW); } // 设置PWM占空比,取速度的绝对值 ledcWrite(channel, abs(speed)); }接下来,我们创建一个DifferentialDrive类来协调左右电机,实现机器人的整体运动。
// 文件:DifferentialDrive.h #ifndef DIFFERENTIAL_DRIVE_H #define DIFFERENTIAL_DRIVE_H #include "Motor.h" class DifferentialDrive { private: Motor leftMotor; Motor rightMotor; float maxSpeed; // 最大速度值 (对应PWM的255) public: DifferentialDrive(Motor &left, Motor &right, float maxSpd = 255.0); void begin(); // 基础运动控制 void stop(); void moveForward(int speed); void moveBackward(int speed); void turnLeft(int speed); void turnRight(int speed); // 高级控制:直接设置左右轮速度 void tankDrive(int leftSpeed, int rightSpeed); // 高级控制:通过线速度和角速度控制 (类似ROS的cmd_vel) void arcadeDrive(float linear, float angular); }; #endif// 文件:DifferentialDrive.cpp #include "DifferentialDrive.h" DifferentialDrive::DifferentialDrive(Motor &left, Motor &right, float maxSpd) : leftMotor(left), rightMotor(right), maxSpeed(maxSpd) {} void DifferentialDrive::begin() { leftMotor.begin(); rightMotor.begin(); } void DifferentialDrive::stop() { leftMotor.drive(0); rightMotor.drive(0); } void DifferentialDrive::moveForward(int speed) { speed = constrain(speed, 0, 255); leftMotor.drive(speed); rightMotor.drive(speed); } void DifferentialDrive::moveBackward(int speed) { speed = constrain(speed, 0, 255); leftMotor.drive(-speed); rightMotor.drive(-speed); } void DifferentialDrive::turnLeft(int speed) { speed = constrain(speed, 0, 255); leftMotor.drive(-speed); rightMotor.drive(speed); } void DifferentialDrive::turnRight(int speed) { speed = constrain(speed, 0, 255); leftMotor.drive(speed); rightMotor.drive(-speed); } void DifferentialDrive::tankDrive(int leftSpeed, int rightSpeed) { leftSpeed = constrain(leftSpeed, -255, 255); rightSpeed = constrain(rightSpeed, -255, 255); leftMotor.drive(leftSpeed); rightMotor.drive(rightSpeed); } void DifferentialDrive::arcadeDrive(float linear, float angular) { // 将线速度和角速度转换为左右轮速度 // 这是一个简化模型,实际机器人需要根据轮距等参数调整 linear = constrain(linear, -1.0, 1.0) * maxSpeed; angular = constrain(angular, -1.0, 1.0) * maxSpeed; int leftSpeed = linear + angular; int rightSpeed = linear - angular; // 防止速度超出范围 leftSpeed = constrain(leftSpeed, -255, 255); rightSpeed = constrain(rightSpeed, -255, 255); tankDrive(leftSpeed, rightSpeed); }6. 主程序框架与Wi-Fi遥控实现
有了底层的驱动库,我们就可以构建主程序了。这里我们实现一个通过网页遥控机器人的功能。
// 文件:desktop_esp32_robot.ino #include <WiFi.h> #include <WebServer.h> #include "DifferentialDrive.h" // ==== 网络配置 ==== const char* ssid = "Your_WiFi_SSID"; // 替换为你的Wi-Fi名称 const char* password = "Your_WiFi_PASS"; // 替换为你的Wi-Fi密码 WebServer server(80); // 创建Web服务器对象,端口80 // ==== 电机引脚定义 (根据你的实际接线修改) ==== #define LEFT_MOTOR_PWM 16 #define LEFT_MOTOR_IN1 17 #define LEFT_MOTOR_IN2 5 #define RIGHT_MOTOR_PWM 18 #define RIGHT_MOTOR_IN1 19 #define RIGHT_MOTOR_IN2 21 #define LEFT_MOTOR_CHANNEL 0 // LEDC通道0用于左电机PWM #define RIGHT_MOTOR_CHANNEL 1 // LEDC通道1用于右电机PWM // ==== 创建电机和驱动器对象 ==== Motor leftMotor(LEFT_MOTOR_PWM, LEFT_MOTOR_IN1, LEFT_MOTOR_IN2, LEFT_MOTOR_CHANNEL); Motor rightMotor(RIGHT_MOTOR_PWM, RIGHT_MOTOR_IN1, RIGHT_MOTOR_IN2, RIGHT_MOTOR_CHANNEL); DifferentialDrive robot(leftMotor, rightMotor); // ==== 网页HTML界面 (内嵌) ==== const char* htmlPage = R"rawliteral( <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> body { text-align: center; font-family: Arial; } .control-panel { margin: 50px auto; width: 300px; } .btn { width: 100px; height: 100px; font-size: 24px; margin: 5px; border: none; border-radius: 10px; cursor: pointer; } .forward { background-color: #4CAF50; } .backward { background-color: #f44336; } .left { background-color: #2196F3; } .right { background-color: #FF9800; } .stop { background-color: #9E9E9E; width: 210px; } .speed-slider { width: 80%; margin: 20px 0; } </style> </head> <body> <h2>ESP32 桌面机器人遥控器</h2> <div class="control-panel"> <div> <button class="btn forward" ontouchstart="sendCmd('F')" ontouchend="sendCmd('S')" onmousedown="sendCmd('F')" onmouseup="sendCmd('S')">前进</button> </div> <div> <button class="btn left" ontouchstart="sendCmd('L')" ontouchend="sendCmd('S')" onmousedown="sendCmd('L')" onmouseup="sendCmd('S')">左转</button> <button class="btn stop" ontouchstart="sendCmd('S')">停止</button> <button class="btn right" ontouchstart="sendCmd('R')" ontouchend="sendCmd('S')" onmousedown="sendCmd('R')" onmouseup="sendCmd('S')">右转</button> </div> <div> <button class="btn backward" ontouchstart="sendCmd('B')" ontouchend="sendCmd('S')" onmousedown="sendCmd('B')" onmouseup="sendCmd('S')">后退</button> </div> <p>速度: <span id="speedValue">150</span></p> <input type="range" min="50" max="255" value="150" class="speed-slider" id="speedSlider" onchange="updateSpeed()"> </div> <script> let currentSpeed = 150; function updateSpeed() { currentSpeed = document.getElementById("speedSlider").value; document.getElementById("speedValue").innerText = currentSpeed; } function sendCmd(cmd) { var xhr = new XMLHttpRequest(); xhr.open("GET", "/action?cmd=" + cmd + "&speed=" + currentSpeed, true); xhr.send(); } </script> </body> </html> )rawliteral"; // ==== 处理根路径请求,返回遥控网页 ==== void handleRoot() { server.send(200, "text/html", htmlPage); } // ==== 处理动作命令 ==== void handleAction() { String cmd = server.arg("cmd"); int speed = server.arg("speed").toInt(); Serial.printf("收到命令: %s, 速度: %d\n", cmd.c_str(), speed); if (cmd == "F") { robot.moveForward(speed); } else if (cmd == "B") { robot.moveBackward(speed); } else if (cmd == "L") { robot.turnLeft(speed); } else if (cmd == "R") { robot.turnRight(speed); } else if (cmd == "S") { robot.stop(); } server.send(200, "text/plain", "OK"); } // ==== 处理未找到的路径 ==== void handleNotFound() { server.send(404, "text/plain", "404: Not Found"); } void setup() { Serial.begin(115200); delay(1000); // 初始化机器人驱动器 robot.begin(); robot.stop(); Serial.println("机器人电机初始化完成。"); // 连接Wi-Fi WiFi.begin(ssid, password); Serial.print("正在连接到Wi-Fi"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\n连接成功!"); Serial.print("IP地址: "); Serial.println(WiFi.localIP()); // 设置Web服务器路由 server.on("/", handleRoot); server.on("/action", handleAction); server.onNotFound(handleNotFound); // 启动Web服务器 server.begin(); Serial.println("HTTP服务器已启动"); } void loop() { server.handleClient(); // 处理客户端请求 // 这里可以添加其他循环任务,如传感器读取 }7. 代码上传、运行与验证
- 选择开发板和端口:在Arduino IDE中,选择
工具->开发板->ESP32 Arduino-> 选择你的具体型号(如ESP32 Dev Module)。然后选择正确的串口端口。 - 修改Wi-Fi配置:在代码中,将
Your_WiFi_SSID和Your_WiFi_PASS替换为你自己的Wi-Fi名称和密码。 - 编译并上传:点击上传按钮。首次上传可能需要按住ESP32板上的
BOOT按钮。 - 查看串口监视器:上传完成后,打开串口监视器(波特率115200)。你将看到ESP32连接Wi-Fi的过程,并最终打印出它的IP地址,例如:
IP地址: 192.168.1.100。 - 遥控测试:在同一Wi-Fi网络下的手机或电脑浏览器中,输入上述IP地址。你将看到一个简单的遥控界面。点击方向按钮,机器人应做出相应动作。滑动滑块可以调整运动速度。
成功标志:
- 串口监视器打印出IP地址。
- 网页能正常打开,按钮有响应。
- 点击网页按钮时,机器人电机转动,且串口打印出对应的命令日志(如“收到命令: F, 速度: 150”)。
8. 功能扩展:增加超声波避障
让机器人具备基础感知能力是下一步。我们添加一个HC-SR04超声波传感器,实现简单的自动避障。
接线:
- VCC -> 5V (或3.3V,但3.3V时测距可能变短)
- Trig -> ESP32 GPIO 25
- Echo -> ESP32 GPIO 26
- GND -> GND
代码修改与添加: 在原有主程序的基础上,添加以下内容。
// 在文件开头的引脚定义区域添加 #define TRIG_PIN 25 #define ECHO_PIN 26 // 在全局变量区域添加 const int OBSTACLE_DISTANCE_CM = 20; // 障碍物阈值,20厘米 unsigned long lastCheckTime = 0; const unsigned long CHECK_INTERVAL = 200; // 避障检测间隔200毫秒 bool autoMode = false; // 手动/自动模式标志 // 在setup()函数中,初始化超声波引脚 pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT); // 新增函数:读取超声波距离 float readDistanceCM() { digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2); digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW); long duration = pulseIn(ECHO_PIN, HIGH, 30000); // 超时30ms,约5米 // 计算距离 (声速 340 m/s = 0.034 cm/μs, 距离 = 时间 * 声速 / 2) float distance = duration * 0.034 / 2.0; if (distance == 0 || distance > 500) { // 过滤无效值 distance = 500.0; } return distance; } // 新增函数:自动避障逻辑 void autoAvoidObstacle() { float dist = readDistanceCM(); Serial.printf("前方距离: %.1f cm\n", dist); if (dist < OBSTACLE_DISTANCE_CM) { Serial.println("检测到障碍物!执行避障动作。"); robot.moveBackward(150); // 后退 delay(500); robot.turnRight(180); // 右转 delay(300); robot.stop(); delay(200); } else { robot.moveForward(150); // 前进 } } // 修改loop()函数,加入模式切换和自动避障逻辑 void loop() { server.handleClient(); // 每200毫秒检查一次是否切换到自动模式,并执行避障 if (millis() - lastCheckTime > CHECK_INTERVAL) { lastCheckTime = millis(); // 这里可以通过一个网页按钮或物理开关来切换autoMode // 例如,假设我们通过访问 /mode?set=auto 来开启自动模式 // 为了演示,我们简化处理:如果收到特定命令,则切换模式 // 实际项目中,你需要添加对应的服务器路由来处理模式切换 if (autoMode) { autoAvoidObstacle(); } } } // 在Web服务器路由部分,添加一个处理模式切换的接口(示例) server.on("/mode", []() { String set = server.arg("set"); if (set == "auto") { autoMode = true; server.send(200, "text/plain", "Auto Mode ON"); } else if (set == "manual") { autoMode = false; robot.stop(); server.send(200, "text/plain", "Manual Mode ON"); } else { server.send(400, "text/plain", "Bad Request"); } });网页添加模式切换按钮: 在HTML页面中增加两个按钮,用于切换模式。
<!-- 在HTML的control-panel div内,速度滑块下方添加 --> <div style="margin-top:20px;"> <button onclick="setMode('auto')" style="padding:10px 20px; margin:5px;">开启自动避障</button> <button onclick="setMode('manual')" style="padding:10px 20px; margin:5px;">切换为手动遥控</button> </div> <script> function setMode(mode) { var xhr = new XMLHttpRequest(); xhr.open("GET", "/mode?set=" + mode, true); xhr.send(); alert("已切换至" + (mode=='auto'?'自动':'手动') + "模式"); } </script>现在,你的机器人就具备了基础的前方避障功能。在手动模式下,你可以通过网页遥控;切换到自动模式后,它会自己前进,并在遇到20厘米内的障碍物时后退并转向。
9. 常见问题与排查思路
在制作和调试过程中,你几乎一定会遇到下面这些问题。不要慌,按顺序排查。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| ESP32无法上传程序 | 1. 驱动未安装 2. 串口被占用 3. 板子型号选错 4. 未进入下载模式 | 1. 检查设备管理器端口识别 2. 关闭其他串口软件 3. 按住BOOT键再点击上传 | 1. 安装CP210x或CH340驱动 2. 选择正确的COM口和开发板 3. 上传时按BOOT,完成后按EN重启 |
| Wi-Fi连接失败 | 1. SSID/密码错误 2. 路由器屏蔽 3. 信号太弱 | 查看串口打印的连接过程 | 1. 确认密码和网络名称(区分大小写) 2. 尝试手机热点 3. 检查路由器是否设置了MAC过滤 |
| 网页能打开,但按钮无反应 | 1. ESP32与客户端不在同一网络 2. 防火墙/路由器设置 3. JavaScript执行错误 | 1. 检查IP地址是否在同一网段 2. 浏览器控制台查看错误(F12) | 1. 确保手机/电脑连接同一Wi-Fi 2. 尝试关闭防火墙 3. 检查HTML/JS代码格式 |
| 电机不转或只震动 | 1. 电源功率不足 2. 接线错误或虚焊 3. PWM频率不对 4. 电机驱动模块未使能 | 1. 测量电机供电电压 2. 用万用表检查通路 3. 单独测试电机 | 1. 更换电量足的电池 2. 检查STBY引脚是否接高电平 3. 确认PWM引脚和通道配置正确 |
| 机器人走不直 | 1. 左右电机性能差异 2. 轮子打滑或安装不平行 3. 电池电压下降导致速度不一致 | 观察空载和负载下的差异 | 1. 在代码中对左右轮速度进行微调补偿 2. 紧固轮子,确保机械结构对称 3. 使用带编码器的电机进行闭环控制 |
| 超声波测距不准或无数据 | 1. 电压不足(3.3V驱动可能不稳定) 2. 声波被干扰 3. 脉冲等待超时 | 1. 测量VCC电压 2. 检查是否有柔软物体吸收声波 3. 调整 pulseIn超时时间 | 1. 尝试给HC-SR04供5V电(注意电平转换) 2. 确保传感器前方空旷 3. 增加滤波算法,取多次测量平均值 |
10. 项目优化与进阶方向
这个基础项目只是一个起点。你可以从以下几个方向深入,把它变成一个真正的“学习平台”:
10.1 增加闭环控制(编码器)
目前的开环控制无法应对地面摩擦、电池电压变化等干扰。给电机加上编码器,使用PID算法进行速度闭环控制,能让机器人运动精度大幅提升。你需要:
- 购买带AB相输出的编码器电机。
- 使用ESP32的PCNT(脉冲计数器)外设读取编码器信号。
- 实现一个PID控制器,根据编码器反馈调整PWM输出。
10.2 实现更复杂的自主行为
结合多个传感器(如红外巡线模块、陀螺仪),可以让机器人完成更复杂的任务。
- 巡线机器人:使用3-5个红外对管,实现PID巡线算法。
- 自平衡机器人:使用MPU6050陀螺仪,实现类似Segway的平衡控制(挑战较大)。
- SLAM建图(高级):配合Raspberry Pi和激光雷达,运行ROS,实现室内建图与导航。此时ESP32可以作为底层电机控制器,通过串口接收上位机的速度指令。
10.3 改进通信与控制方式
- 蓝牙遥控:使用
BluetoothSerial库,开发手机App或通过电脑串口工具进行蓝牙控制,摆脱对Wi-Fi网络的依赖。 - MQTT接入物联网平台:让机器人通过MQTT协议上报传感器数据,并接收来自云端的指令,实现远程监控与控制。
- WebSocket实时控制:替代HTTP轮询,实现更低延迟的网页遥控体验。
10.4 结构设计与3D打印
开源社区有很多优秀的机器人底盘设计。你可以在Thingiverse、Printables等网站搜索“ESP32 Robot Chassis”,下载并3D打印一个更坚固、更专业的底盘,甚至为传感器设计专用的安装支架。
这个桌面ESP32双轮机器人项目,其价值远不止于让两个轮子转起来。它是一把钥匙,帮你打开了嵌入式系统、实时控制、传感器融合和物联网通信的大门。从理解一个PWM信号如何控制电机转速,到协调多个传感器实现自主决策,每一步的深入都是对系统设计能力的锤炼。
建议你将本文的代码作为基础框架,先确保最基本的遥控功能跑通。然后,选择一个你最感兴趣的进阶方向(比如PID速度控制或蓝牙遥控),逐个攻破。过程中遇到问题,ESP32庞大的社区和丰富的资料几乎能确保你找到答案。最重要的是动手去做,在调试电机为什么不转、传感器为什么没数据的过程中,你学到的东西远比读十篇教程要多。
