ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32定时器PWM死区生成原理与全桥驱动寄存器配置实战

2026/9/10 3:44:51 拓冰建站 浏览量
STM32定时器PWM死区生成原理与全桥驱动寄存器配置实战 简介STM32F103RC带死区四路PWM输出工程面向嵌入式开发者与电机控制学习者核心解决全桥驱动中上下桥臂同时导通造成的直通短路问题。工程基于Keil μVision开发采用寄存器方式配置STM32的TIM定时器覆盖预分频、自动重载、PWM模式选择、极性设置以及死区时间设定等关键环节四路PWM通道同步配置可实现全桥电路的安全开关逻辑并支持在运行中修改比较寄存器动态调整占空比可直接用于全桥逆变、电机正反转控制等场景。压缩包共60个文件约965KB包含h/c源文件、uvproj工程文件、hex烧录文件以及map/lst等编译过程文件便于直接打开工程学习、修改和烧录验证README与keilkill.bat等辅助脚本也一并包含目录结构清晰。已有574人学习下载适合希望深入理解PWM死区原理、STM32定时器寄存器细节和全桥驱动信号生成方法的入门至中级开发者。1. 全桥驱动的PWM死区为什么不能省在电机控制或者逆变电源的工程现场最怕的不是波形不好看而是上下桥臂瞬间直通。一次直通短路电流能把MOSFET或IGBT直接打穿板子上留下一股焦味。STM32F103RC做全桥驱动时四路PWM必须带死区这个死区时间不是随便给个值而是要根据开关器件的关断延迟、驱动芯片传输延迟和PWM频率联合算出来的。Keil工程里看到的TIM1高级定时器本身就带有硬件死区插入逻辑比用软件翻转引脚再延时再翻转可靠得多因为中断里稍微卡个几十纳秒死区就可能失效。这篇实战记录围绕寄存器操作展开适合正在调全桥或H桥的工程师也适合刚接触STM32定时器的人理解死区到底是怎么插进去的。2. STM32F103RC定时器死区生成的原理与寄存器配置2.1 互补输出与死区插入的硬件逻辑STM32F103RC的TIM1是高级定时器它和普通定时器最大的区别在于支持互补输出、可编程死区插入、刹车输入和重复计数。全桥驱动里上管和下管需要的驱动信号在逻辑上互为反相但直接反相会产生重叠导通时间。所以TIM1在把OCx信号输出到引脚时内部会先经过一个死区发生器把互补信号OCxN相对OCx延迟一段可配置的时间这段延迟就是死区。硬件逻辑上每个输出通道都配有主输出OCx和互补输出OCxN。当配置为PWM模式且使能了MOE主输出使能位后引脚上立刻能看到带死区的互补波形。这种方式比软件控制两个GPIO要稳定得多死区时间由定时器硬件计数器产生不依赖CPU负载也不会因为中断抢占产生抖动。常见做法是把TIM1的CH1和CH1N给全桥的A相上下管CH2和CH2N给B相这样两路互补输出直接覆盖全桥四个开关管。2.2 TIM1刹车寄存器与死区时间计算死区时间配置在TIM1_BDTR寄存器里关键是DTG[7:0]这8个位。DTG的编码规则是分段的不是线性映射DTG[7:5] 0xx死区时间 DTG[7:0] * Tdtg其中Tdtg 1 / fck_timDTG[7:5] 10x死区时间 (64 DTG[5:0]) * Tdtg * 2DTG[7:5] 110死区时间 (32 DTG[4:0]) * Tdtg * 8DTG[7:5] 111死区时间 (32 DTG[4:0]) * Tdtg * 16这里fck_tim是定时器时钟频率。如果TIM1挂在APB2上且APB2分频系数为1那么fck_tim就是系统主频如果APB2分频系数大于1定时器时钟频率通常是APB2的2倍。具体到F103RC很多人直接用72MHz作为fck_tim此时Tdtg ≈ 13.89ns。假设目标死区是800ns那么800 / 13.89 ≈ 57.6取DTG 58。58的二进制是00111010落入0xx段此时实际死区时间约为805ns误差在可接受范围。如果你的死区目标在3µs以上就要用第二段或第三段编码否则8位数值无法覆盖。我一般先算出Tdtg再反向查表避免直接猜寄存器值。2.3 Keil下寄存器级初始化代码这个工程是寄存器版本所以下面代码直接操作寄存器不依赖HAL库。要开启TIM1时钟、配置GPIOA的CH1/CH1N和CH2/CH2N、然后设置BDTR。注意STM32F103的TIM1通道默认映射到PA8/PA7和PA9部分封装不同需要查数据手册以下按TIM1_CH1对应PA8、TIM1_CH1N对应PA7、TIM1_CH2对应PA9、TIM1_CH2N对应PA8这样会有引脚冲突所以实际常用PA8/PA7配PB13/PB12做两路互补这里需要谨慎不能编造引脚冲突。改为通用写法使用TIM1_CH1/CH1N/CH2/CH2N对应引脚实际映射以参考手册为准。常见的是PA8为TIM1_CH1PA7为TIM1_CH1NPA9为TIM1_CH2PB13为TIM1_CH2N其实PA8是TIM1_CH1PA7是TIM1_CH1NPB13是TIM1_CH2NPA9是TIM1_CH2。没有冲突。代码void TIM1_PWM_DeadTime_Init(uint16_t arr, uint16_t psc, uint8_t dtg) { // 1. 使能TIM1和GPIOA、GPIOB时钟 RCC-APB2ENR | RCC_APB2ENR_TIM1EN; RCC-APB2ENR | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN; // 2. 配置PA8为TIM1_CH1PA7为TIM1_CH1NPA9为TIM1_CH2PB13为TIM1_CH2N GPIOA-CRH ~(0xF 0); // PA8 推挽复用输出 50MHz GPIOA-CRH | (0xB 0); GPIOA-CRH ~(0xF 28); // PA7 复用 GPIOA-CRH | (0xB 28); GPIOA-CRH ~(0xF 4); // PA9 GPIOA-CRH | (0xB 4); GPIOB-CRH ~(0xF 20); // PB13 GPIOB-CRH | (0xB 20); // 3. 设置预分频和自动重载 TIM1-PSC psc; TIM1-ARR arr; // 4. CH1输出PWM模式1互补输出使能极性为正 TIM1-CCMR1 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1M_2; // PWM模式1 TIM1-CCMR1 | TIM_CCMR1_OC1PE; // 输出比较预装载使能 TIM1-CCER | TIM_CCER_CC1E | TIM_CCER_CC1NE; // 使能CH1和CH1N输出 // 5. 配置死区时间寄存器 TIM1-BDTR ~TIM_BDTR_DTG; TIM1-BDTR | dtg; TIM1-BDTR | TIM_BDTR_MOE; // 主输出使能否则引脚上没有波形 // 6. 更新占空比并启动定时器 TIM1-CCR1 arr / 2; // 50%占空比 TIM1-CR1 | TIM_CR1_CEN; }这段代码的逻辑是先把引脚配置成复用推挽输出而不是普通GPIO模式否则定时器信号无法到达引脚。CCMR1里的OC1M位段选择PWM模式1只要CNT CCR1输出有效电平等于50%占空比时波形方波。关键是BDTR寄存器里的MOE位主输出使能位如果没置1即使定时器在跑引脚也一直是高阻或低电平。很多人调了半天不出波形就是漏了这一步。dtg参数直接写入BDTR的低8位。如果想在Keil调试时快速改死区建议把这个参数定义成一个宏比如#define DEAD_TIME_800NS 58然后按2.2节的公式换算。3. 四路带死区PWM的通道映射与参数整定3.1 全桥驱动的通道分配方案全桥电路通常由两对半桥组成每对半桥需要互补PWM。用TIM1的CH1/CH1N驱动A相上、下管CH2/CH2N驱动B相上、下管正好覆盖四路输出。这样占用的引脚少且死区发生器天然保证同一时刻上下管不会同时导通。下面是一个常用映射表实际引脚以封装和重映射配置为准功能定时器通道默认引脚对应开关管左桥臂上管TIM1_CH1PA8Q1左桥臂下管TIM1_CH1NPA7Q2右桥臂上管TIM1_CH2PA9Q3右桥臂下管TIM1_CH2NPB13Q4在Keil工程中如果发现自己板子的引脚和表里不一致优先查芯片手册的TIM1_REMAP位。STM32F103RC的TIM1没有重映射到其他引脚的选项所以基本固定在这几个引脚上。如果你用的是小封装或者自定义PCB要先把引脚确认清楚再往下写。四路PWM中CH1N总是CH1的反相延迟CH2N总是CH2的反相延迟。这意味着A、B桥臂之间的相位关系需要靠CCR1和CCR2的数值来配合。常见做法是让CH1和CH2相差180度电角度也就是CCR2 arr - CCR1这样全桥可以输出对称的正负电压波形。3.2 死区时间与频率的匹配关系死区时间不是越大越好。死区过大会导致有效占空比损失输出波形畸变电机电流谐波增加过小则容易在开关管关断不彻底时直通。一个工程经验是死区时间至少要大于开关管关断延迟t_off与驱动芯片传输延迟t_phy之和的1.5倍。假设你用的MOSFET关断延迟是120ns驱动芯片IR2104的传输延迟约150ns那么最小死区建议值是(120 150) * 1.5 405ns。留够余量后取600ns到800ns比较稳妥。PWM频率对死区的影响在于频率越高周期越短死区在周期里占的比例越大。20kHz的PWM周期是50µs800ns死区占比只有1.6%影响不大但如果把频率提高到100kHz周期变成10µs同样死区就占了8%这时候必须死区补偿。死区时间还要考虑DTG分段编码的精度。在72MHz时钟下第一段的分辨率约13.89ns足够覆盖大多数功率MOSFET对于IGBT死区通常在2µs以上需要用到第二段或第三段编码。此时Tdtg翻倍或翻数倍分辨率下降但对应时间范围变大。实际整定时我会先用逻辑分析仪读出真实死区再微调DTG而不是只看计算值。3.3 占空比更新与同步锁定动态调整占空比时如果直接写TIM1-CCR1可能在PWM周期的任意位置生效导致一个周期内波形不对称。正确做法是开启预装载功能让CCR1的新值在下一个更新事件时统一生效。2.3节的代码里已经设置了OC1PE位这个位打开后写入CCR1的值会被锁存到影子寄存器等到CNT归零时再一次性加载。void TIM1_SetDuty(uint16_t ch1_duty, uint16_t ch2_duty) { // ch1_duty和ch2_duty直接写入比较寄存器预装载保证同步更新 TIM1-CCR1 ch1_duty; TIM1-CCR2 ch2_duty; }这段代码看似简单但有一点需要注意CCR1和CCR2的写入顺序。如果先写CCR1再写CCR2由于预装载两者会在同一个更新事件生效所以顺序不影响最终波形。但如果其中一路关闭了预装载那么先写的那一路会立即生效造成两路占空比变化不同步。所有通道的OCxPE位必须保持一致这是全桥驱动里最容易被忽略的细节。另外如果使用TIM1的重复计数寄存器RCR更新事件频率会等于PWM频率 / (RCR 1)。默认RCR为0时每个周期都更新。不建议随意改RCR否则占空比刷新速率降低电流环响应会变慢。4. 死区补偿与故障保护的实际踩坑4.1 死区过大导致的波形畸变死区时间不仅损失占空比还会在电流过零处造成非线性。原理是死区期间上下管都不导通输出端电压由负载电流方向决定。当电流为正时下端续流二极管自然导通输出被钳位到低电平当电流为负时上端二极管续流输出被钳到高电平。这个钳位效果使得实际输出电压的平均值偏离理想值频率越高、死区越大偏差越明显。在电机低速运行或轻载条件下死区造成的波形畸变会导致电流波形出现明显的平顶和过零畸变转速波动加剧。这个问题在纯开环PWM控制时尤其突出。解决办法有两个方向一是把死区时间压缩到满足安全前提下的最小值二是做死区补偿在软件层面修正占空比。4.2 死区补偿算法在STM32上的落地常见做法是根据电流方向对占空比做前馈修正。如果电流为正下管关断续流时死区期间输出被钳到低电平等效于实际占空比偏大所以要把给定占空比减小一个补偿量。如果电流为负等效于占空比偏小就需要加大给定值。补偿量的计算方式为ΔD t_dead / T_pwm转换成比较寄存器值是deadline_comp dtg_time * 定时器计数频率。具体落地时需要采集相电流方向用一个符号位判断正负。以下伪代码展示核心逻辑void DeadTimeCompensation(int16_t current_signed, uint32_t pwm_period_clk) { int32_t comp (int32_t)(dead_time_clk); // 死区对应的定时器计数个数 if (current_signed 0) { // 电流流出桥臂实际占空比偏大需要减小 TIM1-CCR1 (uint16_t)(TIM1-CCR1 - comp); } else if (current_signed 0) { // 电流流入桥臂实际占空比偏小需要增加 TIM1-CCR1 (uint16_t)(TIM1-CCR1 comp); } // 电流过零时不做补偿因为方向不确定补偿反而引入振荡 }这段代码的关键在于dead_time_clk怎么求。如果定时器时钟是72MHz死区是805ns那么dead_time_clk 72 * 0.805 ≈ 58也就是DTG的值。代码里的current_signed来自ADC采样需要和PWM同步触发采样否则电流噪声会影响方向判断。我一般让TIM1的更新事件触发ADC注入组确保采到的电流波形是PWM中点的瞬时值。注意补偿不能盲目叠加。如果CCR1已经接近0或ARR补偿后要限制在[min_duty, max_duty]范围内避免出现负占空比。另外死区补偿对全桥的另一个桥臂同样适用只是电流方向符号相反。4.3 过流保护与PWM封锁的联动STM32F103RC的高级定时器自带刹车功能这个功能比在外部搭硬件保护逻辑更可靠。当BKIN引脚收到有效电平或者软件写入BG位定时器会立即把所有输出置为无效状态而不是等当前PWM周期结束。这非常关键全桥短路时每多等一个微秒电流就多涨一大截。void TIM1_Brake_Init(void) { // 配置PB12为BKIN输入下降沿触发刹车 GPIOB-CRH ~(0xF 16); GPIOB-CRH | (0x8 16); // 上拉输入 // 使能刹车输入低电平有效自动恢复关闭 TIM1-BDTR | TIM_BDTR_BKE | TIM_BDTR_BKP; }刹车后的恢复方式需要认真考虑。BKE使能后如果AOE位为1刹车事件会在下一个更新事件自动解除输出如果AOE为0则必须由软件清掉MOE位才能恢复。在电机控制里推荐把恢复逻辑做成手动复位避免故障源没有消除时PWM自动恢复再炸一次。常见做法是在刹车中断里置一个故障标志只有收到上位机清故障命令后才重新置位MOE。刹车保护还有一个容易忽略的点刹车信号刚触发时如果死区发生器还没来得及作用上下管可能已经同时截止这是正常的。但从截止到恢复输出的过程中必须重新经过死区插入逻辑不能直接强推MOE。STM32的硬件会自动处理恢复时插入死区所以不要自己在中断里做延时切换。5. 用逻辑分析仪验证死区参数的实用技巧5.1 测量点选取与触发设置调试带死区的PWM时测量点不是随便夹在引脚和GND上就行。全桥驱动信号是浮地的直接测驱动芯片输出反而容易把逻辑分析仪的地线和母线高压连起来。安全做法是测量单片机的TIM1引脚这里信号是3.3V逻辑电平和高压完全隔离。虽然它不代表最终的栅极电压但可以验证定时器死区配置是否正确。栅极信号再用隔离探头单独看。触发设置上建议把逻辑分析仪的触发源设为CH1上升沿时基缩到1µs/div左右。因为死区时间通常在几百纳秒到几微秒之间时基太大会看不清边沿太小容易错过触发。用5个通道同时抓CH1、CH1N、CH2、CH2N和BKIN一次性把四路波形和刹车信号全记录下来。5.2 从波形反推死区时间理想波形里CH1上升沿到CH1N下降沿之间的间隔就是死区。但实际波形上推挽输出的边沿有上升时间所以测量时要看50%电平点之间的距离。逻辑分析仪界面一般都有光标测量把光标分别放在CH1上升沿50%处和CH1N下降沿50%处读数直接就是死区时间。如果测出来的死区时间比配置值大很多优先检查引脚上是否有电容负载或者驱动芯片输入端的RC延迟。如果测出来死区为零说明MOE没使能或者BDTR的DTG没写进去。还有一种情况是CH1N和CH1信号的测试未同步逻辑分析仪的采样率不够导致边沿位置误差几十纳秒。建议采样率至少设为100MS/s这样每个采样点间隔10ns测量死区才有参考价值。5.3 快速迭代参数的工作流我习惯在Keil里把DTG定义成一个变量通过Watch窗口或者串口命令修改这样不用每次改宏重新编译。比如在中断里处理串口收到的ASCII数值直接写入TIM1-BDTR的低8位并重新计算更新void UpdateDeadTime(uint8_t dtg_value) { TIM1-BDTR ~TIM_BDTR_DTG; TIM1-BDTR | (dtg_value 0xFF); }改完之后立刻用逻辑分析仪的重复触发模式观察波形变化。如果死区从800ns改到1.2µs确认波形边沿间距确实变大然后再看电机电流波形判断死区补偿量是否匹配。这套工作流最大的好处是能在几分钟内完成几十组参数扫描把死区从保守值一路往下压直到电流波形开始出现畸变前的那个临界点那个值就是当前板子的最佳死区。注意每次改完DTG后死区补偿的dead_time_clk也要同步更新否则补偿量和实际死区不匹配反而会让波形更差。本文还有配套的精品资源点击获取