第三章 单片机PWM输出:STM32定时器配置、PWM输出模式设置、GPIO复用功能
好,咱们进入实战环节了。前面聊了那么多PWM的原理和调光算法,现在终于要跟硬件打交道了。这一章,我带你手把手把STM32的PWM输出调通。
说实话,我最早接触STM32的PWM时,也被那一堆寄存器搞晕过。后来发现,其实核心就三件事:定时器怎么配、PWM模式怎么选、GPIO怎么切到定时器上去。搞明白这三步,剩下的就是调参数了。
3.1 STM32定时器:PWM的心脏
STM32的定时器,说白了就是个会数数的硬件计数器。它从0开始,一直数到某个设定值,然后翻回来重新数。这个过程中,我们可以设置一个比较值——当计数值等于比较值时,输出引脚的电平就翻转一下。这就是PWM的底层原理。
我个人习惯把定时器分成三类:
- 基本定时器(TIM6、TIM7):只能计时,不能输出PWM。别在这上面浪费时间。
- 通用定时器(TIM2~TIM5):最常用的,有4个独立通道,每个通道都能输出PWM。我项目中90%的情况都用它。
- 高级定时器(TIM1、TIM8):功能最全,支持互补输出、死区插入。做电机驱动、H桥电路时必用。
3.2 定时器配置:三步走
配置定时器输出PWM,我总结了一个三步走的套路。你按这个来,基本不会出错。
第一步:确定时钟源
STM32的定时器时钟来自APB总线。一般来说,APB1上的定时器(TIM2~TIM7)时钟是72MHz,APB2上的(TIM1、TIM8)也是72MHz。但要注意,如果APB分频系数不是1,定时器时钟会翻倍。嗯,这个细节容易忘,我当年就因为这个算错过PWM频率。
第二步:设置自动重装载值(ARR)和预分频器(PSC)
这两个参数决定了PWM的频率。公式很简单:
PWM频率 = 定时器时钟 / ((PSC + 1) * (ARR + 1))
举个例子,我想要20kHz的PWM频率,时钟是72MHz:
72000000 / ((PSC + 1) * (ARR + 1)) = 20000
假设 PSC = 71,那么 ARR + 1 = 72000000 / (72 * 20000) = 50
所以 ARR = 49
重要:ARR的值决定了PWM的分辨率。ARR越大,你能调节的占空比档位就越多。比如ARR=49,你只有50档可调;如果ARR=999,就有1000档。LED调光的话,我建议ARR至少设到255以上,不然调光时能看到明显的跳变。
第三步:配置捕获/比较寄存器(CCR)
CCR就是比较值。当定时器计数值小于CCR时,输出高电平;大于等于CCR时,输出低电平(或者反过来,取决于极性设置)。所以占空比就是:
占空比 = CCR / (ARR + 1) * 100%
比如ARR=999,CCR=500,占空比就是50%。
3.3 PWM输出模式设置
STM32的PWM有两种模式:PWM模式1和PWM模式2。区别很简单:
| 模式 | 计数值 < CCR | 计数值 >= CCR |
|---|---|---|
| PWM模式1 | 有效电平(通常是高) | 无效电平(通常是低) |
| PWM模式2 | 无效电平 | 有效电平 |
说白了,模式1是向上计数时先高后低,模式2是先低后高。我99%的情况都用模式1,因为更符合直觉——CCR越大,高电平时间越长,灯越亮。
3.4 GPIO复用功能配置
定时器的PWM信号要通过GPIO引脚输出。你不能直接把GPIO设成推挽输出就完事了——得把它复用成定时器的功能。
具体步骤:
- 使能GPIO时钟:比如用GPIOA,就调用RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)。
- 使能定时器时钟:比如用TIM2,就调用RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE)。
- 配置GPIO为复用推挽输出:GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP。
- 选择复用功能:对于STM32F1系列,用GPIO_PinRemapConfig()配置引脚映射;对于F4系列,用GPIO_PinAFConfig()选择AF编号。
这里有个容易搞混的地方:不同型号的STM32,同一个定时器的通道可能映射到不同的引脚。比如TIM2的通道1,在F103C8T6上是PA0,但在F103ZET6上可能是PA0或者PA15。所以一定要查数据手册的引脚定义表。
3.5 完整代码示例
说了这么多,来段完整的代码吧。这是一个用TIM2的通道1在PA0上输出1kHz、50%占空比PWM的配置:
void PWM_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 2. 配置GPIO为复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置定时器时基
TIM_TimeBaseStructure.TIM_Period = 999; // ARR = 999
TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC = 71
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 4. 配置PWM模式1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // CCR = 500, 占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 5. 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
这段代码跑起来,PA0上就能测到1kHz的PWM波形了。频率怎么算的?72MHz / (71+1) / (999+1) = 1000Hz,没错吧?
3.6 调试技巧与常见问题
最后分享几个我实际调试中遇到的问题:
- 波形出不来?先检查GPIO的复用功能有没有配对。用示波器量一下引脚,如果一直是高或低,多半是复用没配好。
- 频率不对?检查PSC和ARR的计算。我建议先算好理论值,再用逻辑分析仪抓一下实际波形对比。
- 占空比调不到0%或100%?有些定时器在CCR=0或CCR=ARR+1时会有异常。解决办法是软件里做限幅,比如CCR最小设1,最大设ARR。
- 多个通道互相影响?每个通道的CCR是独立的,但ARR是共享的。所以如果你要用不同频率的PWM,得用不同的定时器。
核心总结:STM32的PWM输出,记住三个关键参数——PSC决定计数步长,ARR决定周期长度,CCR决定占空比。配好时钟、选对模式、设对引脚,剩下的就是调CCR的事了。
下一章,我们会把这些PWM信号真正用到LED上,实现平滑的调光效果。到时候你会看到,前面这些配置工作,都是为后面的算法打基础的。