4. STM32定时器配置与PWM输出:定时器时基单元配置、PWM模式选择、输出比较通道设置、死区时间插入
好,咱们进入正题。霍尔传感器输出的是频率随转速变化的脉冲信号,但很多时候我们需要把它变成占空比可调的PWM波,去驱动电机或者控制执行器。这时候,STM32的定时器就是主角了。
我个人习惯把定时器配置拆成四个步骤:时基单元 → PWM模式 → 输出通道 → 死区时间。每一步都有坑,咱们一个一个说。
核心逻辑:霍尔传感器的频率信号作为定时器的输入捕获,经过运算后,再通过另一个定时器的PWM输出通道生成目标波形。说白了,就是“测进来,算一下,输出去”。
4.1 时基单元配置——PWM的“心跳”
定时器的时基单元,说白了就是给PWM提供时间基准。它由三个关键寄存器组成:预分频器(PSC)、自动重装载寄存器(ARR)和计数器(CNT)。
我刚开始做的时候,总搞不清PSC和ARR到底谁影响频率、谁影响占空比。其实很简单:
- PSC:把定时器时钟分频,决定计数一次的时间。比如72MHz时钟,PSC=71,那计数频率就是1MHz,每个计数周期1μs。
- ARR:决定PWM的周期。ARR=999,那PWM周期就是1000个计数周期,也就是1ms,对应1kHz。
- CNT:从0数到ARR,然后归零重新开始。
我的经验:霍尔传感器输出的频率范围通常在几十Hz到几百kHz。我一般先把PSC设成0(不分频),然后用ARR来调周期。这样调试时只需要改一个参数,少踩坑。
举个例子,假设霍尔传感器输出100Hz信号,我们需要输出同频PWM:
// 定时器时钟 72MHz,不分频,ARR=719999
// PWM频率 = 72MHz / (0+1) / (719999+1) = 100Hz
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 预分频器
TIM_TimeBaseStructure.TIM_Period = 719999; // 自动重装载值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分割
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
4.2 PWM模式选择——模式1还是模式2?
STM32的定时器支持两种PWM模式,区别在于输出电平的翻转时机:
| 模式 | 计数小于CCR时 | 计数大于CCR时 | 典型应用 |
|---|---|---|---|
| PWM模式1 | 有效电平(通常高电平) | 无效电平(通常低电平) | 正逻辑驱动,如LED调光 |
| PWM模式2 | 无效电平 | 有效电平 | 负逻辑驱动,或需要反相波形 |
我个人习惯用PWM模式1,因为更符合直觉——CCR越大,占空比越大。但有一次我调试一个电机驱动板,发现电机反转了,查了半天才发现是霍尔传感器输出极性反了。把模式改成模式2,问题解决。
注意:霍尔传感器的输出极性可能因型号而异。有些传感器输出高电平表示有磁场,有些则相反。配置PWM模式前,先用示波器确认一下传感器输出极性,不然你会怀疑人生的。
4.3 输出比较通道设置——CCR是灵魂
输出比较通道的核心是捕获/比较寄存器(CCR)。它决定了PWM的占空比。配置时要注意三个点:
- CCR值范围:必须在0到ARR之间。CCR=0时占空比0%,CCR=ARR时占空比100%。
- 输出极性:可以配置为高电平有效或低电平有效。配合PWM模式使用。
- 预装载使能:建议开启。这样CCR值会在下一个周期开始时才生效,避免在计数过程中突然改变导致波形异常。
// 配置TIM2通道1为PWM模式1,输出极性高
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 360000; // CCR值,占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCPreload = TIM_OCPreload_Enable;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
这里有个细节:CCR值怎么算?占空比 = CCR / (ARR+1) × 100%。比如ARR=719999,想要50%占空比,CCR=360000。我一般先算好再写进去,省得反复改。
避坑指南:我曾经在调试时发现PWM输出一直为低电平,折腾了半天才发现是忘记使能定时器的主输出(TIM_CtrlPWMOutputs(TIM2, ENABLE))。高级定时器有这个开关,普通定时器没有。如果你用TIM1或TIM8,记得加上这行。
4.4 死区时间插入——保护你的H桥
死区时间,说白了就是上下桥臂同时关断的时间。在电机驱动中,如果上管还没完全关断、下管就导通了,那直接短路烧管子。死区就是防止这种情况的。
STM32的高级定时器(TIM1、TIM8)支持死区插入,通过刹车和死区寄存器(BDTR)配置。死区时间由DTG位决定,计算公式有点绕:
| DTG[7:0]范围 | 死区时间计算公式 | 示例(t_dts=125ns) |
|---|---|---|
| 0x00 ~ 0x7F | DT × t_dts | DTG=0x64 → 100×125ns=12.5μs |
| 0x80 ~ 0xBF | (64+DT[5:0]) × 2 × t_dts | DTG=0xA0 → (64+32)×2×125ns=24μs |
| 0xC0 ~ 0xFF | (32+DT[4:0]) × 8 × t_dts | DTG=0xE0 → (32+0)×8×125ns=32μs |
嗯,这个表看着有点复杂。我一般直接用STM32CubeMX的图形界面配,它会自动算出死区时间。但如果你手写代码,记住一个原则:死区时间至少是开关管关断时间的2倍。比如MOS管关断时间200ns,死区设500ns以上比较稳妥。
// 配置死区时间,t_dts=125ns,DTG=0x64 → 12.5μs
TIM_BDTRInitTypeDef TIM_BDTRInitStructure;
TIM_BDTRInitStructure.TIM_OSSR = TIM_OSSR_Enable; // 运行模式关闭状态
TIM_BDTRInitStructure.TIM_OSSI = TIM_OSSI_Enable; // 空闲模式关闭状态
TIM_BDTRInitStructure.TIM_LOCKLevel = TIM_LOCKLevel_1; // 锁定级别
TIM_BDTRInitStructure.TIM_DeadTime = 0x64; // 死区时间
TIM_BDTRInitStructure.TIM_Break = TIM_Break_Disable; // 刹车功能
TIM_BDTRInitStructure.TIM_BreakPolarity = TIM_BreakPolarity_High;
TIM_BDTRInitStructure.TIM_AutomaticOutput = TIM_AutomaticOutput_Enable;
TIM_BDTRConfig(TIM1, &TIM_BDTRInitStructure);
警告:死区时间不是越大越好。死区太大,波形失真严重,电机效率下降;死区太小,有短路风险。我一般从1μs开始试,用示波器观察上下管波形,确保没有交叠再逐步减小。安全第一。
4.5 完整配置流程——串起来跑一遍
好了,四个步骤都讲完了。咱们把它们串起来,看看一个完整的PWM输出配置长什么样:
- 使能时钟:RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
- 配置GPIO:把PA0设为复用推挽输出,复用功能为TIM2_CH1。
- 配置时基:PSC=0,ARR=719999,向上计数模式。
- 配置PWM模式:模式1,输出极性高,预装载使能。
- 设置CCR:TIM_SetCompare1(TIM2, 360000);
- 使能输出:TIM_Cmd(TIM2, ENABLE);
- (高级定时器)使能主输出:TIM_CtrlPWMOutputs(TIM2, ENABLE);
你想想看,其实每一步都不难,但顺序错了或者漏了一步,波形就是出不来。我刚开始学的时候,经常忘记使能GPIO的复用功能,结果折腾半天发现引脚没输出。嗯,这些坑踩过一次就记住了。
调试小技巧:配置完成后,先用示波器看引脚有没有波形。如果没有,检查三个地方:时钟使能了吗?GPIO复用配了吗?定时器使能了吗?80%的问题出在这三个地方。