【STM32】HAL库教程-PWM输出
使用工具和依赖
- 芯片:STM32F411CEU6(芯片手册)(STM32中文参考手册)
- 代码初始化软件:STM32CUBEMX
- IDE: MDK-ARM Keil5
- 依赖:stm32f4xx hal库
什么是PWM
脉冲宽度调制(PWM = Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出,通过控制有效电平在一个周期内的占比,来近似模拟信号进行控制的一种技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。
PWM模拟连续信号:
PWM基本概念:
- PWM周期:PWM波形从低电平到高电平再到低电平所持续的时间
- PWM频率:单位时间内一个PWM周期的重复次数,\(f=1/T\)
- PWM占空比:一个PWM周期内高电平时间和总时间的比值,\(\sigma=T_{high}/T\)
定时器功能介绍
STM32F411xC/E系列共有8个定时器:
高级定时器(TIM1);通用定时器(TIM2、TIM3、TIM4、TIM5);基本定时器(TIM9、TIM10、TIM11)。
通用定时器TIMx的功能包括:
- 16位(TIM3和TIM4)或 32位(TIM2和TIM5) 向上、向下、向上/向下自动装载计数器
- 16位可编程 预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值
- 4个独立通道
- 输入捕获(Input capture)
- 输出比较(Output compare)
- PWM生成(边缘或中间对齐模式)
- 单脉冲模式输出
- 使用外部信号控制定时器和定时器互连的同步电路
- 如下时间发生时产生中断/DMA:
- 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
- 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
- 输入捕获
- 输出比较
- 支持针对定位的增量(正交)编码器和霍尔传感器电路
- 触发输入作为外部时钟或者按周期的电流管理
通用定时器框图如下:

输出比较/PWM工作原理
和输出比较/PWM相关的时基单元:
预分频器寄存器(TIMx_PSC)
预分频器PSC,有一个输入时钟CK_PSC和一个输出时钟CK_CNT。输入时钟CK_PSC就是系统时钟源的输出,输出CK_CNT则用来驱动计数器CNT计数。通过设置预分频器PSC的值(0~65535)可以得到不同的CK_CNT,计算公式:\(f_{CNT}=f_{PSC}/(PSC[15:0]+1)\)计数器寄存器(TIMx_CNT)
高级/通用定时器的计数器有三种计数模式,分别为向上计数模式、向下计数模式和中心对齐计数模式。- 向上计数模式:计数器从0开始计数,每来一个CK_CNT脉冲计数器就增加1,直到计数器的值与自动重载寄存器ARR值相等, 然后计数器又从0开始计数并生成计数器上溢事件,计数器总是如此循环计数。如果禁用重复计数器,在计数器生成上溢事件就马上生成更新事件(UEV); 如果使能重复计数器,每生成一次上溢事件重复计数器内容就减1,直到重复计数器内容为0时才会生成更新事件。
- 向下计数模式:计数器从自动重载寄存器ARR值开始计数,每来一个CK_CNT脉冲计数器就减1,直到计数器值为0, 然后计数器又从自动重载寄存器ARR值开始递减计数并生成计数器下溢事件,计数器总是如此循环计数。如果禁用重复计数器, 在计数器生成下溢事件就马上生成更新事件;如果使能重复计数器,每生成一次下溢事件重复计数器内容就减1,直到重复计数器内容为0时才会生成更新事件。
- 中心对齐模式:计数器从0开始递增计数,直到计数值等于(ARR-1)值生成计数器上溢事件, 然后从ARR值开始递减计数直到1生成计数器下溢事件。然后又从0开始计数,如此循环。每次发生计数器上溢和下溢事件都会生成更新事件。
自动重载寄存器(TIMx_ARR)
自动重载寄存器ARR用来存放与计数器CNT比较的值,如果两个值相等CNT就复位并递减重复计数器。可以通过TIMx_CR1寄存器的ARPE位控制自动重载影子寄存器功能, 如果ARPE位置1,自动重载影子寄存器有效,只有在事件更新时才把TIMx_ARR值赋给影子寄存器。如果ARPE位为0,则修改TIMx_ARR值马上有效。捕获/比较寄存器(TIMx_CCR)
通过比较计数器CNT的值跟比较寄存器CCR的值,两者相等时输出参考信号OCxREF的信号的极性就会改变,其中OCxREF=1(高电平)称之为有效电平, OCxREF=0(低电平)称之为无效电平,并且会产生比较中断CCxI,相应的标志位CCxIF(SR寄存器中)会置位。 然后OCxREF再经过一系列的控制之后就成为真正的输出信号OCx/OCxN.
输出比较的工作流程(如上):
TIMx_CCMR1:OC1M[2:0]用于输出比较模式选择
000: 冻结,001: 匹配时有效,010: 匹配时无效,011: 翻转电平,100: 强制无效,101: 强制有效,110: PWM模式1,111: PWM模式2
PWM模式1:TIMx_CNT < TIMx_CCR1 时通道1为有效电平,否则为无效电平
PWM模式2:TIMx_CNT < TIMx_CCR1 时通道1为无效电平,否则为有效电平oc1ref:根据CNT和CCR1的比较结果输出有效电平/无效电平
oc1ref = 0 无效电平,oc1ref = 1 有效电平TIMx_CC1P:CC1P表示输出极性
CC1P = 0 高电平为有效电平,CC1P = 1 低电平为有效电平TIMx_CCER:CC1E表示输出使能
CC1E = 0 禁止输出,CC1E = 1 信号输出到对应的输出引脚
根据PWM模式1/2和向上/向下计数的不同选择,可排列组合出4种PWM输出波形:
| PWM模式1 | PWM模式2 | |
|---|---|---|
| 向上计数 | ![]() |
![]() |
| 向下计数 | ![]() |
![]() |
呼吸灯实验讲解
我们结合刚才学习的PWM和定时器基础知识,完成呼吸灯实验,实现板上LED亮度的渐变调节功能。
CUBEMX初始化配置
设置外部时钟
设置高速外部时钟HSE,选择RC振荡器

设置烧录和调试接口
设置Debug选项为Serial Wire

设置时钟树
点击锁相环PLLCLK,将系统时钟HCLK设置为最大值100MHz

设置定时器
点击TIM2,设置定时器时钟源为内部时钟源,设置定时器CH1为PWM Generation模式

- PWM频率 \(f_{pwm}=f_{clk}/(PSC+1)/(ARR+1)=100M/100/500=2kHz\),注意:PSC和ARR要在实际值基础减1
- PWM占空比 \(duty=(CCR+1)/(ARR+1)\),CCR可以在Pulse中设为固定值,也可以在程序中再修改
- Mode PWM模式,这里选1
- Pulse(占空比值) 16位二进制数(0-65536),作为PWM的初始脉冲宽度,先按默认值0
- Fast Mode PWM脉冲快速模式,决定从CNT和CCR匹配到引脚有输出,中间经过的时钟周期长短,这里不使能
- CH Polarity PWM极性,设置为低电平 PS: 由于LED是低电平点亮,所以我们把极性设置为Low
- CH Idle State PWM通道不输出时的状态,Set为高电平,Reset为低电平
项目文件配置
设置项目名称,设置存储路径,选择所用IDE(KEIL 5)

选择仅复制所需的.c和.h库文件,选择每个功能生成独立的.c和.h文件
然后点击GENERATE
CODE,创建工程
Keil代码编写
核心函数讲解
以下是跟PWM相关的核心HAL库函数,需要重点理解:
初始化与通道配置(了解即可)
HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim)- 作用:把定时器配置成“PWM功能已初始化”的状态,配置基础时基单元(诸如PSC、ARR、CounterMode、ClockDivision、AutoReloadPreload 等)
- 常见调用位置:CubeMX生成的
MX_TIMx_Init()内部通常会调用 - 注意:这一步不等于某个通道已经输出PWM,通道需要进一步Config配置 + Start启动输出
HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim, TIM_OC_InitTypeDef *sConfigOC, uint32_t Channel)- 作用:通过关键字段
TIM_OC_InitTypeDef,配置某个通道为PWM模式(1或2)、极性、比较值、预装载等 - 常见调用位置:在CubeMX生成的
MX_TIMx_Init()内部通常会调用 - 特点:一个定时器有多个PWM通道时,每个通道都要分别Config配置
- 作用:通过关键字段
HAL_TIM_MspPostInit(TIM_HandleTypeDef *htim)- 作用:配置PWM 引脚复用(GPIO AF)、速度、上下拉等
- 特点:若只配置了定时器,但没配置PostInit或引脚AF不对,那么即便后续Start启动输出也无法在引脚上看到波形。
启动与停止输出(重点)
HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)- 作用:使能对应通道输出并启动计数器,让PWM从该通道输出
- 内部通常会做的事:
- 使能该通道比较输出(CCxE)
- 使能计数器(CEN)
- 对高级定时器(TIM1/TIM8 等)可能涉及主输出使能(MOE)相关处理
- 典型用法:初始化后对需要的通道逐个Start
HAL_TIM_PWM_Stop(TIM_HandleTypeDef *htim, uint32_t Channel)- 作用:关闭该通道PWM输出,并在合适情况下停止计数器
占空比、频率、相位在运行时的更新(重点)
记住:PWM的周期由PSC和ARR决定,占空比由CCR决定
__HAL_TIM_SET_COMPARE(TIM_HandleTypeDef *htim, uint32_t Channel, uint32_t Compare)- 作用:直接写 CCRx(更新占空比)
- 特点:若该通道 CCR 预装载启用(常见配置),则在更新事件 UEV(计数器溢出/Update)时生效,避免毛刺;若未启用预装载,写入后可能立刻生效,存在边沿瞬态的风险。
__HAL_TIM_GET_COMPARE(htim, Channel)- 作用:读当前 CCR 值(便于调试或闭环计算)
__HAL_TIM_SET_AUTORELOAD(htim, arr)- 作用:写 ARR(改变周期/频率的关键之一)
- 特点:若启用 ARR 预装载,同样通常在 UEV 生效
__HAL_TIM_SET_PRESCALER(htim, psc)- 作用:写 PSC(分频),常用于粗调频率范围
- 特点:PSC 更新的生效时机与具体 TIM 实现相关,工程上通常配合产生一次更新事件
__HAL_TIM_SET_COUNTER(htim, cnt)/__HAL_TIM_GET_COUNTER(htim)- 作用:写/读 CNT(可用于相位对齐、同步启动、调试计数状态)
配置下载工具
新建的工程所有配置都是默认的,我们需要选择ST-Link下载调试接口,勾选上下载后复位运行

我们修改main.c文件创建一个呼吸灯的例程。
定义变量:
1
2
3/* USER CODE BEGIN 1 */
uint16_t pwmVal = 0;
/* USER CODE END 1 */
使能 TIM2 的 PWM Channel1 输出:
1 | /* USER CODE BEGIN 2 */ |
在while循环中添加核心逻辑:
1 | /* USER CODE BEGIN WHILE */ |



