前言

前面我们用 HAL_Delay() 做过延时,但 HAL_Delay() 有一个严重问题:它在等的时候,CPU 什么也做不了。更精确、更灵活的计时要靠硬件定时器

定时器是 STM32 最强大的外设之一。这一篇我们从最基础的定时中断开始,一路做到 PWM 呼吸灯。


一、F407 的定时器家族

F407 有 14 个定时器,分三类:

类型 编号 位数 主要用途
高级定时器 TIM1、TIM8 16 位 PWM、互补输出、刹车、死区(电机控制)
通用定时器 TIM2、TIM5 32 位 定时中断、PWM、输入捕获
通用定时器 TIM3、TIM4 16 位 定时中断、PWM、输入捕获
通用定时器 TIM9~TIM14 16 位 定时中断、PWM
基本定时器 TIM6、TIM7 16 位 纯粹定时,无外部 IO

仅 TIM2 和 TIM5 是 32 位,TIM3/TIM4 是 16 位通用定时器。这是 F407 相较 F103 的重要差异——F103 的 TIM2~TIM5 全是 16 位,而 F407 的 TIM2/TIM5 升级到了 32 位。


二、定时器的工作原理

打个比方:

你每秒往碗里丢一颗豆子,规定碗里装 1000 颗时响铃,然后清空碗重新数。这就是定时器。

在 STM32 里:

  • 时钟源:你的手(丢豆子的速度)

  • 计数器(CNT):碗里的豆子数

  • 自动重装载值(ARR):碗的容量(1000)

  • 更新事件(溢出):响铃

定时时间公式:

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

其中:

  • PSC(Prescaler):预分频系数,把定时器时钟降到你需要的频率

  • ARR(Auto-Reload):计数值,计到 ARR 就溢出

例子: 需要一个 1 秒的定时中断

定时器时钟 = 84MHz(APB1,TIM2~TIM5)
目标频率 = 1Hz(1秒)
分频方式:(PSC + 1) × (ARR + 1) = 84,000,000
​
常用的分法:
PSC = 8399  → 分频后 10kHz(0.1ms 一跳)
ARR = 9999  → 计 10000 次后溢出,即 1 秒

验证:(8399+1) × (9999+1) / 84,000,000 = 1.0s


三、CubeMX 配置:定时中断

3.1 选择定时器

TIM2(通用 32 位定时器,挂在 APB1 上,时钟 84MHz)。

3.2 参数设置

在 Pinout & Configuration → TimersTIM2

  • Clock Source:选择 Internal Clock

  • Prescaler(PSC)8400 - 1(CubeMX 中填入 8399,即计算值减 1;寄存器从 0 开始计数,实际分频 = 8399 + 1 = 8400,分频到 10kHz)

  • Counter ModeUp

  • Counter Period(ARR)10000 - 1(产生 10kHz / 10000 = 1Hz)

  • Auto-reload preloadEnable

为什么写成 8400-110000-1?因为寄存器从 0 开始计数,设 8400-1 实际是 0~8399 共 8400 个时钟周期。

3.3 使能中断

切换到 NVIC Settings 标签 → 勾选 TIM2 global interruptEnabled

3.4 生成代码

Ctrl+S → GENERATE CODE


四、代码:定时中断翻转 LED

main() 中启动定时器:

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2);  // 启动定时器中断
/* USER CODE END 2 */

添加中断回调函数:

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);  // 1 秒翻转一次
    }
}
/* USER CODE END 4 */

编译下载,LED 应该以 2 秒为周期翻转(1s 亮 + 1s 灭)。

这和 HAL_Delay() 有什么区别?

方式 优缺点
HAL_Delay(500) + TogglePin 延时期间 CPU 空转,什么都不能干
定时器中断 CPU 在主循环里可以干别的事,中断来了才切换 LED
// 定时器方案:主循环可以处理其他任务
while (1)
{
    // 处理按键、读取传感器、更新屏幕……
    // LED 的翻转由硬件定时器在后台完成
}

五、PWM 输出——呼吸灯

5.1 什么是 PWM?

PWM(脉冲宽度调制):快速切换高低电平,通过改变占空比(高电平时间占比)模拟出不同的"电压"。

