尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

基于ESP32的桌面机器人:低成本入门PWM控制与Wi-Fi遥控实践

基于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的LEDCLED PWM控制器硬件外设可以生成非常稳定且频率可调的PWM信号远比用analogWrite软件模拟要精确和高效。这是我们实现平滑运动控制的硬件保障。3. 物料清单与电路连接在开始编程前请准备好以下硬件。总成本可以控制在百元以内。组件型号/规格数量说明主控板ESP32开发板如ESP32 DevKitC, NodeMCU-32S1核心处理器电机驱动模块TB6612FNG 或 L298N1推荐TB6612效率高、发热小直流减速电机3-6V带编码器可选2决定机器人的动力和速度轮子与电机配套2机器人底盘亚克力或3D打印件1套包括底盘、电机支架、万向轮电源18650锂电池两节及电池盒1套提供3.7V*27.4V动力电源电压转换模块AMS1117-3.3V 或 MP1584EN1将7.4V降压为3.3V给ESP32供电超声波传感器HC-SR041用于避障功能可选杜邦线公对公、公对母若干用于连接电路连接示意图以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点击文件-首选项。在“附加开发板管理器网址”中填入https://espressif.github.io/arduino-esp32/package_esp32_index.json如果有多个URL用逗号隔开。点击工具-开发板-开发板管理器。搜索“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 Rrawliteral( !DOCTYPE html html head meta nameviewport contentwidthdevice-width, initial-scale1 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 h2ESP32 桌面机器人遥控器/h2 div classcontrol-panel div button classbtn forward ontouchstartsendCmd(F) ontouchendsendCmd(S) onmousedownsendCmd(F) onmouseupsendCmd(S)前进/button /div div button classbtn left ontouchstartsendCmd(L) ontouchendsendCmd(S) onmousedownsendCmd(L) onmouseupsendCmd(S)左转/button button classbtn stop ontouchstartsendCmd(S)停止/button button classbtn right ontouchstartsendCmd(R) ontouchendsendCmd(S) onmousedownsendCmd(R) onmouseupsendCmd(S)右转/button /div div button classbtn backward ontouchstartsendCmd(B) ontouchendsendCmd(S) onmousedownsendCmd(B) onmouseupsendCmd(S)后退/button /div p速度: span idspeedValue150/span/p input typerange min50 max255 value150 classspeed-slider idspeedSlider onchangeupdateSpeed() /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 25Echo - ESP32 GPIO 26GND - 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?setauto 来开启自动模式 // 为了演示我们简化处理如果收到特定命令则切换模式 // 实际项目中你需要添加对应的服务器路由来处理模式切换 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 stylemargin-top:20px; button onclicksetMode(auto) stylepadding:10px 20px; margin:5px;开启自动避障/button button onclicksetMode(manual) stylepadding:10px 20px; margin:5px;切换为手动遥控/button /div script function setMode(mode) { var xhr new XMLHttpRequest(); xhr.open(GET, /mode?set mode, true); xhr.send(); alert(已切换至 (modeauto?自动:手动) 模式); } /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-Fi2. 尝试关闭防火墙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庞大的社区和丰富的资料几乎能确保你找到答案。最重要的是动手去做在调试电机为什么不转、传感器为什么没数据的过程中你学到的东西远比读十篇教程要多。
返回列表