本篇继 [[ZZZ-12-定时器]] 和 [[ZZZ-10-定时器中断]]。
由于 STM32F103C8T6 最小系统板上的 PC13(板载 LED) 并不直接支持定时器的硬件 PWM 输出通道,我们在 PA0 引脚上通过一个电阻接一个 LED(或者直接用杜邦线接一个 LED 到 PA0 和 GND 之间)
第一步:STM32CubeIDE 图形化配置 (.ioc)
- 选择定时器:在左侧树状菜单点击 Timers -> TIM2。
- 设置时钟源:将 Clock Source 设置为 Internal Clock。
- 启用 PWM 通道:将 Channel 1 设置为 PWM Generation CH1。
- 此时你会发现引脚图上的 PA0 变绿了,标注为
TIM2_CH1。
- 此时你会发现引脚图上的 PA0 变绿了,标注为
- 配置参数(关键公式计算):
- 假设你的系统时钟(HCLK)是 72MHz。
- 我们要产生一个 1kHz 的 PWM 波(这个频率肉眼看不出闪烁,且调节丝滑)。
- Prescaler (PSC):输入
71(即 $72-1$)。此时频率变为 $72MHz / 72 = 1MHz$。 - Counter Period (ARR):输入
999(即 $1000-1$)。此时频率变为 $1MHz / 1000 = 1kHz$。 - Pulse (初始占空比):先设为
0。
- 生成代码:按
Ctrl + S。
第二步:编写呼吸灯逻辑 (main.c)
我们需要在 while(1) 中不断修改 CCR(捕获/比较寄存器)的值,这个值决定了占空比,也就是灯的亮度。
1. 启动 PWM
在 main 函数的 MX_TIM2_Init() 之后,while(1) 之前,加入启动命令:
|
|
2. 实现呼吸逻辑
在 while(1) 循环中,写一个循环让亮度增加,再写一个循环让亮度减小:
|
|
这段代码本质上是一个状态机,它在做“折返运动”:
if (direction):这代表“正在变亮”阶段。- 当
pwm_value一路增加到1000(撞到了天花板),说明已经最亮了,没法再亮了。 - 于是执行
direction = 0;。这相当于下令:“好了,掉头,开始往回走(变暗)”。
- 当
else:这代表“正在变暗”阶段。- 当
pwm_value一路减小到0(撞到了地板),说明已经全灭了,没法更暗了。 - 于是执行
direction = 1;。这相当于下令:“好了,掉头,开始往回走(变亮)”。
- 当
为什么频率(Frequency)要设为 1000Hz:人眼在观察快速闪烁的物体时,如果闪烁频率超过 50Hz ~ 60Hz,大脑就会认为这个光是连续的。就像手机屏幕刷新率,动画看起来越丝滑。频率设置为 1000,对于人眼来说就“绝对平滑了”;
第三步:代码深度解析
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_value): 这个宏直接操作的是TIM2->CCR1寄存器。- 当
pwm_value为 0 时:占空比为 0%,灯全灭。 - 当
pwm_value为 500 时:占空比为 50%,灯半亮。 - 当
pwm_value为 999 时:占空比为 100%,灯全亮。
- 当
- 为什么是 1ms 延时? 如果去掉
HAL_Delay(1),CPU 运行速度极快,灯会在微秒级的时间内完成呼吸,你的肉眼看起来灯只是一直微亮。1ms 的延时能让整个呼吸周期大约在 2 秒左右(0 到 1000 再到 0,共 2000 次操作)。
假如调整了 ARR
- 假设在 CubeIDE 的
.ioc文件里,把 Counter Period (ARR) 从999改成499。 - 生成代码。
- 如果不改代码:你会发现灯的亮度变化不对了。因为
pwm_value会加到 1000,而定时器数到 499 就回头了。这意味着pwm_value在 500 到 1000 这段时间里,灯始终是 100% 亮度,呼吸感会变得“很肉”(亮很久才开始变暗)。 - 修正方法:你必须把代码里的
1000也改成500。
结构图解
|
|
- ARR 就像是“分母”,固定了总长度。
- CCR (pwm_value) 就像是“分子”,决定了高电平占多大比例。
小结
__HAL_TIM_SET_COMPARE的作用是:修改 CCR 寄存器的值。- 硬件动作:每秒钟进行几千万次
CNT vs CCR的比较,根据结果决定引脚是 0 还是 1。 - 你的任务:确保
pwm_value在0到ARR之间变化,从而实现从 0% 到 100% 的亮度调节。