占空比 0%:       ━━━━━━━━━━━━━━ 一直低(灭)
占空比 25%:      ▇▇▇▇▂▂▂▂▂▂▂▂ 大部分时间低(微亮)
占空比 50%:      ▇▇▇▇▇▇▂▂▂▂▂▂ 一半时间高(半亮)
占空比 75%:      ▇▇▇▇▇▇▇▇▇▇▂▂ 大部分时间高(很亮)
占空比 100%:     ▇▇▇▇▇▇▇▇▇▇▇▇ 一直高(最亮)

人眼有视觉暂留效应,PWM 频率 > 50Hz 时就感觉不到闪烁,只感受到平均亮度。

5.2 PWM 和定时器的关系

通道输出 PWM 不需要中断,完全由硬件自动完成——CPU 只需要设一下占空比(CCR 寄存器),计时器自己会翻转引脚。

计数器从 0 到 ARR 递增
当 CNT < CCR 时,输出高电平
当 CNT ≥ CCR 时,输出低电平

5.3 CubeMX 配置 PWM

TIM3(挂在 APB1 上,84MHz),选择一个支持 PWM 输出的引脚(查 datasheet 的 Alternate Function 表)。

PB0(TIM3_CH3) 为例:

  1. 左侧选 TIM3

  2. Clock Source:Internal Clock

  3. Channel3PWM Generation CH3

  4. 参数设置:

    • Prescaler(PSC)84 - 1(分频到 1MHz)

    • Counter ModeUp

    • Counter Period(ARR)1000 - 1(PWM 频率 = 1MHz / 1000 = 1kHz)

    • Auto-reload preloadEnable

    • Pulse(CCR)500(初始占空比 50%)

  5. NVIC Settings 不需要勾选任何中断(PWM 不需要中断)

  6. 图中 PB0 自动变成 TIM3_CH3

PWM 频率 = 定时器时钟 / (PSC+1) / (ARR+1) = 84MHz / 84 / 1000 = 1kHz — 人眼看不到闪烁。

5.4 代码:呼吸灯

/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);  // 启动 PWM 输出
/* USER CODE END 2 */

主循环中改变占空比实现呼吸效果:

/* USER CODE BEGIN 3 */
uint16_t duty = 0;
uint8_t direction = 1;  // 1=变亮, 0=变暗

while (1)
{
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, duty);

    if (direction)
        duty += 5;
    else
        duty -= 5;

    if (duty >= 1000)
        direction = 0;
    if (duty == 0)
        direction = 1;

    HAL_Delay(10);  // 每 10ms 调整一次
}
/* USER CODE END 3 */

LED 引脚接 PB0(加上限流电阻),应该能看到 LED 从暗→亮→暗循环呼吸。

如果亮度变化不明显,试试把 duty += 5 改成 += 2,减少步进值,过渡更平滑。


六、PWM 的其他应用

PWM 不只是用来点灯的:

应用 原理
舵机控制 20ms 周期,1ms=0°,1.5ms=90°,2ms=180°
电机调速 占空比越大,电机转速越快
有源蜂鸣器 不同频率发出不同音调(乐谱)
LED 调光 改变亮度,调色温
模拟音频 高速 PWM + 低通滤波 ≈ DAC

七、常见坑

问题 原因
定时器中断没触发 NVIC 没勾选、没调 HAL_TIM_Base_Start_IT()
定时时间不准 PSC 和 ARR 忘记减 1(寄存器从 0 开始)
PWM 引脚没输出 忘了配 Alternate Function、引脚选错了
LED 一直最亮不呼吸 占空比自动重装载没开(Auto-reload preload = Disable)
呼吸频率不对 HAL_Delay(10) 加上循环里的计算时间,实际周期会偏慢

八、总结

概念 要点
定时中断 PSC 分频 + ARR 计数 → 周期执行代码
PWM 硬件自动翻转引脚 → CPU 只需改占空比
32 位定时器 TIM2 和 TIM5 可以计数到 2^32,几乎不可能溢出;TIM3/TIM4 是 16 位

练习:

  1. 把定时中断改成 10ms 触发一次,在主循环中累计变量实现秒级计时

  2. 尝试用两个定时器分别输出两个不同频率的 PWM,控制两个 LED 各自呼吸

  3. 把呼吸频率调快(减 HAL_Delay),看看多快会开始闪烁(找到人眼视觉暂留的极限)

下一篇我们引入 外部中断 + 按键消抖,让 STM32 学会"响应外部事件"。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