ARTICLE DETAIL

资讯详情

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

DSP28335 ePWM模块详解:从时基配置到死区与故障保护

DSP28335 ePWM模块详解:从时基配置到死区与故障保护 简介面向电机控制与电力电子领域开发者这份基于TI TMS320F28335的PWM波生成代码以工程包形式提供完整的脉宽调制实现方案适用于直流调速、电机驱动、电源转换等典型场景。资源共152个文件、压缩后约565KB其中C源文件、头文件与汇编文件负责核心控制逻辑CCS工程配置文件、链接脚本.lkf、编译日志等则完整记录了开发环境构建与调试过程便于导入Code Composer Studio直接查看和修改。目前已有1939人学习配套的个人空间讲解博客对代码原理、外设初始化流程、周期和占空比配置步骤及常见排错方法进行了细化说明。对需要熟悉C28x内核DSP开发、理解PWM机制的中高级嵌入式工程师而言这份可编译的工程能帮助快速上手并规避初期配置误区。 第一次接触DSP28335的人打开技术手册看到EPwm章节的时候基本都会有点发怵。TBCLK、TBPHS、AQCTLA、DBCTL、TZSEL……满屏寄存器缩写像在看天书。但如果你真正写过一个产生PWM波的工程回头再看这些寄存器其实就是一套流水线时基负责数数比较寄存器负责在特定时刻发指令动作模块负责把指令翻译成引脚电平后面的死区和故障保护模块都是在给这套逻辑加保险。这篇内容就是把我用得比较顺手的这套配置拆开讲适合刚拿到DSP28335开发板、想确认跑到电机驱动器和开关电源上的PWM波形到底怎么出来的朋友。我会直接给你一套能用的代码再把几个容易卡住的地方讲透尤其是死区、故障保护以及调试时“明明配置了却没波形”的坑。1. F28335的ePWM和单片机定时器PWM的差异为什么要单独看它很多人是从STM32转过来的觉得PWM不就是定时器比较输出嘛F28335折腾出这么多子模块是不是故意增加学习成本。我的结论是这套设计不是为了难为人而是为了在高速电机控制和数字电源场景下把“产生PWM”这件事做得足够可靠。1.1 6组ePWM模块的资源分配DSP28335内部有6组完整的ePWM模块分别是ePWM1到ePWM6每组输出两路信号ePWMA和ePWMB对应的引脚是GPIO0到GPIO11。每组模块都是独立的意味着你可以同时做出12路PWM而且这12路PWM可以通过同步信号SYNCI和SYNCO串联起来实现多路波形之间的相位管理。这跟普通单片机定时器最大的区别在于它不仅输出固定频率和占空比还允许你在运行中动态调整每一路的相位、死区时间、故障响应方式。比如三相逆变器需要六路PWM三组ePWM各输出一对互补波形靠软件同步信号保证三相互差120度这套机制在F28335上是原生支持的。1.2 每个ePWM模块内部的7个子模块每组ePWM又分成了几个部分按信号流向排是时基TBTime-Base产生计数时钟决定PWM周期。计数比较CCCounter Compare设定比较值与计数器比较后产生事件。动作限定AQAction Qualifier把比较事件映射成引脚置高、置低或翻转。死区DBDead-Band在互补输出之间插入延时防止上下桥臂直通。斩波PCPWM Chopper在高频载波上调制适合脉冲变压器驱动场景。故障保护TZTrip-Zone收到外部故障信号后强制PWM输出到安全电平。事件触发ETEvent-Trigger产生中断或触发ADC采样实现“PWM周期中点同步采样”。初学者最需要先掌握的是TB、CC、AQ这三个模块因为频率和占空比就是这三者算出来的。死区DB和故障保护TZ则是工程化必不可少的部分后面单独讲。斩波PC平时用得少很多人的代码里甚至不会碰它你只要知道存在这个功能就行。2. 时基和动作子模块先算清楚频率和占空比我见过不少人拿着官方例程改两个参数结果输出频率和自己预想的差了一倍然后一脸疑惑地来问我。这基本都是没搞懂F28335的时基时钟树是怎么走的。2.1 从主频到TBCLK的换算关系F28335的主频SYSCLKOUT最高可以跑到150MHz。ePWM模块内部有自己的一套分频逻辑公式是TBCLK SYSCLKOUT / (HSPCLKDIV × CLKDIV)其中HSPCLKDIV是高倍分频位可选1、2、4、8、10、14、16等CLKDIV是低倍分频位可选1、2、4、8一直到128。这两个值设置得是否顺手直接决定后面算周期时好不好算。我举一个实际例子。假设SYSCLKOUT为150MHz配置HSPCLKDIV 2、CLKDIV 1则TBCLK 75MHz。这个整数关系在后续计算中非常舒服。如果你要产生20kHz的PWM用递增计数模式Up-Count那么周期寄存器的计算公式是TBPRD 1 TBCLK / fs 75MHz / 20kHz 3750所以TBPRD 3749。如果用增减计数模式Up-Down因为一个完整周期要数两趟从0数到TBPRD再数回0公式变成TBPRD 1 TBCLK / (2 × fs) 75MHz / 40kHz 1875所以TBPRD 1874。这就是为什么同样频率下两种计数模式写进TBPRD的数值刚好差一倍。2.2 CC比较寄存器与占空比的关系占空比由CMPA或CMPB决定。以递增计数模式为例PWM的周期就是计数器从0递增到TBPRD再清零的过程。如果设置TBPRD 3749那么完整周期对应3750个计数时钟。想要50%占空比CMPA就设为1875。计数过程是计数器从0开始递增当它等于CMPA时产生一个比较事件CAU你可以在AQ模块中定义这个事件让引脚输出低电平。之后计数器继续递增到TBPRD然后清零产生ZRO事件零事件你在AQ模块里定义让引脚输出高电平。于是每个周期内0到CMPA是高电平CMPA到周期结束是低电平占空比自然就是CMPA与TBPRD的比值。这里有一个新手容易误解的地方CMPA的数值含义是“比较点出现的时刻”它不是百分比而是具体的计数个数。要得到50%的占空比就要算TBPRD的一半要得到30%就要算3750×30% 1125然后把1125写进CMPA。这个换算关系在动态调占空比的场景里很重要后面代码部分我会再演示。2.3 AQ动作子模块把计数事件翻译成引脚电平AQ模块就是一组动作定义。每个动作由两部分组成触发事件和输出行为。触发事件包括ZRO计数器到零、PRD计数器等于周期值、CAU递增时比较匹配、CAD递减时比较匹配。输出行为包括置高、置低、翻转、不变。一个典型的递增计数PWM动作配置是这样ZRO AQ_SET计数器清零时输出高电平CAU AQ_CLEAR递增计数到CMPA时输出低电平这两条组合在一起就得到了一个标准的PWM波形。AQ模块的灵活性在于你可以为每个事件指定任意动作甚至可以让CAU和CAD配合实现翻转模式——比如在一个周期内递增到CMPA时翻转、递减到CMPA时再翻转得到中心对称的波形。如果你用增减计数模式建议不要只用ZRO和CAU而是考虑CAU和CAD的配合。因为增计数到达CMPA和减计数到达CMPA是两个不同时刻你可以分别指定动作实现中心对称的高电平时段分布在周期中央或两端这取决于你的应用需求。最常见的配置是CAU AQ_CLEAR递增到CMPA时输出低CAD AQ_SET递减到CMPA时输出高这么配置出来的波形高电平部分集中在计数的峰值附近。如果你希望高电平集中在零值附近那就把动作对调CAU置高、CAD清零。这一点很容易搞反建议拿到示波器后先量一下确认波形是你要的那个样子。3. 一套直接抄作业的最小PWM波形代码现在进入正题。下面这份代码我实测过默认输出约20kHz、50%占空比的PWM波形引脚是GPIO0和GPIO1对应ePWM1A和ePWM1B。工程基于TI的DSP2833x外设库这也是目前大多数开发板默认的工程框架。3.1 初始化函数的完整代码#include DSP2833x_Device.h #include DSP2833x_Examples.h void InitEPwm1(void) { EALLOW; // 将GPIO0和GPIO1复用为ePWM1A、ePWM1B GpioCtrlRegs.GPAMUX1.bit.GPIO0 1; GpioCtrlRegs.GPAMUX1.bit.GPIO1 1; EDIS; // 时基子模块20kHz递增计数模式 EPwm1Regs.TBPRD 3749; // 周期值75MHz / 20kHz - 1 EPwm1Regs.CMPA.half.CMPA 1875; // 50% 占空比 EPwm1Regs.CMPB 0; EPwm1Regs.TBPHS.half.TBPHS 0; // 相位偏移为0 EPwm1Regs.TBCTR 0; // 计数器清零 EPwm1Regs.TBCTL.bit.CTRMODE TB_COUNT_UP; // 递增计数 EPwm1Regs.TBCTL.bit.PHSEN TB_DISABLE; // 禁止相位加载 EPwm1Regs.TBCTL.bit.HSPCLKDIV TB_DIV2; // 150MHz / 2 75MHz EPwm1Regs.TBCTL.bit.CLKDIV TB_DIV1; // 75MHz / 1 75MHz EPwm1Regs.TBCTL.bit.PRDLD TB_SHADOW; // 周期值使用影子加载 // 动作子模块ZRO时置高CAU时清零 EPwm1Regs.AQCTLA.bit.ZRO AQ_SET; EPwm1Regs.AQCTLA.bit.CAU AQ_CLEAR; // B路输出与A路反相先用AQ模块做一次最简单的互补 EPwm1Regs.AQCTLB.bit.ZRO AQ_CLEAR; EPwm1Regs.AQCTLB.bit.CAU AQ_SET; }这段代码里有个小细节值得解释AQCTLB我直接做了“ZRO清零、CAU置高”这样在GPIO1上能看到一个和A路反相的波形占空比也是50%。如果你的应用只是单路PWMGPIO1不用管保持不配置即可。3.2 主函数里如何初始化并动态调占空比在主函数里你只需要调用InitEPwm1()。但要注意F28335的ePWM时钟默认不是全开的在InitSysCtrl()里需要先把对应外设时钟打开。很多例程在InitPeripheralClocks()里已经统一处理了如果你是自己搭的工程记得确认这一句SysCtrlRegs.PCLKCR0.bit.EPWM1ENCLK 1;没有这个时钟使能后面写再多寄存器都不会有波形输出这是新手排查时最容易忽略的一个点。动态调占空比可以直接对CMPA赋值。比如在一个循环里让占空比从10%到90%来回变while (1) { for (int i 375; i 3375; i 10) { EPwm1Regs.CMPA.half.CMPA i; DELAY_US(1000); } }这里375对应10%占空比3375对应90%占空比。CMPA默认是影子加载模式也就是当前周期结束后再更新到实际比较寄存器不会在周期中间跳变这个特性对电机控制非常友好。如果你需要立即生效可以把CMPA的加载模式改成立即加载模式但我建议一般不要这么干周期中途突跳很容易在电机上产生顿挫。3.3 为什么这套代码选择递增计数而不是增减计数我在这套入门代码里用的是递增计数模式而不是很多人听到PWM就想到增减计数。原因很简单递增计数模式逻辑直观占空比计算不需要考虑“来回数两趟”的问题对初学者是最友好的起点。等你把基本波形跑出来后再切换到增减计数模式做中心对称调制那时候你已经有能力通过调整AQ动作来保证波形方向正确了。4. 死区与故障保护热搜里反复出现的DB和TZ到底怎么配公众号后台搜索日志里关于“dbfed”“dbred”“pwm死区”的问题特别多。这很正常因为死区模块一旦配错轻则波形占空比失真重则上下桥臂直通烧管子。我在这里把这块拆开讲。4.1 死区时间的计算与DBCTL寄存器设置死区模块的输入源是AQ模块产生的原始PWM信号输出源是经过上升沿延时DBRED和下降沿延时DBFED处理后的信号。死区时间不是直接填“2us”这种数值而是填入一个延时计数个数换算关系是死区时间 延时计数个数 / TBCLK还是以TBCLK 75MHz为例如果你想要2us的死区那么延时计数个数 2us × 75MHz 150。所以EPwm1Regs.DBRED 150; EPwm1Regs.DBFED 150;死区主源选择是很多人卡住的地方。DBCTL寄存器里的IN_MODE位决定上升沿延时和下降沿延时各取自哪一路信号。常见的做法是上升沿和下降沿都取A路信号然后通过DBRED和DBFED分别给上升沿和下降沿延时EPwm1Regs.DBCTL.bit.IN_MODE 0x0; // 上升沿和下降沿都来自A路 EPwm1Regs.DBCTL.bit.POLSEL 0x1; // B路输出反相形成互补 EPwm1Regs.DBCTL.bit.OUT_MODE 0x3; // DBA和DBB都启用死区模块在电机驱动场景下这种配置可以保证同一桥臂的上下管之间先关断后导通留出一段安全间隔。如果换用边沿分离模式上升沿用A路信号、下降沿用B路信号就需要额外确保A和B之间有明确的关系否则可能出现死区时间忽大忽小的问题。我建议新手先固定用IN_MODE 0x0等你能在示波器上看到两路互补波形并理解死区发生了什么再尝试其他组合。4.2 故障保护TZ子模块的工程意义故障保护模块是F28335 PWM真正“硬核”的地方。当外部出现过流、过压信号时TZ模块可以在硬件层面直接强制PWM输出到安全电平不需要CPU中断响应。这点在电机驱动上极其重要因为中断响应通常有几微秒的延迟而硬件动作可以在一个时钟周期级别完成。TZ模块的配置分为三步选择故障输入源TZSEL寄存器决定TZ1到TZ6哪些有效。确定故障动作TZCTL寄存器故障时强制输出高、低还是高阻。清除锁存状态TZCLR寄存器写完手动清零让PWM恢复输出。典型配置代码如下EPwm1Regs.TZSEL.bit.OSHT1 1; // TZ1作为一次性短路锁存故障源 EPwm1Regs.TZCTL.bit.TZA TZ_FORCE_LO; // A路故障时强制低电平 EPwm1Regs.TZCTL.bit.TZB TZ_FORCE_LO; // B路故障时强制低电平 EPwm1Regs.TZCLR.bit.OST 1; // 清除故障锁存标志配置完后GPIO12对应TZ1每次拉低ePWM1A和ePWM1B都会被强制拉低。这种机制在做H桥和三相桥时非常必要相当于给驱动器上了一道硬件保险。有一点要特别提醒F28335的GPIO12到GPIO17默认是TZ输入引脚如果你把它们当普通GPIO用却没有在GPAMUX/GPBMUX里正确配置复用它们的默认状态可能会影响TZ模块的判断。我遇到过一种情况TZ引脚悬空由于内部无上拉导致电平漂移PWM偶尔“神秘”丢失。后来强制给TZ引脚配置内部上拉并把TZ功能禁用问题才消失。4.3 增减计数模式与中心对称波形的动作配置如果你要做逆变器这类需要中心对称PWM的项目可以把第3节的计数模式改成TB_COUNT_UPDOWN然后把动作子模块调整为EPwm1Regs.AQCTLA.bit.CAU AQ_CLEAR; EPwm1Regs.AQCTLA.bit.CAD AQ_SET;这种配置下计数器递增到CMPA时输出低递减到CMPA时输出高高电平位于计数波形的峰值之间。需要再次提醒的是在增减计数模式下CMPA同样的数值对应的实际占空比计算方式与递增计数不同因为周期值变成了2×TBPRD个时钟。这个差异很多人栽过跟头改模式前务必先算一遍。5. 我在调试PWM波形时踩过的几个坑5.1 用CCS直接观察内部波形的方法没有示波器的时候CCS本身也能帮助确认PWM配置是否正确。在Debug视图下点击菜单Tools → Graph选择Single Time或Dual Time把显示变量填成EPwm1Regs.TBCTR和EPwm1Regs.CMPA.half.CMPA刷新后能看到计数器和比较值的实时曲线。这个功能虽然看不到引脚电平但可以确认计数模式、周期和比较值是否符合预期尤其适合在硬件还没接好时先做软件验证。多路PWM调制时还可以把多个模块的TBCTR同时添加进Graph的数组里用Dual Time对比它们的相位关系。比如想确认ePWM1和ePWM2是否相差120度把TBPHS设置好之后这个Graph工具是最快的验证手段。5.2 引脚复用与时钟使能问题这是找我咨询最多的两类问题。第一种是代码初始化了ePWM1但GPIO0没有配置复用结果引脚始终是普通IO状态输出自然没有波形。第二种是PCLKCR0中EPWM1ENCLK没置1寄存器写入不生效程序跑完了但外设根本没工作。排查顺序建议这样先确认SysCtrlRegs.PCLKCR0里对应位是1再用CCS Graph看TBCTR是否在计数最后检查GPAMUX1寄存器里GPIO0对应的位是1。这三个环节全部正常后基本可以排除软件配置问题剩下的就是看测量方式了。5.3 测量问题带来的错觉有几次我被“PWM没波形”折腾了半天最后发现示波器探头地线没夹好或者探头衰减比设置错误。尤其是在用长接地夹线测量高频PWM时波形上会出现大量振铃和毛刺看起来像配置错了实际上只是测量环路太大引入了干扰。建议测量F28335的PWM波形时探头尽量用短接地弹簧不要用长地线夹。如果板子上的PWM信号经过了电平转换芯片也需要注意转换芯片的延迟和驱动能力——我遇到过电平转换芯片驱动能力不足导致输出波形上升沿变缓PWM频率高一点就畸形了。另外还碰到过一个关于Flash运行速度的问题如果你把PWM初始化代码放在Flash里跑Flash的等待周期会让某些精确到微秒级的时序初始化变慢。这个问题对PWM本身影响不大但如果你在初始化后紧接着做ADC同步采样偶尔会出现采样点偏移。建议把PWM初始化函数复制到RAM中执行F28335的例程框架里提供了memcpy函数常见做法是memcpy(PwmInitRamFuncs, PwmInitFlashFuncs, (Uint32)PwmInitRamFuncs_size);这个习惯在工程化项目里是标配尤其是涉及上电瞬间时序控制时。最后再分享一个实用小技巧调试死区参数时不要直接设目标值而是先设一个很大的死区比如5us以上在示波器上把两路波形的死区分明看得清清楚楚后再逐步往小调。这样既能直观理解死区的工作方式也能避免因为死区过小而看不出来“实际是否生效”。等你熟练掌握这套流程后回头再看DSP28335的ePWM你会觉得它其实是一套逻辑非常清晰的状态机只不过状态多一些、命名怪一些罢了。本文还有配套的精品资源点击获取
返回列表