3、STM32定时器PWM输出:定时器概述、PWM模式配置、输出引脚映射

各位做嵌入式开发的朋友,今天咱们来聊聊STM32定时器输出PWM这件事。说实话,PWM这东西在项目里太常见了——调个LED亮度、控制个电机转速、驱动个舵机,哪哪都离不开它。我刚开始用STM32的时候,对着参考手册看了半天,总觉得定时器那堆寄存器有点绕。后来踩了几个坑,才慢慢摸清楚门道。

这一章,我就把定时器PWM输出的核心知识点掰开揉碎了讲。咱们从定时器整体结构说起,再到PWM模式怎么配,最后聊聊引脚映射那些容易出问题的地方。

3.1 STM32定时器家族概览

STM32的定时器,说白了就是一堆计数器。但不同型号的芯片,定时器资源差别还挺大。我最早用F103的时候,觉得定时器够用了,后来换到F407做电机控制,才发现高级定时器的互补输出和刹车功能有多香。

咱们先看看常见的定时器分类:

定时器类型 计数器位数 典型型号 PWM输出能力 主要特点
基本定时器(TIM6/TIM7) 16位 所有系列 ❌ 无 仅用于定时、触发DAC
通用定时器(TIM2~TIM5) 16/32位 F1/F4/F7/G4 ✅ 4通道独立PWM 最常用,带捕获/比较
高级定时器(TIM1/TIM8) 16位 F1/F4/F7/G4 ✅ 带互补输出+刹车 电机控制、H桥驱动
低功耗定时器(LPTIM) 16位 L0/L4/L5 ✅ 有限通道 低功耗模式下工作

关键点:做PWM输出,首选通用定时器。如果要做电机控制(需要互补PWM和死区插入),必须用高级定时器。我见过有人用通用定时器硬做H桥驱动,结果还得自己软件插死区,折腾得够呛。

每个通用定时器有4个捕获/比较通道(CH1~CH4),每个通道都可以独立输出PWM。这意味着一个定时器就能同时控制4路PWM,频率相同但占空比可以不同。嗯,这里要注意——频率是共享的,因为所有通道共用同一个计数器。

3.2 PWM模式配置实战

配置PWM输出,其实就三步:定频率、定极性、定占空比。我习惯先把这三件事想清楚再动手写代码。

3.2.1 频率怎么算?

PWM频率由三个参数决定:定时器时钟、预分频器(PSC)、自动重装载值(ARR)。公式很简单:

PWM频率 = 定时器时钟 / (PSC + 1) / (ARR + 1)

举个例子,STM32F103的TIM2挂载在APB1上,时钟是72MHz。我想要一个20kHz的PWM,分辨率做到1000级(也就是占空比可以调0~999):

72MHz / (PSC + 1) / (ARR + 1) = 20kHz
ARR + 1 = 1000  →  ARR = 999
PSC + 1 = 72MHz / 20kHz / 1000 = 3.6  →  PSC = 2.6

取整:PSC = 2,实际频率 = 72M / 3 / 1000 = 24kHz
或者:PSC = 3,实际频率 = 72M / 4 / 1000 = 18kHz

注意:PSC和ARR都是整数,算出来有小数必须取整。我曾经在一个项目里没注意这个,算出来PSC=2.6直接写了2,结果频率偏了20%,电机嗡嗡响了好久才排查出来。

3.2.2 模式选择:PWM1还是PWM2?

STM32的PWM模式有两种:PWM模式1和PWM模式2。区别就一句话——有效电平的位置不同。

模式 向上计数时 向下计数时 典型应用
PWM模式1 CNT < CCR时有效,否则无效 CNT > CCR时有效,否则无效 最常用,正逻辑控制
PWM模式2 CNT > CCR时有效,否则无效 CNT < CCR时有效,否则无效 反逻辑或特殊波形需求

我个人习惯用PWM模式1,配合有效电平为高,这样占空比越大,输出高电平时间越长,逻辑上更直观。你想想看,CCR=500时占空比50%,CCR=900时占空比90%,多好理解。

3.2.3 代码配置示例

下面是一个完整的HAL库配置代码,输出20kHz、占空比50%的PWM:

// 1. 使能定时器时钟
__HAL_RCC_TIM2_CLK_ENABLE();

// 2. 配置GPIO为复用推挽输出
GPIO_InitTypeDef gpio = {0};
gpio.Pin = GPIO_PIN_0;          // TIM2_CH1
gpio.Mode = GPIO_MODE_AF_PP;
gpio.Pull = GPIO_NOPULL;
gpio.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &gpio);

