ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32高级定时器PWM互补输出与死区时间配置详解

STM32高级定时器PWM互补输出与死区时间配置详解 1. 为什么PWM互补输出值得单独拎出来讲如果你玩过STM32的普通PWM输出比如用TIM1的通道1直接驱动一个LED做呼吸灯那你可能觉得PWM不过如此——配个预分频器、自动重装载值、比较值使能输出完事。但一旦你开始接触电机驱动、逆变器、LLC谐振变换器、半桥/全桥拓扑这类场景普通PWM就完全不够用了。这些场景里你经常需要两个互补的PWM信号去驱动上下两个功率管而且这两个信号之间必须插入一段死区时间否则上下管同时导通瞬间就会炸管。这就是PWM互补输出要解决的问题。STM32的高级定时器TIM1、TIM8、TIM15、TIM16、TIM17等具体看型号硬件层面就支持互补输出和死区插入不需要你在软件里手动翻转IO或者用两个定时器去凑。用STM32CubeMX配置的话整个过程可能十分钟就能跑通但如果你不理解背后的寄存器逻辑和死区计算方式调波形的时候就会一头雾水。这篇文章我打算把互补输出的配置流程从头到尾捋一遍包括CubeMX里每个参数到底在改哪个寄存器、死区时间怎么算、刹车功能怎么用、实际调试时示波器上该看什么。适合已经会用CubeMX配普通PWM、但还没碰过互补输出的朋友也适合正在做电机驱动或者电源类项目、需要快速把互补PWM跑起来的开发者。下面所有内容基于STM32F103C8T6和TIM1来演示其他型号的高级定时器逻辑基本一致只是通道数量和引脚分配有差异。2. 互补输出的核心机制与CubeMX配置逻辑2.1 高级定时器和通用定时器的本质区别STM32的定时器分三类基本定时器TIM6、TIM7、通用定时器TIM2~TIM5等、高级定时器TIM1、TIM8等。互补输出这个功能只有高级定时器才有。通用定时器你翻遍参考手册也找不到TIMx_BDTR这个寄存器因为它压根不存在。高级定时器比通用定时器多出来的东西主要有三块刹车输入Break Input、死区时间发生器Dead-Time Generator、互补输出通道Complementary Output。这三者在硬件上是绑在一起的都归TIMx_BDTR寄存器管。你可以把BDTR理解成一个“安全阀”寄存器它决定了主输出和互补输出能不能真正送到引脚上以及两者之间插入多长的死区。为什么通用定时器不做这个因为通用定时器的定位是定时、计数、普通PWM输出而高级定时器的定位就是电机控制和数字电源。你去看STM32F103的数据手册TIM1的引脚通常标注为TIM1_CH1、TIM1_CH1N这个N就是Complementary互补的意思。CH1和CH1N是一对CH2和CH2N是一对CH3和CH3N是一对CH4比较特殊通常没有互补输出只有CH4本身。2.2 互补输出的信号关系与死区的作用先明确一个概念互补输出不是简单的“反相”。如果只是反相你用一个反相器芯片就够了何必用定时器硬件。互补输出的核心在于死区时间——在CH1从高变低之后CH1N不会立刻从低变高而是等待一段死区时间再变高反过来CH1N从高变低之后CH1也要等一段死区时间再变高。这段死区时间的存在是为了确保上下两个功率管不会同时导通。以半桥驱动为例上管导通时下管必须关断下管导通时上管必须关断。但功率管的关断不是瞬间完成的栅极电荷需要释放时间如果上管还没完全关断就打开下管瞬间就会形成直通短路电流可能达到几百安培管子直接炸掉。死区时间就是留给管子安全关断的窗口。死区时间的计算方式在参考手册里有明确公式。以STM32F103为例死区时间由TIMx_BDTR寄存器的DTG[7:0]位段控制分四种计算模式DTG[7:5]计算公式死区时间范围假设tDTS1/72MHz≈13.89ns0xxDT DTG[7:0] × tDTS0 ~ 127×13.89ns ≈ 0 ~ 1.76μs10xDT (64 DTG[5:0]) × 2×tDTS1.78μs ~ 3.52μs110DT (32 DTG[4:0]) × 8×tDTS3.56μs ~ 7.08μs111DT (32 DTG[4:0]) × 16×tDTS7.11μs ~ 14.16μs这个表你不需要背CubeMX里填死区时间的时候它会自动帮你算。但你要知道的是死区时间的精度受限于tDTS而tDTS来自定时器的时钟分频。如果你把定时器时钟设成72MHztDTS就是13.89ns死区时间的最小调节步进就是这个值。如果你需要更精细的死区调节可以考虑提高定时器时钟或者接受这个精度限制。2.3 CubeMX中互补输出的配置路径打开CubeMX选好芯片型号之后在Pinout视图里找到TIM1。点击TIM1的通道1你会看到几个选项Output Compare CH1、PWM Generation CH1、PWM Generation CH1 CH1N、PWM Generation CH1 CH1N CH2 CH2N等等。这里要选的是**PWM Generation CH1 CH1N**这个选项同时使能了CH1和CH1N两个输出。选完之后你会看到CH1对应的引脚比如PA8和CH1N对应的引脚比如PB13自动被分配了。注意CH1N的引脚不是随便选的它是芯片内部固定映射的你只能在CubeMX给出的可选引脚里挑。STM32F103C8T6的TIM1_CH1N默认在PB13如果你这个引脚被其他功能占了就得重新规划。接下来进入Configuration标签页点开TIM1的设置。这里有几个关键参数需要改Mode确认Channel1选的是PWM Generation CH1 CH1NChannel2如果不用就保持Disable。Counter SettingsPrescaler和Counter Period决定PWM频率。假设72MHz时钟Prescaler0Counter Period999那PWM频率就是72MHz/100072kHz。PWM Generation Channel 1Pulse设初始占空比比如500就是50%。CH Polarity和CHN Polarity分别设主输出和互补输出的有效电平。通常都设High但如果你后面接的是低电平有效的驱动芯片就要改成Low。Break and Dead-Time Settings这是互补输出的核心。Dead Time要填一个值CubeMX会根据你填的值自动算DTG位段。Break Input可以先Disable等基础功能跑通了再回来加。配置完生成代码CubeMX会在MX_TIM1_Init()里帮你把BDTR寄存器配好。你只需要在main函数里调用HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1)和HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1)两个输出就同时跑起来了。注意互补输出必须用HAL_TIMEx_PWMN_Start这个扩展函数普通HAL_TIM_PWM_Start只启动主输出CH1N不会动。3. 死区时间计算与刹车功能的实操细节3.1 死区时间的实际计算过程CubeMX里填死区时间的时候你填的是纳秒值它自动帮你反推DTG。但你要知道它怎么算的不然换个芯片或者换个时钟频率就懵了。以STM32F103、TIM1时钟72MHz为例tDTS1/72MHz≈13.89ns。假设你需要500ns的死区时间。500/13.89≈3636在0~127范围内所以DTG[7:5]0xxDTG[7:0]360x24。CubeMX会把这个值写进BDTR的DTG位段。你可以在生成的代码里看到类似htim1.Init.DeadTime 36;这样的赋值但CubeMX通常直接给你算好你不需要手动改。但这里有个坑CubeMX填死区时间的时候单位是纳秒但它内部计算用的是整数。如果你填500ns它算出来36.0正好整除。如果你填510ns510/13.89≈36.72它会取整到36或者37实际死区时间就变成500ns或514ns。所以如果你对死区精度要求很高最好自己算一遍然后填一个能整除的值。另外死区时间不是越大越好。死区太大PWM有效占空比范围会被压缩电机低速时可能转矩不够死区太小功率管关断不彻底有直通风险。一般MOSFET的半桥驱动死区在200ns~1μs之间比较常见具体要看管子的关断时间和驱动电路的拉低能力。IGBT的话死区通常要更大因为关断拖尾电流时间长。3.2 刹车输入的功能与配置刹车功能Break Input是高级定时器另一个安全机制。你可以把它理解成一个“紧急停止”按钮。当刹车引脚检测到有效电平时定时器会立刻把主输出和互补输出都拉到无效状态具体是拉高还是拉低取决于Polarity设置同时可以触发一个中断让你在中断里做故障处理。刹车输入在CubeMX的Break and Dead-Time Settings里配置。你需要使能Break Input然后设置Break Polarity高电平触发还是低电平触发、Break Filter滤波防止毛刺误触发、Automatic Output自动恢复输出。Automatic Output这个选项要特别注意如果使能了刹车信号消失后定时器会自动恢复输出如果不使能刹车后必须手动调用__HAL_TIM_MOE_ENABLE()才能恢复输出。在电机驱动里通常不使能自动恢复因为故障消失后直接恢复输出可能很危险应该让软件判断后再决定。刹车引脚通常是TIM1_BKIN在STM32F103C8T6上对应PB12。这个引脚如果你不用刹车功能可以当普通IO用一旦使能了刹车它就被定时器占用了。3.3 互补输出与普通PWM的代码差异普通PWM的启动代码是HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1);互补输出的启动代码是HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1);注意第二行用的是HAL_TIMEx_PWMN_Start这个函数在stm32f1xx_hal_tim_ex.c里。如果你只调了第一行CH1N引脚不会有任何输出示波器上只能看到CH1的波形。很多人第一次配互补输出生成代码后只看到一路PWM就是因为漏了这行。另外修改占空比的时候主输出和互补输出是联动的。你调__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, value)CH1和CH1N的占空比会同时变化CH1N的占空比是CH1的补数再减去死区影响。你不需要单独去改CH1N的比较值硬件自动处理。4. 从零搭建一个互补PWM输出的完整流程4.1 硬件准备与引脚规划我手头用的是STM32F103C8T6最小系统板就是那种蓝色小板子上面有个Micro USB口和两个跳线帽。这个板子的TIM1_CH1在PA8TIM1_CH1N在PB13。PA8和PB13都引出来了可以直接接示波器探头。如果你用的是其他板子比如STM32F407或者G0系列引脚可能不一样。CubeMX里选好芯片后在Pinout视图里点TIM1的通道它会自动高亮可用的引脚。你只需要确认CH1N的引脚没有被其他外设占用就行。如果被占了要么换通道比如用CH2/CH2N要么重新规划引脚分配。示波器方面两通道就够了。CH1接PA8CH2接PB13共地。如果你有逻辑分析仪更好可以同时看两路波形和死区时间。没有示波器的话用两个LED分别接PA8和PB13也能看个大概——你会看到两个LED交替闪烁亮度取决于占空比。但死区时间用LED是看不出来的必须用示波器。4.2 CubeMX工程配置 step by step新建工程选STM32F103C8Tx。在Pinout视图里找到TIM1点Channel1的下拉菜单选PWM Generation CH1 CH1N。你会看到PA8和PB13被自动分配。如果PB13显示红色冲突说明被其他功能占了你需要去那个功能里把它释放掉。进入Clock Configuration确认TIM1的时钟源是72MHz。STM32F103的TIM1挂在APB2总线上APB2默认72MHz。如果你改了时钟树确认一下APB2的分频系数别让TIM1时钟变成36MHz或者更低。回到Configuration点TIM1。在Parameter Settings里Counter SettingsPrescaler71Counter Period999。这样PWM频率72MHz/(72×1000)1kHz。1kHz比较低方便示波器观察死区。实际电机驱动通常用10kHz~20kHz你可以后面再改。PWM Generation Channel 1Mode选PWM mode 1Pulse50050%占空比CH PolarityHighCHN PolarityHigh。Break and Dead-Time SettingsDead Time填1000单位ns即1μs。Break Input先Disable。生成代码选MDK-ARM或者STM32CubeIDE看你习惯。我用的Keil5生成后打开工程。4.3 代码编写与波形验证在main.c里找到MX_TIM1_Init()确认它被调用了。然后在main函数的while(1)之前加上HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1);编译下载。用示波器看PA8和PB13。你应该能看到两路1kHz的方波占空比各50%但两路不是完全反相的——在PA8下降沿之后PB13会延迟1μs才上升在PB13下降沿之后PA8也会延迟1μs才上升。这个延迟就是死区时间。如果你把示波器的时间轴展开到1μs/div可以清楚看到死区。如果看不到死区检查两个地方一是HAL_TIMEx_PWMN_Start有没有调用二是BDTR寄存器的MOE位有没有置1。MOE位是主输出使能如果它为0CH1和CH1N都不会输出。HAL_TIM_PWM_Start和HAL_TIMEx_PWMN_Start内部都会检查并设置MOE但如果你手动改过BDTR可能把它清了。4.4 动态调整死区时间和占空比实际项目中死区时间可能需要在运行时调整。比如电机低速时用大死区保证安全高速时用小死区提高效率。你可以通过修改BDTR寄存器来动态改死区__HAL_TIM_SET_DEADTIME(htim1, 72); // 设置死区时间为72×13.89ns≈1μs但注意这个宏在HAL库里的实现方式因版本而异有些版本没有这个宏你需要直接操作寄存器TIM1-BDTR ~TIM_BDTR_DTG; TIM1-BDTR | 72;改占空比就更简单了__HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, 300); // 30%占空比CH1N会自动跟着变不需要额外操作。5. 调试互补PWM时最容易踩的坑5.1 常见问题速查表现象可能原因排查方法CH1有输出CH1N无输出没调HAL_TIMEx_PWMN_Start检查代码里有没有这行两路都没有输出MOE位为0读BDTR寄存器确认bit151死区时间不对DTG计算错误或时钟频率不对确认TIM1时钟重新算DTG波形有毛刺刹车滤波没开或PCB干扰使能Break Filter检查地线占空比调到0或100%时波形异常比较值等于或超过ARR限制Pulse在1~ARR-1之间刹车触发后无法恢复Automatic Output没使能手动调__HAL_TIM_MOE_ENABLE()5.2 实操心得与避坑技巧第一死区时间不要照搬别人的值。我见过有人做电机驱动直接抄了网上的代码死区设了2μs结果电机低速时抖动严重转矩明显不够。后来用示波器一看2μs死区在20kHz PWM下占了4%的周期有效占空比被压缩得很厉害。死区时间要根据你的功率管型号、驱动芯片拉低能力、母线电压来综合确定。一般MOSFET半桥200ns~500ns就够了IGBT可能要1μs以上。第二CubeMX生成的代码里BDTR的配置在MX_TIM1_Init()里但MOE位是在HAL_TIM_PWM_Start里才置位的。如果你在MX_TIM1_Init()之后、HAL_TIM_PWM_Start之前去读BDTRMOE是0这是正常的。不要以为配置错了。第三互补输出的两路波形在示波器上看起来“不是完全反相”是对的。因为死区的存在两路都是高电平的时间为零但两路都是低电平的时间等于死区时间。如果你看到两路同时为高哪怕只有几纳秒都说明死区没生效赶紧检查BDTR。第四如果你用HAL库的HAL_TIM_PWM_Start启动CH1再用HAL_TIMEx_PWMN_Start启动CH1N两个函数的调用顺序无所谓。但如果你只调了其中一个另一个不会自动启动。我习惯先调主输出再调互补输出这样示波器上先看到CH1再看到CH1N调试时心里有数。第五刹车引脚不用的时候一定要在CubeMX里Disable掉。如果你使能了刹车但没接刹车信号引脚悬空可能被干扰触发导致输出突然关闭。我遇到过客户反馈“PWM跑着跑着就停了”查了半天发现是刹车引脚悬空环境噪声触发刹车。后来把刹车Disable或者接一个上拉/下拉电阻就好了。第六PWM频率和死区时间要匹配。如果你用100kHz PWM死区1μs那死区占了10%的周期占空比调节范围只剩90%而且波形严重失真。高频PWM必须配小死区通常死区要小于周期的1%。比如100kHz周期10μs死区最好在100ns以内。这时候就要考虑用更高时钟的定时器或者专用驱动芯片了。5.3 进阶互补输出在电机FOC中的应用如果你做的是无刷电机FOC控制互补输出是必须的。FOC里三个半桥需要三对互补PWMTIM1的CH1/CH1N、CH2/CH2N、CH3/CH3N正好够用。CubeMX里选PWM Generation CH1 CH1N CH2 CH2N CH3 CH3N三对一起使能。FOC里死区时间通常设得比较小因为MOSFET开关频率高20kHz~50kHz死区大了会影响电流波形。但也不能太小否则直通风险大。一般根据MOSFET的Qg和驱动电阻来算Qg越小、驱动电阻越小关断越快死区可以越小。实际调试时用示波器看相电流波形如果波形有畸变或者电机发热严重可能是死区不合适。另外FOC里通常会用刹车功能做过流保护。电流采样电路检测到过流立刻触发刹车TIM1硬件关断所有输出比软件中断快得多。刹车触发后FOC算法里要检测刹车状态做故障处理然后等故障清除后再手动恢复输出。6. 示波器实测波形分析与参数微调6.1 实测波形解读我用1kHz PWM、50%占空比、1μs死区配置跑了一遍示波器上看到的是这样的CH1PA8高电平500μs低电平500μsCH1NPB13在CH1高电平期间保持低电平在CH1下降沿之后延迟1μs才上升到高电平然后保持高电平499μs再下降。CH1N下降之后CH1也延迟1μs才上升。如果你把时间轴放大到2μs/div可以清楚看到死区窗口。两路都是低电平的那段时间就是死区。用示波器的光标功能量一下应该是1μs左右。如果量出来是0.5μs或者2μs说明DTG计算有偏差回去检查TIM1时钟频率和CubeMX里填的死区值。6.2 参数微调的实际操作假设你发现死区实际是1.2μs但你想要1μs。先算一下1μs/13.89ns≈721.2μs/13.89ns≈86.4。CubeMX里你填的是1000ns它算出来72但实际测到1.2μs说明tDTS不是13.89ns。可能TIM1时钟不是72MHz而是60MHz或者别的。去Clock Configuration里确认APB2的时钟然后重新算tDTS。如果确认时钟没错但死区还是偏大可能是示波器探头的地线太长引入了振铃导致边沿判断不准。把探头地线换成弹簧地针再量一次。如果还是偏大检查BDTR寄存器的DTG位段是不是被其他代码改了。有些HAL库版本在HAL_TIM_PWM_Start里会重写BDTR把DTG覆盖掉。你可以在启动PWM之后手动再写一次BDTR。6.3 互补输出的扩展应用互补输出除了驱动半桥还可以做同步整流。比如在LLC谐振变换器里副边用MOSFET代替二极管做整流原边和副边的PWM需要严格同步。STM32的高级定时器可以输出多对互补PWM配合刹车和死区正好满足这种需求。另外互补输出还可以做H桥驱动。两个半桥组成H桥每个半桥用一对互补PWM通过控制两对PWM的相位差来实现正反转和调速。这时候死区时间要分别设置因为两个半桥的功率管可能不一样。STM32的BDTR只有一个死区发生器所以两个半桥的死区时间是一样的。如果两个半桥需要不同死区就得用两个高级定时器比如TIM1和TIM8。我个人在实际操作中的体会是互补输出这个功能CubeMX配置起来很快但真正花时间的是死区时间的确定和波形验证。我建议第一次跑的时候把PWM频率设低一点比如1kHz死区设大一点比如2μs这样示波器上容易看。等波形确认没问题了再逐步调到实际工作频率和死区。另外刹车功能一定要在项目初期就加上不要等炸了管子再回头补。硬件刹车比软件保护快一个数量级关键时刻能救你一命。
返回列表