如果你手里的元件 是 SG90 的“360 度连续旋转”版本,那么你多半会经历一个很拧巴的阶段:把原来驱动普通舵机的那套writeMicroseconds代码原样搬过来,发现舵机要么一直转,要么转起来没有停的意思,甚至你给它传了一个“90 度”的指令,它却停在那里发呆。这不是 Arduino UNO 烧录失败,也不是库写错,而是你可能正在用一个理想化“位置执行器”的思维,去控制一个本质上是“速度型执行器”的设备。
这件事往深了说,涉及两个概念:writeMicroseconds到底在写什么,以及什么是开环控制。如果只照着网上片段复制代码,你只能碰巧跑通一种情况,一旦换了负载、电压、任务目标,整个系统就会回到“不可控”。这篇文章不打算只给你一段能用的 SG90 代码,而是想把连续旋转舵机的内部变化、控制信号含义、Arduino UNO 实测过程,以及为什么你最后很可能需要闭环反馈这件事讲透。
1. 同样叫 SG90,其实已经不是同一台“舵机”
1.1 标准舵机的“定位”靠的是电位器
普通 SG90 能停在指定角度,是因为它内部有一条完整的小闭环。很多教程只教你接线和调用writeMicroseconds,却不会提醒你:舵机里面有个输出轴,输出轴上通常连着一个电位器。电机转动时,电位器的阻值会跟着变化,控制板读取这个阻值,就能知道输出轴当前大概在什么角度。
你看似在用 Arduino UNO 给舵机发“目标角度”,实际上舵机内部一直在做比较:目标脉冲宽度对应的位置,和当前电位器反馈回来的位置,二者有没有误差。有误差就继续转,误差为零就保持。标准舵机停下来以后,如果你用手去掰舵臂,它会反抗,甚至发出“咔咔”的声音,因为它内部的小闭环还在努力把输出轴纠正回目标角度。
这也是为什么标准舵机适合做机械臂、摄像头云台、机器人关节这类需要“知道输出端在哪”的场景。
1.2 连续旋转舵机拆掉的其实是“告诉控制器当前位置”的那只眼睛
SG90 连续旋转版本,也就是 360° 版本,内部结构上最大的变化,是把输出轴上的电位器位置反馈去掉,或者用固定电阻替代。这就像一个人闭着眼睛,只能听指挥说“向前”“向后”“停”,但自己并不知道手伸到了哪里。
这时候你再看控制关系,就会发现它已经不是角度闭环了。它的内部驱动电路仍然会收到脉冲宽度,然后把它理解成一个大致的期望值。但因为没有了当前角度反馈,它无法判断“现在到了没有”。于是同一个信号在不同阶段会产生不同效果:
- 脉冲宽度在中位附近,内部误差小,输出轴基本不动。
- 脉冲宽度偏向一端,内部产生持续误差,电机朝一个方向连续旋转。
- 脉冲宽度偏向另一端,电机反向旋转。
整个过程从使用者的视角看,就是一个典型的开环控制。你发出的是“速度和方向”命令,而不是“绝对角度”命令。
1.3 真正在做的事,更像在控制一台减速电机
有一个更容易理解的类比:连续旋转舵机其实更像一个自带减速齿轮箱的直流电机,只不过它的驱动信号沿用了舵机的脉冲宽度协议。
它能用来做很多“转圈”的事,比如驱动小车轮子、带动云台持续扫描、转动某个绕线机构。但如果你把它当作“能旋转到任意角度的舵机”来用,那就要失望了。它没有输出轴角度传感器,也没有编码器,舵机和 Arduino UNO 都不知道当前转到哪。
下面这张表可以帮你快速区分:
| 对比项 | 普通 SG90 | SG90 360° 连续旋转版 |
|---|---|---|
| 内部反馈 | 有电位器反馈 | 无输出角度反馈 |
| 控制对象 | 输出轴角度 | 电机方向与等效速度 |
| 收到中位信号 | 停在目标中位 | 试图停在“静止”状态 |
| 收到端点信号 | 转到最大角度并保持 | 持续向一个方向旋转 |
| 能否返回当前位置 | 可以 | 不能 |
| 是否适合做机械臂关节 | 适合 | 不适合 |
如果你一开始就纠结“为什么我用writeMicroseconds传一个 90 的值它不转圈”,那大概率是还没有完成这个思维转换:连续旋转舵机不是能转到 90°、180°、360° 的舵机,它是一台披着舵机外衣的电机。
2. 用 writeMicroseconds 前,先理解它为什么和 write(角度) 不同
2.1 写在 PWM 信号里的不是“度数”,而是脉冲宽度
Arduino 的 Servo 库里面,writeMicroseconds(1500)并不是在说“转过 1500 微米”或者“转 1500 度”,而是指定一个高电平持续时间。舵机控制信号是一个周期性脉冲,频率通常在 50Hz 左右,也就是说周期大约 20ms。在这 20ms 里,高电平持续 1000µs、1500µs 或者 2000µs,舵机内部电路通过识别这个高电平持续时间来决定动作。
如果把它看成普通 PWM,占空比大约分别是 5%、7.5%、10%。但舵机控制真正关心的不是“百分之几的占空比”,而是在每个周期里高电平到底持续了多少微秒。这也是为什么有writeMicroseconds这个接口,而不是只提供一个 0~255 的占空比寄存器。
在常见标准舵机里,脉冲宽度通常对应角度范围。很多资料默认:
- 500µs 左右对应角度下限。
- 1500µs 左右对应中位。
- 2500µs 左右对应角度上限。
但具体数值会因为舵机品牌、内部电路版本、库的初始映射而略有不同。连续旋转舵机则正好利用了脉冲宽度的分段关系:你能把 1500µs 附近想象成“停”,向两端延伸就是“向一个方向转得越来越快”或“向另一个方向转得越来越快”。
2.2 write(90) 不是“转 90 度”的替代写法
很多初学者会犯一个错误:看到普通舵机代码里用servo.write(90)表示转到中间角度,于是在连续舵机上也想用servo.write(90)来让舵机转到某个绝对角度。但write()在函数内部仍然会把角度换算成一段脉冲宽度。不同版本、不同库的实现可能有差异,但大方向一致:输入 90 左右,最终对应的脉冲宽度大概率接近 1500µs。
也就是说,write(90)放到连续旋转舵机上,结果多半是“停”,而不是“转到 90 度方向”。
如果你手里的库函数本身支持直接指定目标角度,那也改变不了物理层面没有角度反馈的事实。更稳妥的做法是,在连续旋转舵机这类设备上直接使用writeMicroseconds,去控制脉冲宽度。虽然这个名字看起来有点底层,但它反而能让你更清楚自己在做什么。
2.3 第一段可以看到结果的完整示例
下面是一个最基础的三段式测试,适合先确认你的连续 SG90 是否能响应不同方向:
#include <Servo.h> Servo rotationServo; const int sigPin = 9; void setup() { rotationServo.attach(sigPin); rotationServo.writeMicroseconds(1500); delay(1000); } void loop() { // 一个方向转 2 秒 rotationServo.writeMicroseconds(1000); delay(2000); // 中位停 4 秒 rotationServo.writeMicroseconds(1500); delay(4000); // 反方向转 2 秒 rotationServo.writeMicroseconds(2000); delay(2000); // 中位停 4 秒 rotationServo.writeMicroseconds(1500); delay(4000); }烧录后,如果舵机正常,你应该会看到:一个方向转 2 秒,停 4 秒,再反向转 2 秒,再停 4 秒。这个例子虽然简单,但它能帮你快速区分“代码逻辑问题”和“硬件反馈缺失问题”。
3. Arduino UNO 实测:先找“停止点”,再测速度
3.1 接线其实没有多复杂,但电源要心里有数
SG90 连续旋转舵机通常有三根线。不同厂家颜色略有不同,常见的是:
- 棕色或黑色:地线 GND。
- 红色:电源正极,多数资料里使用 5V。
- 橙色或黄色:信号线,接到 Arduino UNO 的数字引脚,比如 9 号引脚。
测试时最简单的接法,是把舵机的 5V 接到 Arduino UNO 的 5V 引脚上,GND 接到 GND,信号线接到 9。这种接法在没有负载、只是转一转的情况下通常能跑通。
但要注意,Arduino UNO 的 5V 引脚更多是“板载稳压输出”性质,它适合给少量传感器和轻负载供电,不适合把所有电流压力都放在这里。SG90 这类小舵机,堵转或瞬间启动电流并不小,如果此时又接了很多传感器,或者让舵机长时间带动有阻力的机构,USB 输入电流可能撑不住,轻则舵机抽搐,重则 Arduino UNO 重启。
所以更稳妥的测试建议是:
- 先做单舵机轻载测试,再考虑加大负载。
- 如果用 USB 给 Arduino UNO 供电,不要同时从 5V 引脚给大负载供电。
- 如果有多个舵机或带负载,使用独立 5V 电源,并把电源负极和 Arduino UNO 的 GND 连在一起。
- 不管电源从哪里来,地线必须连通。
不要一上来就把代码逻辑放在“速度假设”上。先让系统有一个稳定的物理连接,再讨论控制问题。
3.2 上电后别让代码一上来就转
写测试程序时,我习惯在setup()里先把舵机信号设成停止中位。原因很简单:不是所有引脚在单片机上电瞬间都处于一个确定的低电平状态,如果你程序刚开始就跑方向指令,舵机可能会在上电瞬间猛转一下。
把rotationServo.writeMicroseconds(1500);放到setup()中,相当于让设备先回到“静止”状态。连续舵机本身没有锁止能力,但它至少不会因为程序主动发命令而意外乱转。
如果你在做实物小车的项目,这一点尤其重要。上电瞬间冲出去,哪怕只有几百毫秒,也足够让设备撞到东西或者让调试进度倒退。
3.3 用三段脉冲做第一次转动测试
刚才那段代码已经包含三段式测试,但你实际操作时,不要只看着舵机转就结束,我建议你重点观察两个信息:
第一,writeMicroseconds(1000)和writeMicroseconds(2000)的方向是否相反。如果你的舵机接线方向和你预期不一致,不需要改代码,可以把两个值互换理解,或者在后期程序里加一个方向常量来统一控制。
第二,writeMicroseconds(1500)是否真的能让舵机停止。很多连续舵机出厂标称中位是 1500µs,但个体差异、装配误差,甚至供电电压变化,都会让实际停止点不完全落在 1500µs。如果你的舵机在 1500µs 时还在缓慢爬行,那说明这不是“方向指令写错了”,而是你的中位需要校正。
3.4 找“中点”比找“速度”更重要
找中点是一个很朴素的方法:把舵臂拆掉,或者把输出轴上的圆盘固定好,然后写一个小循环,让舵机从 1430µs 到 1570µs 之间来回切换,每次加 5µs 到 10µs,停一会儿,观察舵机在哪个值附近不再转动。
#include <Servo.h> Servo testServo; const int sigPin = 9; void setup() { testServo.attach(sigPin); Serial.begin(9600); Serial.println("Scan center pulse..."); } void loop() { for (int pulse = 1430; pulse <= 1570; pulse += 10) { testServo.writeMicroseconds(pulse); Serial.print("pulse="); Serial.println(pulse); delay(1200); } for (int pulse = 1570; pulse >= 1430; pulse -= 10) { testServo.writeMicroseconds(pulse); Serial.print("pulse="); Serial.println(pulse); delay(1200); } }这段代码会让舵机长期处于“正在找停止点”的状态。实际操作时,你要边改边看,找到一个让舵机在空载下不明显爬行的值。把它记为centerPulse,以后所有“停”的操作都使用这个centerPulse,而不是一直写死 1500。
很多“舵机停不下来”的问题,并不是代码时序错了,而是停止点偏了 10µs 到 30µs。在小舵机上,这点偏差虽然看起来不大,但足以让输出轴持续缓慢旋转,给调试制造大量噪音。
4. 把开环控制说透:你会转一个方向,但无法保证转过的角度
4.1 开环控制到底少了什么
开环控制的概念并不高深。只要你的系统缺少“输出结果反馈到输入端再参与控制”这一步,它就算是开环控制。
对连续旋转舵机来说,你给的是“PWM 脉冲宽度”,系统实际输出的是“电机方向和转动速度”。但系统没有测量“输出轴已经转了多少度”,也没有把这个角度告诉你或内部控制器。所以整个控制回路是断开的。
这也是为什么很多第一次接触连续舵机的人,会写出类似下面这种逻辑:
- 先给方向脉冲 1 秒。
- 再给停止脉冲。
- 以为舵机刚好转了 90 度。
在实验室空载、电源稳定、机械结构轻飘飘的情况下,这种方法偶尔能碰巧接近目标。但只要负载稍大一点,或者电池电压下降一点,相同时间里的实际转速就变了,结果就是上一次转 90 度,这次转 70 度,下次可能转 110 度。你并没有真的控制角度,你只是控制了一个“让电机转了多久”的时间段。
4.2 为什么“延时多久=多少度”只能作为粗略实验
从工程角度看,“延时多久=多少度”相当于一个没有反馈的查表法:
- 先假设某个脉冲宽度下,空载转速固定。
- 再假设负载、电压、摩擦力不会改变这个转速。
- 最后用时间和速度估算角度。
这些假设在短时间内、低负载下可以成立,但在真实项目里并不可靠。温度变化会让润滑脂黏度变化,电池电压随着放电逐步下降,舵机带动不同机构时摩擦阻力也不一样。任何一个变量改变,都会直接体现在最终角度误差里。
所以“延时控制角度”不是不能用,但它只能用于对定位精度要求很低的场景。比如摄像头需要来回扫描,你根本不在乎它精确停在第几度;比如小车需要转弯,你只要知道“转大概半秒能让车头偏转接近 90 度”,之后还能靠墙或其他传感器修正。
4.3 怎样给速度做一个简单标定
有一种更接近工程实践的做法:不要假设 1000µs 和 2000µs 对应固定速度,而是实测算出该电压下的大致转速。
你可以准备一个简单的指针或在舵机输出圆盘上贴一条胶带,然后执行以下步骤:
- 手动确认
centerPulse,先用这个脉冲让舵机彻底停止。 - 发送一个方向脉冲,比如 2000µs,同时开始计时。
- 让舵机完整转 5 圈,记录耗时。
- 换到反方向,例如 1000µs,同样测量完整转 5 圈耗时。
- 用转过的总角度除以总时间,得到一个“在当前电压和当前负载下”的近似角速度。
如果想让这个过程更正式,你可以在代码里加入millis()计时,把数据打印到串口监视器:
#include <Servo.h> Servo servo; const int sigPin = 9; int centerPulse = 1500; void setup() { servo.attach(sigPin); servo.writeMicroseconds(centerPulse); Serial.begin(9600); delay(1000); Serial.println("Start 5-turn test"); } void loop() { unsigned long startMs = millis(); servo.writeMicroseconds(2000); Serial.print("Expected measure duration for 5 turns..."); delay(5000); // 具体时间根据自己的圈数替换 servo.writeMicroseconds(centerPulse); unsigned long elapsedMs = millis() - startMs; Serial.print("elapsed ms="); Serial.println(elapsedMs); delay(3000); }这里我只写了框架,没有把圈数对应进代码。如果你真的要做粗略定位,我的建议是降低对单次“角度”的依赖,把它改成“重复多次、取平均、加入修正”。
4.4 如果要做闭环,你需要补什么
如果你希望连续舵机能停在精确角度,本质上有几条路:
- 在输出端加编码器或霍尔传感器,把转角反馈给主控。
- 在旋转路径上加光电门、微动开关或磁感应开关,让舵机每次经过零点就重新校准。
- 使用带位置反馈的普通舵机,不要让系统强行工作在没有反馈的硬件基础上。
- 更换成步进电机或带编码器的直流减速电机,在电机层面获取定位能力。
这些方案里,有些比 SG90 贵,有些比 SG90 复杂,但它们解决的是同一个问题:闭环反馈。
如果你的项目真的需要“这次转 90 度,下次再转 90 度,两次位置一致”,那连续旋转舵机不是一个合适的选择。它适合做持续旋转和粗略方向控制,但做不了高重复定位。
5. 最容易踩的四个坑和一套排查顺序
5.1 不动、抖动、漂移:先按输入、电源、接线、参数排查
调试 SG90 连续舵机时,不用急着怀疑代码。我的排查顺序一般是这样:
- 先看信号:舵机是否真的收到了脉冲。可以用示波器看 9 号引脚,也可以简单换一个引脚再试。
- 再看电源:舵机是否收到足够电压。电压不足时,舵机可能会轻微抽搐,或者只在空载时转,带一点阻力就停。
- 再看接线:信号线是否插错?电源正负是否反了?地线是否连到了 Arduino UNO?
- 再看代码:是否调用了
attach?是否在setup里给了一个停止脉冲?是否用了不合理的脉宽值? - 最后看参数:1500µs 是否真的是这个舵机的停止点?1000µs 和 2000µs 是否超出可识别范围?
很多“一动不动”的问题,最后都归结为:信号线插到了模拟引脚上,或者忘记attach。很多“转起来不回位”的问题,其实不是舵机坏了,而是因为它根本不是位置反馈舵机。
| 症状 | 优先检查项 | 下一步 |
|---|---|---|
| 完全不动 | 接线、电源、attach | 用别的 PWM 引脚测试 |
| 抖一下就不动 | 电源电流不足 | 换独立 5V 电源 |
| 转到没停 | 停止脉宽不对 | 找 centerPulse 替代 1500 |
| 停止后缓慢爬行 | 负载/电压变化 | 重新标定 centerPulse |
| 方向与预期相反 | 信号接线或方向理解 | 交换 1000/2000 逻辑 |
5.2 坑点一:把 180°舵机程序直接搬过来
最常见的一个错误,是机械臂项目里标准舵机程序写得好好的,代码一换到连续 SG90 上就不行。
拿普通舵机来说,writeMicroseconds(1500)是让输出轴回到机械中位,这个位置可以测量、可以保持。但连续舵机没有“机械中位”这个角度概念,它只是通过脉冲宽度让电机停转。当你说“回到中位”时,你实际发出的只是“停”命令,输出轴可能停在任意位置。
所以,项目里那些“先回到初始角度,再执行动作”的启动逻辑,在连续舵机上是无效逻辑。你要么加外部限位开关,要么把启动逻辑改成“先进入待机,不做任何位置假设”。
5.3 坑点二:不要迷信 1500 这个魔法数字
对于标准舵机,1500µs 对应中位通常没有大问题。对于连续舵机,1500µs 更接近“让驱动误差归零”的一个参考点,但机械装配、电子元件差异、占空比精度都会导致实际停止点偏移。
我在调试中见过最夸张的情况,是某个舵机要在 1520µs 附近才停得比较稳。另一个舵机却在 1480µs 附近更安静。如果你把 1500 写死,它可能就在 1500 附近慢慢爬,看起来像电机“失控”,其实只是没有校准到个体停止点。
如果你的程序里有很多地方都用到了停止脉冲,最好定义一个全局变量:
int servoStopUs = 1500;第一次校准时,在串口监视器里修改这个值,直到舵机不爬行为止。后期如果更换舵机,只需要重新校准这一个变量。
5.4 坑点三:供电不稳会让“速度标定”全部失效
连续舵机有两个明显特点:第一,没有位置反馈;第二,转速受电压影响非常大。你用 USB 供电做出来的速度数据,换到独立电源、锂电池或者不同容量的电源适配器后,大概率会变。
所以,如果你要做速度标定,必须注明测试电压和测试状态。最好的做法是:
- 保证舵机供电和主控供电用的是同一个稳定系统。
- 不要让电机高潮电流拉到 Arduino UNO 复位。
- 不要在启动瞬间读取速度数据。
- 如果舵机数量多,给每路电源加电容,或者使用舵机电源板。
电源这件事听着很基础,但它影响的不是舵机能不能转,而是你的延时估算还准不准。
5.5 坑点四:负载一旦改变,速度标定会失效
空载时,2000µs 可能让舵机转得飞快;加上一个减速箱传动轮,转速会明显下降;如果是带动一个摄像头滑台,摩擦力稍微不均匀,转速就会波动。
这个问题的本质还是开环控制。因为你没有转速反馈,所以你预设的“速度”只是理想值,一旦负载变化,实际转速就会偏离假设。这也是为什么连续舵机适合做云台转动、玩具车驱动这类阻力相对小的任务,但不适合做需要抵抗负荷、精确定位的机构驱动。
6. 学完方向控制后还要问:这个“舵机”到底适不适合你的项目
6.1 一个常用的“按时间转向”函数模板
如果你只是想做实验或小范围演示,可以封装一个很轻量的函数。它的核心逻辑是:给一个方向脉冲,运行一段时间,再回到停止脉冲。
void moveForDuration(int pulse, unsigned long durationMs) { servo.writeMicroseconds(pulse); delay(durationMs); servo.writeMicroseconds(servoStopUs); delay(100); // 让舵机回执停止指令 }调用时只要传一个“接近端点”的方向脉冲和时间:
moveForDuration(1900, 500); // 可能让小车转了大约某个角度但这个函数不能保证每台设备、每次运行的最终角度一致。它适合的场景是:你想让某个转盘每次转一圈左右,或者让小车粗略转个弯,后面还有开关或传感器修正。
如果你想在代码里通过angleMs = angle / speed * 1000这样做换算,我建议你先慢下来。因为你在把一个误差很大的开环系统,假装成闭环系统来使用。真正的项目里,这个误差会积累得比你想象中快得多。
6.2 它真正适合的场景
聊到这里,我更愿意把 SG90 360° 连续旋转舵机看作“一个廉价的小型执行电机”。它的优势是:接口简单、代码少、体积小、自带减速箱、力矩比用 Arduino 驱动小直流电机再加 H 桥要方便很多。
比较适合它的场景包括:
- 360° 缓慢旋转的云台或监控旋转盘。
- 扫地机器人模型、玩具小车的轮子驱动。
- 需要持续转动的闸门、圆盘、展示转台。
- 作为“被控物体一直在转”的实验平台,用来测试其他算法。
因为这些场景不依赖输出轴停在某个精确角度,所以开环控制带来的误差影响不算致命。
6.3 它不适合哪些项目
如果项目里出现下面这些描述,请直接考虑别的方案:
- “舵机要回到离初始位置 90° 的地方。”
- “每次按下按键,圆盘要精确旋转 60°。”
- “机械臂的第二关节需要停在指定角度并保持不动。”
- “舵机没有装限位开关,但程序假设它始终从已知角度开始。”
这些需求天然需要位置反馈。哪怕你用再精妙的 PWM 控制方法,也不能在没有角度传感器的连续舵机上解决定位问题。这不是写得不够好,而是物理反馈缺失。
6.4 选型前先问自己:控制对象要的是位置,还是转速
回到文章开头那个困惑:你拿到的 SG90 是 360 度连续旋转版,它可以用 Arduino UNO 的writeMicroseconds跑起来,但它跑起来后到底能做什么,取决于你如何理解这个系统。
如果控制对象是“结果角度”,那就需要一台自带位置反馈的标准舵机,或者步进电机,或者带编码器的闭环电机。
如果控制对象是“方向、速度和持续时间”,那连续旋转舵机可以胜任,前提是你能接受开环控制带来的误差。
我自己的选择习惯很简单:先看需求里有没有“重复定位精度”。只要这个概念出现,连续舵机立刻出局;只要需求是“能转、能停、能换方向、动作幅度不要求精确”,连续舵机就很顺手。
你可以在第一次跑通代码之后,做一次小实验:不加负载,让舵机以 2000µs 转两秒,再停下来,记录舵臂指向;重复三次,看看每次终点差多少。这个实验不需要复杂仪器,但它会让你直观感受到开环控制边界到底在哪。
知道了这个边界,你才不会在后续工程里把它当精确设备来依赖。这比记住任何一段 Arduino 代码都更重要。