ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

51单片机控制SG90舵机:PWM脉宽、程序实例与避坑指南

51单片机控制SG90舵机:PWM脉宽、程序实例与避坑指南 简介51单片机控制SG90舵机的原理与编程资源面向刚接触单片机或舵机控制的学习者重点解决PWM信号如何产生、舵机如何转到指定角度并保持以及模拟舵机与数字舵机驱动方式的差异。压缩包共25个文件、大小约146KB包含浅谈SG90舵机的说明文档以及按键控制、自动控制两套Keil工程工程中既有C源码和hex烧录文件也有uvproj/uvopt/bak等工程备份、lst/m51等编译输出文件便于直接编译烧录也方便对照排查问题。内容预览可看出作者分别演示了手动按键和自动循环两种控制场景配合文档中对舵机、模拟舵机、数字舵机及PWM脉冲宽度调制原理的讲解读者既能理解概念也能动手验证。该资源已有4065人学习下载适合快速入门SG90舵机与51单片机的结合应用。1. 51单片机控制SG90舵机先搞清楚它到底在控什么拿 51 单片机控制 SG90 舵机最常见的坑不是没程序而是把舵机当成普通电机给个高电平它转撤了高电平它停。实际情况完全不同。SG90 内部有一个小电机、一组减速齿轮和一个电位器反馈你要给的是一个周期约 20ms、高电平脉宽在 0.5ms 到 2.5ms 之间变化的 PWM 波舵机才能稳定地停在 0 到 180 度之间的某个角度上。脉冲宽度不对它会一直往一侧顶频率不对它会嗡嗡抖动甚至发热。这篇文章把 SG90 的控制原理、51 单片机产生 PWM 的两种常见做法、一套可以直接烧录的完整程序以及供电、量化和仿真里的几个坑一次讲完。适合刚在学 51 单片机、准备做课程设计或者手里已经有一块开发板加一个 9g 舵机但转起来就抖的同学。核心就一句你控的不是电机的通断是脉冲宽度。2. SG90舵机的转动原理与控制信号参数2.1 SG90内部结构与位置反馈怎么闭环拆开一个 SG90里面基本就是直流电机、减速齿轮组、电位器和一个控制板。网上搜SG90舵机原理图能看到典型标注电机驱动齿轮组齿轮组带动输出的同时电位器也跟着转电位器分压值就代表当前输出轴位置。控制板做的事情可以理解为一个比较器。外部输入的 PWM 信号经过处理后变成一个电压参考值这个参考值对应目标角度电位器分压是实际角度。两者有偏差时控制板驱动电机正转或反转直到偏差归零。这就是为什么舵机能停在固定角度而普通直流电机不能。所以判断舵机好坏有个很实用的方法上电后不给信号用手轻轻转动输出轴能感觉到明显的阻力或者能听到内部齿轮被别住的声音说明机械部分和电位器反馈基本正常。如果转起来特别松多半是齿轮扫齿了。2.2 角度与脉宽的对应关系表SG90 的控制信号标注为 50Hz也就是周期 20ms。角度不是由占空比单独决定的而是由高电平时长决定。标准对应关系如下表目标角度(°)高电平脉宽(ms)一个20ms周期内的占空比(%)00.52.5450.753.75901.57.51352.2511.251802.512.5这个对应关系基本是线性的换算公式是脉宽(ms) 0.5 (目标角度 / 180) × 2。也就是说每变化 0.1ms 脉宽对应角度变化约 9 度。这句结论后面还会反复用到因为它直接决定了程序的参数设计。另外注意 SG90 还有一种连续旋转版本同样是 50Hz 信号但脉宽控制的是方向和转速1.5ms 是停止小于它反转、大于它正转。买舵机时看准S G90 180度还是360度连续旋转不少课程设计返工就是栽在这个地方。2.3 周期为什么锁在20ms能不能改SG90 的控制板默认按 50Hz 周期来解析脉宽但它真正关心的其实是每次高电平持续的时间周期在一个范围内都能工作。实测下来周期在 16ms 到 25ms 之间多数舵机都能转只是停止精度和稳定性会差一些表现为到位后持续抖动。但我不建议为了省定时器资源故意去改周期。原因是 SG90 控制板内部对脉宽判断有门限周期偏差太大时舵机会把上一个周期的高电平延续到下一个周期里造成误判。稳妥的做法就是严格按照 20ms 周期、0.5ms~2.5ms 高电平时长来设计。51 单片机课程设计里标准的示例也都是这个参数没必要做特殊优化。2.4 三根线怎么接信号、电源和共地SG90 引出三根线颜色基本固定橙线或黄线是信号线接单片机 IO 口红线接正电源棕线接地。接线关系可以看成下面这样舵机线色作用51单片机侧连接橙/黄PWM信号输入P1.0示例红电机电源 4.8V~6V独立5V电源正极棕电源地/信号地单片机GND和电源GND这里最容易出问题的是供电不是信号。SG90 空载电流大约在 100mA 到 200mA堵转瞬时电流可能冲到 600mA 以上。如果用电脑 USB 口或者开发板自带的 LDO 直接给舵机供电往往一转动就电压跌落然后单片机复位、舵机抖动。常见做法是单独用一个 5V 稳压模块或者四节五号电池给舵机供电然后把负极和单片机开发板的地接在一起形成共地这样单片机输出信号对舵机来说才有基准。提示信号线可以直接接 51 单片机的 P1 口不需要加三极管或电机驱动芯片因为 SG90 内部已经有控制电路IO 口只负责送脉宽信号。3. 51单片机生成SG90控制PWM的两种典型方案3.1 方案一延时翻转只适合验证思路看完原理最容易想到的做法是延时拉高然后延时拉低像是在控制 LED 闪烁一样。写出大概是这个样子// 晶振12MHz简单延时控制P1.0输出 while (1) { P1_0 1; // 拉高信号线 delay_us(1500); // 保持1.5ms对应90度 P1_0 0; // 拉低 delay_us(18500); // 保持18.5ms凑够20ms周期 }delay_us是一个空循环延时函数两个延时的总和是 20ms。这个代码非常简单但它有两个问题第一延时期间 CPU 什么都不干串口、按键、传感器全部停摆第二如果中途来了中断或者延时函数本身不够精确脉宽和周期都会漂舵机就会来回抖动。如果只是第一次跑通原理抱着舵机看它转到 90 度这个方案五分钟就能看到结果。但只要继续往程序里加功能比如用串口调角度、读取按键、跑一个 1602 显示它马上就不够用了。所以下面重点说中断方案。3.2 方案二定时器中断加软件计数主流且通用51 单片机控制 SG90 舵机最稳妥的写法是让定时器产生一个固定间隔的基准比如 0.1ms 一次中断在中断里做一个软件计数器。计数器值小于脉宽计数时输出高电平大于等于脉宽计数时输出低电平计数器加到 200 就归零因为 200 个 0.1ms 正好是 20ms。用代码表达就是下面这样// 定时器0中断服务函数11.0592MHz晶振0.1ms进入一次 void timer0_isr(void) interrupt 1 { TH0 0xFF; TL0 0xA4; // 重装初值定时约0.1ms pwm_cnt; // 软件计数器加1 if (pwm_cnt pwm_high) { P1_0 1; // 未到高电平时长输出高 } else { P1_0 0; // 超过脉宽输出低 } if (pwm_cnt 200) { // 20ms周期结束计数器清零 pwm_cnt 0; } }中断服务函数里只做三件事重装初值、计数、比较输出。pwm_high是一个全局变量主程序随时改变它的值就改变了高电平时长也就改变了舵机角度。这个方案最明显的好处是 PWM 波形由中断稳定维持主循环可以同时做串口处理、按键扫描、显示刷新这些事舵机不会因为主循环卡顿而抖。3.3 定时器初值计算和晶振选择0.1ms 这个基准时间放在哪颗晶振上对应初值不一样。计算方法就是求出 0.1ms 占多少个机器周期机器周期等于晶振频率除以 120.1ms 需要的机器周期数等于 0.0001 秒除以机器周期。晶振频率机器周期0.1ms对应的计数个数定时器重装初值11.0592MHz约1.085us920xFFA412MHz1.000us1000xFF9C如果你只是控制舵机12MHz 晶振算出来是整数用着舒服。但很多课程设计里同时要跑串口9600bps 波特率在 11.0592MHz 下误差为零在 12MHz 下误差接近 8%基本上没法稳定通信。所以我个人的选择是程序里只要带串口调试就默认按 11.0592MHz 计算定时器初值用 0xFFA4也就是每次中断约等于 0.1ms一个完整周期约 19.97ms对舵机来说完全没问题。用 STC 系列单片机时还要注意烧录软件里选择的 IRC 频率要和代码里的计算值一致。如果芯片设置的频率和初值不匹配舵机不会完全不转但表现出来就是 90 度定位不精准、抖动明显。3.4 为什么不用单片机内部的硬件PWM如果用的是 STC89C52RC芯片本身没有硬件 PWM 外设只能靠定时器模拟。如果是 STC15 或者 STC8 系列内部自带 PWM 模块配置好寄存器就能输出多路独立频率的 PWM精度还比中断模拟高得多。那为什么这篇文章还在讲定时器中断法因为题目限定在 51 单片机最常见的基础教材和开发板都以 AT89S52 和 STC89C52 为主。定时器中断法写一次就能覆盖这些型号而且代码逻辑和舵机控制原理是一一对应的理解之后换到别的单片机也只是换个定时器 API。如果你的开发板是 STC15 系列我建议直接查数据手册里的 PCA 或者 PWM 寄存器配置成 50Hz、脉宽可调这属于硬件输出CPU 占用几乎为零是另一个更优的路径。4. 完整程序串口命令控制SG90角度4.1 程序功能和硬件准备这一章的完整程序要实现的效果是51 单片机上电后舵机先回到 90 度然后通过串口发送 0 到 180 的数字舵机就转动到对应角度。比如发送90加回车舵机转回中间位置发送0舵机转到一侧尽头发送180转到另一侧尽头。硬件接线只需要四样东西51 单片机开发板、SG90 舵机、独立 5V 供电和一根串口线。舵机信号线接 P1.0红色接 5V 电源正极棕色同时接开发板 GND 和电源负极。串口助手使用 9600bps、8 位数据、1 位停止位发送新行。4.2 完整C语言代码下面这段代码可以直接在 Keil C51 里新建工程编译烧录。晶振按 11.0592MHz 计算定时器 0 做 0.1ms 中断产生 PWM定时器 1 做串口波特率。#include reg52.h #define PWM_PERIOD 200 // 200 * 0.1ms 20ms sbit SG90_PWM P1^0; // 舵机信号线接 P1.0 unsigned char pwm_cnt 0; // 0.1ms累计计数 unsigned char pwm_high 15; // 高电平计数15表示1.5ms即90度 unsigned char cmd_buf[4]; // 串口命令缓存 unsigned char cmd_len 0; unsigned char got_cmd 0; // 定时器0做PWM基准11.0592MHz晶振下每次中断约0.1ms void timer0_init(void) { TMOD 0xF0; TMOD | 0x01; // 定时器0模式116位 TH0 0xFF; TL0 0xA4; // 重装初值定时约0.1ms ET0 1; EA 1; TR0 1; } void timer0_isr(void) interrupt 1 { TH0 0xFF; TL0 0xA4; pwm_cnt; if (pwm_cnt pwm_high) { SG90_PWM 1; // 高电平阶段 } else { SG90_PWM 0; // 低电平阶段 } if (pwm_cnt PWM_PERIOD) { pwm_cnt 0; // 周期重置 } } // 定时器1做串口波特率9600bps void uart_init(void) { TMOD 0x0F; TMOD | 0x20; // 定时器1模式28位自动重载 SCON 0x50; // 串口模式1允许接收 TH1 0xFD; // 11.0592MHz下9600bps TL1 0xFD; TR1 1; ES 1; } // 串口中断收到以 \n 或 \r 结尾的指令时置位 got_cmd void uart_isr(void) interrupt 4 { if (RI) { RI 0; if (cmd_len 4) { cmd_buf[cmd_len] SBUF; } else { cmd_len 0; // 防止溢出 } if (cmd_len 0 (cmd_buf[cmd_len - 1] \n || cmd_buf[cmd_len - 1] \r)) { got_cmd 1; } } } // 角度转PWM高电平计数0度590度15180度25 void set_servo_angle(unsigned char angle) { if (angle 180) { angle 180; } pwm_high 5 (unsigned int)angle * 20 / 180; } void main(void) { timer0_init(); uart_init(); set_servo_angle(90); // 上电先回中 while (1) { if (got_cmd) { got_cmd 0; if (cmd_len 2) { unsigned char i; unsigned char angle 0; // 把命令里的数字字符转成数值忽略结尾的回车换行 for (i 0; i cmd_len - 1; i) { if (cmd_buf[i] 0 cmd_buf[i] 9) { angle angle * 10 (cmd_buf[i] - 0); } } set_servo_angle(angle); } cmd_len 0; } } }4.3 代码逻辑和几个关键参数整个程序的关键在set_servo_angle这个函数。角度到计数的高电平换算公式是pwm_high 5 angle * 20 / 180这个 5 对应 0.5ms也就是 0 度20 对应 180 度所需的 2ms 脉宽变化量。算完之后中断里pwm_cnt从 1 数到 200前pwm_high个计数输出高电平其余输出低电平正好是周期 20ms 的 PWM 波。串口接收部分用了最简单的逐字节中断方式。主程序每次只处理一条完整命令命令以回车或换行结尾。cmd_buf限定 4 字节所以最大支持180\n这种长度既省 RAM 也不会越界。如果想要支持更大的角度范围或者字符串指令可以把数组开大并加上索引保护。实际使用中要注意这个方案的角度分辨率大约是 9 度因为 0.1ms 一个计数档位换算下来就是 9 度一档。你发送 45 度最终落在 45 度附近发送 50 度仍然可能落在 45 度或者 54 度。这是定时器中断模拟 PWM 在这个精度下的正常表现不是舵机坏了。4.4 下载验证和波形核对烧录程序后打开串口助手选择对应串口号波特率设为 9600。发送90加回车舵机应该回到中间发送0舵机会往一个方向转到极限并轻微保持发送180舵机会往另一个方向转到极限。如果舵机完全不动优先检查三件事信号线是不是接在 P1.0电源负极和单片机 GND 是不是共地PWM 中断有没有跑起来。判断中断有没有跑可以在主循环里加一个 LED 翻转或者用示波器看 P1.0 引脚的波形。示波器测量时把时基放在 5ms 每格触发通道选 P1.0能看到一个周期大约 4 格的方波那就是 20ms。再把时基调到 1ms 每格用光标量一下高电平宽度对照第 2 章的表格就能直接验证程序输出是否正确。提示如果你用 12MHz 晶振把定时器 0 初值改成 0xFF9C、串口波特率初值改成 0xE8 附近程序同样能跑但串口稳定性不如 11.0592MHz。所以我建议直接换 11.0592MHz 晶振省得调半天波特率。5. 进阶多路舵机、平滑转动和仿真避坑5.1 用一个定时器扩展到两路舵机中断法天然支持多路舵机扩展因为周期是共享的只需要给每路舵机准备一个独立的pwm_high变量再单独分配一个 IO 口。例如加一个sbit SG90_PWM2 P1^1;中断里这样比较if (pwm_cnt pwm_high_1) SG90_PWM1 1; else SG90_PWM1 0; if (pwm_cnt pwm_high_2) SG90_PWM2 1; else SG90_PWM2 0;两路舵机共用同一个 20ms 周期角度互不干扰。要提醒的是中断服务函数里不要写任何延时或者复杂运算保持几十微秒内退出。扩展很多路时每路都占 IO 和全局变量超过四路之后我更倾向于换带硬件 PWM 的 STC 单片机或者外接 PCA9685 这类 PWM 驱动芯片。5.2 让舵机平滑转动而不是猛甩默认情况下从 0 度直接切到 180 度舵机会以它自己的最大速度甩过去对机械结构冲击很大。想模拟缓慢转动可以在主循环里逐步逼近目标计数void servo_smooth_to(unsigned char target_high, unsigned int step_delay) { while (pwm_high ! target_high) { if (pwm_high target_high) { pwm_high; } else { pwm_high--; } delay_ms(step_delay); // 每一步之间的间隔 } }这个函数每步只改变一个 PWM 计数舵机每步大约转动 9 度step_delay可以取 10ms 到 30ms数值越大转动越慢。这个写法简单但很好用做云台稳定和机械臂动作时比直接跳变舒服得多。5.3 仿真和硬件的几个现实差异很多同学先拿 Proteus 仿真 51 单片机验证舵机程序。Proteus 里的 SG90 模型能反映基本脉宽对应关系波形正确性可以验证但它不会模拟真实堵转电流和供电跌落所以在仿真里很正常一到实物就抖的情况普遍存在。遇到这种问题先把舵机单独供电再查共地最后用示波器量 P1.0 的高电平脉宽是否在 0.5ms 到 2.5ms 之间。量到脉宽正确但舵机仍然抖动优先怀疑电源容量不足脉宽偏差大再回头检查定时器初值和晶振频率设置。本文还有配套的精品资源点击获取
返回列表