ZZZ-13-呼吸灯

本篇继 [[ZZZ-12-定时器]] 和 [[ZZZ-10-定时器中断]]。

由于 STM32F103C8T6 最小系统板上的 PC13(板载 LED) 并不直接支持定时器的硬件 PWM 输出通道,我们在 PA0 引脚上通过一个电阻接一个 LED(或者直接用杜邦线接一个 LED 到 PA0 和 GND 之间)

第一步:STM32CubeIDE 图形化配置 (.ioc)

  1. 选择定时器:在左侧树状菜单点击 Timers -> TIM2
  2. 设置时钟源:将 Clock Source 设置为 Internal Clock
  3. 启用 PWM 通道:将 Channel 1 设置为 PWM Generation CH1
    • 此时你会发现引脚图上的 PA0 变绿了,标注为 TIM2_CH1
  4. 配置参数(关键公式计算):
    • 假设你的系统时钟(HCLK)是 72MHz
    • 我们要产生一个 1kHz 的 PWM 波(这个频率肉眼看不出闪烁,且调节丝滑)。
    • Prescaler (PSC):输入 71(即 $72-1$)。此时频率变为 $72MHz / 72 = 1MHz$。
    • Counter Period (ARR):输入 999(即 $1000-1$)。此时频率变为 $1MHz / 1000 = 1kHz$。
    • Pulse (初始占空比):先设为 0
  5. 生成代码:按 Ctrl + S

第二步:编写呼吸灯逻辑 (main.c)

我们需要在 while(1) 中不断修改 CCR(捕获/比较寄存器)的值,这个值决定了占空比,也就是灯的亮度。

1. 启动 PWM

main 函数的 MX_TIM2_Init() 之后,while(1) 之前,加入启动命令:

1
2
3
4
/* USER CODE BEGIN 2 */
// 启动 TIM2 的通道 1 输出 PWM
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
/* USER CODE END 2 */

2. 实现呼吸逻辑

while(1) 循环中,写一个循环让亮度增加,再写一个循环让亮度减小:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* USER CODE BEGIN WHILE */
uint16_t pwm_value = 0;   // 亮度值
uint8_t direction = 1;    // 1: 变亮, 0: 变暗

while (1)
{
    // 改变亮度值
    if (direction) {
        pwm_value++;
        if (pwm_value >= 1000) direction = 0; // 达到最大 ARR 值,开始变暗
    } else {
        pwm_value--;
        if (pwm_value <= 0) direction = 1;    // 达到最小,开始变亮
    }

    // 关键函数:修改 TIM2 通道 1 的比较值 (CCR)
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_value);

    // 稍微延时,否则变色太快肉眼看不清
    HAL_Delay(1); 

    /* USER CODE END WHILE */
}

这段代码本质上是一个状态机,它在做“折返运动”:

  • if (direction):这代表“正在变亮”阶段。
    • pwm_value 一路增加到 1000(撞到了天花板),说明已经最亮了,没法再亮了。
    • 于是执行 direction = 0;。这相当于下令:“好了,掉头,开始往回走(变暗)”。
  • else:这代表“正在变暗”阶段。
    • pwm_value 一路减小到 0(撞到了地板),说明已经全灭了,没法更暗了。
    • 于是执行 direction = 1;。这相当于下令:“好了,掉头,开始往回走(变亮)”。

为什么频率(Frequency)要设为 1000Hz:人眼在观察快速闪烁的物体时,如果闪烁频率超过 50Hz ~ 60Hz,大脑就会认为这个光是连续的。就像手机屏幕刷新率,动画看起来越丝滑。频率设置为 1000,对于人眼来说就“绝对平滑了”;

第三步:代码深度解析

  1. __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_value): 这个宏直接操作的是 TIM2->CCR1 寄存器。
    • pwm_value0 时:占空比为 0%,灯全灭。
    • pwm_value500 时:占空比为 50%,灯半亮。
    • pwm_value999 时:占空比为 100%,灯全亮。
  2. 为什么是 1ms 延时? 如果去掉 HAL_Delay(1),CPU 运行速度极快,灯会在微秒级的时间内完成呼吸,你的肉眼看起来灯只是一直微亮。1ms 的延时能让整个呼吸周期大约在 2 秒左右(0 到 1000 再到 0,共 2000 次操作)。

假如调整了 ARR

  1. 假设在 CubeIDE 的 .ioc 文件里,把 Counter Period (ARR)999 改成 499
  2. 生成代码。
  3. 如果不改代码:你会发现灯的亮度变化不对了。因为 pwm_value 会加到 1000,而定时器数到 499 就回头了。这意味着 pwm_value 在 500 到 1000 这段时间里,灯始终是 100% 亮度,呼吸感会变得“很肉”(亮很久才开始变暗)。
  4. 修正方法:你必须把代码里的 1000 也改成 500

结构图解

1
2
3
4
5
6
7
8
电平
 ^
 |      ______          ______          ______
 |     |      |        |      |        |      |
 |_____|      |________|      |________|      |________> 时间
       
       <---- 一个周期 (ARR 决定) ---->
       <-亮-> (CCR/pwm_value 决定)
  • ARR 就像是“分母”,固定了总长度。
  • CCR (pwm_value) 就像是“分子”,决定了高电平占多大比例。

小结

  • __HAL_TIM_SET_COMPARE 的作用是:修改 CCR 寄存器的值。
  • 硬件动作:每秒钟进行几千万次 CNT vs CCR 的比较,根据结果决定引脚是 0 还是 1。
  • 你的任务:确保 pwm_value0ARR 之间变化,从而实现从 0% 到 100% 的亮度调节。
Licensed under CC BY-NC-SA 4.0