4. 定时器PWM输出实战:PWM模式原理、捕获/比较寄存器(CCR)作用、配置呼吸灯效果
各位同学,今天我们来聊聊嵌入式开发里一个非常经典、也非常有成就感的话题——PWM输出。说白了,PWM就是通过快速开关信号,让负载(比如LED、电机)感受到一个“平均”的效果。你想想看,如果让LED以极快的频率闪烁,人眼是看不出来的,只会觉得亮度变了。这就是PWM的精髓。
4.1 PWM模式原理:从方波到模拟量
PWM的全称是Pulse Width Modulation,脉冲宽度调制。它的核心思想很简单:固定频率,改变占空比。
- 频率:信号每秒钟变化的次数,决定了PWM的“快慢”。
- 占空比:高电平时间占整个周期的比例,决定了“有效值”。
举个例子,一个周期是100μs,高电平持续30μs,那么占空比就是30%。如果驱动LED,它看起来就是30%的亮度。我在项目中遇到过用PWM控制舵机的情况,占空比稍微偏一点,舵机角度就不对,那时候真是调得头皮发麻。
核心公式:
占空比 = (CCR值) / (ARR值 + 1) × 100%
其中ARR是自动重装载寄存器,决定周期;CCR是捕获/比较寄存器,决定高电平宽度。
定时器内部是怎么工作的呢?它有一个计数器,从0一直数到ARR,然后归零重新开始。在这个过程中,计数器会不断和CCR的值做比较。当计数器的值小于CCR时,输出高电平;大于等于CCR时,输出低电平。这样,一个完整的PWM波形就生成了。
4.2 捕获/比较寄存器(CCR)的作用
CCR,全称Capture/Compare Register,捕获/比较寄存器。这个名字其实暴露了它的双重身份:
- 捕获模式:当外部信号发生变化时,CCR会“抓住”当前计数器的值,用于测量脉宽或频率。
- 比较模式:CCR与计数器不断比较,当相等时触发事件,比如翻转输出引脚。
在PWM输出中,我们用的是比较模式。CCR的值直接决定了占空比。你想想看,如果ARR是999,CCR是500,那么占空比就是50%。如果CCR是0,占空比就是0%,LED全灭。如果CCR是999,占空比就是100%,LED全亮。
我的个人习惯:在配置PWM时,我会先把ARR和CCR的关系理清楚。ARR决定频率,CCR决定占空比。两者独立配置,互不干扰。但要注意,CCR的值绝对不能超过ARR,否则输出会一直保持高电平,占空比变成100%。
我曾经在一个项目中,因为CCR写成了ARR+1,结果LED死活不按预期亮度变化,查了半天才发现是寄存器溢出了。嗯,这种低级错误,犯过一次就再也不会忘了。
4.3 配置呼吸灯效果:从理论到实践
呼吸灯,说白了就是LED的亮度从暗到亮、再从亮到暗,循环往复,像呼吸一样。实现这个效果,我们需要做两件事:
- 固定PWM频率:比如1kHz,这样人眼不会看到闪烁。
- 动态改变CCR值:让占空比从0%逐渐增加到100%,再逐渐减少到0%。
下面是一个基于STM32的配置示例,我习惯用HAL库,但寄存器级别的理解更重要。
// 定时器基本配置
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 72MHz / (71+1) = 1MHz
htim2.Init.Period = 999; // 1MHz / (999+1) = 1kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim2);
// PWM通道配置
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM模式1:递增时CCR以下为高
sConfigOC.Pulse = 0; // 初始占空比0%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
// 启动PWM
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
// 呼吸灯主循环
while (1) {
// 从暗到亮
for (int i = 0; i <= 999; i++) {
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, i);
HAL_Delay(1); // 每步延时1ms
}
// 从亮到暗
for (int i = 999; i >= 0; i--) {
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, i);
HAL_Delay(1);
}
}
注意:HAL_Delay(1)的精度是1ms,如果你想让呼吸更平滑,可以用定时器中断或者更精确的延时。另外,CCR的变化步长也可以调整,步长越小,呼吸越细腻,但CPU负担也越大。
这段代码的逻辑很简单:主循环里用两个for循环,一个递增CCR,一个递减CCR。每次改变后延时1ms,这样整个呼吸周期大约是2秒(1000步×2×1ms)。
4.4 避坑指南与经验总结
在实际项目中,PWM配置有几个常见的坑,我给大家列一下:
| 常见问题 | 原因 | 解决方案 |
|---|---|---|
| LED不亮或常亮 | CCR值超出ARR范围 | 确保CCR ≤ ARR |
| LED闪烁明显 | PWM频率太低 | 提高频率到1kHz以上 |
| 呼吸不平滑 | CCR步长太大或延时不准 | 减小步长,使用定时器中断 |
| 引脚无输出 | GPIO未配置为复用功能 | 检查GPIO模式,设置为AF_PP |
我曾经在一个电机控制项目中,因为PWM频率设置得太低(只有50Hz),结果电机发出刺耳的啸叫声。后来查资料才知道,人耳对几百赫兹的声音特别敏感,最后把频率调到20kHz才解决问题。嗯,这种经验,书本上可不会告诉你。
总结一下:
- PWM的核心是频率和占空比,由ARR和CCR决定。
- CCR在比较模式下控制占空比,在捕获模式下测量信号。
- 呼吸灯就是动态改变CCR,让占空比周期性变化。
- 实际调试时,先用示波器看波形,确认频率和占空比是否正确。
好了,这一节的内容就到这里。PWM这个东西,说难不难,说简单也不简单,关键是要理解计数器、ARR和CCR三者之间的关系。你只要把这个三角关系搞清楚了,剩下的就是写代码的事了。