拿到一个丝印上写着“SG90”的舵机,如果它是 360 度连续旋转版本,请立刻忘掉“标准舵机是按角度转动”的习惯。你会发现servo.write(90)并不能让它停在中间,servo.write(0)也不会让它转 180 度后再停下来。它可能一直转,也可能在某个脉冲下缓慢爬行,甚至发writeMicroseconds(1500)后它还在慢慢转。
这不是接线错了,也不是舵机坏了,而是没有理解它的本质:360 度连续旋转舵机是一个“开环速度控制设备”,不是“位置控制设备”。标准 SG90 内部有位置反馈,能闭环锁住角度;而 360 度版本通常取消了电位器反馈或者把反馈固定在中位,控制信号实际只决定“朝哪个方向转、转多快”,不决定“转多少度”。
本文我用 Arduino UNO + SG90 360 度连续旋转舵机,把整套控制逻辑拆开讲清楚。内容包括:
- SG90 标准角度版和 360 度连续旋转版怎么区分;
writeMicroseconds()为什么是更合适的控制 API;- 硬件接线、基础代码、停止点校准;
- 开环控制下如何用时间估算转动量;
- 常见故障排查和工程建议。
读完你能独立跑通一个“转向 + 停 + 反向转”的示例,并且知道什么时候该加编码器或限位开关,把开环系统升级成闭环。
1. 为什么 SG90 360 度版本不是“能转 360 度的 SG90”
1.1 名字相同,内部逻辑不同
标准 SG90 是最常见的 9g 微型舵机。它的内部结构包括直流电机、减速齿轮、电位器和一块控制电路。工作原理可以简化成一句话:
输入一个目标位置的 PWM 脉冲,控制电路读取电位器反馈的当前位置,计算误差,然后驱动电机向误差减小的方向转动,直到当前位置和目标位置一致。
这就是典型的闭环位置控制。电位器就是反馈传感器。所以标准 SG90 可以快速到达 0°、45°、180° 这样的角度位置,并且外部负载不太大时,位置能保持住。
而市面上的“SG90 360 度连续旋转舵机”,从机械结构上往往取消了电位器与输出轴的联动,或者直接把电位器替换成固定电阻分压。这样一来,控制电路不再知道输出轴到底在哪个角度,它只拿输入脉冲与一个固定中位做比较。
比较结果如果是“正偏差”,电机就一直往一个方向转;如果是“负偏差”,电机就一直往另一个方向转;如果接近零偏差,电机才停。
所以 360 度连续旋转舵机本质上是一个:
用 PWM 脉宽控制方向与转速的直流电机驱动模块。
很多新手会把它理解成“能转 360 度的 SG90”,其实不对。标准 SG90 的旋转范围通常约 180°,而 360 度连续旋转舵机是可以无限圈连续旋转的,只是没有角度反馈,不能精确告诉你转到哪了。
1.2 拿到手后怎么快速区分
如果你手边已经有一个舵机,不确认它是标准角度版还是连续旋转版,可以用下面方法初步判断:
- 用手轻轻捏住输出舵盘,尝试转动。
- 如果转起来明显有阻力,并且松手后会自动弹回一个固定位置,大概率是标准角度舵机,内部存在电位器位置反馈。
- 如果转动比较顺畅,没有明显回中力,可以转很多圈,说明大概率是连续旋转版本。
不过这个判断不是 100% 准确,有些电机减速箱本身也有阻力。更可靠的方法是在程序中发一个位置指令,观察动作。发write(90),如果舵机快速回到中间位置并停住,就是标准角度版;如果它不回到中间,甚至继续转动,那就是连续旋转版。
1.3 闭环与开环:一个关键区别
| 项目 | 标准角度舵机 | 360 度连续旋转舵机 |
|---|---|---|
| 反馈方式 | 电位器反馈当前位置 | 无位置反馈或固定中位 |
| 控制目标 | 到达指定角度 | 转动方向和转速 |
| 控制类型 | 闭环位置控制 | 开环速度控制 |
| 常用 API | write(angle) | writeMicroseconds(us) |
| 能否自动回到固定位置 | 能 | 不能 |
| 典型应用 | 机械臂关节、云台、机器人关节 | 小型轮子、持续旋转结构、云台扫动 |
从控制角度看,标准舵机是“我告诉你目标角度,它自己努力转过去”;连续旋转舵机是“我告诉你往左转、转快点,但到底转了多少,它不负责汇报”。
这个区别决定了后续所有代码写法。
2. writeMicroseconds 与 write 有什么区别
2.1 Servo 库中的两个核心 API
Arduino IDE 自带的Servo.h库封装了舵机控制的底层细节。最常用的两个方法是:
servo.write(angle); servo.writeMicroseconds(us);write(angle)的参数是角度,范围一般是 0 到 180。库内部会把角度映射成对应的脉冲宽度,再发送给舵机。对标准角度舵机来说,这个 API 很符合直觉。
writeMicroseconds(us)的参数是高电平脉冲宽度,单位是微秒。RC 舵机通常工作在 50Hz,即周期约 20ms,其中高电平脉冲宽度决定了控制量。常见范围是 500us 到 2500us,很多舵机的实用范围是 1000us 到 2000us。
2.2 为什么 write 会让连续旋转舵机失控
标准舵机里,write(0)、write(90)、write(180)分别对应不同的角度。但连续旋转舵机没有角度反馈,write(0)实际只是发出一个接近 500us 或 1000us 的脉冲,这个脉冲会让舵机朝某一方向全速转动。
它不会在 0 度停下来,因为它根本不知道 0 度在哪里。
同理,write(90)只是输出大约 1500us 的中性脉冲。问题是,不同批次的连续旋转舵机,真正停止点不一定严格在 1500us。有的在 1460us,有的在 1530us。如果你直接write(90),舵机可能仍然以很慢的速度转动。
所以控制 360 度连续旋转舵机时,我更推荐直接使用writeMicroseconds()。它让你从“角度思维”切换到“脉宽思维”,更贴近舵机实际工作的信号层面。
2.3 脉宽、方向与速度的关系
对典型的连续旋转舵机,控制规律大致如下:
| 脉宽 | 表现 |
|---|---|
| 低于 1500us(如 1000us) | 一个方向全速转动 |
| 接近 1500us(如 1480~1520us) | 停止或不确定 |
| 高于 1500us(如 2000us) | 另一个方向全速转动 |
| 与 1500us 偏差越小 | 速度越慢 |
注意,这里的 1000us 和 2000us 并非所有舵机都一样。有的 SG90 360 版本支持更宽的脉宽范围,有的则只在 700us 到 2300us 之间有效。具体范围以你实际购买到的舵机说明书为准。
“接近 1500us 就停止”也有一个坑:停止点不一定正好是 1500us。即使是同型号同批次,也可能有微小差异。后面我会专门写一个校准程序帮你找到真正停止点。
3. 环境准备与硬件接线
3.1 需要准备的器材
- Arduino UNO 开发板 x1
- SG90 360 度连续旋转舵机 x1
- 面包板 x1
- 杜邦线若干
- USB 数据线 x1
- 外部 5V 电源(可选,但强烈推荐)
建议再准备一瓶带颜色的胶带或记号笔。后面做开环角度标定时,需要在舵机输出盘上做标记。
3.2 SG90 三根线怎么接
SG90 的线序有固定规律,但不同厂商线色可能略有差异。常见标准接线如下:
| 舵机线颜色 | 含义 | 接 Arduino UNO |
|---|---|---|
| 棕色或黑色 | GND | GND |
| 红色 | VCC(电源正极) | 5V 或外部 5V 正极 |
| 橙色或黄色 | 信号 | D9 |
如果你的舵机线色不是上面这种,不要凭印象接,先用万用表确认线序,或者查看商品页说明。接反电源线很容易烧毁舵机控制板。
3.3 一个舵机如何供电
SG90 是 9g 微型舵机,单个舵机在轻载测试时,理论上可以尝试从 Arduino UNO 的 5V 引脚取电。但要注意:UNO 的板载稳压器供电能力有限,如果舵机堵转或瞬间启动电流较大,可能导致 Arduino 复位,表现为舵机抽搐、USB 断开或程序重新运行。
所以更稳妥的接线有两种:
方案一:直接接 UNO 5V,适合空载短时间验证。
SG90 红 → Arduino 5V SG90 棕 → Arduino GND SG90 橙 → Arduino D9方案二:外部 5V 电源给舵机供电,Arduino 与舵机共地。
SG90 红 → 外部 5V 正极 SG90 棕 → 外部 5V GND 和 Arduino GND(共地) SG90 橙 → Arduino D9共地是为了让 Arduino 输出的信号电平和舵机电源参考地一致。如果地不共,信号可能乱跳,舵机行为会很奇怪。
从工程角度,只要不是只做几秒钟的空载演示,都建议用方案二。后面如果接两个或更多舵机,更不能全部从 UNO 5V 引脚取电。
4. 基础驱动示例:正转、停止、反转
4.1 最小控制代码
在 Arduino IDE 中新建项目,输入以下代码:
#include <Servo.h> Servo servo; const int servoPin = 9; void setup() { servo.attach(servoPin); // 先输出 1500us,让舵机进入停止状态 servo.writeMicroseconds(1500); delay(1000); } void loop() { // 保持停止 servo.writeMicroseconds(1500); delay(2000); // 高速正转 1.5 秒 servo.writeMicroseconds(1900); delay(1500); // 停止 1 秒 servo.writeMicroseconds(1500); delay(1000); // 高速反转 0.8 秒 servo.writeMicroseconds(1100); delay(800); // 回到停止 servo.writeMicroseconds(1500); delay(2000); }这段代码的核心是servo.writeMicroseconds()。它直接告诉舵机当前要输出的脉宽。
- 1900us:高于中性点,舵机会向一个方向转动;
- 1500us:接近中性点,舵机停止;
- 1100us:低于中性点,舵机向另一个方向转动。
其中delay()决定了转动时间。比如高速转 1.5 秒后会停止,但这不是“精确转 1.5 秒对应某个角度”,只是“让执行过程持续一段时间”。
4.2 如何验证是否成功
上传代码后,打开串口监视器没有特别输出,但舵机应该按照下面顺序动作:
- 上电后先保持不动约 1 秒;
- 停止 2 秒;
- 朝一个方向转动 1.5 秒;
- 停止 1 秒;
- 朝另一个方向转动 0.8 秒;
- 再次停止。
如果舵机转了一个方向后没有停下来,而是继续低速转动,说明 1500us 并不是你手上舵机的真正停止点。下一步需要做校准。
5. 用串口校准程序找到真正停止点
5.1 为什么要校准
很多连续旋转舵机的“中位停止脉冲”并不是标准的 1500us。虽然大部分舵机以 1500us 为中性参考,但在实际生产中,元件误差会导致停止点偏移。
另外,舵机还存在“死区”。也就是脉宽变化很小的一段范围内,电机可能不转或者输出力矩很小。死区之外,脉宽稍微偏离中位,舵机就会开始缓慢转动。
如果你发 1500us 后发现舵机还在慢慢爬,不要惊讶,先校准。
5.2 串口校准程序
下面的程序会通过串口监视器接收你输入的脉宽值,并实时发送给舵机。你可以从 1500us 开始,逐步加减,找到舵机真正停止的值。
#include <Servo.h> Servo servo; const int servoPin = 9; int currentUs = 1500; void setup() { Serial.begin(9600); servo.attach(servoPin); servo.writeMicroseconds(currentUs); Serial.println("Servo calibration start"); Serial.print("Current pulse width: "); Serial.println(currentUs); Serial.println("Input a value between 1000 and 2000"); } void loop() { if (Serial.available() > 0) { int value = Serial.parseInt(); // 简单防呆,避免误输入越界值 if (value >= 800 && value <= 2500) { currentUs = value; servo.writeMicroseconds(currentUs); Serial.print("Set to: "); Serial.println(currentUs); } else { Serial.println("Value out of range"); } } }操作步骤:
- 编译上传程序;
- 打开串口监视器,波特率选择 9600;
- 在输入框发送
1500,观察舵机是否停转; - 如果还在缓慢转动,发送
1480、1520、1460、1540等值,反复比较; - 找到一个输出轴完全静止、即使轻轻用手扭动也有反向抵抗力的脉宽值;
- 把该值记为
NEUTRAL_US。
你可能发现多个相邻值都能让舵机停止,比如 1485us 到 1515us 都在死区内。这时取中间值作为中性脉宽即可。
比如最终测得停止点在 1480us,那么后续代码中就不要再用 1500us 作为中位,而应该定义:
const int NEUTRAL_US = 1480;这是一个非常关键的实测步骤。标题里说“Arduino UNO 实测”,真正的实测不是看舵机能不能转,而是校准出你手上这只舵机的停止点。
6. 开环控制下的角度估算与场景实践
6.1 连续旋转舵机能做什么
由于精度不高,360 度连续旋转舵机不适合做机械臂关节或其他需要精确角度定位的场景。它更适合:
- 小型小车驱动轮;
- 水平云台持续扫描;
- 模型风机、转盘等匀速旋转结构;
- 要求转向灵活、不要求精确角度的机器人底盘。
典型例子是双舵机小车。两个 360 度连续旋转舵机分别驱动左右轮,通过差速实现前进、后退和转向:
- 前进:左右轮同向转动;
- 后退:左右轮反向转动;
- 原地左转:左轮反向转,右轮正向转;
- 停车:两个舵机都输出中性脉宽。
这种控制模型简单,但真正的难点是:开环控制没法保证两个轮子转速完全一致,所以小车走直线可能会偏。
为什么?因为 PWM 脉宽决定了控制信号,但实际转速还受电池电压、负载、地面摩擦力、舵机个体差异影响。你给两个舵机都发 1900us,它们实际转速未必完全一致。
6.2 如何估算转过的圈数
开环控制下,想粗略实现“转到某个角度”,通常的思路是:
先选定一个固定脉宽,测量单位时间内转过多少圈,然后用时间推算目标角度。
具体做法如下:
- 在舵机输出盘上贴一段彩色胶带作为标记;
- 发送一个固定脉宽,例如 1000us,让舵机全速转动;
- 记录一个固定时间,比如 2 秒;
- 时间到后立刻发送中性脉宽停止;
- 观察舵机输出盘转了多少圈;
- 重复几次取平均,得到“2 秒约转 N 圈”的经验值。
之后你要让它转大约 5 圈,就按这个经验值估算需要多长时间,然后改为:
const int NEUTRAL_US = 1500; // 根据校准结果修改 const int SPEED_US = 1000; // 全速方向 servo.writeMicroseconds(SPEED_US); delay(calculatedMs); servo.writeMicroseconds(NEUTRAL_US); delay(100);注意,这只是估算,不是精确控制。因为每次启动、停止、负载变化都会带来误差,而且误差会累积。
如果你只想测试这个思路,可以先写一个简单标定代码:
#include <Servo.h> Servo servo; const int servoPin = 9; const int NEUTRAL_US = 1480; // 改成你校准到的值 void setup() { Serial.begin(9600); servo.attach(servoPin); servo.writeMicroseconds(NEUTRAL_US); delay(1000); } void loop() { // 全速转 2 秒 servo.writeMicroseconds(1000); delay(2000); // 停止 servo.writeMicroseconds(NEUTRAL_US); delay(2000); // 观察结束后,进入死循环,避免反复自动测试 while (1) { delay(1000); } }上传后观察输出盘转动的圈数,再把时间参数换算成你要的角度。这是个典型的开环标定实验。
6.3 开环控制的局限
开环控制最大的问题是没有“确认机制”。
你发出“转 2 秒”的指令后,如果舵机中途堵转,程序并不知道,也没有任何自动修正能力。结果就是最终位置和你预期位置偏差很大。
所以在实际项目中,如果位置精度很重要,你有几个选择:
- 换成标准角度舵机,利用内部电位器闭环定位;
- 换成步进电机或带编码器的直流电机,做位置闭环;
- 在连续旋转舵机的输出端增加限位开关,每次启动时回零,消除累积误差;
- 增加霍尔编码器或光电码盘,把开环系统升级成闭环。
“开环控制适合结构简单、误差不敏感、成本敏感的小项目”是一个比较准确的判断。真正的机器人项目,精度要求稍高就会考虑闭环。
7. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 舵机完全不动 | 供电不足、接线错误、程序没跑 | 检查线色、重新上传例程 | 外部 5V 供电,共地,确认信号线接对 |
| 发 1500us 还在慢慢转 | 中性点偏移 | 用串口校准程序扫描脉宽 | 校准出真正停止点,定义NEUTRAL_US |
| 舵机只能全速转,不能调速 | 脉宽变化太快或范围不对 | 从 1000us 到 2000us 逐步测试 | 确认舵机支持的脉宽范围 |
| Arduino 重启或舵机抽搐 | 舵机电流过大拉低电压 | 观察 UNO 是否重启 | 改用外部电源,不直接从 5V 引脚取电 |
| 舵机发热严重 | 持续堵转或长时间高速运行 | 触摸外壳温度 | 串口通信空闲时输出中性脉宽,避免长时间堵转 |
| 两个舵机转速不一致 | 个体差异、供电不稳 | 更换 PWM 引脚或测量电压 | 使用同批次舵机,追求一致需闭环 |
servo.read()读了不对 | 读到的只是目标值不是实际值 | 打印串口观察 | 连续旋转舵机没有角度反馈,不能依赖read() |
需要特别提醒的是,很多新手会把servo.read()当成实际反馈来用。实际上,标准舵机读取的是库内部记录的最后目标值,不是传感器返回值。360 度连续旋转舵机则更不可能告诉你当前角度。如果你需要真实角度,只能外接编码器或位置传感器。
另外,如果你用两个舵机做小车,尽量不要让一个舵机在堵转状态下持续工作。堵转电流大、发热快,容易损坏舵机内部齿轮或电机。
8. 最佳实践与工程建议
8.1 把中性点和水头引脚写成常量
控制代码不要到处直接写1500。建议开头统一定义:
const int NEUTRAL_US = 1480; const int MIN_US = 1000; const int MAX_US = 2000; const int servoPin = 9;这样换一只舵机时,只需要重新校准并修改NEUTRAL_US,不需要翻遍全项目。
对多档速度,可以封装成函数:
#include <Servo.h> Servo servo; const int NEUTRAL_US = 1480; const int MIN_US = 1000; const int MAX_US = 2000; void setServoPulse(int speedPercent) { speedPercent = constrain(speedPercent, -100, 100); int pulseUs; if (speedPercent >= 0) { pulseUs = NEUTRAL_US + (MAX_US - NEUTRAL_US) * speedPercent / 100; } else { pulseUs = NEUTRAL_US + (NEUTRAL_US - MIN_US) * speedPercent / 100; } servo.writeMicroseconds(pulseUs); } void setup() { servo.attach(9); servo.writeMicroseconds(NEUTRAL_US); delay(500); } void loop() { setServoPulse(60); delay(1000); setServoPulse(0); delay(1000); setServoPulse(-60); delay(1000); }需要说明的是,这个映射仍是近似关系。由于舵机内部电路和死区存在,小百分比脉宽下可能不转,或者速度并非完全线性。
8.2 注意电源和共地
SG90 360 连续旋转舵机虽然体积小,但堵转电流仍然可能很大。单舵机测试建议配备一个能提供 1A 以上电流的 5V 电源,并把电源负极与 Arduino GND 连接。
如果条件不足,至少做到:
- 每次只接一个舵机进行测试;
- 不要长时间堵转;
- 避免 USB 供电驱动多舵机。
8.3 测试流程先空载再带载
新舵机到手,先不要直接装到车体或机械结构上。先在空载状态下验证:
- 舵机能否按要求正转、反转、停止;
- 校准停止点;
- 测量不同脉宽下的转速;
- 确认稳定后再接入实际负载。
接入负载后,转速会发生变化,之前空载标定的圈数不再可靠,需要重新做带载标定。
8.4 设计“软启停”,不要瞬间正反转
如果你的程序需要让舵机从全速正转立即切换到全速反转,建议先让舵机回到中性脉宽,等待几百毫秒后再反向。
瞬间正反转会让电流冲击变大,齿轮也更容易磨损。一个简单做法是:
servo.writeMicroseconds(1000); delay(1000); servo.writeMicroseconds(NEUTRAL_US); delay(300); servo.writeMicroseconds(2000); delay(1000);8.5 为闭环升级预留接口
如果项目是做课程设计或长期迭代,建议在硬件设计时预留编码器接口或限位开关输入引脚。哪怕第一版先用开环跑通功能,后续也能比较容易地加反馈。
软件上可以把“设置目标速度”和“是否到达目标”分开抽象。开环阶段,到达判断可以用延时;闭环阶段,只需要替换为编码器计数判断,不需要改太多业务逻辑。
9. 总结
SG90 360 度连续旋转舵机的入门关键,不是背几个 API,而是理解它和标准角度舵机的本质差异。标准舵机是“位置闭环”,360 度连续旋转舵机是“开环速度控制”。控制它时,不要带着角度思维,要用writeMicroseconds()直接控制脉宽。
实际项目中最容易踩的坑有三个:
- 不知道停止点可能偏移,直接写死 1500us;
- 把开环控制当成精确角度控制;
- 直接用 Arduino 5V 引脚驱动多个舵机,导致供电不稳定。
解决办法也很明确:先区分手上舵机是标准版还是连续旋转版,再用串口校准程序找到真正停止点,然后以“脉宽 + 时间”的方式估算转动量。如果精度不够,就需要引入编码器或限位开关,把系统升级成闭环。这个从开环到闭环的思考路径,比单纯跑通一个例子更有价值。