手里攒着一块ESP32开发板,想做机械臂、云台或者智能小车,第一个绕不开的玩法就是控制舵机。很多朋友在这步被卡住:照着Arduino UNO的教程写analogWrite,结果舵机纹丝不动;或者舵机一转,开发板直接重启。其实都不怪舵机,问题出在ESP32的PWM体系和供电方案上。这篇文章就把“ESP32 舵机控制”这条路从入门走到进阶完整捋一遍,从信号原理、LEDC通道、多路扩展、平滑运动、闭环控制到远程操作,全部用实测过的代码和参数说话。新手可以照着接线,老手可以直接抄高级应用的关键设计。
1. 基础不打好,后面全是坑:舵机信号与供电设计
1.1 舵机到底在等什么信号
不是所有的舵机都叫“角度舵机”。最常见的标准舵机,内部是一颗直流电机、一组减速齿轮、一个电位器反馈和一个控制电路。电位器实时读当前角度,控制电路把目标角度和你发来的脉冲宽度不断对比,驱动电机朝目标位置转。它要的信号非常像汽车刹车灯:每隔20毫秒来一个高电平脉冲,高电平持续多久,角度就对应多少。
说人话就是:一帧信号周期固定在20ms(也就是50Hz),脉宽在0.5ms到2.5ms之间,脉宽1.5ms时舵机在中间位置0°,0.5ms对应最左端,2.5ms对应最右端。市面上常见的9g舵机、MG996R都属于这一类。另有一种360°连续旋转舵机,脉冲宽度控制的不是角度而是转动的速度和方向:1.5ms是停,小于它就是正转,大于就是反转,这在做小车底盘时很常用,但控制逻辑完全不一样,千万别混用。
还要注意数字舵机和模拟舵机的差别。模拟舵机靠控制电路持续发送小脉冲维持位置,对控制信号敏感,容易抖动;数字舵机内部以更高频率处理信号,响应快、死区小,同样角度下保持力更强。ESP32控制它们时,软件层面几乎没有差别,但数字舵机对供电质量要求更高,电压波动时更容易吱吱叫。
1.2 为什么选ESP32而不是传统单片机
很多人惯性思维是“舵机嘛,用Arduino UNO就能转”,确实能转,但一玩多路或者要联网就露馅了。UNO只有一个硬件定时器能产生稳定PWM,真正能好用的舵机信号通道其实没几路,同时转三个舵机就得手写软PWM,CPU直接吃满。而ESP32的LEDC(LED Control)模块原生支持16路PWM通道,频率、分辨率都能单独设置,这对舵机控制是降维打击。
再加上ESP32自带双核240MHz处理器,一个核跑舵机控制逻辑,一个核跑WiFi/蓝牙协议栈,互不拖累。你可以在手机上打个滑块远程控制舵机,舵机那边位置更新照样毫秒级响应,这是传统单片机想都不敢想的场景。还有一个细节,ESP32的GPIO输出3.3V逻辑电平,大多数舵机控制芯片也能正确识别3.3V信号,接线时不需要额外电平转换,直接信号线连GPIO就行。
1.3 接线和供电,80%问题出在这一步
舵机线一般是三条:红(电源)、黑或棕(地)、黄/橙/白(信号)。和ESP32连接时,除了信号线要用GPIO,最重要的是电源绝对不要从开发板的3.3V引脚取电。
我见过太多人把舵机红线直接怼到开发板3.3V上,结果一上电,舵机猛转一下,开发板就黑屏重启。原因非常简单:9g舵机正常工作电流在100到300mA,堵转时可以到1A以上;MG996R这种大扭矩舵机堵转电流可以飙到2.5A以上。ESP32主板上的3.3V稳压器通常只有500mA量级,根本扛不住。正确做法是外接电源:
- 小型舵机(9g、SG90):用5V/2A的USB供电模块或者两节18650电池组。
- 大扭矩舵机(MG996R、DS3218):用5V/5A及以上的DC-DC降压模块,从7.4V锂电降压到5V。
- 所有方案都要把外部电源的GND和ESP32的GND连在一起,这叫共地,否则信号电平基准不一致,舵机会乱跳。
- 在舵机电源正负极两端加一个470uF到1000uF的电解电容,能有效吸收堵转时的尖峰电流,防止电压瞬间塌掉。
2. 代码驱动舵机:从库函数到底层LEDC参数
2.1 开发环境与舵机库的安装
ESP32在Arduino IDE下开发依旧是主流。先装ESP32开发板支持包:打开“文件 - 首选项 - 附加开发板管理器网址”,填入官方JSON地址,然后到开发板管理器搜索ESP32安装。这一步做完,选择对应的开发板型号就能编译了。
舵机库方面,首选ESP32Servo库,直接在库管理器搜索安装。这个库是对Arduino原生Servo库的ESP32移植,接口几乎一样,但它内部用的是LEDC通道,而不是传统的AVR定时器。装完后,一行#include <ESP32Servo.h>就能开始。
提示:如果用的是MicroPython开发,也可以用
machine.PWM直接输出舵机需要的脉宽,但实时性和库生态不如Arduino框架。除非你整个项目已经用MicroPython写完了,否则我更推荐Arduino C++,做精细控制时调试起来更方便。
2.2 最简单的一版:让舵机转起来
下面这个示例是“喂饱舵机”的起步代码,完成角度0到180度来回摆动。
#include <ESP32Servo.h> Servo myservo; int servoPin = 13; int angle = 0; void setup() { myservo.attach(servoPin, 500, 2500); // 引脚,最小脉宽us,最大脉宽us } void loop() { for (angle = 0; angle <= 180; angle += 1) { myservo.write(angle); delay(15); } for (angle = 180; angle >= 0; angle -= 1) { myservo.write(angle); delay(15); } }这里attach的第二个和第三个参数值得说明一下。它定义了最小角度和最大角度对应的脉宽。500us对应0°,2500us对应180°,这符合标准舵机规格。如果你的舵机在0°和180°时存在咔哒声或转不到位,就该调整这两个值。很多国产舵机实际范围是600us到2400us,过度压到500us反而会堵转发热。
2.3 深入LEDC原理:库背后发生了什么
ESP32Servo库好用,但你得知道它背后的LEDC机制,否则碰到通道冲突时根本无从查起。LEDC分成两组:高速通道和低速通道,一共16路。每一路可以绑定到几乎任意GPIO引脚上,所以16个通道理论上可以控制16个舵机,只要引脚不打架。
LEDC参数有三个:频率、分辨率、占空比。舵机需要50Hz频率,也就是每20ms一个脉冲。分辨率决定占空比的步进精度,比如用14位分辨率,就是把一个周期分成2的14次方份,即16384个刻度。50Hz周期是20ms,也就是20000us,每个刻度对应约1.22us。舵机常见的1us精度在这个配置下绰绰有余。
手动方式也可以直接写,不依赖库:
#include <driver/ledc.h> #define SERVO_PIN 13 #define SERVO_CH 0 void setServoAngle(int angle) { int us = map(angle, 0, 180, 500, 2500); uint32_t duty = (uint32_t)(us * 16384 / 20000); // 14位分辨率,50Hz周期20000us ledc_set_duty(LEDC_LOW_SPEED_MODE, SERVO_CH, duty); ledc_update_duty(LEDC_LOW_SPEED_MODE, SERVO_CH); } void setup() { ledc_timer_config_t timer_conf = { .speed_mode = LEDC_LOW_SPEED_MODE, .duty_resolution = LEDC_TIMER_14_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = 50, .clk_cfg = LEDC_AUTO_CLK }; ledc_timer_config(&timer_conf); ledc_channel_config_t channel_conf = { .gpio_num = SERVO_PIN, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = SERVO_CH, .intr_type = LEDC_INTR_DISABLE, .timer_sel = LEDC_TIMER_0, .duty = 0, .hpoint = 0 }; ledc_channel_config(&channel_conf); } void loop() { setServoAngle(90); delay(1000); }这里最关键的一行是us * 16384 / 20000。因为分辨率为14位,一个完整100%占空比周期对应的计数值是16384,而50Hz周期是20000us。舵机要500us的脉冲,就取周期中500us对应的份额。这就是LEDC模式下精确输出舵机脉宽的核心换算。
2.4 角度与脉宽的映射,为什么不能瞎写一个线性公式
标准舵机的角度和脉宽是线性关系:
角度0° -> 500us 角度90° -> 1500us 角度180° -> 2500us所以换算公式是us = 500 + (angle / 180) * 2000。Arduino的map函数干的就是这件事。但实际使用中,线性映射只对“标准舵机”成立,有些舵机在两端存在非线性区,接近0°或180°时,1us脉宽变化可能对应好几度。所以做精确位置控制时,强烈建议实测校准:
- 先把舵机转到机械中位,用直尺或量角器确认实际角度。
- 在代码里直接写
writeMicroseconds(1500),观察舵机是不是正好在中位。 - 逐渐减小脉宽,找到0°点的最小脉宽;再逐渐增大,找最大脉宽。
- 把校准后的值写进
attach(pin, minUs, maxUs)。
我在某六足机器人项目里校准过十几个舵机,每一只的端值都不一样,有的500us就到了0°,有的要550us。端值不校准,六条腿站都站不平。
3. 多路舵机与电源管理:机械臂和机器人的必经关卡
3.1 多路舵机的通道和引脚规划
一个机械臂通常要四到六个舵机,一台六足机器人就是十几个。ESP32虽然有16路LEDC通道,但引脚分配要特别小心。以下是规划引脚时的几条经验:
- 避开只作为输入模式的纯GPIO引脚,例如GPIO34到GPIO39只能输入,不能输出PWM。
- 避开和板载Flash、PSRAM相关的引脚,比如GPIO6到GPIO11,否则可能导致启动失败。
- 烧录程序时,GPIO0、GPIO2等引脚状态会影响是否进入下载模式,最好避免在这些引脚挂舵机信号线,或者至少保证线足够短。
一个比较稳妥的分配方案是给舵机信号用GPIO12、13、14、15、16、17、18、19、21、22、23、25、26、27这类常规输出引脚。每路信号线上串一个100到220欧姆的电阻,不仅能抑制干扰,还能在舵机故障时保护ESP32的GPIO。
3.2 PCA9685扩展16路PWM的实战
当舵机数量超过10路,或者你想把ESP32的定时器资源留出来做其他事,PCA9685这颗16路PWM驱动板就是标准答案。它用I2C通信,只需要两根线,就能扩展出16路独立PWM,最多可以级联62块,也就是992路PWM。
接线非常简单:PCA9685的SDA接ESP32的GPIO21,SCL接GPIO22,VCC接3.3V,V+接舵机电源5V,GND两边共地。信号输出端直接插舵机,每路都能带一定电流的舵机负载。
配合Adafruit PWM Servo Driver Library实现舵机控制:
#include <Wire.h> #include <Adafruit_PWMServoDriver.h> Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); #define SERVOMIN 150 // 对应约0.5ms #define SERVOMAX 600 // 对应约2.5ms void setup() { pwm.begin(); pwm.setPWMFreq(50); // 舵机标准频率 } void setServoAngle(uint8_t channel, uint8_t angle) { uint16_t pulselen = map(angle, 0, 180, SERVOMIN, SERVOMAX); pwm.setPWM(channel, 0, pulselen); } void loop() { for (uint8_t ch = 0; ch < 6; ch++) { setServoAngle(ch, 90); } delay(1000); }PCA9685的计数值范围和LEDC不同,它芯片内部是12位分辨率,一个周期分成4096份。50Hz周期20ms,0.5ms脉宽就是0.5 / 20 * 4096 = 102左右,2.5ms脉宽是2.5 / 20 * 4096 = 512左右。SERVOMIN=150和SERVOMAX=600是常见库的默认值,但不同舵机也有差异,建议按实测微调。
注意:PCA9685默认供电V+接了舵机电源后,如果这块板子的板载稳压器不额外供电,I2C逻辑电平可能被拉高到5V,和ESP32的3.3V GPIO不完全匹配。我的做法是把PCA9685的VCC也接5V,同时SDA和SCL各串一个1k欧姆电阻做电平兼容,实测非常稳定。
3.3 大负载供电设计:电容、稳压模块和电流预算
多路舵机一起动,瞬间电流不是简单相加,因为每路舵机启动时机不同,但堵转时同时发生的概率很大。以六足机器人为例,六只MG996R同时堵转,理论峰值能到15A,这个数字直接把普通5V/3A电源打穿。
所以大负载供电设计要按三步走:
- 估算最大电流:每只舵机堵转电流乘以舵机数量,留20%余量。
- 选择电源:5V输出的DC-DC降压模块额定电流必须大于估算值。从2S锂电池(7.4V)降压,推荐使用BEC模块或者可调降压模块,输出电流至少5A起。
- 做电容储能:在电源输出端并一个大电容组,常见做法是两个2200uF电解电容并联,再并一个0.1uF陶瓷电容。陶瓷电容滤高频噪声,电解电容扛瞬时大电流。6个舵机同时从静止加速到全速时,这个电容组能平滑掉电流尖峰,避免电压跌落引发的舵机复位。
还有一个非常隐蔽的坑:舵机电源地和ESP32电源地必须连在一起,但大电流地线回路不要经过ESP32的GND引脚出去。正确做法是:电池负极先到舵机电源负极,再从舵机电源负极拉一根独立的细线到ESP32 GND,不要让几十安培的舵机电流从开发板的地平面流过,否则ADC读数会乱跳,WiFi也会断。
4. 优雅地控制舵机:平滑运动、闭环稳定和任务分配
4.1 舵机抖动和暴力甩头的解法:插值与梯形调速
直接write(180),舵机像是被人踹了一脚,瞬间全力甩过去。对机械臂来说,这不仅丑,还会带来过冲、齿轮冲击、底座晃动。真正的解决方案是让舵机按设定速度匀速或者加减速移动。
最基础的做法是定时插值。不阻塞主循环,用millis()判断时间到了就步进一定小角度:
#include <ESP32Servo.h> Servo myservo; int currentAngle = 0; int targetAngle = 180; int stepSize = 2; // 每步角度 unsigned long lastMove = 0; int intervalMs = 15; // 步进间隔 void smoothMotor() { if (millis() - lastMove >= intervalMs) { lastMove = millis(); if (currentAngle < targetAngle) { currentAngle = min(currentAngle + stepSize, targetAngle); } else if (currentAngle > targetAngle) { currentAngle = max(currentAngle - stepSize, targetAngle); } myservo.write(currentAngle); } } void setup() { myservo.attach(13, 500, 2500); } void loop() { smoothMotor(); }这种方式叫线性插值,能解决“甩头”的问题,但启动和停止瞬间加速度仍然突变,表现在舵机上就是一个起步顿挫。再进一步就是梯形速度规划:先匀加速,到达最大速度后匀速,接近目标时匀减速。写起来也不复杂,维护一个速度变量,根据当前位置和目标位置的距离决定加减速。
我在一个五轴机械臂项目上用了梯形规划后,整个臂的启停肉眼可见地顺滑,而且重复定位精度明显提高,因为减速过程让舵机几乎没有过冲,电位器反馈更容易收敛。
4.2 闭环控制:让舵机知道自己的实际角度
标准舵机内部自带电位器闭环,它知道自己转到哪了,但这个闭环精度取决于舵机内部电位器的线性度。很多便宜舵机标称精度0.1°,实际重复精度能差到2°到3°。对云台、机械臂这种高要求场景,需要外部传感器做真闭环。
最简单的改造是给舵机输出轴加一个高精度电位器,把电压值送回ESP32的ADC引脚,用PID算法修正目标。这个方案成本低,但电位器本身有机械磨损,寿命一般。
更常见的组合是MPU6050陀螺仪加云台舵机。MPU6050测量云台姿态角,把当前角度和期望角度做差,通过PID输出PWM修正量给舵机。核心代码框架如下:
#include <Wire.h> #include <MPU6050_tockn.h> MPU6050 mpu6050(Wire); float targetAngle = 0; float currentAngle = 0; float kp = 2.0; // 先只调P,直到出现等幅振荡,再调D消除振荡 void setup() { Wire.begin(); mpu6050.begin(); mpu6050.calcGyroOffsets(true); myservo.attach(13, 500, 2500); pinMode(0, INPUT_PULLUP); // 任意按键做调零 } void loop() { mpu6050.update(); currentAngle = mpu6050.getAngleX(); if (digitalRead(0) == LOW) { targetAngle = currentAngle; } float error = targetAngle - currentAngle; float output = kp * error; float servoAngle = constrain(90 + output, 0, 180); myservo.write(servoAngle); delay(10); }PID调参口诀是“先P,后D,最后I”。我实测的经验:P太小,云台跟不上动作;P太大,会持续振荡;D能抑制振荡但太大会让舵机高频震颤。很多场景其实不需要I项,因为舵机本身有内闭环,静态误差不像电机驱动系统那么严重。I项加多了反而会造成缓慢漂移。
4.3 FreeRTOS任务划分:别让网络请求堵住舵机控制
ESP32默认跑的是FreeRTOS,Arduino的loop本质上也是一个低优先级任务。一旦用了WiFi、蓝牙,网络事件处理时会打断舵机控制,导致信号毛刺、舵机抖动。这种情况就要主动用任务划分。
比如把舵机控制放到核心0上的高优先级任务,把Web服务器和WiFi放到核心1上的普通优先级任务。用xTaskCreatePinnedToCore把控制任务固定在某个核上:
void servoTask(void *param) { for (;;) { updateSmoothServo(); vTaskDelay(pdMS_TO_TICKS(10)); } } void setup() { xTaskCreatePinnedToCore(servoTask, "ServoTask", 4096, NULL, 2, NULL, 0); xTaskCreatePinnedToCore(networkTask, "NetTask", 4096, NULL, 1, NULL, 1); }这里优先级的数字越大优先级越高,舵机任务给2,网络任务给1,确保网络再忙,舵机也不会受影响。注意任务栈大小别给太少,我自己遇到过栈溢出导致看似随机复位的问题,排查方式是在任务里多打印剩余栈空间,或者直接把栈从默认的2048字节加到4096字节。
5. 远程控制舵机:Web、App与机器人上位机联动
5.1 手机浏览器直接控制:ESP32内嵌Web服务器
ESP32最吸引人的地方就是自带WiFi,它自己能当一个小服务器。手机连到ESP32的热点,或者让ESP32连家里路由器,浏览器打开一个页面,用滑块拖舵机角度。
代码思路是:用WebServer库注册一个/servo接口,接收GET参数angle,然后直接控制舵机。页面端可以嵌入一个滑块,每次滑动就向服务器发送角度请求。
#include <WiFi.h> #include <WebServer.h> #include <ESP32Servo.h> const char* ssid = "MyRobot"; const char* password = "12345678"; Servo myservo; WebServer server(80); void handleServo() { if (server.hasArg("angle")) { int angle = server.arg("angle").toInt(); angle = constrain(angle, 0, 180); myservo.write(angle); server.send(200, "text/plain", String(angle)); } else { server.send(400, "text/plain", "Missing angle"); } } void setup() { myservo.attach(13, 500, 2500); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } server.on("/servo", handleServo); server.begin(); } void loop() { server.handleClient(); }这只是一个最精简版,实际项目建议在页面里加一个WebSocket或者fetch轮询,让服务器主动推送当前舵机状态,避免多端操作时状态不同步。滑块每移动1度就发一次HTTP请求,延迟在局域网内大约几十毫秒,完全够用。远程公网访问就别想了,家庭网络没有固定公网IP,直接用内网穿透服务又涉及敏感内容,不建议折腾,哪怕局域网控制也足够做一台桌面机械臂了。
5.2 MQTT、BLE和串口:不同远程场景的选型
Web服务器适合局域网内单点控制,但机器人和上位机联动时,MQTT是更合理的选择。MQTT是发布订阅模式,ESP32订阅一个/robot/arm/cmd主题,上位机或者另一块板子发布一条{"ch0":90,"ch1":45}消息,ESP32收到就执行。这套结构的好处是解耦,控制端和执行端不需要知道彼此在哪里,只要连同一个MQTT broker(消息服务器)就行。
如果只想手机近距离控制,不需要路由器,BLE是更好的选择。ESP32作为BLE服务端,手机作为客户端写入特征值,ESP32在特征值回调里解析数据控制舵机。BLE延迟比Web低,功耗也比WiFi低,适合遥控小车这类需要低频状态同步的设备。
串口协议则是最稳定的兜底方案。很多机械臂项目采用角度值(1字节)+通道号(1字节)+校验(1字节)的简单帧格式,通过串口接收上位机指令。这个方法没有WiFi环境依赖,调试时还能用串口监视器模拟上位机,是每一步都值得保留的调试通道。
5.3 机械臂路径规划的联动配置
远程控制只是搬运工,真正有意思的是和机器人路径规划联动。一个典型场景:上位机用逆运动学算出一组关节角度序列,然后把整个序列通过MQTT或者串口发给ESP32,ESP32负责把每个角度平滑地执行出来。
这里有一个值得注意的分工思路:上位机负责计算“去哪”,ESP32只负责“怎么去”。也就是说,逆运动学矩阵、轨迹插值这种重计算放在电脑端,ESP32收到的是一个个离散的关节目标角,然后由我在4.1节写的平滑插值代码去执行。这样ESP32的实时负担小,响应也快,不会因为计算逆运动学导致舵机信号卡顿。
实际项目里还要考虑通信失败的情况。我的做法是:每条控制指令带一个递增的序号,ESP32收到序号不连续时,说明中间丢包,立即停止运动并回报错误。舵机不比电机,一个错误的角度指令就可能导致机械臂撞到硬限位,严重时能扫断齿轮。
6. 实际调试中反复踩的坑,现在帮你一次避开
6.1 舵机抖动、吱吱叫和发烫
这三种症状经常同时出现。先判断是供电问题、信号问题还是机械问题。
供电不足导致的抖动,特征是舵机静止时偶尔抽搐,一动起来就发软。用万用表测舵机电源端电压,转动时如果掉到4.5V以下,基本就是电源小了或者电容不够。信号毛刺导致的抖动,特征是舵机快速来回小幅度摆动,像在打冷颤。这时检查GPIO驱动能力、杜邦线是否过长或者和电源线绞在一起。
吱吱叫最容易被误判为舵机坏了,其实是舵机在目标位置持续发力保持时,控制电路不断微调产生的正常声音。但如果吱吱声伴随明显发热,说明舵机正在堵转,比如串行舵机臂被外力卡住,或者角度指令超出机械范围。解决方式是用软限位加机械限位双保险,在代码里用constrain限制角度,在结构上增加挡块。
6.2 ESP32不断的自动重启和烧录失败
ESP32重启的经典原因有两个:一个是程序跑飞,看门狗复位;另一个是电压跌落导致芯片欠压复位。区分方法很简单,在串口监视器里看重启后的打印信息。如果是重启后输出abort()或Guru Meditation Error,是软件问题;如果像断电一样突然黑屏再重启,无任何打印,那几乎是供电被舵机拉垮了。
舵机一转就把ESP32拉重启,90%是供电问题。记住这条铁律:舵机的电源永远不要从开发板取,哪怕开发者板子标了5V引脚。用独立电源供电后,再把ESP32用充电宝或者电脑USB单独供电,两路电源只共地。
烧录失败大多是GPIO12、GPIO15这些与下载模式有关的引脚接了设备导致的。解决方式是在烧录时断开舵机信号线,烧完再插回去,用起来最省心。
6.3 一个排查表,按图索骥
我把这些年遇到的高频故障整理成一张表,实际排查时对照症状快速缩小范围。
| 症状 | 可能原因 | 处理方式 |
|---|---|---|
| 舵机完全不动 | 信号线没接对、引脚被占用 | 用Serial.print确认引脚电平翻转;换一个空闲GPIO |
| 舵机只朝一个方向转 | 脉宽范围设置过窄 | 调大attach的脉宽范围;实测实际最小值 |
| 一转就重启 | 电源不够、电容不足 | 外接5V电源,并1000uF电容,确认共地 |
| 抖动且发热 | 堵转、PWM频率杂散 | 检查机械限位,缩短信号线,换数字舵机 |
| 位置不准 | 舵机线性度差、脉宽未校准 | 用实测量角器校准每个舵机的min/max |
| 连WiFi后舵机卡顿 | 网络中断CPU | 用FreeRTOS把舵机任务固定到另一核心 |
| 长时间运行后失灵 | 电源线压降、接头松脱 | 加粗电源线,改用插簧端子或直接焊接 |
最后再分享一个实操习惯:每次改动接线后,上电的第一件事是把舵机转到中位,然后手动掰动机械结构,确认没有阻碍。舵机中位是1500us,在代码里先固定进setup跑一次,这是所有关节设备调试的安全起点。我自己就是靠这个习惯,避开了无数次上电瞬间舵机横扫撞限位的惨剧。
ESP32做舵机控制这件事,入门门槛不高,但要真正做到稳定、平滑、可远程控制,里面藏着不少需要实际趟一遍的细节。这几个月做六足和机械臂下来,我最深的感受是:先把供电和引脚规划理清楚,后面的路就顺了。你如果手里也有吃灰的ESP32和舵机,不妨从第二节的示例代码跑起来,先让它动起来,再一步一步往上叠加你的想法。