4、定时器与PWM生成:STM32定时器原理、PWM模式配置、占空比与频率计算、使用PWM模拟加热功率(0-100%)
各位同学,欢迎来到第四讲。
今天咱们聊聊定时器和PWM。说实话,在座椅加热通风模块里,PWM就是那个真正干活的家伙。你想想看,加热垫要多少度、风扇转多快,全凭PWM说了算。我当年第一次做加热控制时,以为直接给电就行了,结果要么烫屁股要么没感觉。后来才明白,PWM才是精准控温的灵魂。
4.1 STM32定时器:不只是数数那么简单
STM32的定时器,说白了就是个硬件计数器。它内部有个寄存器,每来一个时钟脉冲就加1。加到设定值就归零,同时触发一个事件——比如更新中断、或者翻转一个引脚。
我个人习惯把定时器分成三类:
- 基本定时器(TIM6、TIM7):只能计时,不能输出PWM。适合做系统心跳。
- 通用定时器(TIM2~TIM5):能输出PWM,有捕获/比较通道。这是咱们的主力。
- 高级定时器(TIM1、TIM8):功能最全,支持互补输出、死区插入。做电机控制时常用。
在座椅加热通风模块里,我一般用通用定时器就够了。加热和风扇控制,不需要那么花哨的功能。
核心寄存器:
- PSC(预分频器):把系统时钟分频,降低计数频率
- ARR(自动重装载寄存器):计数的上限值
- CCR(捕获/比较寄存器):决定PWM的占空比
4.2 PWM模式配置:三步走
配置PWM输出,其实就三步。我在项目中教过不少新人,按这个顺序来,基本不会出错。
- 配置GPIO为复用功能——把引脚交给定时器管
- 配置定时器时基——设置PSC和ARR,决定频率
- 配置输出比较通道——设置CCR,决定占空比
嗯,这里要注意:GPIO的复用功能编号一定要查手册。我见过有人把TIM2的通道1映射到了PA0,结果死活不出波形。后来发现PA0的复用功能是TIM2_CH1_ETR,不是普通的TIM2_CH1。这种坑,踩一次就记住了。
下面给个标准配置代码,用的是TIM2的通道1,输出到PA0:
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 2. 配置GPIO为复用功能
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 映射复用功能
GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM2);
// 4. 配置定时器时基
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 83; // 84MHz / (83+1) = 1MHz
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 999; // 1MHz / (999+1) = 1kHz
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 5. 配置PWM模式
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
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);
// 6. 启动定时器
TIM_Cmd(TIM2, ENABLE);
我的小技巧:配置完先别急着调占空比。先用示波器看看频率对不对。如果频率不对,后面调占空比全是白费功夫。我曾经在一个项目里折腾了两小时,最后发现是PSC算错了——少减了一个1。
4.3 占空比与频率计算:别被公式吓到
其实计算很简单。就两个公式:
- 频率 = 系统时钟 / (PSC+1) / (ARR+1)
- 占空比 = CCR / (ARR+1) × 100%
举个例子。系统时钟84MHz,我想输出1kHz的PWM:
- 先分频:84MHz / 84 = 1MHz(PSC=83)
- 再计数:1MHz / 1000 = 1kHz(ARR=999)
- 50%占空比:CCR = 500
你看,是不是很简单?
注意:ARR和CCR都是16位的,最大值65535。如果你要输出很低的频率,比如1Hz,ARR就得设到999999——但ARR最大只有65535。怎么办?要么加大PSC,要么换32位定时器(比如TIM2、TIM5在某些系列上是32位的)。
4.4 用PWM模拟加热功率:0-100%的奥秘
加热垫本质上是个电阻。给它通电就发热,断电就冷却。但人感觉到的热量,其实是平均功率。
PWM控制加热功率,说白了就是控制通电时间占整个周期的比例:
- 0%:一直断电,不加热
- 100%:一直通电,满功率加热
- 50%:一半时间通电,一半时间断电,半功率
我在做座椅加热模块时,遇到过一个问题:PWM频率选多少合适?
- 频率太低(比如10Hz):加热垫会一抖一抖的,人能感觉到温度波动
- 频率太高(比如20kHz):MOS管开关损耗变大,发热严重
- 经验值:100Hz~1kHz,既平滑又高效
下面是个实用的函数,把0-100的加热等级转成CCR值:
// 加热等级:0-100
void SetHeatingLevel(uint8_t level)
{
if(level > 100) level = 100;
uint16_t ccr_value = (uint16_t)((uint32_t)level * (TIM2->ARR + 1) / 100);
TIM2->CCR1 = ccr_value;
}
调用的时候:
SetHeatingLevel(0); // 关闭加热
SetHeatingLevel(50); // 半功率
SetHeatingLevel(100); // 满功率
避坑指南:我曾经在批量生产时发现,同一批板子,同样的代码,有的加热快有的加热慢。查了半天,发现是MOS管的驱动电压不够。PWM高电平只有3.3V,而MOS管需要5V才能完全导通。后来加了个电平转换电路,问题解决。所以,PWM输出后一定要检查驱动能力,尤其是驱动大功率负载时。
小结
这一讲咱们把定时器和PWM的核心内容过了一遍。从定时器原理到PWM配置,从频率计算到加热功率模拟。说实话,PWM这东西,看着简单,但真正用好还是需要一些经验的。尤其是频率选择和驱动电路设计,这两块最容易出问题。
下一讲,咱们聊聊ADC采样——怎么把温度传感器的模拟信号变成数字量。到时候你会看到,PWM和ADC配合起来,就能实现闭环温度控制了。
好,今天就到这里。有问题随时交流。