ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

STM32F103C8T6驱动SG90舵机:TIM PWM频率与占空比映射实战

STM32F103C8T6驱动SG90舵机:TIM PWM频率与占空比映射实战 简介这份源码面向嵌入式初学者与需要快速验证舵机控制的开发者围绕STM32F103C8T6驱动SG90舵机这一典型场景提供可直接参考的PWM控制实现。资源包共4个文件以2个C源文件与1个头文件为核心分别承担舵机角度控制逻辑、初始化配置与接口声明另附1张舵机接口图辅助接线理解压缩包约24KB体量轻便、便于移植到自己的工程中。内容涉及定时器PWM模式配置、GPIO复用推挽输出、预分频与自动重装载寄存器设置以及按角度换算脉宽占空比等关键环节并给出初始化与角度设定函数的组织方式方便读者对照修改周期与CCR值。目前已有5074人学习下载适合作为机器人、遥控模型等项目中舵机驱动的入门参考也能帮助读者理解PWM信号与角度之间的对应关系并在此基础上扩展多路舵机或中断更新方案。1. 从一次舵机抖动的排查说起STM32F103C8T6 驱动 SG90 到底该怎么写很多人第一次用 STM32F103C8T6 点 SG90 舵机都会遇到同一个玄学现象明明代码里写了 50Hz 的 PWM舵机要么纹丝不动要么像抽风一样来回抖偶尔还伴随电源电压被拉低导致单片机复位。问题往往不在舵机本身而在于 PWM 频率、占空比映射和供电这三件事没对齐。这份源码就是围绕这三个痛点展开的用 TIM 定时器输出 50Hz PWM把 0.5ms2.5ms 脉宽映射成 0°180°再配合独立的 5V 供电让 SG90 稳定工作。它适合刚上手 STM32 标准外设库、想快速把舵机跑起来或者准备做机械臂、云台、智能小车这类需要多路舵机控制的开发者。2. 项目定位与技术选型为什么是 TIM PWM 而不是软件延时2.1 硬件与软件栈的搭配逻辑STM32F103C8T6 是 Cortex-M3 内核主频 72MHz有 4 个通用定时器 TIM1TIM4其中 TIM1 是高级定时器TIM2TIM4 是通用定时器。SG90 属于模拟舵机控制信号是周期 20ms、高电平 0.5ms2.5ms 的 PWM 波。用硬件定时器输出 PWMCPU 只需要初始化一次之后完全不占用主循环这是它比软件延时或软件 PWM 更靠谱的根本原因。源码采用 STM32 标准外设库不是 HAL 库。标准库的寄存器操作更直接代码体积小对 F103C8T6 这种 64KB Flash、20KB RAM 的芯片更友好。常见做法是把 TIM2 或 TIM3 的某个通道映射到 PA0PA3 或 PB0PB1 这类支持定时器复用的引脚上通过 GPIO 复用推挽输出把 PWM 送出去。选型上还有几个细节值得说清楚。第一SG90 的工作电压是 4.8V6V而 STM32 的 IO 输出是 3.3VPWM 信号线可以直接接但舵机电源必须单独供 5V不能从单片机 3.3V 稳压器上取电。第二多路舵机时最好用同一个定时器的不同通道这样四路 PWM 的频率和相位天然一致代码也好维护。第三如果项目里还要跑串口、I2C 等外设注意定时器通道和这些外设的引脚不要冲突。2.2 目录结构与文件职责这份源码的目录结构比较典型按标准库工程的惯例组织SG90_STM32F103/ ├── CMSIS/ # 内核启动文件与系统初始化 ├── FWLIB/ │ ├── inc/ # 标准外设库头文件 │ └── src/ # 标准外设库源文件 ├── USER/ │ ├── main.c # 主程序舵机角度控制逻辑 │ ├── stm32f10x_it.c # 中断服务函数 │ └── system_stm32f10x.c # 系统时钟配置 ├── HARDWARE/ │ ├── TIMER/ │ │ ├── timer.c # PWM 初始化与占空比设置 │ │ └── timer.h │ └── LED/ # 可选用于状态指示 └── OBJ/ # 编译输出目录USER 目录放业务逻辑HARDWARE 目录放外设驱动这种分层让改舵机引脚或换定时器时只需要动 HARDWARE/TIMER 下的文件main.c 里的角度控制逻辑基本不用改。如果你要加第二路、第三路舵机扩展点也集中在这里。2.3 关键参数速查参数取值说明PWM 周期20ms对应 50Hz舵机标准控制频率高电平范围0.5ms2.5ms对应 0°180°定时器时钟72MHzAPB1 定时器倍频后预分频系数72-1得到 1MHz 计数频率自动重装载值20000-11MHz 下计 20000 次为 20ms比较值范围5002500对应 0.5ms2.5ms这张表是后面代码里所有计算的依据改频率或改角度范围时对着它调就行。3. 核心模块拆解TIM PWM 初始化与角度映射的完整链路3.1 定时器初始化从时钟使能到 PWM 输出先看 TIM3 通道 1 的初始化代码这是整个项目最核心的一段#include timer.h // 通用定时器3通道1的PWM初始化 // arr: 自动重装载值 // psc: 时钟预分频数 void TIM3_PWM_Init(u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 1. 使能TIM3和GPIOA时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置PA6为复用推挽输出TIM3_CH1默认映射在PA6 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 3. 定时器基础配置 TIM_TimeBaseStructure.TIM_Period arr; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler psc; // 预分频值 TIM_TimeBaseStructure.TIM_ClockDivision 0; // 时钟分割 TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; // 向上计数 TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); // 4. PWM模式配置通道1 TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; // PWM模式1 TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; // 初始比较值 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; // 高电平有效 TIM_OC1Init(TIM3, TIM_OCInitStructure); // 5. 使能预装载寄存器 TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM3, ENABLE); // 6. 启动定时器 TIM_Cmd(TIM3, ENABLE); }这段代码的逻辑链条是先开时钟再配 GPIO 复用然后设定时器时基接着配 PWM 通道最后启动。每一步的顺序不能乱尤其是时钟使能必须放在 GPIO 和定时器配置之前否则配置写不进去。参数说明arr和psc由调用方传入。在 main 里调用TIM3_PWM_Init(19999, 71)代入公式PWM频率 72MHz / ((psc1) * (arr1))得到72000000 / (72 * 20000) 50Hz正好是舵机需要的频率。TIM_Pulse就是比较值 CCR决定高电平时间后面改角度就是改这个值。3.2 角度到占空比的映射函数初始化完成后控制角度就是改 CCR 的值。写一个映射函数把 0°180° 转成 5002500 的比较值// 设置舵机角度范围0~180度 // 角度0度对应0.5ms高电平180度对应2.5ms高电平 void SG90_SetAngle(float angle) { u16 ccr; // 限幅防止超出舵机机械行程 if (angle 0) angle 0; if (angle 180) angle 180; // 线性映射angle / 180 * (2500 - 500) 500 ccr (u16)(500 angle * (2000.0f / 180.0f)); TIM_SetCompare1(TIM3, ccr); }映射公式是线性的0° 对应 500180° 对应 2500中间按比例插值。用浮点算是为了角度分辨率更细如果项目对 Flash 和速度敏感可以改成整数运算ccr 500 angle * 2000 / 180效果一样。限幅这一步别省。SG90 的实际机械行程可能略小于标称的 180°如果直接给 2500 以上的值舵机会顶到限位发出咔咔声长时间堵转容易烧。我一般会在调试阶段先用 10° 和 170° 试确认不顶限位再放开到 0° 和 180°。3.3 主程序里的调用节奏main.c 里的逻辑很直白初始化之后循环改变角度#include stm32f10x.h #include timer.h #include delay.h int main(void) { delay_init(); // 延时函数初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); TIM3_PWM_Init(19999, 71); // 50Hz PWM while (1) { SG90_SetAngle(0); // 转到0度 delay_ms(1000); SG90_SetAngle(90); // 转到90度 delay_ms(1000); SG90_SetAngle(180); // 转到180度 delay_ms(1000); } }这里有个容易被忽略的点舵机从 0° 转到 180° 需要时间SG90 的规格是 0.1s/60°转 180° 大约 0.3s。如果延时太短比如 100ms 就切下一个角度舵机还没到位就收到新指令表现出来就是抖动或不到位。延时给 500ms 以上比较稳妥具体看你的舵机批次。3.4 多路舵机的扩展方式如果要控制四路舵机用 TIM3 的四个通道就行PA6、PA7、PB0、PB1 分别对应 CH1CH4。初始化时把四个通道都配上然后封装一个按通道设角度的函数// 按通道设置角度ch范围1~4 void SG90_SetAngleCh(u8 ch, float angle) { u16 ccr; if (angle 0) angle 0; if (angle 180) angle 180; ccr (u16)(500 angle * (2000.0f / 180.0f)); switch (ch) { case 1: TIM_SetCompare1(TIM3, ccr); break; case 2: TIM_SetCompare2(TIM3, ccr); break; case 3: TIM_SetCompare3(TIM3, ccr); break; case 4: TIM_SetCompare4(TIM3, ccr); break; default: break; } }四路共用一个定时器频率天然同步不会出现某路 PWM 周期漂移的问题。注意 GPIO 初始化时要把四个引脚都配上别只配一个。4. 编译、烧录与上电验证从零把舵机跑起来4.1 工程编译与下载配置源码是标准库工程用 Keil MDK 打开 .uvprojx 文件即可。编译前确认两件事一是 Options for Target 里的 Device 选的是 STM32F103C8二是 Debug 选项卡里选对了下载器常见的是 ST-Link 或 J-Link。如果用的是串口下载需要把 BOOT0 拉高、BOOT1 拉低下载完再改回 BOOT0 拉低运行。编译输出在 OBJ 目录生成的 .hex 文件可以直接用下载器烧录。如果编译报错找不到 stm32f10x.h检查 C/C 选项卡里的 Include Paths 是否包含了 FWLIB/inc、CMSIS 和 USER 目录。4.2 接线与供电接线是新手翻车最多的地方列一下SG90 线色接到说明棕色/黑色GND与单片机共地红色5V 外部电源正极不要接单片机 3.3V橙色/黄色PA6PWM 信号共地这一步必须做否则 PWM 信号没有参考电平舵机会乱动。5V 电源的电流能力至少 1ASG90 空载电流约 100mA堵转能到 700mA 以上用单片机板上的 3.3V 或 USB 口供电很容易触发复位。4.3 上电后的验证步骤烧录完成后按这个顺序验证先不接舵机用示波器或逻辑分析仪看 PA6 有没有 50Hz、高电平 1.5ms 的波形初始角度 90° 时。没有示波器的话接一个 LED 加电阻到 PA6能看到微弱闪烁说明 PWM 在输出。然后接上舵机上电后舵机应该先转到 90° 附近再按 main 里的节奏在 0°、90°、180° 之间循环。如果舵机不动先量 PA6 电压万用表测平均值应该在 0.25V1.25V 之间变化。如果电压不变说明 PWM 没输出回去查定时器初始化和 GPIO 复用配置。如果电压在变但舵机不动查供电和共地。5. 避坑与常见问题排查舵机抖动、复位、角度不准的根因5.1 舵机持续抖动或嗡嗡响现象舵机停在某个角度附近小幅抖动伴随嗡嗡声。原因PWM 信号不稳定或者供电电压波动导致舵机内部比较电路反复调整。常见的是电源电流不足舵机一动电压就掉舵机又往回找位置。解决换独立 5V 2A 电源电源正负极到舵机之间并一个 470μF1000μF 电解电容信号线尽量短。如果还抖检查定时器配置里TIM_OCPolarity是不是设成了 High设反了会导致脉宽逻辑颠倒。5.2 单片机上电就复位现象接上舵机后单片机反复重启串口打印乱码或程序跑飞。原因舵机启动瞬间电流冲击把 5V 或 3.3V 拉低触发单片机欠压复位。解决舵机电源和单片机电源分开供只在 GND 上共地。如果必须共用加大滤波电容并在软件里避免上电瞬间就让舵机大角度转动可以先给 90° 再慢慢转。5.3 角度和预期对不上现象写 0° 舵机转到 20°写 180° 只到 160°。原因SG90 个体差异不同批次的脉宽范围不完全一致或者机械安装时舵盘没对准。解决先做校准写一个循环从 500 到 2500 每次加 100记录舵机实际能到的两个极限位置把映射公式里的 500 和 2500 换成实测值。机械上把舵盘拆下来让舵机先转到 90°再把舵盘装到机械中位。5.4 多路舵机互相干扰现象单独控制一路正常四路一起动就有的不动或抖动。原因四路同时启动电流叠加电源扛不住或者 GPIO 初始化漏配了某个通道的引脚。解决电源按每路 1A 余量配四路至少 4A。软件上避免四路同时大角度跳变可以错开几十毫秒依次给指令。检查 GPIO 初始化里四个引脚是否都设成了GPIO_Mode_AF_PP。5.5 改了定时器或引脚后不工作现象把 TIM3 换成 TIM2或者把 PA6 换成 PA0代码编译通过但没波形。原因不同定时器通道对应的默认复用引脚不同TIM2_CH1 默认在 PA0TIM3_CH1 默认在 PA6换定时器必须同时换引脚。另外 TIM2 挂在 APB1 上时钟使能要用RCC_APB1PeriphClockCmd别写成 APB2。解决查芯片参考手册的复用功能映射表确认定时器通道和引脚的对应关系。如果要用重映射还得开 AFIO 时钟并调GPIO_PinRemapConfig。6. 二次开发改造从单路舵机到多路联动与角度平滑拿到这份源码后最常做的改造有三个方向加路数、加平滑、加联动。加路数前面已经讲了用同一 TIM 的多通道把SG90_SetAngle扩展成带通道参数的版本就行。这里重点说平滑和联动这两个是让舵机从「能转」到「转得好看」的关键。平滑的核心是别让 CCR 一步跳到位而是分多次小步逼近。写一个带速度参数的函数// 平滑转动到目标角度step为每步角度delay_ms为每步间隔 void SG90_MoveSmooth(float target, float step, u16 interval) { static float current 90.0f; // 记录当前角度 if (target 0) target 0; if (target 180) target 180; while (current target - step) { current step; SG90_SetAngle(current); delay_ms(interval); } while (current target step) { current - step; SG90_SetAngle(current); delay_ms(interval); } current target; SG90_SetAngle(current); }step控制平滑度给 12 度比较自然给 10 度以上就接近直接跳。interval控制速度给 1020ms 时转动看起来连续。注意current用 static 保存多路舵机时每路要各自维护一个当前角度变量不能共用一个。联动改造适合机械臂或云台场景。比如做一个两轴云台X 轴和 Y 轴要同时从当前位置转到目标位置如果分别调用平滑函数会一个转完另一个再转看起来别扭。做法是把两个轴的目标和当前值都拿出来在一个循环里同时更新void Gimbal_MoveTo(float tx, float ty, float step, u16 interval) { float cx 90.0f, cy 90.0f; // 当前角度实际项目里应保存为全局 while (fabs(cx - tx) step || fabs(cy - ty) step) { if (cx tx) cx step; else if (cx tx) cx - step; if (cy ty) cy step; else if (cy ty) cy - step; SG90_SetAngleCh(1, cx); SG90_SetAngleCh(2, cy); delay_ms(interval); } SG90_SetAngleCh(1, tx); SG90_SetAngleCh(2, ty); }这样两个轴同步逼近运动轨迹是直线插补的近似视觉效果比先后转动好很多。如果要做圆弧插补就在循环里按角度算坐标原理一样。验证改造效果时我习惯用逻辑分析仪抓两路 PWM 的波形看高电平时间是不是同步变化、有没有跳变。没有逻辑分析仪就用两个舵机装在一个十字架上肉眼观察运动是否协调。改完平滑参数后重点看低速时有没有卡顿、高速时有没有过冲过冲就减小 step 或加大 interval。从那以后我每次改舵机代码都强制先用示波器确认 50Hz 和脉宽范围再上舵机避免把机械结构拧坏。这套流程帮我省了不少返工。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表