从 PWM 无输出到上下管直通:GTM 配 L9945 推挽的连环踩坑记
用 AURIX TC38x 的 GTM(TOM 产生 PWM + DTM 插死区)驱动 ST L9945 预驱做推挽输出,这次调试经历了前后两段完全不同的故障:第一段,引脚上什么波形都没有;第二段,波形出来了,L9945 却报上下管直通。 两段加起来一共四个配置错误,前三个让信号"出不来",最后一个让信号"错着出来"。本文按这个顺序讲,每一节附一条可迁移的经验。


一、第一段故障:引脚上什么都没有
最初的任务是配置四路推挽(NON1/3、NON2/4、NON5/7、NON6/8 四组),代码写完烧进去,示波器往引脚上一搭——什么都没有。逐层扒代码,发现配置链路上叠着三个错误。
1.1 ToutSel 移位量少了 ×4:写了个寂寞
GTM 的 TOUTSEL 寄存器控制每个输出引脚接哪一路内部信号,每个引脚的 SEL 位段是 4 bit,而代码按 2 bit 移:
/* 错误:SEL 位段是 4bit,偏移应该是 idxToutselSelUB * 4 */
regVal |= selVal << idxToutselSelUB;
/* 修复后 */
regVal |= selVal << (idxToutselSelUB << 2U);
后果是路由配置根本没写进目标位段,引脚上接的信号和注释里标的完全不是一回事。
1.2 死区通道号算错:四路死区全写进 CH0
SetDtmDeadtime 里把 TOM 通道号 >>2 当 DTM 通道号用,不管配哪个通道,DTV(死区时间寄存器)全都写到 DTM0 的通道 0:
/* 错误 */
idxDtmChanUB = idxTomChanUB >> 2U;
/* 修复后:调用方传入的就是 DTM 通道号 0~3,不要再移位 */
idxDtmChanUB = idxTomChanUB;
1.3 打包寄存器整写:四通道只剩最后一个
DTM 的 CH_CTRL2 是四通道共享的 32 bit 寄存器(每通道 8 bit),原代码整寄存器赋值,后写的通道把先写的配置清掉:
/* 错误 */
infoRegDtmPS->CH_CTRL2.U = (0x88U << (idxDtmChanUB << 3U));
/* 修复后:读-改-写 */
uint32 regVal = infoRegDtmPS->CH_CTRL2.U;
regVal &= ~(0xFFU << (idxDtmChanUB << 3U));
regVal |= (0x99U << (idxDtmChanUB << 3U));
infoRegDtmPS->CH_CTRL2.U = regVal;
这一段的经验:调 GTM 这类寄存器密集的外设,先把"配置是否真的写进去了"用调试器读回验证,再谈波形。位段宽度和打包寄存器的读-改-写,是这类驱动最高发的两类错误。
二、第二段故障:波形出来了,芯片报警了
三个 Bug 修完,PWM 终于从引脚上出来了。但量推挽输出脚(J2-57),波形呈 10 ms 周期的 burst——一小段 PWM 之后被整体拉掉,过 10 ms 又起来。空载、占空比调多低都一样。同时 L9945 报:
- 高边输入 NON1 报 STG(对地短路)
- 低边输入 NON3 报 STB(对电池短路)

