这次我们来看一个容易被“先入为主”带偏的硬件控制问题:手头的 SG90 舵机,到底能不能当普通 180 度舵机用?
如果你买的型号是SG90 360 度连续旋转舵机,那么答案是不建议直接按角度写。它的真实控制方式是writeMicroseconds() 输出脉宽控制速度,不是 write() 控制角度。这篇内容会围绕 Arduino UNO 展开,讲清楚这类舵机的本质、接线、启动测试、停机值校准、串口实时控制,以及为什么它属于开环控制。
先说结论,方便你判断是否需要继续往下看:
- 360 度连续旋转舵机没有角度限位,不能精准定位到指定角度。
- 控制信号仍然是 50Hz 左右的 PWM,核心调用是
Servo.writeMicroseconds()。 - 脉宽 1500us 附近通常是停止点,但不是所有舵机都刚好是 1500,必须实测校准。
- 这类舵机适合转台、云台旋转、简单小车轮子驱动,不适合机械臂关节或需要反馈控制的场景。
- 后面会给出 3 份可以直接烧录到 Arduino UNO 的测试代码,以及串口输入脉宽的调试方法。
下面直接进入正题。
1. SG90 360 度舵机核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | SG90 变体舵机控制,属于 360 度连续旋转舵机 |
| 控制方式 | 50Hz PWM 脉宽控制,核心函数writeMicroseconds() |
| 开环/闭环 | 开环控制,不带位置反馈,无法获知当前角度 |
| 可调参数 | 脉宽值、旋转方向、电机速度、运行时间 |
| 能实现的功能 | 连续正转、连续反转、停止、不同转速的近似控制 |
| 不能实现的功能 | 精确角度定位、位置闭环、力矩保持时的角度校正 |
| 推荐控制板 | Arduino UNO、Nano、Mega 等 AVR 平台均可 |
| 信号引脚 | 舵机信号线接 UNO 数字引脚,示例中使用 D9 |
| 供电建议 | 建议外部 5V 供电并共地,避免直接占用 UNO 板载稳压电流 |
| 适合读者 | 刚入门 Arduino 舵机控制的开发者、做简单转台/小车方向的开发者 |
这个表很重要的一点是:360 度连续旋转舵机是“去掉限位并改写控制逻辑”的产物。不同厂家、不同批次的 SG90-360,控制脉宽范围和停止点都会存在差异。网络上“1000us 正转、1500us 停止、2000us 反转”只能作为默认参考,真正上电以后需要逐个测试。
2. SG90 360 度连续旋转舵机和普通 180 度舵机差在哪
2.1 机械结构差异
普通 SG90 舵机内部有机械限位,输出轴只能在 0 到 180 度左右摆动。你在驱动它的时候,如果脉宽超过最大或最小角度,舵机会顶在限位处,电流升高、发出“嗡嗡”声,长时间堵转还会发热。
360 度连续旋转舵机内部去掉了这个限位挡块,或通过改电路让电机可以连续旋转。它输出的不是角度变化,而是连续转动。
2.2 控制语义差异
普通 180 度舵机,角度和脉宽大致对应:
- 0 度对应脉宽约 500us 或 1000us
- 90 度对应脉宽约 1500us
- 180 度对应脉宽约 2500us 或 2000us
所以直接用write(90)让舵机转到中间位置是合理的。
但 360 度连续旋转舵机上,角度概念不存在了。脉宽变成“停止 / 正转速度 / 反转速度”的指令:
- 中间脉宽附近(通常约 1500us)对应电机停止。
- 脉宽偏大或偏小对应不同方向的旋转。
- 偏离中心越远,电机转速越快。
正因为没有反馈,舵机不知道自己转到哪里,这就是明显的“开环控制”。
2.3 购买时如何分辨
SG90 型号非常多,看包装和商品标题时要注意。如果标题写“SG90 360 度连续旋转”“SG90 无角度限制”,那就是这种版本;如果写“SG90 9g 舵机 180 度”,那才是普通角度舵机。
低成本舵机的批次差异很大。有些号称 360 度连续旋转,实际死区很宽,很难找到完全停止的脉宽;有些在同一脉宽下正反转速度也不对称。因此拿到手后先跑一遍测试程序,比直接翻参数更靠谱。
3. 适用场景与安全边界
3.1 适合的场景
- 需要一个持续旋转的云台底盘,比如小型摄像头云台的左右旋转轴。
- 做一个简单的转盘演示,展示圆周运动。
- 小车上使用这类舵机作为减速轮驱动,虽然精度和扭矩都有限,但入门够用。
- 模拟雷达扫描效果,可以做 360 度旋转,但没角度反馈就需要配合外部传感器做限位。
- 测试 Arduino PWM 输出、脉宽控制电机基本逻辑。
3.2 不适合的场景
- 需要精确到达某个角度的机械臂关节。
- 需要位置闭环的高精度云台指向。
- 需要承受持续大扭矩的负载或长时间堵转。
- 对重复精度有要求的生产级设备。
360 度连续旋转舵机本质上是给小功率设备做“速度调节”的,而不是做“位置控制”的。如果坚持让它在没有反馈的情况下完成精确角度动作,结果会非常脆弱:供电电压波动、齿轮间隙、负载变化都会累计误差。
3.3 使用边界与安全提醒
- 舵机没有限位,如果外部结构加了机械挡板,不要高速长时间让舵机顶住挡板,否则齿轮和电机容易损坏。
- 空载测试最安全,尽量不要一开始就带大负载。
- 涉及自制转台、小车、云台时,注意不要夹到手指,不要让 USB 线缠绕到旋转轴。
- 本项目只适合学习、原型验证和合法自用设备改造,不要用于未经授权的自动化设备或任何违规场合。
4. 硬件准备与 Arduino UNO 接线
4.1 硬件清单
| 器件 | 数量 |
|---|---|
| Arduino UNO 或兼容板 | 1 块 |
| SG90 360 度连续旋转舵机 | 1 个 |
| 杜邦线(公对公或母对公) | 3 根 |
| 外部 5V 电源 | 可选但推荐 |
| USB 数据线 | 1 根 |
| 串口监视器 | 由 Arduino IDE 提供 |
如果手上只有普通 SG90,也不影响练习,只是你的舵机会被限位挡住,达不到连续旋转效果。
4.2 SG90 引脚定义
SG90 的 3 根线颜色常见如下:
| 线色 | 功能 | 接法 |
|---|---|---|
| 棕色 | GND | 接 Arduino GND 和外部电源 GND |
| 红色 | VCC | 接 5V 电源正极 |
| 橙色或黄色 | Signal | 接 Arduino 数字引脚,示例接 D9 |
不同厂家线序可能不同,接线前仔细看舵机标签。插反信号线有可能损坏舵机驱动板或引脚。
4.3 供电方式选择
原则只有一条:舵机的电源不要只依赖 UNO 的板载 5V 输出,能用外部电源就尽量用外部电源。
常见的错误接法是直接把舵机红线接到 UNO 的 5V 引脚,然后 USB 供电给 UNO。这种做法对于 1 个空载 SG90 也许能跑,但一旦舵机启动电流变大,USB 口电压被拉低,Arduino 可能自动复位。更坏的情况是舵机堵转,电流长时间过大。
推荐接线方式:
Arduino UNO SG90 360 D9 -------> 橙色信号线 5V (来自外部电源) --- 红色正极,注意先不接 UNO 5V,而是接外部电源+ GND <------- 棕色 GND 外部电源 GND ----> Arduino GND更稳妥的接法:
- 舵机正极接外部 5V 电源。
- 舵机 GND 同时连接 Arduino GND 和外部电源 GND,这叫共地。
- Arduino 用 USB 独立供电。
- 舵机信号线直接接 Arduino D9。
4.4 UNO 引脚占用说明
Arduino 的 Servo 库在 UNO 上会占用 Timer1。Timer1 与引脚 D9、D10 的 PWM 输出有关。当使用 Servo 库时,D9 和 D10 的analogWrite()会受到影响,不能同时可靠工作。如果后面需要 D10 输出 PWM 控制其他设备,需要重新规划引脚。
5. 第一个测试程序:用 writeMicroseconds 把舵机转起来
5.1 代码思路
连续旋转舵机的输入是脉宽。Servo库中:
servo.writeMicroseconds(1500);表示输出 1500 微秒的脉宽,这是最常见的停止值。
但要注意,writeMicroseconds()并不是“写微秒时间长度”这么简单,它最终会换算成对应周期的 PWM 占空比。Arduino Servo 库默认周期约 20ms,也就是 50Hz,符合常规遥控舵机信号标准。
5.2 示例代码
下面程序会让舵机按顺序执行:停止 -> 正转 -> 停止 -> 反转 -> 停止。每个状态持续 2 秒,方便观察方向是否正确。
#include <Servo.h> Servo myServo; const int servoPin = 9; void setup() { Serial.begin(9600); // 显式设置脉宽范围,避免不同商家舵机范围不一致 myServo.attach(servoPin, 500, 2500); myServo.writeMicroseconds(1500); Serial.println("SG90 360 test start"); delay(1000); } void loop() { // 停止 Serial.println("1500us -> stop"); myServo.writeMicroseconds(1500); delay(2000); // 正转 Serial.println("1000us -> forward"); myServo.writeMicroseconds(1000); delay(2000); // 停止 Serial.println("1500us -> stop"); myServo.writeMicroseconds(1500); delay(2000); // 反转 Serial.println("2000us -> reverse"); myServo.writeMicroseconds(2000); delay(2000); }5.3 预期观察现象
烧录程序后,舵机应该先停 2 秒,然后转动约 2 秒,再停 2 秒,再反向转动 2 秒,循环运行。
如果 1000us 时舵机不转,只听到“吱吱”声,说明 1000us 落在停止死区内,或者舵机批次的最小运行脉宽不够低。可以改成 900 或 800 再试。如果 2000us 时舵机停转或轻微抖动,也可以继续加大到 2100 甚至 2400。
这个测试的意义不是运行一个“好看”的动作,而是确认:
- 舵机信号线是否接对。
- 供电是否足够。
- 当前脉宽与旋转方向、停止位置的实际对应关系。
6. 串口实时控制与停机值校准
6.1 为什么需要校准
“1500us 停止”是一个理论值。实际 SG90-360 舵机的停止点可能落在 1450 到 1550 之间,甚至有些批次漂移更严重。如果 1500us 输出后舵机还在慢慢转,后面的控制逻辑会整个乱掉。
所以拿到舵机后的第一步,不是直接写小车控制代码,而是先在串口监视器里手动输入脉宽,找到它的真实停止点。
6.2 串口输入脉宽控制代码
这份代码读取串口十进制整数,并把它作为脉宽输出。你可以在 Arduino IDE 串口监视器输入1500、1000、2000等值,观察舵机状态。
#include <Servo.h> Servo myServo; const int servoPin = 9; void setup() { Serial.begin(9600); myServo.attach(servoPin, 500, 2500); myServo.writeMicroseconds(1500); Serial.println("Input pulse width, e.g. 1500"); } void loop() { if (Serial.available() > 0) { int value = Serial.parseInt(); if (value >= 500 && value <= 2500) { myServo.writeMicroseconds(value); Serial.print("Set pulse: "); Serial.println(value); } else { Serial.println("Invalid range, use 500-2500"); } } delay(10); }6.3 校准步骤
- 打开串口监视器,波特率选 9600。
- 输入
1500,观察舵机是否完全停止。 - 如果仍在微转,尝试输入
1490、1480、1470、1510、1520等,直到舵机完全停住。 - 把找到的“完全停止值”记为
STOP_PULSE。 - 输入
1000,舵机应朝一个方向转动;输入2000,舵机应朝另一个方向转动。如果方向和你预期相反,调整程序中正转/反转对应的脉宽即可。 - 继续用
900、1100、2000、2100测试,记录基本转速差异。
校准完成后,把1500替换成自己实测的STOP_PULSE。比如你的舵机在1480才停,那么代码中所有停止位置都写1480。
6.4 死区问题
连续旋转舵机的死区指的是:脉宽在某个范围内时,舵机既不转也不产生明显扭矩。死区过宽会让控制“迟钝”,指令变化一点但舵机没反应;死区过窄则可能导致舵机在停止点附近抖动。
如果发现舵机在停止点附近出现来回抖动,最直接的办法是:把停止脉宽设置在死区中心,避免使用临界值。
7. 开环控制入门:动作时间与角度估测
7.1 什么是开环控制
开环控制的意思是:控制器发出指令后,不检查执行结果是否达到预期。360 度连续旋转舵机就是典型的开环执行器——它没有角度传感器,没有编码器,也没有电流反馈。你发出“以某速度转 1 秒”,它理论上转过的角度是多少,控制器并不知道。
这意味着,如果你希望它“转 90 度后停下”,只能通过“某个脉宽跑多少毫秒”来估算。这种估算会受到供电电压、负载、温度、齿轮阻力影响,误差会逐步累积。
7.2 开环动作序列示例
下面的程序演示一个不含反馈的简单流程:启动 -> 正向转 1.5 秒 -> 停止 -> 反向转 1.2 秒 -> 停止。
这里用STOP_PULSE变量统一管理停止值,校准后只需要改一个地方。
#include <Servo.h> Servo myServo; const int servoPin = 9; const int STOP_PULSE = 1500; // 改为你实测的停止值 const int FORWARD_PULSE = 1000; // 正向脉宽 const int REVERSE_PULSE = 2000; // 反向脉宽 void setup() { Serial.begin(9600); myServo.attach(servoPin, 500, 2500); myServo.writeMicroseconds(STOP_PULSE); delay(1000); } void moveForMs(int pulse, int durationMs) { myServo.writeMicroseconds(pulse); delay(durationMs); myServo.writeMicroseconds(STOP_PULSE); delay(500); } void loop() { Serial.println("forward 1.5s"); moveForMs(FORWARD_PULSE, 1500); Serial.println("reverse 1.2s"); moveForMs(REVERSE_PULSE, 1200); delay(2000); }实际运行时会发现,两次运行之间,最终停留在哪个角度往往不一致。这正好说明开环控制的局限。
7.3 辅助限位与外部传感器
如果你确实需要在一个开环系统里“大致回到起点”,可以加一个外部传感器做辅助:
- 在转台上安装一个挡片,配合红外对管或霍尔传感器,每次触碰到传感器就清零位置。
- 使用电位器检测转轴位置,把模拟电压读到 Arduino,形成简单的角度反馈。
- 使用 MPU6050 陀螺仪估算旋转角速度,不过积分漂移会随时间增大。
这些方法都不需要改造舵机本身,但只有加上反馈之后,控制系统才从“开环”变成“闭环”,才能谈得上有定位能力。
8. 资源占用、供电影响与性能观察
8.1 Arduino UNO 上的软硬件资源
Arduino UNO 的 ATmega328P 资源有限。Servo 库在 UNO 上会占用 Timer1,同时库内部最多可以驱动 12 路舵机,但每路舵机的刷新仍然依赖同一个定时器中断。
实际项目中如果还要用millis()做非阻塞计时,需要注意:Servo 库的中断频率较高,虽然millis()不受影响,但如果你自己注册了Timer1中断或依赖 D9、D10 的 PWM 输出,就很可能冲突。
更稳妥的设计是:把“舵机控制”和“业务逻辑”在程序结构上分离,舵机只使用一个引脚,其他 PWM 输出尽量放到 D3、D5、D6、D11 这些由 Timer2/Timer0 控制的引脚。
8.2 电流与电压观察方法
实测过程中建议这样观察:
- USB 供电时,舵机高速转动瞬间,USB 电压可能被拉低。如果 Arduino 莫名其妙复位,优先怀疑供电。
- 用万用表串联测量舵机电流,可以非常直观地看到空载、转动、堵转三个状态下的电流差值。
- 堵转时电流最大,因此不要让舵机长时间顶着机械结构。
不同批次 SG90-360 的工作电压、堵转电流差异很大。以你手头实物为准,不要直接把某个教程的电流值硬套到自己项目里。
8.3 转速非线性的影响
脉宽从停止点向两侧偏移得越多,舵机转速通常越快,但“脉宽差值”和“转速”不是严格的线性关系。比如从 1500 改为 1300,转速可能变化很小;从 1200 改为 1000,转速增速却很明显。
所以在做开环动作序列时,不要把转速直接按脉宽差值线性估算。在实际负载下,建议用秒表或计时器量出“1 秒大概转几圈”,再用转速做时间换算。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 上电后完全不动 | 信号线没接对、脉宽落在停止点或死区 | 检查接线颜色,使用串口程序输入偏离中心脉宽 | 重新接信号线;输入 1000 或 2000 测试 |
| 舵机持续“嗡嗡”响但不转 | 供电不足或脉宽仍在死区附近 | 听声音判断是否堵转;用可调脉宽逐个扫描 | 改外部电源;逐渐把脉宽调到更偏中心的值 |
| 1500us 时舵机还在缓慢转动 | 停止点不在默认中心值 | 串口输入 1450-1550 之间的值逐个测试 | 找到真实停止值并更新STOP_PULSE |
| 1000us 和 2000us 转向不符合预期 | 方向定义与自己设计不一致 | 测试中记录实际方向 | 在代码里把“正转”和“反转”的常量值对调 |
| 正反转速差异大 | 舵机批次差异、供电电压不稳 | 空载对比 1000us 和 2000us 的转速 | 如果应用允许,给不同方向设置不同脉宽值 |
| Arduino 在转动瞬间重启 | 电流过大,USB 口供电能力不足 | 打开串口监视器观察复位信息 | 舵机使用外部 5V 电源,GND 共地 |
| 转动到一半卡住并抖动 | 机械负载过大或外部挡板限制 | 让舵机空载测试 | 去掉负载;不要在有机械限位的情况下持续旋转 |
D9 或 D10 的analogWrite()失效 | Servo 库占用 Timer1 | 查看项目里是否同时使用这两个引脚 | 舵机换到其他引脚,或在架构上分开控制 |
| 串口输入脉宽没反应 | 波特率不一致或程序被阻塞 | 检查串口监视器波特率是否为 9600 | 确认Serial.begin(9600)与实际选择一致 |
| 停止时舵机“吱吱”微调 | 停止脉宽落在死区边缘 | 用更小步进找中心 | 使用更稳定的停止值,不要在临界值附近运行 |
10. 最佳实践与后续升级方向
10.1 调试阶段建议
先把舵机固定在测试台或桌面上,不要让它在桌上乱跑。调试时尽量使用“停止 -> 低速 -> 停止”的节奏,不要一上来就给最大脉宽,避免突然的高转速和冲击。
把找到的停止脉宽、正转速度范围、反转速度范围记录在代码开头,形成一组可复用的常量。这样后续写小车、云台或机械结构时,不必每次都从零校准。
10.2 批量测试思路
如果需要测试多台 SG90-360 舵机的一致性,比如验收一批模块,建议用串口发送指令的方式写一个简单测试脚本。思路是:
- 上位机通过串口发送脉宽值。
- Arduino 接收后输出到舵机。
- 人工或摄像头观察舵机是否动作、是否停在预期位置。
如果只是寿命测试,可以让 Arduino 程序自己循环运行“正转 1 秒、停 0.5 秒、反转 1 秒、停 0.5 秒”,然后记录运行时长。这种批量测试需要避免长时间堵转,并给舵机留出散热时间。
10.3 从开环走向闭环
当你发现开环控制不够用时,升级路径有两条:
- 更换带反馈的舵机或电机,比如带编码器的直流减速电机、总线舵机等。
- 保留 SG90-360,但外部增加编码器、电位器或测速模块,把位置或速度反馈给 Arduino。
如果只是做简单的小车转向或转台旋转,360 度舵机很好用;但一旦需要精确角度、重复定位或闭环调速,就应该尽早换方案。
10.4 把舵机接入更大系统
Arduino UNO 只是其中一种控制器。你可以把同样的脉宽控制逻辑移植到 ESP32、ESP8266、树莓派 Pico 等平台。如果通过上位机或 ROS 控制,也是按“20ms 周期脉宽”发送控制信号。
在接入实际设备前,务必确认:供电能力是否满足、机械结构是否安全、舵机堵转保护策略是否存在。舵机只是执行器,不负责判断边界,边界逻辑要由代码和机械设计来保证。
11. 总结
这篇内容围绕 SG90 360 度连续旋转舵机在 Arduino UNO 上的控制展开,重点是掌握writeMicroseconds()的脉宽控制思路,理解连续旋转舵机为什么是开环控制,以及如何通过串口校准找到真实的停止点。
建议收藏备用。如果你还没开始动手,第一件事不是写复杂控制逻辑,而是先把“串口输入脉宽”程序烧进去,输入 1500、1000、2000,确认舵机的停止、正转、反转都能正常响应。校准完成后,再把脉宽和时间组合成动作序列,后续写小车、转台或云台项目会顺利很多。