3. PWM输出模式详解:模式1与模式2的区别、通道引脚映射与极性配置
各位同学,咱们接着往下聊。上一节我们把PWM的基本原理和定时器配置框架搭起来了,这一节我打算深入进去,把PWM输出模式里最容易让人迷糊的几个点彻底讲透。
说白了,PWM输出就是定时器在背后不停地比较、翻转、更新。但STM32给了你两种比较模式——模式1和模式2,很多人一开始搞不清它俩到底差在哪。再加上引脚映射和极性配置,新手很容易在这里栽跟头。我当年第一次调舵机,就是因为极性搞反了,舵机死活不转,还以为是代码写错了……嗯,咱们今天就把这些坑一个个填平。
3.1 PWM模式1与模式2:核心区别
先看一张对比表,把两个模式的核心差异摆出来:
| 对比项 | PWM模式1 | PWM模式2 |
|---|---|---|
| 计数方向 | 向上计数 | 向上计数 |
| CNT < CCR时 | 输出有效电平 | 输出无效电平 |
| CNT ≥ CCR时 | 输出无效电平 | 输出有效电平 |
| 有效电平位置 | 占空比起始段 | 占空比结束段 |
| 典型应用 | 舵机控制、LED调光 | 互补输出、死区补偿 |
你仔细看这个表,其实模式1和模式2的差别就一句话:有效电平出现在计数值小于CCR的那一段,还是大于等于CCR的那一段。
我个人习惯用模式1做舵机控制。为什么?因为模式1下,CCR值越大,有效电平占的时间越长,占空比越大。这个逻辑很直观——你给CCR写500,占空比就是50%;写900,占空比就是90%。调起来不用动脑子。
模式2刚好反过来。CCR越大,有效电平反而越短。我刚开始用的时候就被这个反逻辑坑过一次——想加大占空比,结果把CCR往大了写,舵机反而往回缩。你想想看,这要是没发现,排查起来得多头疼。
核心记忆法:模式1是“小值有效”,模式2是“大值有效”。但注意,这个“有效”指的是有效电平,不是占空比大小。
3.2 PWM输出通道与引脚映射关系
讲完模式,咱们看看通道和引脚。STM32的定时器通常有4个通道(CH1~CH4),每个通道对应一个GPIO引脚。但问题是——同一个引脚可能被多个外设复用,你得选对才行。
以我常用的STM32F103C8T6为例,TIM2的通道映射是这样的:
| 定时器 | 通道 | 默认引脚 | 重映射引脚 |
|---|---|---|---|
| TIM2 | CH1 | PA0 | PA15 |
| TIM2 | CH2 | PA1 | PB3 |
| TIM2 | CH3 | PA2 | PB10 |
| TIM2 | CH4 | PA3 | PB11 |
这里有个关键点:重映射不是你想用就能用的。你得在GPIO初始化之前,调用GPIO_PinRemapConfig()函数,把引脚功能切过去。我在项目中遇到过一块板子,PA0被别的外设占用了,只能把PWM挪到PA15。结果忘了开重映射时钟,折腾了半小时才找到原因。
我的习惯:画原理图之前,先把定时器通道和引脚对应表列出来,避免后期打架。如果板子已经做好了,优先用默认引脚,实在不行再考虑重映射。
3.3 PWM输出极性配置
极性配置,说白了就是决定有效电平是高电平还是低电平。在STM32的库函数里,通过TIM_OCInitTypeDef结构体的OCPolarity成员来设置:
// 配置PWM输出极性
TIM_OCInitTypeDef TIM_OCInitStructure;
// 有效电平为高电平(常用)
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
// 有效电平为低电平
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
你可能会问:极性有什么用?我直接默认高电平不行吗?
行,但有时候不行。举个例子:你用一个NPN三极管驱动LED,基极高电平导通,那PWM极性就得设成高有效。但如果你用的是PNP管,基极低电平导通,极性就得反过来。我刚开始做电机驱动板的时候,没注意这个细节,结果电机一上电就全速转——因为极性反了,PWM输出一直处于无效电平,但无效电平对电机来说反而是导通状态。
避坑指南:我曾经在调试一个舵机项目时,舵机始终只能转到0°和180°两个位置,中间角度全跳过去了。查了半天,发现是极性设反了——有效电平变成了无效电平,导致CCR的比较逻辑完全颠倒。所以,配置极性之前,先确认你的负载电路是“高有效”还是“低有效”。
3.4 知识结构总览
为了让你更直观地理解这三部分的关系,我画了一张结构图:
从这张图你可以看到,PWM输出配置其实就三个维度:模式、引脚、极性。它们各自独立,但最终输出的波形是三者共同作用的结果。我建议你在写代码之前,先在纸上把这三个参数定下来,再动手。
3.5 实战配置示例
最后,给你一个完整的PWM输出配置代码片段,把今天讲的知识点串起来:
// 以TIM2_CH1为例,PA0输出PWM,模式1,高有效
void PWM_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, 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; // 自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 4. 配置PWM通道
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // 模式1
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // 初始占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 高有效
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 5. 使能定时器
TIM_Cmd(TIM2, ENABLE);
}
这段代码里,模式1、高有效、PA0引脚,三个配置一目了然。你以后写PWM代码,就按这个模板来,把通道号和引脚改一改就行。
一个小技巧:调试的时候,先用示波器看波形,确认频率和占空比对不对,再连负载。我吃过亏——直接连舵机,结果波形不对,舵机嗡嗡响,差点烧了。