资讯动态

TMS320F280049C ePWM死区控制详解:寄存器配置与完整代码实现

发布时间:2026/9/28 17:22:55 来源:尧图企业网站定制
1. 项目缘起与核心需求拆解1.1 为什么死区控制是电力电子开发绕不开的坎做电机驱动或者数字电源的兄弟应该都有体会半桥、全桥拓扑里最怕的就是上下管直通。一旦直通母线电压瞬间被短路轻则炸管重则连驱动芯片一起带走。死区时间就是在这个背景下必须插入的一段“安全间隙”——在关断一个管子之后延迟一小段时间再开通另一个管子确保同一桥臂不会同时导通。TMS320F280049C这颗芯片在实时控制领域用得非常多它属于C2000系列里的Piccolo阵营主频100MHz带FPU和TMUePWM模块的灵活度相当高。它的死区控制不是简单给个固定延时而是可以分别配置上升沿和下降沿的延迟还能选择输入源是来自ePWMxA还是ePWMxB甚至支持半周期使能。这些细节如果没吃透配置出来的波形要么死区不够要么白白损失占空比精度。我见过不少项目死区时间靠硬件RC电路凑合结果温度一变、批次一换参数就飘了。用F280049C的ePWM死区子模块可以把死区时间做到纳秒级精度而且完全由寄存器控制不随环境漂移。这篇文章就把我在实际项目里配置死区控制的完整思路和代码整理出来从寄存器位域到示波器验证一步步说清楚。1.2 目标读者与前置知识这篇内容适合已经上手过C2000系列、至少点亮过ePWM输出方波的开发者。如果你还没接触过F280049C的时钟树和GPIO复用建议先把系统时钟和EPWM引脚配置跑通再来看死区部分。代码基于TI的driverlib库编写寄存器操作和库函数调用会混着讲因为有些细节库函数封装得不够直观直接看寄存器反而更清楚。核心关键词TMS320F280049C、PWM死区控制、ePWM、完整代码。全文的代码都可以直接复制到CCS工程里编译运行硬件平台以LaunchPad F280049C为参考其他板子只需改引脚映射。2. ePWM死区子模块的架构与选型逻辑2.1 死区子模块在ePWM里的位置F280049C的每个ePWM模块由多个子模块组成时基TB、计数比较CC、动作限定AQ、死区DB、PWM斩波PC、事件触发ET、数字比较DC等。死区子模块处在动作限定之后、斩波之前也就是说AQ决定了EPWMxA和EPWMxB的原始翻转时刻DB再对这个信号做延迟处理。这个顺序很关键。如果你在AQ里配置的是EPWMxA在CMPA匹配时置高、EPWMxB在CMPA匹配时置低那么DB收到的就是一对互补信号。DB的任务就是把这对信号的上升沿各自延迟一段时间延迟量由DBRED和DBFED两个寄存器分别控制。2.2 为什么选择分别配置上升沿和下降沿延迟很多应用里上下管的开关特性不一样。比如上管用PMOS或者带自举的NMOS驱动关断速度比开通速度快下管可能用的是不同型号的管子。如果死区时间只给一个固定值可能出现一种情况上管已经可靠关断了下管还在等死区结束才开通白白损失效率或者反过来下管还没完全关断上管就开通了直通风险还在。F280049C的DB子模块允许RED和FED独立配置这就给了精细调节的空间。我的习惯是先用示波器测出上下管栅极波形的实际关断延迟然后RED取上管关断时间加上裕量FED取下管关断时间加上裕量。裕量一般取20%到50%看驱动电路的离散程度。2.3 输入源选择EPWMxA还是EPWMxBDB子模块的输入可以选择来自AQ的EPWMxA信号或者EPWMxB信号由DBCTL寄存器的IN_MODE位控制。这个选择决定了死区是怎么“插入”的。如果选EPWMxA作为输入源那么EPWMxA的上升沿被RED延迟EPWMxB的上升沿被FED延迟两者互补关系由AQ保证。如果选EPWMxB作为输入源逻辑类似但极性相反。大多数互补PWM应用选EPWMxA作为输入源就够了。但有一种情况需要注意如果你在AQ里配置的是“EPWMxA和EPWMxB都独立动作”而不是互补动作那么DB的行为会变得复杂。我建议在死区配置阶段AQ里就配成互补输出DB只负责延迟这样逻辑最清晰。3. 寄存器级配置详解与参数计算3.1 死区时间与寄存器值的换算F280049C的ePWM时钟来自系统时钟经过分频。假设系统时钟100MHzEPWMCLK不分频那么ePWM的时钟周期是10ns。死区寄存器DBRED和DBFED是10位宽最大值1023。实际死区时间 寄存器值 × TBCLK周期。举个例子要设置500ns的死区时间TBCLK周期10ns那么寄存器值 500ns / 10ns 50。这个计算看起来简单但实际项目里TBCLK可能被HS时钟分频器进一步分频所以一定要先确认CLKDIV和HSPCLKDIV的值。我一般会在代码里定义一个宏#define EPWM_CLK_FREQ 100000000UL #define TBCLK_FREQ (EPWM_CLK_FREQ / (EPWM_CLK_DIV * HSP_CLK_DIV)) #define DEADTIME_NS_TO_TICKS(ns) ((uint16_t)((ns) * TBCLK_FREQ / 1000000000UL))这样改时钟配置的时候死区时间不用重新算。3.2 DBCTL寄存器的关键位域DBCTL寄存器控制死区子模块的整体行为几个关键位OUT_MODE决定死区子模块的输出模式。00表示禁用死区01表示使能死区但EPWMxA和EPWMxB都来自输入源10表示使能死区且EPWMxB取反11表示使能死区且EPWMxA取反。互补PWM通常用10。POLSEL极性选择。00表示不翻转01表示EPWMxA翻转10表示EPWMxB翻转11表示两者都翻转。这个位在驱动负逻辑栅极的MOSFET驱动芯片时会用到。IN_MODE输入源选择。0表示来自EPWMxA1表示来自EPWMxB。我踩过的一个坑POLSEL配错了结果示波器上看波形完全反了上下管同时导通。后来发现是驱动芯片是低电平有效但POLSEL没翻转。所以配置完一定要先用示波器看原始波形再接功率级。3.3 死区时间的计算实例假设项目需求母线电压400VMOSFET开通延迟80ns关断延迟120ns驱动电路传输延迟50ns。要求死区时间至少覆盖最坏情况下的关断延迟差。最坏情况一个管子关断需要120ns50ns170ns另一个管子开通需要80ns50ns130ns。死区时间必须大于170ns-130ns40ns但实际要留裕量。我取200ns作为RED和FED的基础值再根据上下管差异微调。如果TBCLK10nsRED20FED20对应200ns。如果上管关断更慢RED可以加到25对应250ns。4. 完整代码实现与逐行解析4.1 工程结构与依赖代码基于TI的C2000Ware driverlib工程里需要包含driverlib.lib和对应的头文件。我用的版本是C2000Ware 4.00.00.00不同版本API可能有细微差异但寄存器定义是一致的。主程序流程初始化系统时钟→配置GPIO为ePWM功能→配置ePWM时基→配置动作限定→配置死区→使能输出→循环。4.2 系统时钟与GPIO配置#include driverlib.h #include device.h #define EPWM1_GPIO_A 0 #define EPWM1_GPIO_B 1 void initSystemClock(void) { Device_init(); Device_initGPIO(); Interrupt_initModule(); Interrupt_initVectorTable(); // 配置EPWM1A和EPWM1B引脚 GPIO_setPadConfig(EPWM1_GPIO_A, GPIO_PIN_TYPE_STD); GPIO_setPinConfig(GPIO_0_EPWM1A); GPIO_setDirectionMode(EPWM1_GPIO_A, GPIO_DIR_MODE_OUT); GPIO_setPadConfig(EPWM1_GPIO_B, GPIO_PIN_TYPE_STD); GPIO_setPinConfig(GPIO_1_EPWM1B); GPIO_setDirectionMode(EPWM1_GPIO_B, GPIO_DIR_MODE_OUT); }这里GPIO的复用配置很关键。F280049C的引脚复用表里GPIO0可以配成EPWM1AGPIO1配成EPWM1B。如果配错了示波器上什么波形都没有。我建议先用GPIO_setPinConfig把复用写对再用GPIO_setDirectionMode设成输出。4.3 ePWM时基与动作限定配置void initEPWM1(void) { // 时基配置增减计数模式周期1000 EPWM_setTimeBasePeriod(EPWM1_BASE, 1000); EPWM_setPhaseShift(EPWM1_BASE, 0); EPWM_setTimeBaseCounter(EPWM1_BASE, 0); EPWM_setTimeBaseCounterMode(EPWM1_BASE, EPWM_COUNTER_MODE_UP_DOWN); EPWM_setClockPrescaler(EPWM1_BASE, EPWM_CLOCK_DIVIDER_1, EPWM_HSCLOCK_DIVIDER_1); // 动作限定CMPA匹配时EPWMxA置高CMPB匹配时EPWMxA置低 EPWM_setActionQualifierAction(EPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_HIGH, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA); EPWM_setActionQualifierAction(EPWM1_BASE, EPWM_AQ_OUTPUT_A, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPA); // EPWMxB与EPWMxA互补 EPWM_setActionQualifierAction(EPWM1_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_LOW, EPWM_AQ_OUTPUT_ON_TIMEBASE_UP_CMPA); EPWM_setActionQualifierAction(EPWM1_BASE, EPWM_AQ_OUTPUT_B, EPWM_AQ_OUTPUT_HIGH, EPWM_AQ_OUTPUT_ON_TIMEBASE_DOWN_CMPA); // 比较值 EPWM_setCounterCompareValue(EPWM1_BASE, EPWM_COUNTER_COMPARE_A, 500); EPWM_setCounterCompareValue(EPWM1_BASE, EPWM_COUNTER_COMPARE_B, 500); }增减计数模式下CMPA在上升沿和下降沿各匹配一次所以EPWMxA会在一个周期内产生一个脉冲。EPWMxB取反形成互补。这里CMPA和CMPB都设成500占空比50%。实际项目里CMPA和CMPB可以不同用来做移相或者不对称PWM。4.4 死区子模块配置void initDeadBand(void) { // 死区时间200nsTBCLK10ns寄存器值20 uint16_t deadband_ticks 20; // 配置DBCTL使能死区EPWMxB取反输入源EPWMxA EPWM_setDeadBandCounterClock(EPWM1_BASE, EPWM_DB_COUNTER_CLOCK_FULL_CYCLE); EPWM_setRisingEdgeDelayCount(EPWM1_BASE, deadband_ticks); EPWM_setFallingEdgeDelayCount(EPWM1_BASE, deadband_ticks); // 关键设置死区输出模式和极性 EPWM_setDeadBandOutputMode(EPWM1_BASE, EPWM_DB_OUTPUT_MODE_B_INVERTED); EPWM_setDeadBandPolarity(EPWM1_BASE, EPWM_DB_POLARITY_ACTIVE_HIGH); EPWM_setDeadBandInputMode(EPWM1_BASE, EPWM_DB_INPUT_EPWMA); // 使能死区 EPWM_enableDeadBand(EPWM1_BASE); }EPWM_setDeadBandCounterClock这个函数容易被忽略。它决定死区计数器是用全周期还是半周期时钟。全周期模式下死区分辨率是TBCLK周期半周期模式下分辨率翻倍但最大死区时间减半。我一般用全周期因为死区时间通常不需要那么高的分辨率。EPWM_setDeadBandOutputMode的参数EPWM_DB_OUTPUT_MODE_B_INVERTED对应DBCTL的OUT_MODE10这是互补PWM的标准配置。4.5 使能输出与主循环void main(void) { initSystemClock(); initEPWM1(); initDeadBand(); // 使能EPWM输出 EPWM_enableOutput(EPWM1_BASE); EPWM_setEmulationMode(EPWM1_BASE, EPWM_EMULATION_FREE_RUN); while(1) { // 主循环可以空转或者做其他任务 } }EPWM_setEmulationMode设成FREE_RUN这样仿真器暂停CPU时PWM继续输出方便调试。如果设成STOP暂停时PWM会停功率级可能进入异常状态。5. 示波器验证与常见问题排查5.1 验证步骤与波形判读代码烧进去之后先把示波器两个通道分别接EPWM1A和EPWM1B。正确的波形应该是EPWM1A高电平期间EPWM1B低电平两者之间有明显的死区间隙。死区间隙在上升沿和下降沿各出现一次。用示波器的光标功能测量死区时间。如果测出来是200ns左右说明配置正确。如果测出来是0检查DBCTL的OUT_MODE是不是配成了00。如果测出来是400ns可能是RED和FED都加了但实际只需要一个方向。我遇到过一种情况死区时间测出来是100ns但代码里设的是200ns。后来发现TBCLK被HSPCLKDIV分频了实际TBCLK周期是20ns所以寄存器值20对应400ns不对这里要仔细算。如果TBCLK20ns寄存器值20对应400ns但测出来100ns说明还有别的问题。最后发现是示波器探头衰减设错了1X探头当10X用了。5.2 常见问题速查表现象可能原因排查方法无波形输出GPIO复用未配置检查GPIO_setPinConfig死区时间为0DBCTL OUT_MODE00读DBCTL寄存器死区时间翻倍RED和FED都使能确认只需一个方向延迟波形极性相反POLSEL配置错误检查驱动芯片极性死区时间不稳定TBCLK被分频确认CLKDIV和HSPCLKDIV上下管同时导通死区时间不足增大RED/FED值5.3 实操心得与避坑技巧第一个坑DBRED和DBFED寄存器在增减计数模式下上升沿和下降沿的延迟是分别作用的。如果你在AQ里配置的是EPWMxA在CMPA上升沿置高、下降沿置低那么RED延迟的是上升沿FED延迟的是下降沿。但EPWMxB是取反的所以它的上升沿对应EPWMxA的下降沿延迟量是FED。这个对应关系一定要搞清楚否则死区加反了。第二个坑死区时间不是越大越好。死区期间负载电流通过体二极管续流会产生额外的导通损耗。死区越大损耗越高输出波形畸变也越严重。我一般把死区时间控制在开关周期的1%到2%之间。比如20kHz开关频率周期50us死区取500ns到1us。第三个坑如果用了PWM斩波模块死区子模块和斩波模块的先后顺序会影响最终波形。F280049C里DB在PC之前所以斩波是在死区之后进行的。如果斩波频率和死区时间有交互需要一起调试。6. 进阶应用死区与ADC触发联动6.1 用ePWM触发ADC采样的时机选择在很多电机控制方案里ADC采样时刻和PWM死区有直接关系。如果采样时刻落在死区期间采到的电流是续流二极管上的电流不是真实的负载电流。所以ADC触发点要避开死区。F280049C的ePWM事件触发子模块可以配置在计数器等于0或者等于周期值时产生ADC启动信号。我通常把触发点设在计数器过零点这时候上下管都处于关断状态电流通过二极管续流采样的是续流电流。如果要采负载电流触发点要设在PWM有效电平的中间时刻。6.2 配置代码示例void initADCTrigger(void) { // ADC触发源选择EPWM1 EPWM_setADCTriggerSource(EPWM1_BASE, EPWM_SOC_A, EPWM_SOC_TBCTR_ZERO); EPWM_setADCTriggerEventPrescale(EPWM1_BASE, EPWM_SOC_A, 1); EPWM_enableADCTrigger(EPWM1_BASE, EPWM_SOC_A); }这段代码把ADC触发点设在计数器过零。实际项目里我会用示波器同时看PWM波形和ADC启动信号确认触发点避开了死区。6.3 死区对ADC采样精度的影响死区期间桥臂中点电压由负载电流方向决定可能高于或低于母线中点。如果ADC采样时刻落在死区里采到的电压和实际负载电压有偏差。这个偏差在低速轻载时尤其明显因为死区占比相对较大。我的做法是在代码里根据电流方向动态调整死区补偿量。电流为正时实际输出电压比指令值低一个死区误差电流为负时实际输出电压比指令值高一个死区误差。补偿量 死区时间 / PWM周期 × 母线电压。这个补偿在电流过零附近要平滑过渡否则会引起振荡。7. 工程化建议与参数整定流程7.1 死区时间的整定步骤第一步查MOSFET数据手册找到开通延迟td_on和关断延迟td_off。第二步查驱动芯片数据手册找到传输延迟tpd。第三步计算最坏情况下的关断时间差Δt (td_off tpd) - (td_on tpd) td_off - td_on。第四步死区时间 Δt × 1.5。第五步用示波器验证如果波形有直通迹象继续增大。这个流程看起来简单但实际项目里MOSFET的td_off会随温度和电流变化。我一般会在高温满负载条件下再测一次取最坏值。7.2 代码的可移植性设计不同项目的死区时间可能不同所以我把死区配置封装成函数参数是纳秒值void setDeadBand_ns(uint32_t ns) { uint16_t ticks (uint16_t)((uint64_t)ns * TBCLK_FREQ / 1000000000ULL); if(ticks 1023) ticks 1023; EPWM_setRisingEdgeDelayCount(EPWM1_BASE, ticks); EPWM_setFallingEdgeDelayCount(EPWM1_BASE, ticks); }这样换项目的时候只需要改一个宏定义不用重新算寄存器值。7.3 调试记录与版本管理死区参数是功率级调试的核心参数之一我建议每次修改都记录在案日期、死区值、母线电压、负载条件、波形截图。用Git管理代码的时候死区参数单独放在一个头文件里方便对比不同版本。我现在的习惯是每个功率板第一次上电先用低压小电流测试死区波形确认无误再逐步升压。这个流程救过我好几次有一次死区配错了低压测试时发现上下管同时导通及时改了参数避免了一次炸管。8. 个人实操体会与后续扩展方向死区控制这件事说到底是和功率器件的开关特性打交道。F280049C给的寄存器配置很灵活但灵活意味着容易配错。我的经验是先把AQ和DB的配合关系画成时序图标清楚每个边沿的延迟方向再写代码。代码写完先用示波器看波形不要急着接功率级。后续如果要做更精细的控制可以考虑用ePWM的数字比较子模块做逐周期死区调整根据负载电流实时改变死区时间。F280049C的DC子模块支持这种操作但配置复杂度会高不少。另外如果项目里有多路ePWM死区配置可以做成一个通用函数传入ePWM基地址和死区时间批量初始化。代码里用到的driverlib函数在不同C2000Ware版本里可能有差异如果编译报错先查头文件里的函数原型。寄存器操作是稳定的库函数只是封装。实在找不到对应函数直接操作DBCTL、DBRED、DBFED寄存器也能实现效果一样。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