
很多 Arduino 初学者会在同一批项目中遇到两种长得很像的 SG90一种是普通角度舵机用来控制云台或机械臂的旋转角度另一种是贴了“360 度”标签的连续旋转版经常被拿来当小车轮子或旋转平台。实际测试时后者用write(90)并不能像预期那样指向中间位置有时甚至是一直接着转。问题并不是舵机坏了而是两种舵机对同一个信号的解释方式完全不同。普通的 180 度舵机把脉宽解释成“目标角度”内部通过电位器反馈让输出轴停在指定角度360 度连续旋转版则把脉宽解释成“方向与速度”脉宽接近中位时停止偏离中位时就朝某一方向连续旋转。外部只负责发脉宽不读取实际转速或转过圈数这种用法就是典型开环控制。下面用 Arduino UNO 配合一块 SG90 360 度连续旋转舵机把writeMicroseconds的用法、停止点校准、方向验证和故障排查整条链路都说清楚。1. 为什么这块“SG90”不是转到指定角度而是一直转1.1 普通角度舵机和 360 度连续旋转舵机的内部差别普通 SG90 属于标准位置型舵机。输出轴旁边有一颗电位器电位器会随着齿轮转动改变阻值。舵机里的控制板比较“目标位置对应的电压”和“当前电位器反馈的电压”两者一致就停止驱动电机。所以程序里只要告诉它目标角度最终输出轴总会停在那个角度附近。这个内部反馈是小舵机能精确停在指定角度的基础。360 度连续旋转版在设计上把机械限位去掉同时取消或弱化了位置电位器反馈。控制板不再追求“让输出轴停到某个角度”而是根据输入脉宽决定电机向哪个方向转、以多快速度转。输出轴可以连续不断旋转没有 0 度到 180 度这样的边界。因此标准舵机写的write(90)在 360 度版本上不产生“转到 90 度”的效果只会被换算成某一脉宽然后转换为对应的转速和方向。1.2 用脉冲宽度决定方向与速度而不是角度舵机信号本质是一串周期约 20ms 的脉冲。角度舵机看的是高电平脉宽和角度之间的关系例如不少产品按 0.5ms 到 2.5ms 对应 0 度到 180 度估算。360 度连续旋转舵机同样接收这个信号但内部逻辑改成了先找“中点脉宽”再比较输入脉宽和中点的差值。输入脉宽约等于中点脉宽时舵机停止。输入脉宽明显小于中点脉宽时舵机朝一个方向旋转。输入脉宽明显大于中点脉宽时舵机朝相反方向旋转。离中点越远转速越快。这个概念最容易理解错的地方在于很多人把“1ms”或“2ms”当成角度边界来记实际上连续旋转舵机根本没有稳定的角度边界信号只表示方向和转速。write(90)在这种场景下不如writeMicroseconds(1500)直白因为后者直接告诉控制板当前发送的脉宽是多少微秒不经过角度换算。1.3 两种舵机选型对比买舵机前先看标签和商品说明不能只看是不是 SG90。同样是 SG90 外形内部可能完全不同。对比项普通 SG90 角度舵机SG90 360 度连续旋转版输出效果输出轴按角度定位输出轴连续正转或反转内部位置反馈有电位器反馈无位置反馈或已禁用位置反馈典型控制信号0.5ms 到 2.5ms 对应角度约 1.5ms 停止偏离中位控制方向与速度write(90)大多转到 90 度附近不一定停止取决于换算后的脉宽writeMicroseconds(1500)通常停到约 90 度位置设计上应停止但需要校准实际中点是否适合做轮子不适合角度范围受限适合做驱动轮但精度依赖外部控制下单和接线前先确认如果是给小车装轮子应该选连续旋转版如果只是控制云台或舵机臂转动普通版更合适。若实物没有明显标签可以通过上电后发送 1500µs 观察能持续旋转的是连续旋转版转到固定位置停住的更可能是普通角度舵机。2. 开环控制是什么只管发信号不管实际转没转2.1 开环和闭环在舵机场景里的区别开环控制的意思是控制器只发出指令不读取执行结果。控制链路通常是“指令 - 驱动电路 - 电机 - 负载”缺少“反馈 - 误差修正”这一环。把普通 SG90 单独拿出来看它内部其实有电位器闭环目标角度和实际角度会被比较。用户面对的是“角度指令”这个接口不需要自己处理位置反馈。但 360 度连续旋转舵机把内部位置反馈取消后用户就只能向它发送速度控制信号。系统不会报告“我转了一圈”“我卡住了”或“我当前速度是多少”所有关于转速和圈数的信息都需要外部补充。这里也解释了一个常见误区有人把“360 度舵机能转 360 度”理解成“能精确转一整圈然后停下来”。实际上360 度版本没有办法从自身判断是否已经转完一圈。程序里写“高速旋转 2 秒再停止”那是靠时间去估算转过的角度不是舵机反馈得到的真实结果。这就是开环控制的典型特征。2.2 360 度版本没有位置反馈所以别把它当精确舵机用实测中你会看到这样一个现象同一段“满速转 1 秒”的程序电池电压高时舵机转得快转过的圈数多电池电压低时转速下降同样 1 秒转过的圈数少。因为程序只控制了时间没有控制转速也没有根据转速修正时间。这不是代码写得不对而是开环控制的固有限制。如果要让舵机作为机器人驱动轮并可靠计算里程最好在电机输出轴或轮子上增加编码器再配合闭环控制。输入材料如果没有提供编码器方案那么这块 SG90 连续旋转舵机更适合先用开环方式验证电机能力而不是直接承担高精度定位任务。3. 接线和参数准备Arduino UNO 与 SG90 360 的连法3.1 硬件清单和连线方式做入门实测不需要复杂设备。准备一块 Arduino UNO、一个 SG90 360 度连续旋转舵机、几根杜邦线、一台安装好 Arduino IDE 的电脑即可。若舵机带负载或要多只电机同时测试再准备一个外部 5V 电源和一个 100µF 到 470µF 的电解电容。SG90 舵机通常引出三根线颜色有棕色、红色、橙色也可能是黑色、红色、黄色。接线对应关系如下舵机引线颜色功能Arduino UNO 接法棕色或黑色GNDGND红色VCC5V 或外部 5V 电源正极橙色或黄色信号数字引脚 9红色线不能接到 3.3V也不能接到高于 6V 的电源上。常见 9g 舵机工作电压以 4.8V 到 6V 居多Arduino UNO 的 5V 引脚适合单只舵机空载测试。如果舵机堵转或负载较大电流可能从几百毫安升到更高依靠 UNO 板载稳压器供电会造成电压跌落严重时 Arduino 会重启因此多舵机或带负载场景应使用独立电源。3.2 舵机控制信号不是普通占空比关键是脉宽周期舵机控制要求的是一个大约 20ms 周期内的高电平持续时间。Arduino 的Servo库在调用attach()后会自动产生约 50Hz 的更新频率并维护正确的高电平时间。使用Servo库时writeMicroseconds(1500)表示每个周期内输出 1500µs 的高电平。很多初学者看到“PWM”就尝试用analogWrite()这是不合适的。analogWrite()控制的是占空比例如 90 和 255 的比例切换频率固定不直接产生舵机所需的 1ms 到 2ms 脉宽。标准做法是让舵机信号引脚连接 Arduino 的 PWM 引脚并使用内置Servo库。信号参数常见值说明信号周期约 20ms典型 50Hz 刷新高电平脉宽500µs 到 2500µs具体范围以舵机型号资料为准实践常用脉宽1000µs 到 2000µs很多 360 版按此范围标定中点脉宽接近 1500µs因批次可能偏移需要实测校准速度关系偏离中点越远转速越高到边界后可能进入平台期3.3writeMicroseconds与write的区别和正确用法Servo库提供两个常用方法write(angle)输入 0 到 180 表示角度库内部把角度换算成脉宽。writeMicroseconds(value)直接输入高电平时间单位是微秒。标准角度舵机用write(90)很好理解。连续旋转舵机再用角度去换算反而容易造成混淆因为你不知道 90 对应的是中点还是偏转方向。使用writeMicroseconds能让程序意图更清晰写 1500 是中点写小于中点的是正转写大于中点的是反转。调用attach(pin, min, max)时min和max会影响write()的角度到脉宽映射。如果不指定Arduino 内置 Servo 库通常会按较宽的默认 MIN 和 MAX 脉宽处理。为了让write()的换算也靠近 1000µs 到 2000µs 范围有的程序会写成attach(pin, 1000, 2000)。但最终建议仍是用writeMicroseconds()控制连续旋转舵机因为它不经过角度语义直接表示你要发多少脉宽排查时思路更清晰。4. 编写第一个实测程序找“停止点”并验证方向4.1 Arduino IDE 环境准备使用 Arduino IDE 1.8 或 2.x 都可以。Servo库是 Arduino AVR 核心自带的库不需要额外安装。把 Arduino UNO 用 USB 线连接电脑在“工具 - 开发板”中选择 Arduino Uno在“工具 - 端口”中选择对应串口然后编译上传。这里要特别注意舵机信号线只接一个引脚程序里的引脚号必须和实际接线一致。例如信号线接在 D9代码里就写 9。引脚不一致是最常见的“编译成功但不动作”的原因。4.2 最小停止程序先把 1500µs 作为假想中点第一次上电不要急着控制小车前进先写一个最简程序让舵机收到 1500µs 信号。程序如下#include Servo.h Servo myServo; const int servoPin 9; void setup() { myServo.attach(servoPin, 1000, 2000); myServo.writeMicroseconds(1500); } void loop() { // 保持当前脉宽输出 }上传完成后观察舵机。若它完全停止说明这块舵机的中点确实接近 1500µs。若它还在缓慢旋转或抖动说明中点并不在 1500µs需要进入下一步校准。4.3 通过串口实时输入脉宽找出真实中点找一个准确的停止点是做后续所有方向测试的基础。可以用一段通过串口输入脉宽的程序来逐点试探。#include Servo.h Servo myServo; const int servoPin 9; void setup() { Serial.begin(9600); myServo.attach(servoPin, 1000, 2000); myServo.writeMicroseconds(1500); Serial.println(input pulse width in microseconds:); Serial.println(example: 1500 1490 1480 1510 1520); } void loop() { if (Serial.available() 0) { int value Serial.parseInt(); if (value 500 value 2500) { myServo.writeMicroseconds(value); Serial.print(pulse ); Serial.println(value); } else { Serial.println(out of safe range); } } }数据在 Arduino 串口监视器的输入框中直接发送。试探顺序建议从 1500µs 开始然后依次输入 1490、1480、1470观察舵机是否从旋转变成停止。如果 1500µs 时它朝一个方向转后面会有一个时刻开始反向两个方向交界处附近通常存在一个停止区选择一个能稳定停住的值作为stopPulse。这个过程中可能出现一个几微秒宽的“死区”也就是脉宽在 1460µs 到 1480µs 之间舵机都不明显转动。出现死区时不要担心取死区中间值即可。连续旋转舵机的中点是硬件参数不同批次可能不同程序里不能把 1500µs 当成永远有效。注意测试时不要让舵机长时间堵转例如用手捏住输出轴不让它转。小舵机堵转电流会明显上升长时间堵转会发热严重时可能损坏舵机内部驱动。5. 用一个串口遥控测试验证开环控制特性5.1 功能设计和测试步骤校准出停止点后就可以验证开环控制的完整行为。设计目标是用串口输入不同命令舵机分别执行停止、慢速正转、快速正转、慢速反转、快速反转。这里不需要精确知道转速是多少只需要观察方向是否随脉宽变化、速度是否随偏移量变化。先把停止点保存为stopPulse再用“停止点 ± 偏移量”生成低速和高速信号。例如停止点是 1470µs慢速信号可以用 1390µs 和 1550µs快速信号可以用 1250µs 和 1690µs。5.2 完整示例代码下面是串口控制测试程序方向命名先用方向 A 和方向 B 表示。实际测试若发现方向相反只调整符号即可。#include Servo.h Servo myServo; const int servoPin 9; // 假设实测停止点为 1470请替换成你自己校准出来的值 const int stopPulse 1470; void setup() { Serial.begin(9600); myServo.attach(servoPin, 1000, 2000); myServo.writeMicroseconds(stopPulse); Serial.println(s: stop); Serial.println(1: direction A low speed); Serial.println(2: direction A high speed); Serial.println(3: direction B low speed); Serial.println(4: direction B high speed); } void loop() { if (Serial.available() 0) { char cmd Serial.read(); switch (cmd) { case s: myServo.writeMicroseconds(stopPulse); Serial.print(pulse ); Serial.println(stopPulse); break; case 1: myServo.writeMicroseconds(stopPulse - 80); Serial.print(pulse ); Serial.println(stopPulse - 80); break; case 2: myServo.writeMicroseconds(stopPulse - 220); Serial.print(pulse ); Serial.println(stopPulse - 220); break; case 3: myServo.writeMicroseconds(stopPulse 80); Serial.print(pulse ); Serial.println(stopP