拓冰建站拓冰建站
首页 / 资讯中心 / 正文

SG90舵机与Arduino UNO实战:PWM控制、角度映射与闭环控制解析

SG90 舵机虽然小但真正想让它“听话”很多人卡在第一步为什么同一个项目里舵机转 0 度、90 度、180 度位置总对不上为什么给它供电后乱抖还有一个更值得思考的问题SG90 这种模拟舵机靠什么保证自己停在目标角度这篇文章就用 Arduino UNO 实测 SG90 180 度位置舵机从 attach 参数开始把 PWM 控制逻辑理清楚再对比一下位置舵机和带反馈编码器的闭环系统到底差在哪。1. SG90 180 度位置舵机核心能力速览能力项说明舵机类型模拟位置舵机PWM 控制角度转角范围按常见型号为 0 到 180 度左右需看具体批次控制信号50Hz PWM脉冲宽度约 0.5ms 到 2.5ms典型高电平宽度0 度约 0.5ms / 90 度约 1.5ms / 180 度约 2.5ms控制方式不支持直接角度 API需通过 Servo.h 封装映射attach 参数引脚号、最小脉冲、最大脉冲闭环控制常见 SG90 为开环位置控制无编码器反馈供电要求建议外部供电避免从 UNO 5V 引脚直接大电流供电适合场景云台、机械臂关节、机器人头部、小车转向机构常见问题抖动、啸叫、角度偏差、供电不足先给结论SG90 是典型的低成本位置舵机它内部确实有一个“位置闭环”但它不是我们常说的带编码器反馈的 PID 闭环系统。SG90 依靠的是电位器反馈舵机内部控制电路根据 PWM 指令和电位器位置的误差驱动电机转动直到误差归零。这个机制决定了它的定位能力是“够用但不是精密”也决定了我们编程时要正确配置 attach 参数。2. SG90 舵机控制原理与 attach 参数详解SG90 使用 50Hz 的 PWM 信号作为控制输入。50Hz 意味着周期是 20ms舵机读取的是高电平脉冲宽度而不是占空比百分比。高电平宽度直接对应目标角度。高电平脉冲宽度理论角度说明0.5ms0 度部分 SG90 批次实际最小角度会略高1.0ms45 度线性区1.5ms90 度中位2.0ms135 度线性区2.5ms180 度部分批次最大只有 170 度左右Arduino Servo.h 库封装的 attach 接口如下servo.attach(pin, minPulseWidth, maxPulseWidth);第一个参数是信号引脚。第二个参数表示最小角度对应的高电平脉宽单位微秒。第三个参数表示最大角度对应的高电平脉宽。比较常见的写法是myServo.attach(9, 500, 2500);但这里有一个工程坑很多 SG90 批次在 500 到 2500 的范围下会“过冲”也就是写入 0 度时舵机仍在使劲往限位处顶发出尖锐的啸叫。更稳妥的做法是根据实际舵机批次把范围收窄。比如代码里可以这样保护舵机#include Servo.h Servo myServo; const int servoPin 9; const int minPulse 544; const int maxPulse 2400; void setup() { myServo.attach(servoPin, minPulse, maxPulse); myServo.write(90); } void loop() { }注意Arduino 官方 Servo.h 对某些 AVR 开发板默认最小脉冲是 544最大是 2400并不完全是理论上的 500 到 2500。这个细节经常导致同一段代码在 UNO 上效果正常换到其他开发板后角度映射出现偏差。理解 attach 参数后write 函数的使用就会清楚很多。Servo.h 内部会把 0 到 180 线性映射到 attach 时设置的 minPulseWidth 到 maxPulseWidth 区间。因此如果你设置的是 500 到 2500再执行 myServo.write(90)库输出的就是 1500us 的高电平脉冲。如果设置的是 544 到 2400输出就会不同。2.1 手动 PWM 控制与 Servo 库控制的区别不使用 Servo.h直接用 Arduino UNO 的 hardware PWM 或 Software PWM 也可以控制舵机。关键在于保持周期为 20ms并且精确控制高电平时间。int servoPin 9; void setup() { pinMode(servoPin, OUTPUT); } void loop() { // 输出 1.5ms 高电平周期 20ms digitalWrite(servoPin, HIGH); delayMicroseconds(1500); digitalWrite(servoPin, LOW); delayMicroseconds(18500); }这种方式存在的问题是程序阻塞loop 里做不了其他事情。另一个问题是精度取决于 delayMicroseconds 的实现在多任务场景下会出现高电平抖动。所以大多数情况下推荐直接使用 Servo.h。Servo.h 库在 AVR 上使用定时器中断来维持 PWM 输出CPU 资源占用比手动阻塞方式低很多。3. Arduino UNO 实测环境准备此处采用通用接线方案。SG90 有 3 根线线色连接棕色/深色线GND红色/中间线VCC建议外部 5V 供电橙色/黄色线信号线接 Arduino UNO 数字引脚控制板是 Arduino UNO。信号线接到 9 号引脚。供电不建议直接接在 UNO 的 5V 引脚上因为 SG90 堵转时的电流瞬时值可能达到几百毫安甚至更高如果从 UNO 板载稳压器取电可能导致板子重启或舵机抖动。更稳妥的办法是单独准备一个 5V 电源把电源地线和 UNO 的 GND 连接起来。对于单舵机简单测试也可以临时从 UNO 5V 取电但如果发现舵机不自检、乱抖、一转动 UNO 就重启基本可以判断是供电不足。需要准备的硬件Arduino UNO 或兼容板SG90 舵机一个180 度位置版本杜邦线若干5V 外部电源或电池盒注意电流要足够USB 数据线用于程序下载和串口调试4. 角度控制基础测试先把最基础的角度扫描跑通。下面这段代码让舵机依次转到 0 度、45 度、90 度、135 度、180 度每次到位后停 1 秒并通过串口打印当前角度。#include Servo.h Servo myServo; const int servoPin 9; const int minPulse 500; const int maxPulse 2500; void setup() { Serial.begin(9600); myServo.attach(servoPin, minPulse, maxPulse); Serial.println(SG90 Angle Test Start); } void loop() { for (int angle 0; angle 180; angle 45) { myServo.write(angle); Serial.print(Target Angle: ); Serial.println(angle); delay(1000); } delay(2000); }预期结果是舵机按 45 度步进转动没有明显啸叫和卡顿。如果 0 度或 180 度位置出现持续嗡嗡声说明舵机在死区外被堵住可以把 maxPulse 缩小到 2400或者把 minPulse 增大到 600。这段测试可以观察到两个问题第一个是机械回程误差从 0 度往 180 度转和从 180 度往 0 度转同一个目标角度可能出现几度偏差。这是因为 SG90 使用的是电位器反馈而非磁编码器齿轮间隙和电位器线性度都会影响重复定位。资料中常标注的精度是“约 1 度左右”但实际手感往往偏差更大。第二个是高速转动时惯性过冲。SG90 是模拟舵机内部没有加速度规划你说转 90 度它就会全速扑过去。如果带动一根较长的摇臂到位后容易看到回弹。这个现象不是 bug而是低成本舵机的物理特性。5. 角度映射与三线控制方式分析5.1 sweep 方式官方 Sweep 例程是所有初学者的必经之路。舵机从 0 度缓慢扫到 180 度再反向扫回来。它验证的是连续旋转能力但对定位意义不大。#include Servo.h Servo myServo; int pos 0; void setup() { myServo.attach(9); } void loop() { for (pos 0; pos 180; pos 1) { myServo.write(pos); delay(15); } for (pos 180; pos 0; pos - 1) { myServo.write(pos); delay(15); } }这里把步进设为 1 度delay 15ms整个来回大约需要 5 秒多。如果你的项目需要舵机“慢慢转”这种逐度逼近的方法是常见做法。但由于没有插值实际运行效果看起来是近似连续的仔细看仍有一格一格的感觉。5.2 使用三线制信号控制SG90 只有一条信号线因此方向不可独立设置角度完全取决于 PWM 脉宽。这意味着你不能“反转”控制只能在程序层面对角度做镜像处理。例如机械安装方向相反时目标角度就需要做一次映射int mirroredAngle 180 - targetAngle;这是一个小细节但很多同学在做机械臂对称安装时踩坑明明两侧代码相同舵机动作却相反。5.3 调整 attach 参数解决角度偏差如果舵机写入 180 度时实际只转到 170 度同时舵机出现啸叫通常不是因为代码 bug而是脉冲宽度范围不匹配。这时要校准。校准思路把 maxPulse 调小观察舵机在 180 度位置是否不再顶死。把 minPulse 调大观察 0 度位置是否平滑停止。记录当前舵机实际能达到的最小和最大角度对应脉宽。实际使用中可以这样校准#include Servo.h Servo calibServo; int pin 9; void setup() { Serial.begin(9600); calibServo.attach(pin, 500, 2500); calibServo.write(180); Serial.println(If noise, reduce max pulse); } void loop() { }用这种方法先让舵机转到极限如果有尖锐高频声在代码里把 maxPulse 从 2500 降到 2400 再测试。通过二分法不断逼近最终找到自己这颗舵机的极限值。6. SG90 的开环控制与闭环控制区别这里把概念说清楚。SG90 在控制理论上属于“带有反馈的位置控制系统”但它的反馈源是内部电位器。电位器是一个线性位置传感器舵机控制芯片比较目标 PWM 对应的电压和电位器反馈电压有偏差就驱动电机直到差值变小。所以它算是一个典型的模拟闭环。但是我们平时说的“闭环控制”在 Arduino 语境里通常指外部传感器参与反馈。比如在舵机输出轴上安装磁编码器或使用关节角度传感器再通过 PID 控制器实时调整 PWM。将这两者区分清楚非常重要。如果项目只是做简单云台角度控制SG90 自带的位置反馈已经足够用。它开环与闭环的区别在于内部电位器反馈解决的是“舵机有没有到达指定 PWM 角度”的问题。外部传感器反馈解决的是“负载变化、外力扰动、齿轮间隙下实际角度是否仍准确”的问题。SG90 没有扭矩传感器、没有电流反馈也没有对外输出的角度测量接口。你不能通过一根信号线知道当前实际角度。如果一个机械臂项目要求高精度重复定位还要求无法抵抗外力扰动时自动纠正那就要考虑直接使用带反馈的串行总线舵机或加装外部角度传感器。6.1 外部闭环控制示例电位器反馈舵机角度先给一个最简单的“伪闭环”验证方案把角度传感器和舵机负载同轴连接用 Arduino 采集传感器电压再用 PID 算法输出 PWM。这与 SG90 内部闭环不是一回事但可以帮助理解闭环控制的构成。假设使用一个线性电位器连接到舵机输出轴#include Servo.h Servo myServo; const int sensorPin A0; const int servoPin 9; double targetAngle 90; double currentAngle 0; double error 0; double lastError 0; double integral 0; double kp 2.0; double ki 0.05; double kd 0.1; void setup() { Serial.begin(9600); myServo.attach(servoPin, 500, 2500); } double readAngle() { int sensorValue analogRead(sensorPin); double angle map(sensorValue, 0, 1023, 0, 180); return angle; } double pidCalculate(double setpoint, double measurement) { error setpoint - measurement; integral error; double derivative error - lastError; lastError error; return kp * error ki * integral kd * derivative; } void loop() { currentAngle readAngle(); double output pidCalculate(targetAngle, currentAngle); output constrain(output, -30, 30); myServo.write(targetAngle output); Serial.print(currentAngle); Serial.print(,); Serial.println(targetAngle); }这个示例的实际意义不是驱动普通舵机实现高精度闭环而是展示环路结构。注意PID 输出直接叠加到目标角度上依赖舵机转角指令和实际传感器角度之间的偏差。由于 SG90 响应速度一般PID 参数不能调太激进否则会振荡。这里要特别提醒不要让 PID 输出叠加值过大更不要把 output 当作独立 PWM 脉宽给到 servo.write。对 SG90 来说调用 myServo.write(x) 指定的是目标角度不是电机转速。这一点和直流电机 PID 调速有本质区别。6.2 SG90 与直流电机 PID 闭环调速的不同SG90 是位置舵机输入是目标角度输出是精确的角位置。直流电机本身不具备位置保持能力输入的是 PWM 占空比输出是转速和转向。因此热词中“PID闭环控制电机转速”和“SG90 位置舵机”的结合最常见的方案是使用 Arduino UNO 输出 PWM 控制 SG90再用外部姿态传感器或角度传感器测量输出端角度构成一个完整的位置闭环而不是对舵机内部的电机做直接速度环 PID。对于机械臂、小车转向结构这种外部角度闭环有一个明显优点齿轮磨损、电位器老化、负载变化带来的角度误差可以通过外部传感器纠正。前提是外部传感器要与舵机输出轴同步安装并且采样频率高于舵机自身的响应频率。7. 供电、抖动、啸叫问题处理供电是 SG90 在 Arduino UNO 实验中大概率遇到的坑。SG90 在空载时工作电流可能只有几十到一百多毫安但启动瞬间或堵转时可能达到 500mA 甚至更高。Arduino UNO 的 AMS1117 稳压芯片输出能力有限从 USB 取电的电流允许范围也有限。所以多舵机项目基本不要考虑直接从 UNO 板取电。常见问题排查问题现象可能原因排查方式解决方案上电后舵机乱抖供电电流不足用万用表测量电压是否跌落外部 5V 供电地线共地转到极限位置啸叫脉冲宽度超出机械限位听声音并拔出摇臂观察修改 attach 的 min/max 值舵机不动作信号线没接到 PWM 引脚检查代码引脚号并重新下载换到 9 或 10 引脚测试程序下载后不动Servo.h 定时器冲突查看是否使用 SoftPWMServo 等库改回官方 Servo 库角度偏差大供电电压低或齿轮有虚位对比低压和正常电压下的表现提高电源输出能力啸叫的处理方式如果是在转动停止后仍然听到高频蜂鸣通常是舵机内部电机仍在“用力”也就是位置误差没有归零。常见原因是 pulse 范围过宽舵机试图转到机械结构不允许的角度。这时候可以把最小和最大脉冲往中间收紧。如果是在舵机保持某个角度时出现轻微抖动这可能是模拟舵机的保持力矩波动或者是供电噪声。用示波器观察电源电压能看到高电平脉冲时电压下降。解决办法是增加一个 100uF 到 470uF 的电解电容并联在电源端如果仍有噪声再加一个 0.1uF 瓷片电容。8. 接口 API 与批量自动测试在 Arduino 场景里谈 API 可以分为两层一是串口控制接口。使用 Arduino UNO 通过串口接收角度指令可以很方便地扩展成上位机控制模式。例如把舵机控制逻辑封装成指令解析通过串口发送字符串“A90”舵机转到 90 度。#include Servo.h Servo myServo; String inputString ; bool stringComplete false; void setup() { Serial.begin(9600); myServo.attach(9, 500, 2500); inputString.reserve(10); } void loop() { if (stringComplete) { inputString.trim(); if (inputString.startsWith(A)) { int angle inputString.substring(1).toInt(); angle constrain(angle, 0, 180); myServo.write(angle); Serial.print(OK ); Serial.println(angle); } inputString ; stringComplete false; } } void serialEvent() { while (Serial.available()) { char inChar (char)Serial.read(); inputString inChar; if (inChar \n) { stringComplete true; } } }通过这个串口接口你可以把 Python 或其他上位机程序作为控制端先让舵机工作再逐步把控制逻辑转移到上位机。二是多舵机批量控制。Arduino UNO 的 Servo.h 库最多支持 12 路舵机但 SG90 这类模拟舵机同时带动 6 个以上时电源不是瓶颈UNO 的定时器和中断频率才是隐患。如果舵机数量多建议使用 PCA9685 PWM 驱动板这样只需要 I2C 两根信号线就能控制 16 路舵机。批量测试建议做成一个脚本通过串口按顺序发送目标角度并等待舵机稳定再采集外部角度传感器的实际值形成自动测试工具。import serial import time ser serial.Serial(COM3, 9600, timeout2) time.sleep(2) angles [0, 45, 90, 135, 180, 135, 90, 45, 0] for angle in angles: cmd fA{angle}\n ser.write(cmd.encode()) response ser.readline().decode().strip() print(fsend: {angle}, response: {response}) time.sleep(1.2) ser.close()注意这里的 COM3 是 Windows 下的串口号Linux 或 macOS 下需要改为 /dev/ttyUSB0 等实际设备路径。如果串口打开失败优先检查 Arduino IDE 中当前使用的是哪一个端口。9. 资源占用与实时性观察SG90 舵机的实时性要从两个层面看第一个是 Arduino UNO 的 PWM 输出实时性。Servo.h 在 AVR 上通过定时器中断刷新舵机脉冲周期约为 20ms。也就是说你调用 write 之后舵机不一定立刻开始运动而是要等下一个刷新周期。这个延迟最高可达 20ms。对一些需要快速响应的项目比如两轮自平衡机器人SG90 的响应速度可能不够。第二个是舵机自身机械响应速度。SG90 空载转动 60 度大约是 0.1 秒级别但这个时间会受负载影响。不同批次、不同电压下的实际速度差异较大。控制周期如果远快于舵机机械响应就会出现控制量已经变化但机械位置还没跟上整体看起来比较“肉”。观察资源占用时可以通过串口周期性打印循环时间。比如在单舵机直接调用 write 的场景里loop 循环时间很短问题不大。但如果你在 void loop() 中执行大量 Serial 打印、传感器读取servo 刷新自然不受影响因为写入目标角度只是设置一个寄存器值具体 PWM 由中断维护。值得留意的是不要在每个 loop 里频繁、无意义地重复调用 myServo.write 同一个角度。频繁写入不会让舵机更稳定反而会增加不必要的计算和潜在抖动。正确做法只在目标角度变化时调用。10. 常见问题与排查方法问题现象可能原因排查方式解决方案舵机不转但发烫信号脉冲范围设置过大或转轴被卡拆下摇臂手动转动检查重新校准脉冲范围检查机械结构舵机转向与预期相反信号线接错或安装方向反换一根信号线或检查代码逻辑修改角度映射为 180 - angle多个舵机同时转动时抖动电源电流不足或共地不良用示波器观察电源电压使用独立电源并加大电容Arduino UNO 自动复位USB 供电不足舵机电流拉低电压断电后重新上电观察外部供电并共地舵机初始角度不是 0 度通电瞬间 PWM 宽度不确定串口打印目标角度确认库行为在 setup 中先 write(0) 等待一段时间转动过程一顿一顿使用了 delay 且步进过大或过小观察代码时序改为逐度逼近或使用非阻塞定时程序能烧录但舵机没反应引脚选择错误或信号线松检查 pinMode 与硬件连接使用 UNO 的 PWM 引脚并重新插线舵机到 180 度后嗡嗡响attach maxPulse 超出极限减小 maxPulse 到 2400 左右重新校准避免长时间堵转调试步骤建议第一步把舵机从结构件上拆下来只测试舵机本身。第二步单独供电确认舵机能否自检。第三步检查信号线是否接在正确引脚。第四步把代码简化到只执行 write(90)。第五步如果 90 度都不正常优先怀疑供电或舵机损坏。第六步打印 attach 参数确认你设置的 min 和 max 没有被其他库覆盖。11. 最佳实践与使用建议将 SG90 180 度舵机应用到 Arduino UNO 项目中这里整理一些实操建议。接线方面建议使用短而粗的杜邦线给舵机供电。电源地和 UNO 地必须连在一起否则信号线无法构成回路。多次实验中常见的“舵机有反应但转不到指定角度”的原因之一就是地线没有连好。电源方面不要让一个舵机吃满 UNO 板载 5V 输出。项目里如果只用一个 SG90用 3 节 5 号电池供电也可以但要注意电池电压下降后角度会受影响。更稳定的是使用 5V 2A 以上的电源适配器或降压模块。代码方面把角度范围约束在 5 到 175 度比较安全。每次修改 attach 参数后要重新上电测试避免舵机在 loop 运行时被新的脉冲范围直接推到机械限位。结构方面SG90 的减速齿轮组是尼龙材质不适合承受较大的轴向冲击。如果你需要做一个会被外界碰到的机械臂关节建议加装机械限位或使用金属齿轮舵机。另外要注意SG90 这类模拟舵机长时间堵转不仅发热厉害还会明显缩短寿命。测试时不要让它长时间保持在一个物理上达不到的极限角度。11.1 软件层面最小模板放进项目前建议把舵机控制部分封装成单独函数或类。#include Servo.h class SG90Servo { private: Servo servo; int pin; int minPulse; int maxPulse; int currentAngle; public: SG90Servo(int p, int minP, int maxP) { pin p; minPulse minP; maxPulse maxP; currentAngle 90; } void init() { servo.attach(pin, minPulse, maxPulse); servo.write(currentAngle); delay(500); } void setAngle(int angle) { angle constrain(angle, 0, 180); currentAngle angle; servo.write(currentAngle); } int getAngle() { return currentAngle; } }; SG90Servo panServo(9, 500, 2400); void setup() { Serial.begin(9600); panServo.init(); } void loop() { panServo.setAngle(120); delay(1000); panServo.setAngle(60); delay(1000); }这类封装的好处是后续如果要换成 270 度舵机或者串行总线舵机只需要修改类内部实现上层逻辑不需要大改。11.2 关于闭环控制的进阶建议如果你的最终目标是闭环控制不建议一上来就直接对 SG90 写复杂 PID。先把底层动作拆成几个阶段目标角度输入。舵机按内部电位器反馈移动到 PWM 对应的角度。外部传感器或摄像头读取实际位置。计算位置误差并修正目标角度。第 2 步和第 4 步之间必须有明确的采样和处理节奏。对 SG90 来说外部采样频率建议控制在 50Hz 以内因为舵机自身刷新频率就是 50Hz。频率过高控制量更新太快PID 输出会出现抖动频率过低系统响应变慢抗扰动能力又会下降。最稳妥的方法是先用上面的电位器反馈方式验证同一轴上的角度读取是否稳定再加入 PID 控制。12. 总结与下一步SG90 配合 Arduino UNO 是一个学习舵机控制的经典组合但入门之后要解决的问题往往不在代码本身而在供电、attach 参数校准和机械安装三个方面。最值得先做的事用串口逐个角度测试当前舵机的实际脉冲范围写下一组适合你这颗舵机的 minPulse 和 maxPulse。接着做带载测试看供电是否足够、结构是否顺滑。最后再考虑要不要引入外部角度反馈实现更高的定位精度。最容易踩的坑是直接把理论 500 到 2500us 当作所有 SG90 的通用配置导致舵机在极限位置顶死啸叫。最稳妥的改进方向是设置 544 到 2400 的标准 Servo.h 范围并在程序里约束目标角度不让舵机到物理极限附近活动。如果继续深入下一步可以学习串行总线舵机例如 ST3215、LX-16A 这类支持角度读取的舵机。它们可以直接返回当前角度做外部闭环编程会容易得多。也可以用 SG90 加磁编码器传感器模拟这种反馈从而理解为什么总线舵机能报告位置数据以及 PID 参数在实际机械系统中整定时的困难所在。做一个建议把这篇博客里的基础角度扫描代码和串口接口代码合在一起先组成一套“SG90 舵机测试台”后续所有舵机相关的硬件测试都复用这套测试台。这样每次拿到新批次的 SG90 或其它模拟舵机都可以快速标定范围并验证角度映射关系排查项目问题时也能很快把“代码问题”和“机械结构问题”区分开。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门