
很多玩 Arduino 的新手第一次接触 SG90 时都会有一个直观感受三根线、一个舵机臂代码里写一句servo.write(90)舵机就能转到中间位置。看起来非常简单但真正上手后问题往往一口气冒出来为什么上电后一动不动为什么一直嗡嗡响为什么角度总是对不齐为什么接上两个舵机Arduino 就自动重启这些问题的根源并不是你抄错了代码而是没有理解 SG90 的本质它是一个内部自带反馈的“位置伺服系统”不是一个用 PWM 调速就能转起来的普通小电机。这篇文章会围绕 180 度位置舵机 SG90 和 Arduino UNO拆解三件事第一脉宽信号和角度之间到底是什么关系第二Servo.attach的pin、min、max参数为什么会影响最终控制精度第三SG90 内部的位置闭环和你在项目里常听到的“PID 闭环控制电机转速”有什么区别。读完你会得到一个可以直接跑通的接线方案、完整的示例代码、一套排查舵机故障的思路也能真正分清哪些控制工作已经由舵机替你完成了哪些闭环问题还需要你重新设计。我的一个基本判断是SG90 不是性能很好的舵机但它几乎是理解“电机→传感器→控制器→执行器”这一整套反馈控制链路的最便宜入口。很多人把时间浪费在换舵机、调脉宽、猜 bug 上不是因为动手能力差而是因为一开始就把“位置舵机”当成了“普通减速电机”去理解。只要把这个心智模型纠正过来后面很多现象都会自己解释清楚。1. 先搞清楚SG90 是“位置舵机”不是普通小电机1.1 SG90 的结构和工作方式SG90 常被叫作 9g 舵机这里的 9g 指向的是它的重量级别不是一个严格行业标准。它在结构上可以拆成四个部分一个小型直流电机、一组减速齿轮、一个电位器、一块很小的控制电路板。直流电机本身转速很高、扭矩很小直接装上负载根本没法精确控制角度。减速齿轮负责把高速旋转降到合适的输出转速同时放大扭矩。电位器连接在输出轴上它会随着舵机臂的转动产生一个与当前角度对应的电压。控制电路板负责读入外部给的脉宽信号把它转换成一个“目标角度电压”再和电位器反馈回来“实际角度电压”做比较。如果两个电压不相等控制板就驱动电机正转或反转让输出轴走向目标角度当两个电压相等也就是实际角度已经靠近目标角度时误差变小电机就会停止。这个过程就是一个非常典型的闭环思想始终用一个反馈量去修正输出量直到反馈值等于给定值。1.2 位置舵机和普通直流电机、步进电机的区别新手最容易搞混的是三样东西普通直流电机、步进电机、位置舵机。它们都能转但控制逻辑完全不同。执行器输入信号是否需要外部反馈能否保持固定角度适合场景普通直流电机电压大小、PWM 占空比需要编码器等传感器才能测角度或速度不能断电后失去力矩驱动轮子、风扇、泵步进电机脉冲序列一般开环位置控制但存在失步可能性能通过保持力矩锁住位置3D 打印机、数控机床SG90 位置舵机周期 20ms 的脉宽信号不需要内置电位器反馈能控制板会不断修正偏差云台、小型机械臂、舵机转向判断为什么可以从“位置保持”这一点理解。普通直流电机只要不供电轴就可以被外力转动步进电机在通电且有保持力矩时能停在一个确定角度但如果负载过大、丢步了它并不会主动告诉你角度偏了SG90 则不一样它的内部电位器一直在检测角度只要你给它一个有效脉宽目标即使外力让舵机臂偏开一点它也会自己转回目标位置。1.3 一个需要特别注意的型号问题市面上常见的 SG90 有一种是“180 度位置舵机”还有一种是“360 度连续旋转舵机”。两者的外观、线序几乎一样但行为完全不同位置舵机是根据脉宽转到指定角度连续旋转舵机是根据脉宽控制转速和方向比如 1500us 停止、1000us 正转、2000us 反转。所以做项目前一定要先确认你买到的是哪一种。如果看到代码示例是基于 180 度角度写的而舵机却一直转个不停不要先怀疑代码先检查型号是不是“连续旋转版”。本项目默认讨论的是 180 度位置舵机。2. 控制原理脉宽、PWM 周期与角度映射2.1 为什么舵机不能用 analogWrite 方法控制很多新手会用驱动直流电机的思路去驱动舵机试图用analogWrite(9, 128)来让舵机转到中间。结果往往只有两种情况舵机完全没反应或者发出尖锐的抖动声。原因是普通直流电机的 PWM 调速关注的是整个周期内“开”的时间比例也就是占空比。占空比越大平均电压越高电机越快。舵机并不是用平均电压来调速的它关心的是信号中高电平到底持续了多少微秒也就是脉冲宽度。analogWrite输出的是一个固定频率、不断调整占空比的方波它和舵机需要的“宽脉冲 固定间隔”信号完全不同。SG90 的控制信号通常是这样的每隔 20ms 产生一次高电平脉冲这个 20ms 对应频率是 50Hz。在这 20ms 中高电平持续的时间从 0.5ms 到 2.5ms 变化不同的脉冲宽度对应不同的目标角度。高电平脉宽常见对应位置500us约 0 度1000us约 45 度1500us约 90 度中位2000us约 135 度2500us约 180 度这里需要强调500us 到 2500us、0 度到 180 度是一个典型映射不同厂家、不同批次的舵机端点存在差异。有的舵机实际范围可能是 550us 到 2450us有的可能是 400us 到 2400us以实体舵机数据手册和实际校准为准。2.2 Arduino 里 write 与 writeMicroseconds 的关系Arduino 的 Servo 库封装了底层细节。调用servo.write(90)时库会先看当前舵机实例使用的是哪一组最小和最大脉宽然后把 0 到 180 的角度等比映射到脉宽区间里。如果舵机是默认attach(9)库在 AVR 平台上的典型默认范围大约是 544us 到 2400us。也就是说write(0)发出去的并不是理论上的 500us而更像是 544uswrite(180)发出去的更像是 2400us。这对于支持 500us 到 2500us 的 SG90 来说通常可以工作但末端角度可能不会精确落在机械极限上。如果你想绕过角度换算直接控制高电平脉宽可以用servo.writeMicroseconds(1500)这表示直接输出 1500us 的脉宽。在校准舵机中位、测试机械极限时writeMicroseconds比write(angle)更直观、更可控。我把这两者的关系总结成一句话write是给普通人看的角度层writeMicroseconds是真正作用在电气层面的脉宽层。2.3 SG90 内部的位置闭环是怎样完成的前面说的脉宽信号本质上只是告诉舵机“请到某个位置”它不是一个持续输出扭矩的调速指令。SG90 的控制电路板会把输入脉宽转换成一个参考电压再和电位器反馈电压比较。当两者相等时误差为零电机不转当两者不等时控制板决定电机的旋转方向和大致驱动强度直到误差进入一个很小的死区。这个死区就是为什么舵机定位后经常存在几度误差的原因之一也是伺服系统稳定工作的必然设计。注意SG90 内部使用的未必是你在书本上看到的标准 PID 算法甚至很多廉价舵机使用的只是“误差正负判断 死区滞环”这类简化逻辑。但从控制论角度看它已经构成了一个完整的反馈闭环。对 Arduino 这一侧的代码来说你只是发出一个位置目标并不需要每毫秒去感知并纠正舵机的实际位置。3. Arduino UNO 接线与供电一个容易翻车的环节3.1 三根线怎么接SG90 常见三根线的颜色是棕色、红色、橙色或黄色。大多数情况下舵机线常见颜色作用接到 Arduino UNO电源负极棕色GND接 Arduino GND电源正极红色VCC通常接 5V接 Arduino 5V信号线橙色/黄色控制信号接任意数字引脚例如 D9接线的第一步不是直接插杜邦线而是先看舵机插头周围的丝印。不同厂家对三针插座的定义可能存在差异有的插座顺序是“正极、负极、信号”有的则是“信号、正极、负极”。如果是用卖家提供的三针插头电源正负极接反会造成舵机损坏所以必须先确认丝印不能只依赖颜色。3.2 电源供电是最大的坑只用一个 SG90 做 180 度空载扫描时直接接 Arduino UNO 的 5V 和 GND 基本能跑起来。但舵机在启动和堵转时会出现短时大电流USB 口供电能力又普遍有限所以当你接两个舵机、或者舵机臂上带了负载、或者动作非常频繁时Arduino 会出现重启、舵机乱跳、持续抖动等奇怪现象。更稳妥的连接方式是给舵机单独准备一个 5V 外部电源同时把外部电源的负极和 Arduino 的 GND 接到一起。这里必须强调“共地”控制信号是相对地平面的电压如果舵机电源和 Arduino 不共地信号线在舵机控制板看来可能是悬空的导致舵机完全无反应或随机乱转。如果使用的是 9V 电池或 12V 适配器不能直接接到舵机 VCC超压会损坏控制板。需要先通过 5V 稳压模块或降压模块把它变成 5V 再供电。很多新手电源模块选错导致舵机要么没力要么发烫其实不是舵机坏了而是电压根本不是舵机需要的工作电压。3.3 安全问题不要让舵机长时间堵转另一个容易忽略的点是堵转。当舵机输出轴已经转到机械极限但你继续要求它转得更远电机处于堵转状态电流会显著上升齿轮也会持续受力。短时间内可能只是出现“咔咔”声时间一长就可能烧坏控制板或打坏齿轮。在程序中设置角度范围时要避免让舵机长时间顶着机械限位在手动校准限位时也应该短时点动测试发现声音变尖或明显卡住就立刻释放指令。这一点对任何小舵机都适用。4. Servo.attach 参数详解与脉冲范围校准4.1 attach 的几种常见写法Servo 库的attach方法在 Arduino UNO 上通常有几种用法#include Servo.h Servo myServo; void setup() { myServo.attach(9); } void setup() { // 自定义最小脉宽和最大脉宽 myServo.attach(9, 500, 2500); }第一种写法只传信号引脚库使用默认脉宽范围。第二种写法指定了引脚、最小脉宽和最大脉宽。这里的min会被当作内部角度 0 度时输出的脉宽max会被当作角度 180 度时输出的脉宽中间角度按线性插值计算。为什么需要手动指定因为 SG90 的标称范围和你实际手上的硬件写出来的并不完全一致。有些 SG90 在 500us 到 2500us 的范围内工作但可能 500us 会让舵机臂顶着左限位发出异响而实际最左端只到 520us。如果不校准就很容易出现“角度到不了指定位置”或者“一端正常、一端卡死”的情况。pin参数也不一定必须是 Arduino 上标有 PWM 波浪号的引脚。Servo 库在 UNO 上是通过定时器中断模拟出精确的脉宽信号所以很多数字引脚都可以用于舵机控制。真正需要留意的是当你使用 Servo 库时它会占用 UNO 上的定时器资源可能会让某些引脚的analogWrite功能不可用。具体占用情况和库实现、板卡型号有关最好的做法是一个引脚只承担一种控制方式不要既写 Servo 又对它做analogWrite。4.2 为什么角度对不齐中位校准方法刚拿到舵机时建议先做一次最简单的中位校准。目的不是测试硬件好坏而是让后续安装舵机臂有一个稳定参考也让代码里的角度和机械结构能对齐。校准流程可以这样理解先不要把舵机臂固定得太紧通电前先让代码输出 1500us 脉宽把舵机转到中位。等舵机稳定后把舵机臂装在一个你觉得合适的方向例如让臂和舵机机身大致垂直。再用writeMicroseconds从 1500us 开始每次增加一点脉宽找到舵机能稳定到达、并且不会卡死机械结构的最右端。再从 1500us 开始每次减小脉宽找到最左端。把找到的两个端点稍作收缩写进attach(pin, leftUs, rightUs)。这种校准属于最基本的“角度偏置校准”它不能消除齿轮间隙带来的回程差也不能提高舵机的绝对精度但能保证你代码里的 0 到 180 和实际情况基本吻合。对 SG90 这类低成本舵机来说做到这一步已经比绝大多数直接抄默认例程的行为稳健很多。5. Arduino UNO 完整示例代码与运行验证下面代码都在 Arduino UNO 上以信号线接 D9 为例。接线时舵机电源如果来自 Arduino 5V只适合做单舵机空载学习如果使用外部 5V 电源记得把外部电源负极和 Arduino GND 接在一起。5.1 示例一0 到 180 度往复扫描这个例子是最常见的舵机扫描验证程序用来确认舵机、接线、代码链路是通的。#include Servo.h Servo myServo; void setup() { myServo.attach(9); myServo.write(90); delay(500); } void loop() { for (int angle 0; angle 180; angle 1) { myServo.write(angle); delay(15); } for (int angle 180; angle 0; angle - 1) { myServo.write(angle); delay(15); } }代码逻辑是每 15ms 让目标角度增加或减少 1 度从而让舵机来回转动。这里的delay(15)并不保证舵机已经转到位只是给舵机一个反应和运动的时间。如果延时太小舵机实际转速跟不上角度更新速度就会出现后段角度越转越小、机械跟不上指令的情况。运行后如果舵机从一端摆到另一端说明信号链路没问题。如果某一端出现“咔咔”声或舵机顶死说明 0 度或 180 度已经超出你这个舵机支持的机械或程序范围应该把扫描范围改小比如从 10 度到 170 度而不是强制让舵机去撞限位。5.2 示例二串口发送角度控制很多云台项目需要根据上位机指令实时控制舵机角度。下面这个程序从串口读取整数并控制舵机转到对应角度。#include Servo.h Servo cameraServo; void setup() { Serial.begin(9600); cameraServo.attach(9, 500, 2500); cameraServo.write(90); Serial.println(Ready. Input angle 0-180:); } void loop() { if (Serial.available() 0) { int angle Serial.parseInt(); if (angle 0 angle 180) { cameraServo.write(angle); Serial.print(Target angle: ); Serial.println(angle); } else { Serial.println(Angle out of range. Please input 0-180.); } } }Serial.parseInt()会从串口缓冲区中读取一个整数。在 Arduino IDE 的串口监视器里输入45、90、120这类数字并发送舵机会切换到对应角度。编译器或库版本不会影响这段代码的核心逻辑但需要保证串口波特率和代码一致也就是 9600。验证成功的标准是串口输入 90舵机转到中位输入 0舵机转到程序允许的位置输入超范围数字串口打印提示而不是驱动舵机。如果串口收到了0但舵机没有动先检查attach是否在setup里执行成功再检查信号线是否插在端口 9 而不是端口 9 旁的 GND。5.3 示例三用 writeMicroseconds 手动校准这个示例更适合配合上一节的校准流程直接用串口设置微秒级脉宽用来观察舵机真实极限。#include Servo.h Servo caliServo; void setup() { Serial.begin(9600); caliServo.attach(9, 500, 2500); caliServo.writeMicroseconds(1500); delay(500); Serial.println(Calibration mode. Send 400-2600 (us):); } void loop() { if (Serial.available() 0) { int us Serial.parseInt(); if (us 400 us 2600) { caliServo.writeMicroseconds(us); Serial.print(Set pulse width: ); Serial.print(us); Serial.println( us); } else { Serial.println(Please send pulse width between 400 and 2600.); } } }运行后可以手动输入 500、1000、1500、2000、2500观察舵机位置。建议先从 1500 开始再逐渐往两边试探不要第一次就输入 2500 强行让舵机撞限位。如果发现 2000 度已经到机械极限而 2500 时出现卡死声音就说明你需要把attach(pin, min, max)中的max调小或者把代码可写入的操作范围限制在安全区域内。5.4 一个容易误解的函数read 读出来的是什么在编写更复杂动作时有人会写这样一段代码int currentAngle myServo.read(); myServo.write(currentAngle 10);这段代码本身没有语法错误但需要理解read()返回的并不是舵机当前真实的物理角度而是 Servo 库最后一次写入的目标角度。如果舵机还没转到位、或者被外力掰开、或者因为堵转没跟上read()的结果都不会反映真实位置。SG90 内部有电位器但它的反馈信息只供内部控制电路使用并不会通过一根线把当前角度送回 Arduino。如果你真的需要读取舵机当前角度做闭环判断需要换用带角度反馈的总线舵机或者外接角度传感器来测量输出轴位置而不是指望read()。6. 什么时候才算真正需要自己写闭环控制6.1 项目的“闭环”到底在哪里很多教程会提到“SG90 舵机闭环控制”这句话容易让人误以为需要自己给舵机加 PID。实际上SG90 已经承接了最底层的位置闭环输入位置目标内部电位器作为位置反馈控制板负责纠正偏差。因此从 Arduino 的角度看你控制 SG90 的方式是开环的。你并没有去读取舵机当前角度也没有计算误差更没有把误差送回给舵机你只是不断给新目标。真正做位置闭环的是舵机内部那块你看不到的控制板。这个分层非常重要单片机充当了上层的“轨迹规划器”舵机内部完成了下层的“执行器伺服”。6.2 外部轨迹规划不是闭环控制有一种代码在常见开源项目里很流行它让舵机从一个角度平滑移动到另一个角度void moveGradual(Servo s, int startAngle, int endAngle, unsigned long stepDelay) { if (endAngle startAngle) { return; } int direction (endAngle startAngle) ? 1 : -1; int current startAngle; while (current ! endAngle) { current direction; s.write(current); delay(stepDelay); } }这段代码看起来像闭环控制但它没有测量真实角度也没有根据误差动态调整输出只是在软件层按固定步长生成一系列目标角度本质上属于“开环轨迹规划”。它可以实现视觉上的平滑运动但不能修正舵机因为负载、齿轮间隙、外力干扰造成的偏差。如果项目里真的需要闭环比如云台被风吹偏后能自动回中、机械臂关节在负载变化下能保持精度那么反馈必须来自真实物理量。你用外部角度传感器去测量负载端位置再把误差交给 PID 去修正这就进入了真正的“外部闭环”范畴。不要把软件延时造成的平滑运动和真正的反馈闭环混为一谈。6.3 想练习 PID 应该换什么硬件搜索词里经常出现“pid 闭环控制电机转速”这说明很多人想在硬件上练习 PID。SG90 并不是一个适合练 PID 的平台因为它的反馈闭环已经封装在内部外部拿不到可靠的实时角速度或位置数据也没有参数可以调。更合适的学习路径是准备一个带霍尔编码器的直流减速电机用 L298N、TB6612 或 DRV8833 驱动。用 Arduino 的中断读取编码器脉冲换算出电机转速。先做最简单比例控制再逐步加入积分和微分观察系统响应。也可以换用带磁编码器的步进电机或直流无刷电机做角度闭环。这些平台的好处是反馈需要通过你写的代码获取控制量也需要你计算 PWM 占空比这样才能真正体会 PID 的 P、I、D 三项各自解决的问题。SG90 和 PID 之间的关系更多是“概念参照”而不是“练手平台”。我觉得这个判断对初学者尤其重要能避免你把三行舵机代码误解成闭环控制项目。7. 运行结果、效果验证与常见问题排查7.1 怎么判断舵机控制真的成功判断一个舵机入门实验是否成功不要只看“它能动”。建议用三个标准代码写入指定角度后舵机能稳定停在目标附近没有持续抖动和明显异响。用手轻轻拨动舵机臂到偏转位置后停止施力舵机会自动回到目标位置附近。串口示例中输入 0、90、180舵机角度不会出现明显错位也不会发生一端顶死。如果第一步都过不了说明你的舵机可能没有在有效控制状态。如果第二步做不到要么舵机控制板损坏要么信号没有持续刷新。Servo 库在attach之后会持续周期性刷新信号正常情况下舵机应该保持“锁住”的状态。7.2 经典问题排查表问题现象可能原因排查方式解决方案舵机完全不动电源没接对、信号线接到非数字引脚、程序中没调用 attach检查接线颜色和丝印检查代码是否在 setup 调用 attach看串口是否正常打印重新接线确保信号线接 D9启动后先执行 attach 和 write舵机乱转或跳变信号受到干扰、电源电压不稳定、代码里角度更新频率过高观察供电指示灯是否闪烁用较短杜邦线连接降低 write 调用频率加 100uF 电解电容使用外部稳压电源缩短信号线长度舵机持续嗡嗡响供电电流不足、目标角度卡在机械极限、舵机 dead zone 过窄把舵机臂拆掉再测试减小角度范围听是否有齿轮打滑声增强供电不要顶死限位程序中限制角度范围角度偏了一点舵机机械误差、安装时舵机臂未在中位、脉宽范围不匹配用 writeMicroseconds(1500) 验证中位检查初始装配方向重新做中位校准微调 attach 的 min 和 max一端正常一端咔咔响代码里的 0 或 180 超出该舵机实际机械限位手动用小步脉宽测试两端极限把扫描范围改为 10 到 170或修改 attach 边界值运行一段时间发烫负载过大、持续堵转、供电电压过高摸舵机外壳温度检查舵机是否有阻力是否能自由转动降低负载停止堵转测试确认供电电压为典型 5V每个问题出现时第一步不要急着改代码而是先看电源。舵机异常十有八九是供电引起的只有排除电源问题再去查信号线和代码才有意义。8. 工程建议从“把舵机转起来”到“