简介这是一份面向51单片机初学者的SG90舵机控制入门资料梳理了舵机作为角度伺服驱动器的本质并区分模拟舵机与数字舵机在PWM信号控制上的差异帮助读者从原理层面理解“不断发送信号”与“一次发送信号”两种工作方式。压缩包内含25个文件主要由说明文档docx、Keil工程文件uvproj/uvopt、C语言源码、编译生成的hex/obj/lst及备份文件组成整体仅146KB轻量易下载。配套文档《浅谈SG90舵机》对PWM脉冲宽度调制原理做了简明讲解程序部分提供按键控制与自动控制两套完整工程可直接编译烧录到51单片机验证。资源已有4000余人学习浏览适合刚接触舵机项目或正在做智能小车、机械臂等课设的学生参考。读者既能从文档中掌握控制逻辑也能通过现成代码快速上手调试角度旋转减少重复造轮子的时间。1. 控制 SG90 舵机51 单片机不需要硬件 PWM 也能做到把 SG90 舵机的橙色信号线接到 51 单片机任意一个普通 IO配合可靠的 5V 电源和共地就能让它转到 0° 到 180° 之间的任意角度。智能车、机械臂、51 课程设计里这个需求出现频率很高初学者最容易卡在两个点上一是不知道角度信息藏在 20ms 周期里的高电平宽度中二是想用 delay 函数手动拼 PWM结果舵机要么不动要么一头顶在限位处嗡嗡响。下面从舵机内部原理讲起给出基于 Timer0 中断的完整 C 代码再把晶振换算、电源抖动、增量步进这几个高频坑一次解开。2. SG90 的转角本质是脉宽不是占空比先把 20ms 框架定下来2.1 舵机内部的电位器在比对什么SG90 里面有一只小直流电机、一组减速齿轮、一个电位器和一块控制板。电机转动的力量通过齿轮箱传到输出轴同时输出轴带动电位器一起转动。控制板把接收到的信号高电平时长存成一个参考值再和电位器反馈电压做对比误差不为零时电机就一直转到误差为零才停。所以对舵机来说脉宽是目标位置的绝对参考不是电机速度。这也解释了为什么 SG90 对高电平宽度敏感对低电平时间反而不敏感。另一个反直觉的结论是控制 SG90 并不需要高频 PWM。SG90 的标称工作频率在 50Hz 附近即周期 20ms高电平宽度在 0.5ms 到 2.5ms 之间中位 1.5ms 对应 90°。20ms 这个周期对旧式 51 单片机相当友好它慢到可以用 16 位定时器直接计时而 1µs 级别的时间分辨率又足够让舵机到达任意角度。2.2 角度、脉宽与 12MHz 定时计数的换算表角度高电平脉宽12MHz 下 Timer0 计数机器周期数0°0.5ms50045°1.0ms100090°1.5ms1500135°2.0ms2000180°2.5ms2500这张表按线性换算角度每变化 1°脉宽变化 (2500 - 500) / 180 ≈ 11.1µs。淘宝上不少 SG90 批次把满量程标定成 1.0ms 到 2.0ms程序里用 0.5ms 到 2.5ms 也能转到两头只是接近极限位时机械结构会先顶住。所以代码里要做角度限幅实际产品建议再留 5° 的余量。2.3 为什么 16 位定时器能吞下整个周期51 单片机用 12MHz 晶振时一个机器周期是 12 / 12MHz 1µs。Timer0 工作在模式 1 是 16 位计数最大计数值 65535对应 65.5ms比 20ms 周期大出三倍多。因此不需要像高频 PWM 那样依赖硬件模块自动翻转软件只要在中断里做两次装载先让 IO 保持高电平 high_us 微秒再让 IO 保持低电平 low_us 微秒两次相加正好 20ms。SG90 控制板对脉宽识别存在死区几十微秒以内的误差不会引起可感知的位置偏移。这个容差让软件定时方案变得可行即使中断入口有几微秒延迟对定位的影响也只在 0.1° 级别。第 5 章会讲怎么用逻辑分析仪把这部分偏差测出来并补偿掉。3. SG90 接线、电源和 Timer0 中断生成 PWM 的最小代码3.1 三根线棕色接地、红色接电源、橙色接 IOSG90 引出的三根线里棕色是地红色接 4.8V 到 5V 电源橙色是信号。信号线可以直接接到 P1^0 这类普通 IO因为经典 51 开发板输出的是 5V 逻辑和 SG90 控制板的工作电平一致。真正要谨慎的是供电舵机起步和堵转时瞬时电流能到 0.6A 到 0.8A多数 51 小板上的 LDO 扛不住这种冲击典型表现是舵机一启动整个板子复位。常见做法是给舵机一路独立 5V比如从 USB HUB 或充电宝引出的 5V 直接接红色线再把两路电源的地接在一起。如果不想加第二路电源至少在舵机电源两端并一颗 470µF 以上的电解电容并尽量靠近舵机引脚。共地是底线信号再正确地不共舵机就会乱抖。3.2 16 位定时器初值要计多少微秒就装 65536 减多少Timer0 模式 1 从装载值往上数到 65535 后溢出。机器周期等于 1µs 时想延时 X 微秒就把 TH0/TL0 设成 65536 - X。比如高电平要 1500 个机器周期装载 64036低电平要 18500 个机器周期装载 47036。中断里每溢出一次就换一套初值同时把 servo 引脚取反一高一低就拼出了 50Hz 的方波。这个方案把 20ms 周期切成两段高电平段和低电平段。变更角度只改高电平段的装载值低电平段自动变成 20000 - high_us。和硬件 PWM 模块的区别是占用一次定时器中断但换来的是任意普通 IO 都能输出伺服信号主循环还能同时做传感器采集。3.3 Keil C51 可直接编译的最小程序#include reg52.h sbit servo P1^0; // SG90 橙色信号线接 P1.0 #define PERIOD_US 20000 // 20ms 周期12MHz 下 1 机器周期 1µs #define MIN_US 500 // 0° 对应 0.5ms #define MAX_US 2500 // 180° 对应 2.5ms unsigned int high_us 1500; // 默认 90° unsigned int low_us 18500; void set_angle(unsigned char deg) { if (deg 180) deg 180; // 限幅防止推到机械极限 high_us MIN_US (unsigned int)deg * (MAX_US - MIN_US) / 180; low_us PERIOD_US - high_us; } void timer0_isr(void) interrupt 1 { if (servo 1) { servo 0; // 高电平段结束 TH0 (65536 - low_us) 8; TL0 (65536 - low_us) 0xFF; } else { servo 1; // 低电平段结束新周期开始 TH0 (65536 - high_us) 8; TL0 (65536 - high_us) 0xFF; } } void main(void) { TMOD 0x01; // Timer0 模式 116 位定时器 set_angle(90); // 先算好 90° 的装载值 servo 1; // 让第一拍直接进入高电平 TH0 (65536 - high_us) 8; TL0 (65536 - high_us) 0xFF; EA 1; // 开总中断 ET0 1; // 开 Timer0 中断 TR0 1; // 启动定时器 while (1); }set_angle 只更新两个全局变量不阻塞主循环。角度换算使用整数运算每一度对应约 11µs对舵机控制足够。中断服务里靠读取 servo 引脚当前电平判断该走哪一段这和用标志位一样可靠还能省一个变量。main 函数先按 90° 的初值预装 Timer0再把 servo 置 1这样复位后第一个脉宽就是 1.5ms舵机不会先经历一段无信号阶段才动。Keil C51 用interrupt 1声明 Timer0 中断SDCC 语法不同把interrupt 1改成__interrupt(1)其余逻辑不变。3.4 参数怎么改占空比不是重点高电平时长才是调节角度时只重新赋值 high_us低电平段会跟着变化周期始终是 20ms。如果看到舵机往一个方向不停转通常是高电平时长写成了超出 2.5ms 的值或者高低逻辑反了。周期从 20ms 变成 10ms 不会立刻损坏舵机但会让内部控制板发热、抖动所以不要追求高刷新率老老实实保持 20ms 就好。不推荐只靠软件延时在 main 里拼脉宽。常见教程写法是servo1; delay_us(1500); servo0; delay_ms(18);对单动作演示没问题但主循环一旦插入按键扫描、串口发送低电平段会在 18ms 和 25ms 之间浮动舵机就会出现明显的滋滋声。定时器中断用硬件周期固定了翻转时刻主循环再多代码也不影响时序。提示这个例程假设晶振是 12.000MHz。如果使用 11.0592MHz机器周期是 1.085µs中断里的初值要按比例换算见第 4 章表格。4. 0° 到 180° 扫动例程与 51 单片机驱动 SG90 的踩坑参数4.1 扫动程序角度步进 10°、停留 30ms// 复用第 3 章的 set_angle、timer0_isr这里只写 main 和简单延时 void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 110; j); // 12MHz 下约 1ms受 Keil 优化级别影响 } void main(void) { unsigned char ang; TMOD 0x01; set_angle(90); servo 1; TH0 (65536 - high_us) 8; TL0 (65536 - high_us) 0xFF; EA 1; ET0 1; TR0 1; while (1) { for (ang 0; ang 180; ang 10) { set_angle(ang); delay_ms(30); } for (ang 180; ang 0; ang - 10) { set_angle(ang); delay_ms(30); } } }set_angle 改变 high_us 之后舵机最迟在下一个 20ms 周期就会收到新脉宽所以程序不需要额外等待舵机到位再发下一条指令delay_ms(30) 只是让机械部分跟上节奏。4.2 三个可调参数步进角度、停留时间、脉宽量程参数示例值调小/调大的效果步进角度10°调小更平滑0° 到 180° 耗时变长调大跳变明显齿轮有撞击声停留时间30ms调小会让舵机跟不上指令出现明显滞后调大运动更慢更稳脉宽量程0.5–2.5ms调小两端角度变小调大可能顶住机械限位堵转电流上升一个值得注意的数字10° 步进、30ms 停留折算下来是 333°/s接近 SG90 空载转速 500°/s 的三分之二。看起来不快但跳变的角加速度很大所以能听到齿轮撞击声。改成每 5ms 走 1°平均速度一样是 200°/s但机械冲击小得多这部分放到第 5 章展开。4.3 换 11.0592MHz 晶振定时初值对照晶振机器周期20ms 周期计数0.5ms 计数1.5ms 计数12.000MHz1.000µs20000500150011.0592MHz1.085µs18432461138211.0592MHz 的机器周期不是整数微秒每个段位都有亚微秒量级的舍入误差累计到 20ms 周期也只有几个微秒舵机完全感知不到。把 set_angle 里的 MIN_US、MAX_US 按实际机器周期微秒数改掉即可。如果用 STC 新一代 1T 单片机定时器计数频率变成 12 倍上述初值公式依然适用只是计数结果全部按比例变化。4.4 复位的、抖动的、嗡嗡叫的先查三个地方板子复位舵机启动瞬间抽走 0.6A 以上开发板电源被拉垮。按 3.1 节加独立电源或大电容解决。原地抖动、滋滋响、不到位手上 SG90 批次的量程和代码不一致。把 MAX_US 从 2500 降到 2400或把角度限幅改成 5° 到 175°多数抖动立刻消失。完全不动先用手轻转舵机输出轴感觉有阻力说明电机和齿轮没坏再用示波器看橙色线上有没有方波没有就查中断有没有开、晶振有没有起振。Proteus 仿真里 SG90 模型只关心脉宽到角度的换算不模拟电机电流和电源跌落仿真通过不代表实物能一次性跑通。电源部分必须在真机上验证。注意不要让舵机长时间停在堵转状态。齿轮被卡住时电机持续大电流容易烧坏舵机内部驱动管也可能把开发板电源烧掉。5. 增量步进让 SG90 走匀速轨迹并用逻辑分析仪验证脉宽5.1 角度每 5ms 加 1°比直接跳变稳得多第 4 章的扫动是跳变式控制舵机每 30ms 收到一个新角度机械结构在跳变点反复冲顶。常见做法是改成增量步进当前角度每次只加 1°软件延时 5ms 后进入下一个 1°。180° 全程走完约 0.9 秒视觉上就是匀速摆动。unsigned char current_angle 90; unsigned char target_angle 90; void set_target(unsigned char deg) { if (deg 180) deg 180; target_angle deg; } // 放在 main 的 while(1) 里与 Timer0 中断并行工作 while (1) { if (current_angle target_angle) current_angle; else if (current_angle target_angle) current_angle--; set_angle(current_angle); delay_ms(5); }每一度对应 11µs 脉宽变化中断在 20ms 周期内完成新旧值切换不会出现半个周期里混入两套脉宽的情况。5ms 是步进刷新间隔不是脉宽刷新越快运动越接近连续曲线。多段路径可以直接改成数组例如 path[] {0, 45, 90, 135, 180, 90}每到达一个目标点就让 current_angle 向下一个点爬。5.2 用逻辑分析仪确认两件事高电平宽度和周期示波器或 24MHz USB 逻辑分析仪都可以探头夹在橙色信号线上触发方式设为上升沿。先测高电平持续时间90° 时应在 1.5ms 附近0° 时约 0.5ms180° 时约 2.5ms。再测相邻两个上升沿之间的时间始终落在 20ms 上下说明定时器工作正常。中断进入和重装载初值本身要消耗十几个机器周期所以实测高电平宽度和初值之间会有可测量的偏差。发现偏高 10 到 20µs就在 set_angle 里把 high_us 统一减掉对应值再用逻辑分析仪复测一次两轮就能校准到位。同一套中断程序换成 MG996R 这种大扭矩舵机也不需要改定时逻辑但电源要从 1A 往上加否则堵转瞬间会把舵机内部驱动电路打穿。本文还有配套的精品资源点击获取
