别再死记公式了!用STM32F103的TIM3输出PWM,我这样理解ARR、PSC和CCR的关系
从心跳到呼吸:用生活化思维理解STM32的PWM三剑客
第一次接触STM32的PWM功能时,我盯着数据手册里那些ARR、PSC、CCR的公式看了整整一天,结果越看越糊涂。直到有天晚上听着自己的心跳声,突然意识到——定时器不就是芯片的心跳吗?而PWM不过是让这颗心脏按照我们的节奏"呼吸"。这种具象化的理解让我豁然开朗,今天我就把这个"顿悟时刻"分享给同样困惑的你。
1. 定时器:STM32的心跳机制
想象你正在数自己的脉搏。每次心跳就是一个"滴答",60次心跳组成一分钟——这和STM32定时器的工作原理惊人地相似。**ARR(Auto-Reload Register)就像你设定的计数目标值,比如60次;而PSC(Prescaler)**则决定了心跳的快慢,相当于你选择用秒表还是沙漏来计时。
当TIM3定时器启用时,时钟信号经过PSC分频后驱动计数器累加。这个分频过程可以用简单的厨房秤来类比:
原始时钟频率 → 面粉(高频信号) PSC值 → 筛网孔大小(分频程度) 分频后频率 → 筛过的面粉(工作频率)假设系统时钟是72MHz,PSC设置为71(实际分频系数为PSC+1),那么定时器的实际工作频率为:
定时器频率 = 72MHz / (71 + 1) = 1MHz这就相当于把原本高速运转的"心脏"调整到了一个适合我们观察的节奏。接下来,ARR决定了心跳多少次完成一个完整周期:
周期时间 = (ARR + 1) / 定时器频率如果ARR设为999,那么PWM周期就是(999 + 1)/1MHz = 1ms,对应1kHz频率。这种"心跳-呼吸"的对应关系,比死记公式直观多了。
注意:实际配置时ARR和PSC都是16位寄存器,最大值65535。PSC为0表示不分频。
2. PWM:给心跳加上韵律
有了规律的心跳,现在要让芯片"唱出歌"来——这就是PWM的精髓。**CCR(Capture/Compare Register)**就是我们的"歌词本",决定了每个周期中高电平的持续时间。占空比的计算突然变得非常简单:
占空比 = CCR / (ARR + 1)配置TIM3输出PWM时,每个通道都有独立的CCR寄存器。这就好比合唱团的四个声部,虽然心跳节奏(ARR)相同,但每个歌手可以有自己的发声时机(CCR)。具体到代码中:
// TIM3 PWM初始化示例 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 时基单元配置 TIM_TimeBaseStructure.TIM_Period = 999; // ARR值 TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // PWM模式配置(以通道1为例) TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 500; // CCR值 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3, &TIM_OCInitStructure); // 启用TIM3 TIM_Cmd(TIM3, ENABLE);这段代码产生的PWM波形特征如下:
| 参数 | 计算公式 | 示例值 | 物理意义 |
|---|---|---|---|
| 频率 | 定时器频率/(ARR+1) | 1kHz | 信号重复的快慢 |
| 周期 | 1/频率 | 1ms | 一次完整波形的时间 |
| 占空比 | CCR/(ARR+1) | 50% | 高电平所占时间比例 |
| 分辨率 | 1/(ARR+1) | 0.1% | 占空比最小调节步进 |
3. 多通道PWM:和谐的四重奏
TIM3的强大之处在于可以同时输出4路独立的PWM信号。就像指挥家控制不同乐器的强弱,我们可以为每个通道设置不同的CCR值:
// 配置四个通道的CCR值 TIM_SetCompare1(TIM3, 300); // 通道1占空比30% TIM_SetCompare2(TIM3, 500); // 通道2占空比50% TIM_SetCompare3(TIM3, 700); // 通道3占空比70% TIM_SetCompare4(TIM3, 900); // 通道4占空比90%虽然ARR和PSC是全局设置(所有通道共享),但通过灵活调整CCR,我们可以实现:
- LED亮度渐变控制(不同占空比)
- 电机速度调节(相同频率不同功率)
- 蜂鸣器音调组合(不同频率需调整ARR)
实际调试时,我习惯用这种结构化的检查清单确认配置:
时钟树验证:
- 确认TIM3时钟已使能(RCC_APB1PeriphClockCmd)
- 检查系统时钟频率(默认72MHz)
GPIO设置:
- 复用功能选择正确(GPIO_PinRemapConfig)
- 推挽输出模式(GPIO_Mode_AF_PP)
定时器基础:
- PSC值是否导致频率超出范围
- ARR值是否满足分辨率需求
PWM特定配置:
- 输出比较模式设为PWM1/PWM2
- 各通道CCR初始值合理
- 输出极性符合外设需求
4. 实战中的避坑指南
第一次使用TIM3输出PWM时,我遇到了波形不稳定的问题。后来发现是因为忽略了影子寄存器机制——修改ARR或PSC需要等待更新事件生效。这就像调整心跳节奏后,身体需要几个周期来适应。
另一个常见误区是认为CCR必须小于ARR。实际上:
- 在PWM模式1下:OCxREF高电平当CNT<CCR
- 在PWM模式2下:OCxREF高电平当CNT>CCR
这意味着CCR可以设为0或ARR+1来产生恒低/恒高电平。下表总结了不同CCR值的效果:
| CCR值范围 | PWM模式1效果 | PWM模式2效果 |
|---|---|---|
| CCR = 0 | 恒低电平 | 恒高电平 |
| 0 < CCR < ARR | 正常PWM | 反向PWM |
| CCR = ARR | 最后一个时钟周期跳变 | 第一个时钟周期跳变 |
| CCR > ARR | 恒高电平 | 恒低电平 |
调试时,这些工具能帮你快速定位问题:
- 逻辑分析仪:直观显示多路PWM时序关系
- Keil调试模式:查看定时器寄存器实时值
- 示波器测量:
- 验证实际频率与计算值是否一致
- 检查占空比精度
- 观察边沿是否有抖动
记得那次为了驱动舵机,我需要50Hz的PWM信号(周期20ms)。按照公式计算:
目标频率 = 50Hz → 周期 = 20ms 定时器频率 = 72MHz / (719 + 1) = 100kHz ARR = (100kHz * 20ms) - 1 = 1999配置后发现舵机响应不正常,最终发现是ARR值超过了某些舵机控制芯片的解析范围。调整为ARR=39999(PSC=71,频率50Hz)后问题解决——这提醒我们理论计算还需结合实际器件特性。
