【STM32】HAL库教程-PWM输出

使用工具和依赖

什么是PWM

脉冲宽度调制(PWM = Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出,通过控制有效电平在一个周期内的占比,来近似模拟信号进行控制的一种技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。

PWM模拟连续信号:

PWM模拟连续信号

PWM基本概念:

  • PWM周期:PWM波形从低电平到高电平再到低电平所持续的时间
  • PWM频率:单位时间内一个PWM周期的重复次数,\(f=1/T\)
  • PWM占空比:一个PWM周期内高电平时间和总时间的比值,\(\sigma=T_{high}/T\)
PWM基本概念

定时器功能介绍

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开始计数,如此循环。每次发生计数器上溢和下溢事件都会生成更新事件。
定时器计数器寄存器的3种工作模式
  • 自动重载寄存器(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. 捕获比较通道的输出部分

输出比较的工作流程(如上):

  1. 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为无效电平,否则为有效电平

  2. oc1ref:根据CNT和CCR1的比较结果输出有效电平/无效电平
    oc1ref = 0 无效电平,oc1ref = 1 有效电平

  3. TIMx_CC1P:CC1P表示输出极性
    CC1P = 0 高电平为有效电平,CC1P = 1 低电平为有效电平

  4. TIMx_CCER:CC1E表示输出使能
    CC1E = 0 禁止输出,CC1E = 1 信号输出到对应的输出引脚

根据PWM模式1/2和向上/向下计数的不同选择,可排列组合出4种PWM输出波形:

PWM模式1 PWM模式2
向上计数 PWM模式1_向上计数 PWM模式2_向上计数
向下计数 PWM模式1_向下计数 PWM模式2_向下计数

呼吸灯实验讲解

我们结合刚才学习的PWM和定时器基础知识,完成呼吸灯实验,实现板上LED亮度的渐变调节功能。

CUBEMX初始化配置

  1. 设置外部时钟
    设置高速外部时钟HSE,选择RC振荡器
    设置RCC

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

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

  4. 设置定时器
    点击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为低电平
  5. 项目文件配置
    设置项目名称,设置存储路径,选择所用IDE(KEIL 5)
    项目文件设置1
    选择仅复制所需的.c和.h库文件,选择每个功能生成独立的.c和.h文件
    项目文件设置2 然后点击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下载调试接口,勾选上下载后复位运行 keil配置下载工具

我们修改main.c文件创建一个呼吸灯的例程。
定义变量:

1
2
3
/* USER CODE BEGIN 1 */
uint16_t pwmVal = 0;
/* USER CODE END 1 */

使能 TIM2 的 PWM Channel1 输出:

1
2
3
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
/* USER CODE END 2 */

在while循环中添加核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* USER CODE BEGIN WHILE */
while (1)
{
while(pwmVal < 500) {
pwmVal++;
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, pwmVal);
HAL_Delay(1);
}
while(pwmVal) {
pwmVal--;
__HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, pwmVal);
HAL_Delay(1);
}
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

参考链接

  1. 野火STM32库开发实战指南

  2. 【STM32】HAL库 STM32CubeMX教程