2.1 先读故障码:反方向双过流 = 直通
空载、两个管子、方向相反的过流故障同时出现,电流路径只剩一种可能:母线 → 上管 → 下管 → 地。上管看到的等效为对地短路,下管看到的等效为对电源短路。看到高低边双管反方向过流,直接查直通(shoot-through),不要先怀疑采样和诊断链路。
2.2 根因:死区加在了错误的边沿上
DTM 每个通道 8 个控制位,关键是 POL0 / DT0 / POL1 / DT1(OUT0 极性、OUT0 死区使能、OUT1 极性、OUT1 死区使能)。原配置 0x88 = 0b10001000:两路输出开死区、极性不反。
问题在于 L9945 的 NON 输入是低有效(低开高关)。DTM 死区的行为是"把有效沿延时",而对这个芯片,需要守的是关断沿而不是导通沿:
| 沿方向 | 对 MOSFET 的意义 | 死区该不该守 |
|---|---|---|
| 输出变低(NON 有效) | 管子导通 | 不该 |
| 输出变高(NON 无效) | 管子关断 | 该守的是这里 |
0x88 配置下死区落在导通沿,换向时上下管同时导通,窗口宽度为 2 倍死区时间——空载也直通。修复为 0x99 = 0b10011001(POL0 | DT0 | POL1 | DT1),OUT0 极性反相,死区回到关断沿上。
这一段的经验:用任何死区模块配推挽之前,先写一张真值表——MCU 输出有效电平 → DTM 极性 → 预驱输入有效电平 → MOSFET 状态,逐格推完再上电。死区方向错了比不加死区更危险。
三、极性反相的连锁反应
POL 反相不是改一个寄存器就完事,它牵连了整条软件链路。
3.1 占空比相位反了
反相后应用层给 30% 占空比,实际输出 70%。修复点选在 IoHwAb 层做补偿,而没用预驱配置里的 bInvert 字段——后者是编译期常量,而推挽使能是标定量,两条路径运行期必然失配。补偿挂在 SetDuty/SynOut 钳位之后,上层无感:
if ((IOHWAB_PWM_PUSHPULL_IDX_INVALID != idxPushPullUB)
&& (0U != IoHwAb_Pwm_bPushPullEnUB_CA[idxPushPullUB]))
{
rDutyUW = (uint16)(IOHWAB_PWM_DUTYCYCLE_FULL - rSetUW); /* 反相补偿 */
}
原则一句话:运行期可变的配置,其所有关联逻辑必须走同一条运行期路径,不能一半编译期一半运行期。
3.2 上电窗口:idle 电平从"安全"变成"全开"
MCAL Pwm 通道配置的 Idle state = HIGH,原本无碍;POL 反相后,idle HIGH 经 DTM 反相变成预驱输入低电平 = 上管全开。代码走读确认这版 MCAL(Infineon Pwm_17_GtmCcu6 20.0.0)在 Pwm_Init 末尾就通过 host trigger 使能 TOM 输出,于是从 CDD 写入 POL 配置到 IoHwAb 初始化把占空比压到"恒关断"之间,存在一段毫秒级窗口,高边不受控导通。
临时缓解:IoHwAb 初始化末尾对推挽高边强制写 0%(经补偿后恰为恒关断)。根治:MCAL 配置里把这几个通道的 Idle state 从 HIGH 改成 LOW。给功率级做极性反相,idle 态安全分析必须跟着重做。
四、一个注释引发的 2.5 倍误差
死区寄存器写的是 tick 数,代码设 400,注释标着 /* 100MHz */,照算是 4 µs。实测死区 10 µs——反推 CMU 时钟实际约 40 MHz,注释是错的。死区时间属于安全参数,时钟树别信注释,以实测或配置推导为准。
五、表象拆解:10 ms burst 不是新 Bug
回头解释那段诡异波形,它是三个机制叠加的合成现象:
- 直通触发 L9945 过流保护锁存,输出被芯片掐掉;
- 软件里 10 ms 周期的异步输出任务不断重写输出控制寄存器,把输出"救"回来;
- 关断段里偶尔的小脉冲,是诊断任务的诊断脉冲,属正常行为。
修掉直通后波形立即恢复正常:高低边互补、死区清晰。经验:示波器上的周期性异常,先拆成"故障本体 + 保护动作 + 周期任务"三层再分析,别把保护动作当故障。

六、推挽输出上电前检查清单
把这次踩过的坑浓缩成一张清单,下次同类工作照着过一遍:
- [ ] 引脚路由/复用配置写完后,读回寄存器验证(位段宽度、移位量)
- [ ] 多通道共享寄存器:全部读-改-写
- [ ] 死区方向:真值表推一遍,确认死区落在关断沿
- [ ] 死区时钟:实测或从时钟树推导,不信注释
- [ ] 极性反相的连带项:占空比相位、idle 安全态、上电窗口
- [ ] 上电窗口:从引脚使能到软件接管之间,功率管处于什么状态?
- [ ] 故障复现后:断电测 MOSFET 的 D-S 电阻,直通过的管子可能已带伤
平台:AURIX TC38x(GTM TOM0 + CDTM0 DTM0)+ ST L9945,自研类 AUTOSAR BSW。记录于 2026-10。




