第4章 定时器与PWM输出:STM32定时器配置、PWM模式选择、输出通道映射

好,咱们进入实战环节了。前面讲了PWM的原理,说白了就是让高低电平按一定比例出现。那在STM32上怎么实现?核心就是定时器。我刚开始学的时候,觉得定时器就是个计数器,后来才发现,这玩意儿简直就是个微型CPU,能干的事太多了。

4.1 STM32定时器家族

STM32的定时器分三类:基本定时器、通用定时器、高级定时器。做PWM输出,最常用的是通用定时器和高级定时器。

定时器类型 PWM通道数 主要用途
基本定时器(TIM6/TIM7) 0 纯计时,不能输出PWM
通用定时器(TIM2~TIM5) 4 标准PWM输出,够用了
高级定时器(TIM1/TIM8) 4+互补通道 带死区控制的PWM,电机控制专用

做按摩仪,通用定时器完全够用。我做过一个项目,用TIM3的四个通道分别控制四个按摩头,效果很好。没必要一上来就上高级定时器,除非你要做电机控制。

4.2 定时器配置三步走

配置定时器输出PWM,其实就三步。我习惯这么记:分频、重装、模式

第一步:时钟分频

定时器的时钟源一般是系统时钟。比如STM32F103,APB1上的定时器时钟是72MHz。这个频率太高了,直接计数的话,一个数就是13.8ns,太精细了。我们需要分频。

// 设置预分频器,72MHz / 72 = 1MHz
TIM_TimeBaseInitStructure.TIM_Prescaler = 71;  // 注意:实际值是Prescaler+1

这里有个坑,我刚开始就踩过。分频值写的是71,实际分频是72。因为寄存器是从0开始计数的。你想想看,如果写0,那就是不分频,1分频。所以想要72分频,就得写71。

第二步:自动重装值

自动重装值决定了PWM的周期。比如我们想要20kHz的PWM,时钟已经是1MHz了,那重装值就是:

// 1MHz / 20000 = 50
TIM_TimeBaseInitStructure.TIM_Period = 49;  // 同样,实际值是49+1=50

这样算下来,PWM频率就是1MHz / 50 = 20kHz。嗯,这里要注意,重装值也不能太大,16位定时器最大是65535。如果你要极低频率的PWM,比如1Hz,那就得把分频设得很大。

第三步:配置PWM模式

这一步最关键。STM32的PWM有两种模式:PWM1和PWM2。区别很简单:

  • PWM1模式:计数值小于比较值时输出有效电平,大于时输出无效电平
  • PWM2模式:正好反过来

我个人习惯用PWM1模式,配合极性设置,逻辑比较直观。你看代码:

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 25;  // 占空比50%,因为Period=49
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;

这里TIM_Pulse就是比较值。当计数器从0数到49,在25这个点翻转电平。占空比就是25/50=50%。

核心公式:

PWM频率 = 定时器时钟 / (Prescaler+1) / (Period+1)

占空比 = Pulse / (Period+1) × 100%

4.3 输出通道映射

定时器配置好了,PWM信号从哪个引脚出来?这就涉及到通道映射了。每个定时器的每个通道都对应固定的GPIO引脚。

以TIM3为例:

通道 对应引脚 复用功能
通道1 PA6 TIM3_CH1
通道2 PA7 TIM3_CH2
通道3 PB0 TIM3_CH3
通道4 PB1 TIM3_CH4

配置GPIO时,要设置为复用推挽输出。我见过有人设成普通推挽,结果PWM死活出不来。原因就是没开复用功能。

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 将PA6映射到TIM3_CH1
GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3);

我的小技巧:画原理图时,我会把定时器通道和引脚的关系列个表贴在旁边。这样Layout时就不会搞混。曾经有一次,我为了省事,随便选了个引脚,结果发现那个引脚没有定时器复用功能,只能重新改板子,教训深刻。

4.4 完整配置示例

好了,把上面这些串起来,就是一个完整的PWM输出配置。我写个标准流程给你看:

void PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 1. 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 2. 配置GPIO
    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);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3);
    
    // 3. 配置定时器
    TIM_TimeBaseStructure.TIM_Prescaler = 71;      // 72分频
    TIM_TimeBaseStructure.TIM_Period = 49;         // 50个计数周期
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 4. 配置PWM通道
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 25;            // 50%占空比
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    
    // 5. 启动定时器
    TIM_Cmd(TIM3, ENABLE);
}

注意:如果你用的是高级定时器TIM1或TIM8,别忘了还要调用TIM_CtrlPWMOutputs(TIM1, ENABLE)来使能主输出。这个坑我踩过,配置了半天没波形,最后发现是主输出没开。

4.5 动态调整占空比

按摩仪需要实时调整力度,也就是占空比。怎么调?直接修改比较寄存器就行:

// 设置占空比为30%
TIM_SetCompare1(TIM3, 15);  // 15/50 = 30%

// 设置占空比为80%
TIM_SetCompare1(TIM3, 40);  // 40/50 = 80%

这里要注意,修改比较值时,最好在定时器中断里做,或者确保修改时定时器正在运行。否则可能会出现一个完整的PWM周期内占空比突变的情况。不过对于按摩仪这种应用,这点小波动根本感觉不到,放心改就行。

好了,这一章的内容就这些。说白了就是:分频定频率,比较定占空比,映射定引脚。记住这三点,STM32的PWM配置你就掌握了。下一章我们讲怎么用PWM控制按摩电机,到时候会用到这里的所有知识。