// 3. 配置定时器时基
TIM_HandleTypeDef htim2 = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 3;       // 72MHz / (3+1) = 18MHz
htim2.Init.Period = 999;        // 18MHz / (999+1) = 18kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim2);

// 4. 配置PWM通道
TIM_OC_InitTypeDef oc = {0};
oc.OCMode = TIM_OCMODE_PWM1;
oc.Pulse = 500;                 // 占空比 500/1000 = 50%
oc.OCPolarity = TIM_OCPOLARITY_HIGH;
oc.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &oc, TIM_CHANNEL_1);

// 5. 启动PWM输出
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

小技巧:调试时可以先配一个极低的频率(比如1Hz),用示波器看波形对不对。确认极性、占空比都正常了,再改回目标频率。这招帮我省了不少排查时间。

3.3 PWM输出引脚映射

定时器配置好了,代码写完了,结果PWM没输出?八成是引脚映射搞错了。STM32的定时器通道可以映射到多个GPIO引脚,但同一时刻一个通道只能映射到一个引脚。

3.3.1 默认映射与重映射

以TIM2为例,它的CH1默认在PA0,但通过AFIO(或GPIO_AFR寄存器)可以重映射到其他引脚:

定时器通道 默认引脚 重映射引脚(部分型号) 备注
TIM2_CH1 PA0 PA15 F1系列支持
TIM2_CH2 PA1 PB3 注意与JTAG冲突
TIM2_CH3 PA2 PB10 —
TIM2_CH4 PA3 PB11 —

避坑指南:我曾经在F103上把TIM2_CH2重映射到PB3,结果怎么都不输出。查了半天才发现PB3是JTDO引脚,调试器占着呢!后来换了PB10才搞定。所以选引脚时一定要先看原理图,确认这个引脚没有被其他外设占用。

3.3.2 重映射配置方法

在F1系列中,重映射通过AFIO寄存器配置:

// 使能AFIO时钟
__HAL_RCC_AFIO_CLK_ENABLE();

// 将TIM2_CH1重映射到PA15
__HAL_AFIO_REMAP_TIM2_PARTIAL();

在F4/G4系列中,重映射通过GPIO的AFR寄存器配置,更灵活:

// 将PA0配置为TIM2_CH1(AF1)
gpio.Alternate = GPIO_AF1_TIM2;
HAL_GPIO_Init(GPIOA, &gpio);

3.4 知识体系总览

说了这么多,咱们用一张图把定时器PWM输出的整体流程串起来:

STM32定时器PWM输出知识体系 ① 定时器选型 ② 频率配置 ③ 模式与极性 ④ 引脚映射 频率 = 时钟 / (PSC+1) / (ARR+1) PSC:预分频器(整数) ARR:自动重装载值(整数) 注意取整误差! PWM模式1 vs 模式2 模式1:CNT<CCR有效 模式2:CNT>CCR有效 极性:高有效/低有效 默认引脚 → 重映射 检查原理图,避免冲突 F1:AFIO重映射 F4/G4:GPIO_AFR ⑤ HAL_TIM_PWM_Start() 💡 先调频率,再调占空比 用示波器验证波形

这张图把整个配置流程串起来了。你从定时器选型开始,沿着箭头往下走,每一步都有对应的配置要点。我建议你下次做PWM项目时,先对着这张图把思路理清楚,再动手写代码。

3.5 常见问题与排查思路

最后,我把自己这些年遇到的PWM输出问题总结了一下,列成清单,方便你排查:

  1. 没有波形输出:先检查GPIO模式是不是AF_PP,再确认定时器时钟有没有使能,最后看HAL_TIM_PWM_Start有没有调用。
  2. 频率不对:算一下PSC和ARR的取值,注意整数取整带来的误差。用示波器实测频率,跟计算值对比。
  3. 占空比不对:检查CCR值是否在0~ARR范围内。如果CCR=0或CCR=ARR,输出就是恒低或恒高,看起来像没PWM。
  4. 引脚没输出:确认引脚映射是否正确,有没有被其他外设占用(尤其是JTAG/SWD引脚)。
  5. 波形抖动:检查定时器时钟是否稳定,有没有在中断里修改CCR值导致不同步。

我的调试习惯:先配一个极低的频率(比如10Hz),占空比设50%,用LED或者示波器看效果。确认基础功能正常后,再改到目标频率。这样能快速定位问题是出在配置上还是参数计算上。

好了,这一章的内容就到这里。PWM输出说难不难,但细节确实不少。你把定时器选型、频率计算、模式配置、引脚映射这几个环节都搞清楚了,后面做项目就会顺手很多。