4. STM32 GPIO与PWM输出:从定时器到电机绕组的信号链路

这一章,我们聊聊STM32怎么把脑子里的控制算法,变成电机能听懂的PWM波。说白了,就是GPIO和定时器那点事。但你别小看它,我见过太多项目死在这第一步——配置错了,电机要么不动,要么乱抖,严重的时候直接炸管子。

咱们一步步来。先从定时器怎么产生PWM讲起,再说互补PWM和死区,最后聊聊GPIO的驱动能力。嗯,这些都是电机驱动的根基。

4.1 STM32定时器:PWM的心脏

STM32的定时器分三类:基本定时器、通用定时器、高级定时器。做电机驱动,你主要跟高级定时器(TIM1/TIM8)打交道。为什么?因为它自带互补输出、死区插入、刹车输入——这些是电机控制硬刚需。

核心概念:PWM的本质就是定时器计数器(CNT)跟一个比较值(CCR)不断比大小。CNT从0数到ARR(自动重装载值),每比一次,输出电平就翻转一次。占空比 = CCR / (ARR + 1)。

举个例子。你想输出20kHz的PWM,系统时钟72MHz。那ARR就设成3599,CCR设成1800,占空比就是50%。

// 以TIM1为例,输出20kHz PWM,占空比50%
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

TIM_TimeBaseStructure.TIM_Prescaler = 0;        // 不分频,72MHz
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = 3599;         // ARR = 3599
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 1800;            // CCR = 1800
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Reset;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);

TIM_Cmd(TIM1, ENABLE);
// 高级定时器别忘了使能主输出
TIM_CtrlPWMOutputs(TIM1, ENABLE);

我的习惯:ARR和CCR我一般用宏定义,方便调试时改。另外,高级定时器有个坑——主输出使能位(MOE)默认是关闭的。你配完所有寄存器,PWM死活不出来,八成是忘了调TIM_CtrlPWMOutputs()。

4.2 互补PWM与死区时间:别让上下管直通

电机驱动用H桥或三相逆变器,上下两个开关管交替导通。理想情况是上管关断瞬间下管立刻导通。但现实是——管子关断需要时间。如果上管还没完全关断,下管就导通了,那就直通短路,电流瞬间飙升,管子秒炸。

所以我们需要死区时间。就是在上管关断后,等一小段时间,再让下管导通。这段时间就叫死区。

参数 说明 典型值
死区时间 上下管切换时的等待时间 0.5µs ~ 3µs
死区设置寄存器 TIMx_BDTR中的DTG[7:0] 取决于定时器时钟
死区影响 太短→直通风险;太长→波形畸变、效率降低 需折中

STM32高级定时器支持硬件死区插入。你只需要配好BDTR寄存器,硬件自动在互补信号之间插入死区。省心,而且精度高。

// 配置死区时间,假设定时器时钟72MHz,死区1µs
// DTG = 72 * 1 = 72,对应BDTR寄存器的DTG位
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 = 72;          // 死区1µs
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);

我曾经踩过的坑:有次做一款低压伺服驱动,死区设了0.3µs,觉得够用了。结果MOS管结电容偏大,关断时间比手册标的长。上电一跑,管子直接冒烟。后来老老实实拿示波器量了实际关断时间,死区改到1µs才稳定。所以,死区时间别光看手册,一定要实测。

互补PWM的配置也很直接。使能TIM_OCInitStructure里的互补输出通道就行。

// 使能互补输出
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;
TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;
TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);

4.3 GPIO驱动能力与上拉/下拉电阻

PWM信号从定时器输出到GPIO引脚,最后送到隔离芯片或驱动芯片。这中间,GPIO的配置直接影响信号质量。

STM32的GPIO输出模式有几种:推挽输出、开漏输出、复用推挽、复用开漏。做PWM输出,我们一般用复用推挽输出(AF_PP)。为什么?因为定时器的输出信号是通过复用功能连接到GPIO的,不是普通IO口控制的。

GPIO模式 适用场景 驱动能力
复用推挽(AF_PP) PWM输出、USART、SPI等 强,可输出高电平
复用开漏(AF_OD) 需要外部上拉的场合 弱,需外接上拉
推挽输出(GP_PP) 普通IO控制 强

驱动能力方面,STM32每个GPIO引脚可以输出最大20mA电流(绝对最大额定值)。但整片芯片的总电流有限制,具体看数据手册。做电机驱动时,PWM信号频率高(10kHz~100kHz),GPIO的压摆率(Slew Rate)也要注意。STM32提供了速度等级配置:2MHz、10MHz、50MHz。我一般设成50MHz,保证信号边沿陡峭,减少开关损耗。

// 配置PA8为TIM1_CH1的PWM输出
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   // 复用推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  // 高速
GPIO_Init(GPIOA, &GPIO_InitStructure);

上拉/下拉电阻怎么选?PWM输出一般不需要内部上拉或下拉,因为推挽输出本身就能驱动高低电平。但有一种情况——芯片刚上电、GPIO还没配置时,引脚默认是浮空输入。这时候如果外部电路已经上电,可能会误触发。我的做法是在外部加一个10kΩ下拉电阻到地,确保上电瞬间PWM引脚是低电平,驱动芯片不会误动作。

4.4 知识结构总览

下面这张图把整个信号链路串起来了。从定时器配置到GPIO输出,每一步都有讲究。

STM32 PWM信号链路结构图 STM32 PWM信号链路结构图 定时器配置 ARR / CCR / 预分频 PWM生成 边沿对齐 / 中心对齐 互补PWM + 死区 BDTR / DTG / 死区插入 GPIO配置 复用推挽 / 速度50MHz 驱动能力 20mA / 压摆率 / 上拉下拉 输出到隔离/驱动 光耦 / 磁隔离 / 驱动芯片 关键提醒 死区时间需实测验证 | GPIO速度设50MHz保证边沿 | 外部下拉电阻防上电误触发

4.5 避坑指南与经验总结

  • 死区时间别照搬手册:我习惯先设一个保守值(比如2µs),然后用示波器看实际波形,再慢慢往下调。调到波形刚好没有直通毛刺为止。
  • GPIO速度别设太低:有次为了省电,把GPIO速度设成2MHz,结果PWM波形变成梯形波,电机嗡嗡响。后来改回50MHz,问题解决。
  • 上电时序要考虑:STM32上电后GPIO默认是浮空输入。如果驱动芯片先上电,PWM引脚悬空,驱动芯片可能误判为高电平。加个外部下拉电阻,或者让驱动芯片后上电。
  • 高级定时器的MOE位:这个我提过好几次了,但还是要强调——TIM_CtrlPWMOutputs(TIM1, ENABLE) 这句不能省。

一句话总结:PWM信号链路从定时器到GPIO,每一步都要精准配置。死区时间、GPIO速度、上拉下拉,这些细节决定了你的电机驱动是稳定运行还是冒烟炸管。