尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32F103C8T6驱动SG90舵机:PWM配置与角度控制详解

STM32F103C8T6驱动SG90舵机:PWM配置与角度控制详解 简介一套基于STM32F103C8T6驱动SG90舵机的完整源码例程面向嵌入式入门开发者与需要快速集成舵机控制的单片机项目。包内共4个文件压缩包仅24KB包含SG90.c驱动源文件、SG90.h头文件、use_example.c使用示例以及舵机接口图驱动层与应用层分离清晰方便对照修改和直接移植。源码涵盖定时器PWM输出、GPIO复用配置、角度到占空比换算、轮询与中断两种更新思路等核心环节并提供InitPWM与SetServoAngle等接口函数可直接调用设定0°到180°任意角度。配套接口图直观展示舵机与MCU的接线关系降低上手门槛示例程序结构简洁适合作为STM32 PWM外设学习、课设或小型机器人项目的参考实现。配合示波器观察PWM波形可快速验证占空比设置是否正确便于调试排错。目前已有5068人学习下载。 手头刚好在整理“STM32F103C8T6驱动SG90舵机源码”这个工程顺手把定时器输出PWM的配置流程、角度换算逻辑、供电和抖动问题一起梳理出来。这东西对新手来说其实非常友好一块十几块钱的STM32F103C8T6最小系统板加一个几块钱的SG90舵机就能搭出云台、小车转向机构、机械臂关节甚至简单的“视觉追踪”装置。我最初就是从控制舵机开始接触PWM的踩过不少坑这篇文章尽量写成一份能直接照着抄作业的完整参考硬件接线、参数计算、源码实现和排障都会讲到。1. 项目概述与核心问题拆解1.1 为什么选择 STM32F103C8T6 SG90 这个组合STM32F103C8T6 是 Cortex-M3 内核的入门主力芯片72MHz 主频64KB Flash20KB RAM价格却只要十块出头。SG90 是最常见的模拟舵机工作电压 4.8V 到 6V扭矩大概 1.8kg/cm控制接口只有一根信号线。两者组合起来成本极低却能覆盖很多动手项目摄像头云台俯仰、智能小车前轮转向、简易机械臂关节、履带机器人炮塔旋转等等。很多人第一反应是“不就用 GPIO 输出高电平吗”实际上舵机对脉冲宽度极其敏感必须由定时器产生精确的 PWM 信号否则舵机要么不动、要么乱抖。这个项目表面上是驱动舵机实际核心是两点一是理解 PWM 的周期和占空比概念二是把定时器的预分频值、重装载值算明白。1.2 控制舵机的三个核心关键点我把这个项目的难点拆成三步后续所有内容都围绕它们展开产生正确的 PWM 波形SG90 需要 20ms 周期50Hz的 PWM 信号高电平时间在 0.5ms 到 2.5ms 之间变化对应舵机角度 0° 到 180°。把角度转成占空比在定时器配置完成后还需要把用户输入的“角度值”映射成“比较寄存器值”这一步是数学换算但很多新手容易算错。稳定的供电舵机启动瞬间电流可以达到几百毫安如果直接从 STM32 的 3.3V 引脚取电电压会瞬间被拉低进而导致单片机复位或舵机抖动。这三件事只要有一件没处理好舵机就会“看起来能亮灯但死活不转”。2. 动手前的硬件准备与接线2.1 SG90 的线序与连接方式SG90 通常有三根线颜色比较统一但也有杂牌会换颜色上手前最好用万用表测一下或者看丝印线色功能接目标棕色GND控制板和电源的 GND红色VCC5V 电源正极橙色/黄色信号线 PWM单片机 GPIO如 PA0信号线直接接到 STM32F103C8T6 的 PA0 即可。PA0 复用为 TIM2_CH1是输出比较通道 1非常常用。注意信号电平SG90 的信号输入端本质上接收的是标准 TTL 电平STM32 输出的 3.3V 高电平完全够用不需要额外加电平转换但前提是必须跟舵机“共地”。2.2 供电方案与共地细节这是我第一次做舵机项目时踩得最惨的坑。我把舵机红色线直接接到了 STM32 板子的 3.3V 引脚上结果舵机刚上电就疯狂抖动随后单片机重新枚举 USB整个程序卡死。原因是 SG90 启动和堵转时的电流远超 LDO 能提供的电流3.3V 瞬间被拉垮。正确做法是单独给舵机供 5V 电源常见方案有三种使用 5V/1A 以上的 USB 电源模块单独给舵机供电。使用 4 节 1.2V 镍氢电池或 1 节 3.7V 锂电池加升压模块适合移动小车。从 STM32 板子的 5V/VIN 引脚取电前提是板子供电能力足够且不要把舵机接在 3.3V 引脚上。不管哪种方案都记得把电源负极和 STM32 的 GND 连在一起这就是“共地”。信号线如果悬空舵机也会有随机动作必须保证信号来源稳定。2.3 定时器与引脚选择我为什么用 PA0/TIM2_CH1STM32F103C8T6 的定时器资源说多不多说少也不少TIM1 是高级定时器TIM2/TIM3/TIM4 是通用定时器。驱动 SG90 只需要最基本的 PWM 输出用 TIM2 最省事因为它是 APB1 总线上的通用定时器初始化简单输出通道默认映射到 PA0不需要开启重映射。这里有个很容易踩的坑如果选择 TIM1_CH1PA8作为 PWM 输出高级定时器在初始化后还要手动开启主输出使能也就是调用TIM_CtrlPWMOutputs(TIM1, ENABLE)。漏掉这一句示波器上波形正常舵机却完全没反应因为输出被关闭了。通用定时器 TIM2 没有这个问题所以我推荐新手先用 PA0 TIM2。另外如果后面还要用串口调试默认的串口1引脚是 PA9/PA10跟 PA0 不冲突可以放心同时使用。3. PWM 控制原理与定时器参数计算3.1 20ms 周期到底意味着什么SG90 舵机内部有一个基准电路会持续产生一个周期 20ms、宽度 1.5ms 的基准脉冲同时接收外部输入的 PWM 信号通过比较两者脉冲宽度的差值来判断转向和角度。所以外部 PWM 信号的周期必须保持 20ms脉宽变化范围在 0.5ms 到 2.5ms 之间。脉宽和角度的对应关系大致如下脉宽角度0.5ms0°1.0ms45°1.5ms90°2.0ms135°2.5ms180°注意这里的角度是相对舵机机械限位而言不同厂家的 SG90 实际角度范围会有几度的出入可以通过软件微调。3.2 分频系数与重装载值的完整计算STM32F103C8T6 的系统主频默认是 72MHz。TIM2 挂在 APB1 总线上当 APB1 预分频为 1 时定时器时钟也可以达到 72MHz。关键公式是定时器计数频率 72MHz / (PSC 1)PWM 周期 (ARR 1) / 定时器计数频率我的目标周期是 20ms也就是 0.02 秒。为了达到 10us 的计数精度我选择 PSC 719这样计数频率 72MHz / 720 100kHz即每个计数代表 10us。此时 ARR 需要满足(ARR 1) / 100000 0.02所以 ARR 1999。算出来之后脉宽和比较值的关系是0.5ms 对应 50 个计数1.5ms 对应 150 个计数2.5ms 对应 250 个计数有人也会用 PSC 7199、ARR 199 的方案这时每个计数是 0.1ms20ms 周期没问题但比较值范围只有 0 到 200最大只能输出 2.0ms 的脉宽没法覆盖完整的 0.5ms~2.5ms所以不推荐。这类参数不是“能用就行”最好还是给够余量。3.3 角度到占空比的换算逻辑当定时器配置成“向上计数、PWM模式1、高电平有效”后只需要把比较寄存器值设置为 50~250舵机就会转到对应的 0°~180°。角度值到比较值的线性映射公式为pulse 50 angle * 200 / 180比如角度 90°pulse 50 90 * 200 / 180 150正好对应 1.5ms 脉宽。如果后续发现舵机到不了边角可以把 200/180 这个斜率微调成 210/180 或者更宽的值从而适配不同厂家的舵机。4. 源码实现与逐段讲解4.1 工程结构划分源码不复杂我习惯分成三个模块servo.c / servo.h舵机初始化、设置角度、平滑转动函数main.c主循环调度演示 0° → 90° → 180° 往复延时函数我用系统的Delay_Init和Delay_ms也可以直接使用 SysTick 延时这样拆分的好处是后续把舵机接到串口或者视觉模块上时主程序只需要调用Servo_SetAngle()不需要关心底层 PWM 配置。4.2 PWM 初始化源码使用标准外设库时的初始化代码如下void Servo_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 1999; TIM_TimeBaseStructure.TIM_Prescaler 719; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 150; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_Cmd(TIM2, ENABLE); }这段代码的关键点有两个GPIO 必须配置成复用推挽输出不是普通推挽TIM_Pulse初始值设成 150上电后舵机默认停在 90° 附近能避免上电瞬间舵机猛甩到极端位置。4.3 角度映射与平滑转动设置角度的核心函数如下void Servo_SetAngle(uint16_t angle) { uint16_t pulse; if (angle 180) angle 180; pulse (uint16_t)(50.0f angle * (200.0f / 180.0f)); TIM_SetCompare1(TIM2, pulse); }这里特别提醒一点如果直接用angle * 200 / 180因为整数除法是向下取整到 180° 时结果其实是 250没问题但中间某些角度会有 1 个计数的误差。1 个计数等于 10us对应舵机角度约 0.18°肉眼根本看不出来所以用浮点只是为了代码可读性更好整数除法也完全可用。如果想实现“慢慢转过去”的效果可以加一个渐变函数void Servo_SmoothTo(uint16_t target, uint16_t delta, uint16_t interval) { uint16_t current 0; Servo_SetAngle(0); current 0; if (target current) { while (current target) { current delta; if (current target) current target; Servo_SetAngle(current); Delay_ms(interval); } } }实际应用时第一次调用前需要获取当前角度或者统一从 0° 开始。这个渐变逻辑在云台摄像头追人、机械臂缓慢夹取等场景非常有用直接给阶跃角度舵机会“咯噔”一下视觉观感差很多。4.4 主函数完整逻辑主函数演示最基本的三点往复运动int main(void) { Delay_Init(); Servo_Init(); while (1) { Servo_SetAngle(0); Delay_ms(1000); Servo_SetAngle(90); Delay_ms(1000); Servo_SetAngle(180); Delay_ms(1000); } }如果舵机上电后有轻微“嗡嗡”声属于正常现象因为舵机在不断修正位置。只要声音不明显发闷、不发烫就不用担心。4.5 如果使用 HAL 库/CubeMX现在很多人习惯用 CubeMX 生成工程思路完全一致。在 CubeMX 中做三件事选择 TIM2使能 Channel1 PWM Generation。Prescaler 填 719Counter Period 填 1999Pulse 可以填 150。生成代码后在 main 函数里调用__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, pulse)即可调节角度。用 HAL 库最需要注意的是初始化顺序CubeMX 生成的MX_TIM2_Init()会自动开启 PWM 通道但如果你想在 main 里先改变 else 分支需要调用HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1)之后才真正有输出。很多人漏了这行导致舵机完全不动。5. 实测踩坑记录与排查技巧5.1 舵机抖动、只响不转这是遇到最多的现象一般有四种原因供电不足舵机一旦受力电流会突增劣质 USB 线压降很大5V 掉到 4V 以下就会抖动。解决方法是换粗线或外接独立电源。PWM 周期不对周期不是 20ms比如误配成 10ms舵机内部的比较逻辑会错乱表现为连续抖动或高速震动。PWM 模式选反PWM1 和 PWM2 的高电平有效/低电平有效相反舵机在一端憋死现象是“转到某个位置就抖”。信号线接触不良杜邦线松动导致信号悬空舵机收到随机脉冲就会乱抖。排查顺序建议是先量供电电压再看信号波形最后检查代码参数。5.2 舵机发热发烫发热一般不是因为持续转动而是因为“堵转”。比如舵机臂被结构件卡住或者角度设置超过机械限位电机一直全力去顶某个位置电流很大温度上升很快。此外SG90 本身是模拟舵机没有堵转保护长时间堵转很容易烧坏。遇到过不止一次新手在代码里写Servo_SetAngle(180)但机械结构只能到 170°舵机臂被外壳挡住几分钟后就开始烫手。解决办法是软件上限制输出角度范围或者调整舵机臂的安装位置。5.3 烧录成功但没反应烧录成功说明芯片本身没问题问题基本出在外设配置上。常见原因有一个使用了 TIM1 作为 PWM 输出却忘记调用TIM_CtrlPWMOutputs(TIM1, ENABLE)。这个函数针对高级定时器的主输出使能不开的话定时器确实在跑但 PWM 信号根本不会被送到 GPIO 引脚。另一个是 GPIO 模式配置错误。PWM 输出脚必须使用复用推挽GPIO_Mode_AF_PP如果误写成普通推挽GPIO_Mode_Out_PP波形也能出来但经常出现不稳定或者波形不完整的情况。我调试时习惯先用示波器或者逻辑分析仪抓一下 PA0 的波形没有示波器可以试着串口打印寄存器值判断定时器有没有真正工作。5.4 角度偏差与线性度问题不同批次、不同品牌的 SG90 对脉宽的响应存在差异。同一个角度值甲舵机可能正好到 90°乙舵机可能只有 85°。这是模拟舵机的品控问题不是代码 bug。解决办法是在角度映射函数里加一个“微调系数”或者把50.0f angle * (200.0f / 180.0f)中的 50 和 200 做成两个可调变量。我实际测试过一款杂牌舵机0° 对应比较值 40180° 对应比较值 260舵机死区还特别大需要加死区补偿逻辑。5.5 串口控制扩展如果想把舵机接入上位机或者视觉模块最简单的方案是通过串口接收角度值。STM32F103C8T6 的串口 1 默认在 PA9/PA10和 PA0 不冲突。我做过一个让串口控制舵机的小实验上位机发送形如90\r\n的文本下位机用整数解析再调用Servo_SetAngle(angle)就能改角度。关键代码片段如下uint8_t recv_buf[8]; uint8_t idx 0; uint16_t angle 0; // 在串口中断或接收回调中处理 if (recv_buf[idx - 1] \n) { angle 0; for (uint8_t i 0; i idx - 2; i) angle angle * 10 (recv_buf[i] - 0); if (angle 180) angle 180; Servo_SetAngle(angle); idx 0; }这种串口控制方式很适合做摄像头云台摄像头识别到目标后把目标在画面中的坐标偏差换算成角度差通过串口或者直接在芯片内部计算后发给舵机就能做简单的视觉追踪。整体来看STM32F103C8T6 驱动 SG90 舵机并不是一个多高级的工程但它涉及的知识点非常基础且重要GPIO 复用、定时器分频、PWM 占空比、供电设计、串口通信。把这套流程吃透以后再去驱动 MG995 大舵机、直流减速电机或者步进电机思路都是相通的。我个人的建议是第一次做的时候不要急着代码写一大堆先拿示波器或者逻辑分析仪确认 PA0 上的波形是否正常再拿电表确认供电稳定最后再去纠结平滑过渡、视觉联动这些花活。硬件问题不解决软件写得再漂亮也是白搭。本文还有配套的精品资源点击获取
返回列表