资讯动态

STM32 HAL库驱动SG90舵机:PWM定时器配置与实战调参

发布时间:2026/10/15 5:10:36 来源:尧图企业网站定制
1. 项目概述从一块舵机到完整控制链路的思考如果你刚开始玩STM32或者已经点过灯、跑过串口那么用HAL库驱动SG90舵机几乎是每个嵌入式爱好者都会遇到的项目。这个标题看着简单——“【PWM】HAL库控制SG90舵机”——但背后牵扯出的技术点其实相当密集定时器的PWM输出模式、预分频与自动重载值的计算、占空比与角度的映射关系、舵机供电带来的纹波问题、以及调试过程中常见的抖动和堵转现象。这篇文章会以我实际调试中的真实经历为线索把一个看似“接三根线就能转”的项目拆成可复现、可排查、可扩展的完整方案。先说结论这个项目能做什么用一颗STM32芯片通过定时器产生50Hz的PWM信号控制SG90舵机在0°到180°之间按指定角度转动。什么场景需要它云台摄像机、机械臂关节、小车转向、门锁驱动、甚至自动喂食器的翻盖机构。如果你正在做DIY机器人或智能硬件原型这块9g小舵机几乎是性价比最高的“肌肉”单元。适合谁来读已经熟悉HAL库基本的GPIO操作、正在学习定时器或PWM的开发者以及被舵机“不听话”折磨到怀疑人生的同学。我遇到过一个很典型的困惑代码明明按网上的教程写了舵机却一动不动检查了一遍接线、供电、时钟配置最后发现是定时器重载值设错了导致PWM频率根本不是50Hz。这一类问题很难通过看代码看出来因为“表面正确”的配置在硬件上就是不工作。所以这篇博文不打算只贴一段“能用就行”的代码而是把每一步配置的字面含义、背后的数学关系和硬件约束全部讲透让你调完这块舵机之后再看其他PWM设备——无源蜂鸣器、LED呼吸灯、直流电机调速——都能顺藤摸瓜。我还想说一个容易被忽略的点SG90舵机和STM32的“共地”问题、供电电压的稳定问题往往比代码本身更容易让你翻车。这些内容我会放在后面的章节详细展开。如果你手上的板子不是某A品牌而是其他开发板也没关系——只要芯片带定时器外设思路完全一致。2. 舵机与PWM的基础认知先弄懂舵机“听”的是什么2.1 SG90舵机的工作原理与性能边界SG90属于模拟舵机内部结构主要由直流电机、减速齿轮组、电位器反馈电路和控制板组成。它的核心逻辑很简单控制板不断读取电位器反馈的实际角度与外部输入的PWM信号所代表的目标角度进行比较误差驱动电机转动直到反馈角度与目标角度一致。你可以把它理解成一个“带反馈的伺服系统”而不只是一个“能转的电机”。参数上SG90的工作电压通常标称4.8V至6V推荐5V。无负载时的工作电流约100mA堵转时能达到650mA甚至更高。力矩方面在4.8V电压下大约为1.2kg·cm到6V可以提升到1.6kg·cm左右。虽然数据不大但用来驱动轻负载的小型云台、轻型机械臂的关节、小车前轮转向等场景完全够用。转速方面4.8V时约为0.12秒/60度这个响应速度在普通模型舵机里算是中等水平。这里有个重点SG90不是连续旋转电机。它的行程范围约为0°到180°某些厂商的版本可能略有差异但绝大多数情况下不要试图让它转超过这个范围。强行超范围转动会导致电位器卡死轻则舵机滋滋响重则齿轮扫齿报废。如果你要做连续旋转的应用——比如驱动小车轮子——那需要的是改装过的360°连续旋转舵机控制逻辑也不一样。性能边界方面还有一个参数容易被忽视控制信号的刷新频率。SG90的标准PWM信号是50Hz也就是周期20ms。在这个周期下控制信号的脉宽范围约为0.5ms到2.5ms对应0°到180°。严格意义上0.5ms对应0°1.5ms对应90°2.5ms对应180°。这个线性映射关系是整个控制代码的核心尺子后面所有参数计算都围绕它展开。2.2 PWM的概念占空比的“开关灯”类比PWMPulse Width Modulation的中文叫脉冲宽度调制。从字面看就是调节脉冲的“宽度”——也就是高电平持续的时间。一个PWM信号由两个核心参数描述频率Frequency和占空比Duty Cycle。频率决定了周期长度占空比决定了高电平在周期中的比例。SG90舵机需要的PWM信号频率是50Hz占空比则根据目标角度在2.5%到12.5%之间变化——2.5%0.5ms/20ms对应0°12.5%2.5ms/20ms对应180°。为了让你更直观地理解打个比方把PWM信号想象成一个人反复开关灯。如果开灯时间占整个周期的10%那么平均亮度就只有最大亮度的10%如果开灯时间占90%平均亮度就是90%。灯的亮度对应PWM输出后的“平均电压效果”。但舵机和灯不一样它靠的不是“平均电压”而是“高电平到底持续了多长时间”——即脉宽Pulse Width。这个区别非常重要同样50%的占空比在50Hz频率下意味着10ms的高电平在500Hz频率下却只意味着1ms的高电平。后者根本不能让SG90动作。所以对舵机来说频率是第一决定因素占空比是在频率确定之后才换算的目标值。为什么舵机必须用50Hz而不是更高频率因为SG90内部的控制电路以20ms周期为基准来解析PWM信号中的脉宽信息。你可以简单地理解为舵机的“采样周期”就是这么设计的超出这个频率范围它就不认识。我曾经试过用1kHz的PWM去驱动SG90结果舵机一顿一顿地抽搐角度也完全不对。测量信号波形之后才意识到频率完全错了。2.3 角度与脉宽的换算关系0°到180°对应0.5ms到2.5ms的脉宽换算公式可以写成这样脉宽脉宽0.5角度180×2.0如果采用50Hz频率周期为20ms则占空比为占空比占空比0.5角度180×2.020×100%举个例子想让舵机转到45°脉宽0.5 (45/180)×2.0 1.0ms占空比(1.0/20)×100%5%。想让舵机转到135°脉宽0.5 (135/180)×2.0 2.0ms占空比10%。这个映射关系是所有代码实现的出发点。实际产品中不同品牌的舵机可能存在微小差异有的舵机0°对应0.4ms180°对应2.4ms但SG90按标准算不会有太大偏差。后面我会讲怎么用代码做“范围校准”把这种差异吃平。3. HAL库定时器PWM的配置逻辑CubeMX与关键参数解析3.1 为什么选定时器的PWM模式而不是软件延时模拟实现PWM有很多种方式最“笨”的是用软件延时控制GPIO翻转。对1个舵机来说纯粹用延时或许能凑合但系统的实时性会变得很差——CPU在主循环里被占用得无法抽身处理其他事情。更关键的是延时方波的时序受中断影响会产生抖动舵机对这种抖动非常敏感表现就是角度不够准确、低频晃动。如果你要控制多个舵机软件延时方案的代码会膨胀成一场灾难。用定时器的PWM模式则完全不同。定时器硬件自动产生PWM波形从产生到输出全程不需要CPU参与。你只需要做一件事设定好周期重载值ARR和比较值CCR然后启动PWM输出。之后想改角度只需修改CCR寄存器硬件会在下一个周期自动应用新值。CPU的负载几乎为零波形精准到微秒级别对舵机来说这是最理想的驱动方式。这也是这个项目用HAL库重做一遍的意义所在——不是为了贴新代码而是为了建立一套干净、扩展性强的PWM控制框架。3.2 CubeMX中各参数怎么填以70MHz系统时钟为例现在进入实操配置。假设你的芯片系统时钟我已配好为70MHz且定时器时钟源为内部时钟RCC的CK_INT那么要产生50Hz的PWM信号计算方法如下先确定自动重载值ARR。定时器时钟为70MHz取预分频系数PSC139则定时器计数频率为计数频率70MHz/(1391)500kHz每个计数周期 1/500kHz 2微秒。如果ARR199则PWM频率为PWM频率500kHz/(1991)2500Hz这对SG90来说太高了。要得到50Hz需要ARR500kHz/50Hz-19999所以正确参数组合是PSC139ARR9999。PWM频率(70MHz / ((PSC1)×(ARR1))) 70,000,000/(140×10000)50Hz。在CubeMX中操作时的步骤设置定时器时钟源为Internal Clock在Parameter Settings里选择Mode为PWM Generation CH1Pulse设为50这个值只是初始值后面代码中修改。预分频PSC设为139自动重载值Counter Period设为9999。不过这里有个容易踩坑的陷阱CubeMX中Counter Period的单位有微秒但是如果你勾选了“使用内部时钟并设置激活计数模式”的话这里直接填9999就行不用考虑单位换算。最保险的做法是这个公式Counter Period (定时器时钟频率 / (PSC1) / 目标频率) - 1。3.3 HAL库PWM相关API与核心函数的实用注释配置完成后HAL库生成的代码会让你调用的API集中在几个固定的函数上。我在下面列出最常用的几个附上个人翻译成“人话”的注释。第一个是初始化定时器PWM的底层函数HAL_TIM_PWM_Init()。它通常在MX_TIMx_Init()内部被调用。这个函数负责设置定时器的时基单元PSC、ARR、计数模式等。正常情况下你不需要直接调用它CubeMX生成的代码会帮你处理好。第二个是启动PWM输出的关键函数HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1);这个函数让定时器真正开始产生PWM波形。如果你忘了调用它GPIO上没有任何输出舵机一动不动。很多人第一次调试舵机失败原因就是这个函数没调用。第三个是动态修改占空比的函数__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, 75);这个宏直接修改捕获/比较寄存器CCR的值。例如PSC139、ARR9999时CCR50对应0.5ms脉宽0°CCR75对应0.75ms脉宽22.5°CCR150对应1.5ms脉宽90°CCR250对应2.5ms脉宽180°。所以你会发现角度控制漂亮地转成了对“比较值”的线性控制。这个转换关系用公式表达就是CCR脉宽(ms)/20ms×10000脉宽0.5角度180×2.0两者一合并就是代码里实现角度控制的核心逻辑。你在驱动SG90时只需要隔一段时间调用一次__HAL_TIM_SET_COMPARE()即可改变角度。3.4 GPIO复用与内部上拉的心得另一个值得注意的点是GPIO的复用配置。在CubeMX中将定时器通道对应的引脚设置为复用功能Alternate Function工作模式一般选推挽输出Push-Pull。GPIO速度可以设为High因为PWM信号的上升沿越陡峭越有利于舵机控制电路识别。即使频率只有50Hz上升沿如果太缓会导致舵机内部处理的触发点不稳定表现为角度抖动。内部上拉/下拉在PWM输出模式下通常不需要因为推挽输出本身有足够的驱动能力。但是曾经有人问过我说“不设置上拉舵机会不会乱转”——我的经验是不会。如果你发现舵机在待机状态会朝某个方向缓慢漂移那多数原因是PWM输出未启动此时引脚悬空舵机内部寄存器受到噪声干扰。解决办法是把未使用的PWM通道设置为输出低电平的普通GPIO而不是把问题归咎于缺少上拉。4. 硬件接线与供电可靠性最容易忽略的“幕后元凶”4.1 接线表与颜色识别SG90的舵机线通常有3根棕色或黑色是GND红色是VCC橙色或黄色是信号线。常见的开发板上GND接GNDVCC接5V信号线接定时器通道对应的GPIO。比如你用TIM2_CH1在部分开发板上对应的引脚是PA0那信号线就接PA0。这里的“共地”问题要特别说明如果你的STM32和舵机使用不同的电源供电必须把两者的GND连在一起否则PWM信号没有参考地舵机无法识别电平逻辑。这个接线问题听着简单但我遇到过不下三次“舵机不转”但代码和配置都正确的案例最后都是因为GND没连。每次排查到最后心情都很复杂所以我把这个坑放在前面重点强调。凡是涉及外部电源给舵机供电的电路第一件事就是确认“共地”。4.2 供电能力与纹波、稳压方案SG90如果只是空载转一转一个小型USB口的5V供电就够了。但一旦舵机带负载——比如推动机械臂的关节——启动瞬间的电流会增大造成电压跌落。如果跌落幅度过大STM32会直接复位。我曾经遇到的现象是舵机一动作整个板子的指示灯就闪一下程序从头开始跑。后来用示波器测量发现舵机启动瞬间电源轨跌到了3.3V以下。这个问题的根源非常典型**舵机由和单片机相同的USB口供电且中间没有滤波电容。**这种场景下舵机转动时产生的反电动势和启动大电流会在电源上产生剧烈波动直接干扰单片机的稳定运行。解决方案从简单到可靠排序至少加一个470μF或更大的电解电容并联在舵机电源两端引脚尽量短。采用独立的5V电源给舵机供电单片机的3.3V或5V独立输出两个电源共地。如果需要更高质量用一颗线性稳压芯片给舵机单独供电。你可能会问“我不加电容行不行”在短暂实验场景中也许行但在需要反复动作、长时间运行的项目中不加电容会让整个系统非常脆弱。我自己的习惯是凡是项目里有舵机接线上一定会预留一个电容位。前期调试可以不加但一轮完整的负载测试之前一定补上。4.3 信号线要不要串电阻关于串电阻我的实际经验是在信号线上串联一个100Ω到1kΩ的电阻是个好习惯。SG90的输入阻抗不高如果GPIO与舵机之间的走线较长信号边沿会产生过冲串一个小电阻可以抑制振铃避免舵机控制板接收到多余的脉冲信号。如果你用杜邦线连接长度超过10cm这个电阻的作用会更加明显。另外不要在信号线上并联大电容否则会把PWM的上升沿彻底抹平导致舵机识别不到脉宽。这是一种“好心办坏事”的操作需要克制住。5. 写代码让舵机转起来从单个角度到连续运动5.1 核心代码结构与角度映射在CubeMX配置完成并生成基础代码后接下来的任务是在业务代码中封装角度控制函数。下面这段代码是我个人项目中的基础模板可直接参考和使用// 舵机角度设置函数 // 输入角度范围0~180 void Servo_SetAngle(TIM_HandleTypeDef *htim, uint32_t channel, uint16_t angle) { if(angle 180) { angle 180; } // 计算比较值: 脉宽(ms) 0.5 (angle/180)*2.0 // 比较值占比: 比较值 (脉宽/20ms) * (ARR1) uint32_t compare (uint32_t)((1000 (angle * 1000) / 90) * 10 / 20); __HAL_TIM_SET_COMPARE(htim, channel, compare); }我简化一下这个计算过程把脉宽用微秒表示0.5ms就是500微秒2.0ms就是2000微秒。angle0时compare250angle90时是750angle180时是1250。但是回忆一下ARR110000比较值在ARR范围0~9999内所以这些值都是合法的。上面的公式中(angle * 1000) / 90实际算出的是“0.5ms之上的增量微秒”乘以10再除以20换算成比较值。这段代码可以直接用只要你确认自己的ARR1确实是10000。5.2 在主程序中控制舵机转动启动PWM输出和调用角度函数的流程完整的代码框架是这样的int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // 启动PWM输出 HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); while(1) { // 转到0° Servo_SetAngle(htim2, TIM_CHANNEL_1, 0); HAL_Delay(1000); // 转到90° Servo_SetAngle(htim2, TIM_CHANNEL_1, 90); HAL_Delay(1000); // 转到180° Servo_SetAngle(htim2, TIM_CHANNEL_1, 180); HAL_Delay(1000); // 回到90° Servo_SetAngle(htim2, TIM_CHANNEL_1, 90); HAL_Delay(1000); } }这段逻辑简单直观舵机依次转到0°、90°、180°、90°每个位置停留1秒。你烧录进开发板后如果一切正常看到的应该是一个稳定、无抖动、明确到位的转动过程。如果舵机发出连续的“滋滋”声说明它在某个方向顶住了——最可能的原因是角度给到了机械限位之外。5.3 连续平滑运动用“步进定时”代替粗暴延时如果你不想让舵机在0°和180°之间来回跳而是像机械臂那样缓慢划出轨迹可以改用步进方式。基本思路是用主循环里的一个小延时每10毫秒改变一小格角度比如每次步进1°或2°。这样100°的行程大约需要50次步进总耗时0.5秒到1秒看起来就是平滑的转动。另一种更好的方案是把角度控制函数放到定时器中断中执行或者使用操作系统的软件定时器。这样主循环可以处理按键、传感器数据而舵机角度依然会根据预设的速度曲线更新。不过对初学者来说先用主循环延时把逻辑跑通再逐步腾挪到中断里是一个有序的进阶过程。5.4 多个舵机的扩展思路如果你打算同时控制2个或更多舵机——例如做一个二自由度云台——优先选择定时器的两个不同通道或者两个不同的定时器。每个通道对应一条独立的PWM输出但角度控制函数是通用的。要特别注意的是多个舵机同时启动时供电电流需求是叠加的。2个SG90同时转峰值电流可能逼近1A普通USB口会吃不消。这时候要么使用外部供电要么限制多个舵机不要同一瞬间启动。一种稳妥的做法是把多个舵机的启动时间错开先让通道1转到目标角度延时100毫秒再让通道2启动。这个缓冲不仅保护电源也让机构上的受力更均匀减少机械振动。6. 调试过程中的常见问题与排查思路6.1 舵机完全不动从电源到代码的排查顺序舵机完全不动是新手遇到最多的故障。排查也不是随便换线而是按顺序来第一步看电源。用万用表测量舵机VCC与GND之间是否有5V左右。如果你用的USB转串口供电注意有些劣质线材电流上限极低电压会被瞬间拉低舵机自然不动。第二步看信号。用示波器或逻辑分析仪测量GPIO输出。如果有50Hz、占空比可调的方波说明定时器配置没问题如果没有或者频率不对回到CubeMX检查PSC和ARR。第三步看接线。信号线是否插对了引脚GND是否共地。这一步产生的故障概率比我预计的高得多。第四步换舵机。如果你的舵机之前被暴力堵转烧过控制板可能已经损坏——这种情况下输入引脚上即使有正确的PWM舵机内部也不会有任何反应。6.2 角度不准或抖动用好“标定偏移”与滤波电容角度不准的原因往往与信号质量或机械结构相关大致有以下几类一是舵机本身批次差异0.5ms和2.5ms对应的不是严格的0°和180°。这时可以在代码中做线性修正比如把角度映射关系改成脉宽0.4 (角度/180)×2.1。标定的方法很简单先用一个固定比较值比如CCR150量出实际角度再反推映射参数两到三次就能收敛。二是供电电压不足电压降到4.5V时舵机的力矩明显下降带负载后可能无法到达目标角度表现为停在途中或来回摆动。三是信号抖动脉冲沿不干净导致舵机内部的脉宽解析结果不稳定。解决方法是前面提到的串100Ω电阻并在VCC和GND之间加470μF电容。6.3 上电瞬间舵机自己转了一下一个常见但简单的现象很多人在给板子上电的一瞬间发现舵机会轻微地动一下。这个现象通常不是故障而是PWM输出启动前的GPIO电平不确定导致舵机收到一个杂散的短脉冲。如果你不希望它有这一下动作办法是在初始化阶段先把GPIO配置为低电平等系统时钟和定时器准备好后再启动PWM输出。CubeMX中可以把GPIO初始输出电平设置成Low或者在你的代码中先调用HAL_GPIO_WritePin()把引脚拉低再启动PWM。6.4 舵机突然反向打死检查机械限位和用户代码反向打死的情况大多不是代码造成的而是你给的角度已经超出0°到180°的物理范围。比如你想让舵机从135°快速转到0°有些SG90在快速运动下会因为惯性冲过头碰到限位块后开始抖。解决方法是限制单次步进角度每次不要超过5°给舵机留出响应时间。同时避免在舵机带负载时快速来回打满行程齿轮磨损会急剧上升。以下是我整理的一张常见问题速查表方便你直接对照排查症状可能原因排查方法解决方案舵机完全不动未启动PWM输出检查是否调用HAL_TIM_PWM_Start调用启动函数舵机完全不动电源电压不足万用表测量VCC外接5V电源或加大电容舵机完全不动接线错误/未共地对照接线表重新接线确保GND连接舵机抖动频率不是50Hz示波器测量PWM频率重新计算PSC和ARR舵机角度不准舵机批次差异实测角度逆推代码中加入标定偏移舵机堵转滋滋响角度超出机械范围观察转轴位置限制角度到0~180°内上电瞬间乱动GPIO初始电平不确定观察上电动作初始化时先拉低GPIO舵机带载无力供电内阻过大测量动态电压独立供电大电容7. 这个项目还能怎么扩展从舵机到整套运动控制系统当你把SG90控制通了之后整个“定时器产生PWM 动态修改比较值”的能力就完全打开了。我强烈建议你沿着这个方向继续做一轮扩展因为同样的知识框架可以迁移到很多其他应用场景。第一个方向是LED呼吸灯与亮度调节。你不需要改定时器频率只需把频率提高到1kHz左右占空比从0%到100%平滑变化就能实现柔和的呼吸灯效果。这和舵机控制一样用的是PWM但关注点从“脉宽时长”转变成了“平均电平比例”。第二个方向是无源蜂鸣器播放音乐。无源蜂鸣器的音调由PWM频率决定音长由持续时长决定。通过修改ARR来改变频率就可以演奏简单的旋律。这一步会让你明白定时器的ARR不仅可以控制“角度”还能控制“音高”。第三个方向是直流电机调速配合H桥驱动芯片如某款经典双H桥模块通过PWM占空比控制电机转速。这里面还会引入“死区”和“N-MOS驱动”的概念属于电力电子和运动控制的交叉区域。第四个方向是做一个3路舵机控制的简易机械臂配合蓝牙模块在手机端发送角度指令。这类项目对综合能力的要求会高一些涉及通信协议解析、状态机设计、多通道PWM管理但工程价值也更高。如果你对这些方向感兴趣建议按顺序动手做。每做一步回过来看看PWM的核心公式你会发现底层的思维方式是贯通的。一块SG90舵机只是一个窗口窗口背后的硬件定时器才是真正值得掌握的东西。再回到开头的那句话这个项目只是“让舵机动起来”做好之后你会获得两样东西一个是硬件层面“电源共地、信号干净、时序准确”的工程直觉另一个是软件层面“配置、启动、动态修改”这套HAL库驾驶肌肉记忆。这两样东西在后面的每一个嵌入式项目里都会反复用到。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