4. 定时器基础与PWM生成:通用定时器原理、PWM模式配置、占空比与频率计算、呼吸灯实现

各位同学,咱们今天聊定时器。说实话,做电机控制这么多年,我打交道最多的外设就是定时器。没有它,PWM出不来,电机就转不了。这一章,咱们把通用定时器的底裤扒干净,看看PWM到底是怎么生成的。

4.1 通用定时器核心原理

通用定时器,说白了就是一个会数数的计数器。它从0开始,一直数到某个设定值,然后清零重新数。这个数数的过程,就是定时器工作的本质。

我习惯把定时器拆成三个部分来理解:

  • 时钟源:给定时器提供节拍,决定它数多快
  • 计数器:核心部件,向上/向下/中央对齐三种模式
  • 比较器:把计数值和设定值比较,产生事件

你想想看,如果时钟频率是72MHz,预分频器设为72-1,那定时器每秒就数100万次。每次计数的时间就是1微秒。这就是定时精度的来源。

核心公式:

定时器溢出频率 = 时钟频率 / (预分频值 + 1) / (自动重装载值 + 1)

PWM频率 = 定时器溢出频率(向上计数模式)

4.2 PWM模式配置详解

PWM,脉冲宽度调制。名字听着高大上,其实就是控制一个方波的高电平时间占比。我在项目中遇到过不少新手,上来就问“PWM频率怎么设”,其实你先得搞清楚你要控制什么。

通用定时器支持两种PWM模式:

模式 输出电平行为 典型应用
PWM模式1 计数值 < CCR时有效电平,否则无效 电机调速、LED调光
PWM模式2 计数值 < CCR时无效电平,否则有效 互补输出、死区控制

嗯,这里要注意:有效电平是可以配置的,通过TIM_CCER寄存器的极性位控制。我一般默认用高电平有效,这样逻辑更直观。

4.3 占空比与频率计算

占空比,就是高电平时间占整个周期的比例。频率,就是一秒钟有多少个周期。这两个参数决定了PWM的控制效果。

举个例子,假设我们要生成一个20kHz的PWM,时钟72MHz:

// 计算步骤
// 1. 确定预分频值:PSC = 72 - 1,得到1MHz计数频率
// 2. 确定自动重装载值:ARR = 50 - 1,得到20kHz
// 3. 设置占空比:CCR = 25,占空比50%

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;

// 时基配置
TIM_TimeBaseStructure.TIM_Prescaler = 71;        // 72MHz / 72 = 1MHz
TIM_TimeBaseStructure.TIM_Period = 49;           // 1MHz / 50 = 20kHz
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

// PWM通道配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 25;              // 占空比 25/50 = 50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);

// 使能定时器
TIM_Cmd(TIM2, ENABLE);

避坑指南:我曾经在项目里把ARR和CCR搞反了,结果电机转得忽快忽慢。记住:ARR决定频率,CCR决定占空比。ARR是周期,CCR是比较值。

4.4 呼吸灯实现

呼吸灯,说白了就是让LED的亮度像呼吸一样慢慢变化。这需要PWM的占空比从0%逐渐增加到100%,再逐渐减小到0%。

实现思路很简单:

  1. 初始化定时器,输出固定频率的PWM
  2. 在主循环中,每隔一段时间改变CCR的值
  3. CCR从0递增到ARR,再从ARR递减到0
// 呼吸灯主循环代码
uint16_t brightness = 0;
uint8_t direction = 1;  // 1: 增加, 0: 减少

while(1)
{
    // 更新占空比
    TIM_SetCompare1(TIM2, brightness);
    
    // 延时,控制呼吸速度
    Delay_ms(10);
    
    // 改变亮度方向
    if(direction)
    {
        brightness++;
        if(brightness >= 49)  // 达到最大亮度
            direction = 0;
    }
    else
    {
        brightness--;
        if(brightness <= 0)   // 达到最小亮度
            direction = 1;
    }
}

注意:呼吸灯的速度取决于延时时间和步进值。延时10ms、步进1,一个完整呼吸周期大约1秒。如果觉得太快,可以加大延时或减小步进。我习惯用定时器中断来做,这样不阻塞主循环。

4.5 定时器知识体系

为了让大家更直观地理解定时器与PWM的关系,我画了一张结构图:

通用定时器PWM生成结构图 时钟源 (72MHz) 预分频器 (PSC) 计数器 (CNT) 自动重装载 (ARR) 比较器 (CCR) PWM输出 关键说明: 1. 时钟经过PSC分频后驱动CNT计数 2. CNT与ARR比较产生溢出事件,决定PWM频率 3. CNT与CCR比较产生匹配事件,决定PWM占空比

这张图把定时器生成PWM的完整链路画出来了。从时钟源开始,经过预分频、计数、比较,最终输出PWM波形。每个环节都对应一个寄存器,理解了这张图,你就掌握了定时器的精髓。

4.6 实战要点总结

  • 频率选择:电机控制一般用10kHz-20kHz,太低会听到噪音,太高会增加开关损耗
  • 分辨率:ARR值越大,占空比调节越精细。但ARR大了,频率就低了,需要权衡
  • 死区插入:驱动H桥时,上下管不能同时导通,需要插入死区时间
  • 更新事件:修改CCR后,要等下一个更新事件才会生效,可以用预装载功能

我的经验:做电机控制,PWM频率我一般选16kHz。为什么?因为人耳对这个频率最不敏感,听不到高频啸叫。而且16kHz刚好是72MHz的整数分之一,计算方便。当然,这只是我个人习惯,具体还得看电机参数。