恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
STM32F1定时器PWM输出全解析:原理、配置与排错技巧
首页
资讯中心
/
STM32F1定时器PWM输出全解析:原理、配置与排错技巧
STM32F1定时器PWM输出全解析:原理、配置与排错技巧
发布时间:2026/9/2 17:53:35
简介ST官方标准外设库版本的STM32F1 PWM输出实验工程适合刚入门STM32的开发者也适合高校单片机课程实验参考。工程基于STM32F103系列围绕PWM波形生成、占空比调节和定时器配置展开配合蜂鸣器、LED、按键、外部中断、独立看门狗和串口等模块演示了PWM在不同外设场景下的实际用法。资源压缩包为RAR格式共32个文件大小约105KB其中c文件10个、h文件14个覆盖HARDWARE、SYSTEM、USER三大部分HARDWARE下为各外设驱动模块SYSTEM提供延时与串口支持USER包含主程序与启动文件另有readme说明、工程配置和hex烧录文件便于直接编译下载验证。已有1001人学习下载。通过这份工程读者可以快速理解定时器PWM输出模式的初始化步骤、频率与占空比计算方式并可将蜂鸣器音量调节、LED亮度控制等模块化代码直接迁移到自己的项目中。1. 实验思路与方案选型1.1 这个实验到底在做什么先说人话PWM就是一组频率固定、脉宽可调的方波信号。你在任何一块STM32F1板子上跑通这个实验手里就等于多了一个百搭工具——调LED亮度、给舵机转角度、控直流电机转速、驱动蜂鸣器音调全都能用它实现。很多做嵌入式几年的朋友回头看发现当初最早学会的片上外设其实就是这几个定时器PWM只是它的一个基本功能而已。这个实验的核心任务很明确让STM32F1的某个定时器引脚持续输出PWM波形并且能用程序改变它的频率和占空比。占空比这个概念第一次接触会觉得有点绕说白了一个周期内高电平持续的时间占整个周期的百分比。比如周期是1ms高电平0.5ms占空比就是50%。如果占空比是0%输出一直低电平100%则一直高电平。PWM的输出质量关键就看这两个参数控得准不准、稳不稳。1.2 为什么非要用定时器硬件PWM网上很多人做PWM实验第一反应是用延时函数不断翻转GPIO电平这个方案俗称软件PWM。它确实能出波形但问题非常明显CPU全程被占死翻转的时机受中断和代码执行顺序影响抖动大。你拿去驱动舵机舵机可能抖得跟筛糠一样拿去做高精度电源控制纹波能让你怀疑人生。软件PWM唯一的好处是几乎不占额外硬件资源引脚随便挑适合对精度完全没要求的场景。硬件PWM则完全不同。定时器在后台自己计数计数值和比较寄存器匹配的那一刻硬件自动翻转引脚整个过程不需要CPU干预。你可以把CPU抽出来去跑显示、跑通信、跑算法PWM波一直在那里稳稳地输出。这个原理用大白话讲软件PWM是人手按开关硬件PWM是装了个自动控制开关啥时候开啥时候关一开始就设死了。对于绝大多数实际项目选硬件PWM是基本常识这个实验的意义就是让你亲手把这个机制跑通。2. 原理拆解与工程配置2.1 STM32F1定时器的PWM生成机制F1系列芯片内部有多个定时器最基本的是TIM2、TIM3、TIM4这种通用定时器以及TIM1、TIM8这种高级定时器。PWM输出依赖的核心寄存器是计数寄存器CNT、比较寄存器CCR、自动重装载寄存器ARR再加上预分频器PSC。定时器的时钟先经过PSC分频然后驱动CNT从0向上计数直到和ARR相等再回0重新开始如此往复形成锯齿波。每一个计数周期里CNT都会和CCR比较一次比较结果直接决定引脚输出高还是低。这里有个很容易误解的点PWM频率不是由CCR决定的而是由PSC和ARR联合决定的CCR只决定占空比。什么意思呢ARR决定了一个周期有多少个计数节拍PSC决定了每个节拍多久。所以频率等于定时器时钟除以(PSC加1)再除以(ARR加1)。CCR则是高电平持续到第几个节拍占空比就是CCR除以(ARR加1)。我见过太多人一上来只调CCR发现频率没变就以为出bug了其实两者分工完全不同。STM32F1还有一个特色高级定时器TIM1和TIM8除了能输出普通PWM还支持互补输出和死区插入。如果你做电机全桥驱动上下桥臂的MOS管不能同时导通否则直接炸管这时候就需要互补PWM中间插一段死区时间。普通定时器没有这个功能只有TIM1和TIM8能做做电源、电机控制的朋友记住这个区别。2.2 用CubeMX配置一次PWM输出现在官方主推HAL库配置工具用STM32CubeMX直接图形化点一点就能生成工程。以最常见的STM32F103C8T6最小系统板为例我一般选TIM3的通道1对应引脚是PA6因为这个引脚默认没有复用冲突Debug下载器也不占用。时钟树先配好外部8MHz晶振经过PLL倍频到72MHzAPB1总线时钟是36MHz但注意定时器时钟会再倍频一次最终到72MHz。很多人容易在这里被绕进去实际看到的就是TIM3挂在72MHz的时钟源上。接下来设定时器参数。PWM频率定成1kHz占空比初始给50%。计算过程很简单72MHz先分频PSC选71等于每个计数节拍1微秒ARR选999整个周期1000微秒正好1kHz。这里的占空比调节精度就是1/1000也就是0.1%一档足够日常使用。如果想把频率调高假设PWM频率要到20kHz用于开关电源或超声驱动那保持PSC为71不变ARR改成35频率就是72MHz除以72再除以36约等于20kHz。注意ARR太小之后占空比调节就会变粗糙从0到100只有36档这是精度和频率之间的天然矛盾选参数时需要权衡。通道配置里PWM模式选Mode1意思是CNT小于CCR时输出有效电平这里有效电平定义为高。如果选Mode2逻辑正好反过来。GPIO要设置为复用推挽输出不要选成普通推挽输出这步忘了的话引脚永远是死的示波器什么都测不到。3. 代码实现与硬件实测3.1 HAL库和标准库的PWM操作CubeMX生成工程后核心代码就几行。启动PWM输出调用HAL_TIM_PWM_Start()要改占空比直接调用__HAL_TIM_SET_COMPARE()宏第一个参数传定时器句柄第二个参数传通道第三个传CCR值。比如要把占空比改到25%ARR是999那CCR就写250。HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); // 主循环里动态调整占空比 uint16_t duty 0; int8_t dir 1; while (1) { __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, duty); HAL_Delay(5); duty dir; if (duty 900) dir -1; if (duty 100) dir 1; }这段代码的效果是让LED灯呼吸从暗到亮再慢慢变暗非常适合验证PWM输出是否正常。能看到呼吸效果就说明PWM的占空比在实时变化频率和引脚配置都没问题。如果手头有逻辑分析仪或示波器把探头夹在PA6上能看到一列整齐的方波。如果是标准外设库的老工程操作方法也类似只是把宏换成TIM_SetCompare1(TIM3, duty)启动用TIM_Cmd(TIM3, ENABLE)配合TIM_CtrlPWMOutputs(TIM3, ENABLE)。注意TIM1这种高级定时器在标准库下必须额外调用TIM_CtrlPWMOutputs才能在主输出上看到波形很多老手升级工程时容易在这里卡住。3.2 常用PWM应用场景的实测方案PWM实验跑通之后可以顺着往下做几个真实项目里高频出现的扩展。第一个是舵机控制。舵机的控制信号周期固定为20ms也就是50Hz脉宽从0.5ms到2.5ms对应0到180度。这意味着PSC设为71、ARR设为19999CCR设为1250对应0度2500对应90度3750对应180度。实测时舵机供电最好不要跟单片机共用同一路电源舵机启动瞬间电流能吃几百毫安共用一路的话电压跌落容易导致单片机复位。我实际遇到过舵机一动OLED屏幕就开始闪后来单独供电才消停。第二个是直流电机调速。小功率电机用MOS管或电机驱动芯片门控通过占空比改变等效输出电压。注意电机对PWM频率有要求太低会听到明显的啸叫太高开关损耗变大一般建议10kHz到20kHz之间。搭个简单的驱动电路用一个NPN加一个P沟道MOS管或者直接用L298N模块就能实现正反转和调速。电源侧记得加大电容电机是感性负载刹车和换向瞬间会产生反向电动势不加吸收的话有炸驱动的风险。还有一个方向是PWM触发ADC采样。这个技巧在一些传感器采集和处理应用里很实用定时器输出PWM的同时利用定时器的触发事件去启动ADC转换保证每次采样都发生在PWM波形的固定相位上。这样做的好处是采样时刻相对PWM噪声源是确定的能有效降低开关噪声对采样结果的影响。STM32F1上实现起来就是在CubeMX里把ADC触发源选为定时器触发输出然后在定时器更新事件或比较事件时启动转换。很多时候不需要额外硬件只是配置上多勾几个选项却能解决示波器上看着正常、采样数据却跳来跳去的老大难问题。3.3 软件PWM的补充思路前面说了硬件PWM是首选但在实际项目里偶尔会遇到定时器通道不够用的情况。比如已经用了三个定时器的六个通道去驱动三个电机还要加一路蜂鸣器音调控制这时候再挤一个定时器出来不容易。软件PWM就是一个兜底方案选一个没被占用且带输出比较中断的定时器比如TIM4只用一个通道作为时间基准在中断里按时间片翻转几个普通GPIO。void TIM4_IRQHandler(void) { if (TIM_GetITStatus(TIM4, TIM_IT_CC1) ! RESET) { TIM_ClearITPendingBit(TIM4, TIM_IT_CC1); if (soft_duty0 0) GPIOB-BSRR GPIO_Pin_0; if (soft_duty1 0) GPIOB-BSRR GPIO_Pin_1; TIM_SetCompare1(TIM4, TIM_GetCapture1(TIM4) 10); } if (TIM_GetITStatus(TIM4, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM4, TIM_IT_Update); if (soft_duty0 0) GPIOB-BRR GPIO_Pin_0; if (soft_duty1 0) GPIOB-BRR GPIO_Pin_1; soft_duty0 10; soft_duty1 10; } }软件PWM的逻辑核心是把一个周期切成很多等份然后用计数变量控制每个引脚在每个等份里输出高还是低。占空比精度取决于等份数等份越多精度越高但中断次数也越多CPU负载越重。实测下来8路软件PWM、每路256级占空比、频率1kHz左右在72MHz的F103上还算能接受CPU占用大概百分之二三十。如果还要同时跑LCD刷新和串口通信就有点紧张了所以只能作为临时方案产品量产时还是得选定时器资源更丰富的芯片。4. 常见问题与排查技巧4.1 输出无波形、引脚没反应的排查顺序PWM实验跑不通九成以上出在这几个地方。第一个是引脚配置错误。PA6如果没配成复用功能或者重映射开了没开对应的AFIO时钟波形是绝对不会出现的。第二个是定时器时钟没使能。用CubeMX自动生成不会有这个问题但手写代码经常漏掉__HAL_RCC_TIM3_CLK_ENABLE()。第三个是PWM输出通道没启动。HAL库只调用HAL_TIM_PWM_Start()不算完如果用了TIM1、TIM8高级定时器还需要检查主输出使能位有没有打开对应HAL库里的__HAL_TIM_MOE_ENABLE或者CubeMX配置里没有勾选主输出波形照样肉眼不可见。排查顺序建议是先看引脚电平是否在切换再看GPIO模式然后看定时器时钟最后看启动函数有没有真的执行。我用示波器测过很多块板子最后发现问题出在一行代码HAL_TIM_PWM_Start的返回值没有检查定时器配置有问题直接返回错误程序还继续跑引脚自然没波形。4.2 频率和占空比不对的常见病根频率不对先检查时钟树。F103的APB1总线时钟上限是36MHz但定时器时钟会自动倍频到72MHz前提是RCC配置里已经把APB1预分频系数设为2。用CubeMX时这是自动处理的但很多人手写初始化RCC时APB1预分频没配结果定时器时钟只有36MHz输出频率直接减半。另外一个常见错误是把PSC、ARR两个寄存器的值搞混有人以为写PSC能直接改占空比结果频率变了占空比没变。记住公式PWM频率 定时器时钟 / ((PSC1) * (ARR1))占空比 CCR / (ARR1)两个变量各司其职。还有一个小坑是ARR和CCR的关系。CCR绝对不允许大于ARR一旦溢出PWM输出行为就不确定了有的是一直高有的是一直低用示波器看会觉得很诡异。所以在程序里加个限制比较重要CCR值在赋值前做个饱和处理防止跑飞。4.3 实测观察和复杂场景的避坑经验没示波器也能轻松验证PWM。最简单的办法是配一个低频PWM比如1Hz让LED一亮一灭用肉眼就能看到周期对不对。再换回1kHz调占空比观察LED亮度变化亮度随占空比平滑变化就说明硬件通路正常。这个方法虽然土但在现场没有工具时非常救命。做电机控制时还要警惕电源端的共地问题。PWM信号本身是逻辑电平而电机属于大电流负载两者地线如果不共地信号参考电位不一致电机转速会忽快忽慢。正确做法是把功率地和信号地在电源入口单点连接并尽量加粗走线。另外PWM信号线从单片机去往电机驱动芯片之间若距离超过10厘米最好串联一个几百欧姆的电阻减少高频振铃也降低对控制器的干扰。另外常有人问一个定时器能不能同时接收PWM数据和输出PWM波形。答案是同一个定时器理论上做不到高精度双向同时进行因为输入捕获和输出比较共用同一套计数器和比较通道。但换个思路一个定时器有四个通道可以用一个通道做输入捕获另外两个通道做输出是完全可以的。比如用TIM2的CH1去捕获外部遥控器的PWM信号同时用CH2、CH3输出两路电调控制信号信号频率和相位稍微错开就行。在实际项目中这种方法可以省掉一个定时器资源值得尝试。4.4 常见问题速查表现象可能原因处理方式引脚完全无波形GPIO未配置为复用推挽输出检查GPIO模式是否为AF_PP开启复用时钟频率比预期低一半APB1预分频配置不对检查RCC配置确认APB1分频系数后定时器时钟被2倍频占空比调节没反应CCR值大于ARR或被一直覆盖检查代码对CCR的赋值路径确保写入地址正确只有示波器看到断断续续方波电源纹波大或干扰严重示波器探头换到靠近芯片电源引脚加10uF和100nF电容高级定时器无输出主输出使能未打开在HAL库中使能__HAL_TIM_MOE_ENABLE检查CubeMX主输出勾选PWM频率对但LED亮度不变化频率太高灯来不及响应降低频率或在PWM上串电阻用电容滤波做简易DAC从我个人实际体验来说PWM这个实验虽然看起来只是几个寄存器的配置但它背后牵扯到定时器时钟树、比较逻辑、引脚复用、中断优先级这些嵌入式底层的核心概念。当年我做这个实验的时候花了一整晚调不出波形最后发现是GPIO复用模式少配置了一行从那以后凡是初始化外设我都会逐项检查复用功能。这个习惯在后来做飞控、做电源、做电机驱动时帮了大忙很多看似玄学的问题其实都是配置遗漏。最后再分享一个实用扩展如果想让PWM波形按特定曲线自动变化比如无刷电机启动时的缓启动、补光板的平滑断流渐变可以开启定时器的DMA请求让DMA在更新事件时自动把CCR值从内存搬运到寄存器。这样CPU完全不用干预波形自动按预设表跑精度和实时性都很有保障实测在F103上跑256点渐变表毫无压力。这个玩法以后做复杂波形输出时会非常常用值得提前试试。本文还有配套的精品资源点击获取