news 2026/9/12 11:06:43

用TMS320F28335实现SPWM:从ePWM到正弦查表全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用TMS320F28335实现SPWM:从ePWM到正弦查表全解析

简介:针对TI TMS320F28335浮点DSP的正弦脉宽调制(SPWM)生成工程,完整源码包支持直接导入CCS开发环境。资源面向电机驱动、逆变器及电源控制方向的工程师和嵌入式学习者,解决在DSP28335上从底层外设配置到SPWM波形输出的完整开发需求。压缩包共71个文件,体积约90KB,包含22个C源文件、6个汇编文件、4个makefile、3个cmd链接脚本及CCS工程配置文件、自定义头文件等,DSP2833x标准外设库与用户代码分离,目录结构清晰。目前已有316人学习下载。工程内不仅提供DSP2833x系列外设驱动源码(如EPwm、Adc、Gpio等),还封装了my_function.h、variable_define.c等自定义模块,便于直接调用和二次修改。代码中针对EPWM定时器进行初始化,可灵活配置载波频率与占空比,帮助开发者深入理解SPWM实现原理;同时附带readme.txt说明和编译链接脚本,可作为DSP28335 SPWM实战项目的高质量参考模板。

1. 用 F28335 做 SPWM,先想清楚是「生成」还是「调制」

手上一份SPWM.zip,解压后是 TMS320F28335 的完整 CCS 工程:spwm.cmy_function.cvariable_define.c加上 TI 标准库DSP2833x_*.c。如果你正在做变频器、异步电机交流调速系统这类 SPWM 生成任务,很容易误以为 SPWM 是在中断里一个点一个点软件输出波形。实际上 F28335 上真正生成 SPWM 的硬件是 ePWM 模块的时基、比较、动作限定和死区单元,CPU 要做的只是周期性更新 CMPA。这个压缩包的价值在于它不是孤立算法,而是一套能直接跑起来的外设配置骨架。这篇文章从 ePWM 计数机制讲起,把查表生成、死区设置、调制度和载波比逐一拆开,最后给出验证波形时避开坑的具体做法。

2. ePWM 时基与动作限定是 SPWM 成波的硬件地基

2.1 时基 TBPRD 与载波频率的换算

F28335 的 ePWM 时基时钟TBCLK默认来自 150MHz 系统时钟,通过TBCTL里的CLKDIVHSPCLKDIV两级分频得到。我一般把两个分频都设为 1,TBCLK=150MHz,这样时基分辨率最高,死区时间也能算得最准。选择增减计数模式后,计数器从 0 增长到TBPRD再递减回 0,形成三角载波,载波频率写为:

f_PWM = TBCLK / (2 * TBPRD)

TBPRD=3750为例,周期是2 * 3750 / 150MHz = 50us,对应 20kHz。20kHz 是 IGBT 逆变器和伺服驱动器最常用的开关频率,工程里spwm.c的初始化参数大多围绕这个频率设计。

目标载波频率TBPRD(TBCLK=150MHz,增减计数)典型应用
40kHz1875小功率 MOSFET 驱动
20kHz3750IGBT 逆变器、通用变频器
10kHz7500中功率电机控制,降低开关损耗
5kHz15000大功率低频场景,噪声明显
EPwm1Regs.TBCTL.bit.CTRMODE = TB_COUNT_UPDOWN; // 增减计数,三角载波 EPwm1Regs.TBPRD = 3750; // TBCLK=150MHz 时载波 20kHz EPwm1Regs.TBCTL.bit.HSPCLKDIV = TB_DIV1; EPwm1Regs.TBCTL.bit.CLKDIV = TB_DIV1; EPwm1Regs.TBCTR = 0; // 计数清零后开始

这里有个容易犯的顺序错误:先改CTRMODE再写TBPRD,否则计数器会按旧边界先跑一个周期,初始化阶段就可能输出一个异常宽脉冲。正确的做法是先设计数模式、写周期值,最后清零TBCTR

2.2 CMPA 与占空比的映射关系

在增减计数模式下,CMPA决定 PWM 上下沿的位置。我习惯增计数到 CMPA 时输出高,减计数到 CMPA 时输出低:

EPwm1Regs.CMPA.bit.CMPA = 1875; // 50% 占空比 EPwm1Regs.AQCTLA.bit.CAD = AQ_SET; // 增计数到 CMPA 置高 EPwm1Regs.AQCTLA.bit.CBD = AQ_CLEAR; // 减计数到 CMPA 拉低

这样高电平宽度等于2 * (TBPRD - CMPA),占空比为D = (TBPRD - CMPA) / TBPRD。所以 CMPA 越小,占空比越大。CADCBD是动作限定子模块的两个关键字段,分别对应增计数和减计数到达比较值时输出引脚的动作。实际 SPWM 里 A 通道做原始调制信号,B 通道通过死区模块生成互补信号。

2.3 影子寄存器:为什么改 CMPA 不会立刻生效

CMPA是双缓冲的,软件写入后先存在影子寄存器,要到指定事件才加载到活动寄存器。常见加载点是归零或周期匹配。对增减计数模式,我建议在归零事件加载,保证一个完整 PWM 周期内 CMPA 恒定,输出没有毛刺。

EPwm1Regs.TBCTL.bit.PRDLD = TB_SHADOW; EPwm1Regs.CMPCTL.bit.SHDWAMODE = CC_SHADOW; EPwm1Regs.CMPCTL.bit.LOADAMODE = CC_CTR_ZERO; // 归零时装载

LOADAMODE=CC_CTR_ZERO是关键。如果改成CC_CTR_IMMEDIATE,脉冲宽度会在一个周期中间跳变,桥臂输出会突然多出一个窄脉冲。这个现象用示波器看就是一个周期突然变窄,容易误判成 CPU 中断卡死。

3. SPWM.zip 工程骨架与 CMPA 更新实现拆解

3.1 工程文件按职责划分

SPWM.zip里的文件很多,但不是每个都要逐行读。我通常把它们分成四组:

分组文件作用
系统启动DSP2833x_SysCtrl.c、DSP2833x_CodeStartBranch.asm、DSP2833x_ADC_cal.asm时钟、看门狗、外设时钟使能
外设驱动DSP2833x_EPwm.c、DSP2833x_Adc.c、DSP2833x_GlobalVariableDefs.c寄存器封装、中断向量初始化
用户算法spwm.c、my_function.c、variable_define.c查表、CMPA 更新、全局变量
链接与调试F28335.cmd、28335_RAM_lnk.cmd、TMS320F28335.ccxml内存分配和调试器配置

DSP2833x_EPwm.c里大部分内容只是 TI 的外设默认配置,真正的 SPWM 策略在my_function.cspwm.c里。variable_define.c则存放查表数组和调制度、载波比等全局变量,方便在 CCS 的 Expression 窗口里实时观察。

3.2 正弦查表生成与最小脉宽限制

工程里没有独立的查表生成脚本,常见做法是在my_function.c里写一个运行时初始化函数,把 400 点正弦表直接算出来。400 点对应 20kHz 载波下的 50Hz 基波,属于同步调制。

#define SPWM_POINTS 400 #define TBPRD_VALUE 3750 float32 spwmDuty[SPWM_POINTS]; uint16 spwmCmp[SPWM_POINTS]; void BuildSpmTable(float32 M) { float32 duty, minDuty = 0.03f; // 最小脉宽 3% for (uint16 i = 0; i < SPWM_POINTS; i++) { duty = 0.5f + 0.5f * M * sinf(2.0f * PI * i / SPWM_POINTS); if (duty < minDuty) duty = minDuty; if (duty > 1.0f - minDuty) duty = 1.0f - minDuty; spwmDuty[i] = duty; spwmCmp[i] = (uint16)(TBPRD_VALUE * (1.0f - duty)); } }

逻辑说明:M是调制度,正弦峰值占空比最大到0.5+0.5MminDuty用来限制最小脉宽,防止输出电压在过零附近出现极窄脉冲,IGBT 来不及完全开通或关断。spwmCmp[i]的转换公式来自前面CMPA = TBPRD * (1 - D),所以占空比越大,CMPA 越小。

参数说明:SPWM_POINTS决定载波比,400 点是 20kHz/50Hz 的结果。如果只做验证不需要同步调制,这个值可以改成 256,但中断频率和基波频率的关系需要重新算。sinf来自 TI 运行库,记得包含math.h

3.3 在 ePWM 中断里更新 CMPA

查表的更新逻辑放在 ePWM 周期中断里,每次取一个点的 CMPA 值写入比较寄存器:

interrupt void epwm1_isr(void) { static uint16 idx = 0; EPwm1Regs.CMPA.bit.CMPA = spwmCmp[idx]; idx++; if (idx >= SPWM_POINTS) idx = 0; EPwm1Regs.ETCLR.bit.INT = 1; // 清中断标志 PieCtrlRegs.PIEACK.all = PIEACK_GROUP3; // 应答 ePWM1 所在 PIE 组 }

中断触发条件配置在初始化代码里:

EPwm1Regs.ETSEL.bit.INTSEL = ET_CTR_ZERO; // 归零事件触发 EPwm1Regs.ETSEL.bit.INTEN = 1; EPwm1Regs.ETPS.bit.INTPRD = ET_1ST; // 每个归零事件都触发 PieCtrlRegs.PIEIER3.all = M_INT1; // 使能 PIE 第 3 组第 1 通道

因为CMPA影子寄存器配置为归零加载,所以中断也选归零触发。进入中断后先写 CMPA,硬件在下一个归零点装载,等于提前一个载波周期准备好数据,输出不会出现半个周期用旧值的情况。如果中断触发条件选了周期匹配,而LOADAMODE还是归零加载,写入的数值要过两个计数方向才生效,相位会比预期滞后。

4. 调制度、载波比和死区在 DSP28335 上的整定方法

4.1 调制度 M 的上限与输出电压

SPWM 调制中,单相桥输出基波电压峰值是M * Vdc / 2;三相异步电机负载上线电压基波有效值约为M * Vdc * sqrt(3) / (2 * sqrt(2))。调制度 M 最大值是 1,但实际往 1 推时,正弦峰值附近的脉冲非常窄,加上死区后等效输出电压会提前饱和。

在线修改调制度的函数很容易写:

void SetModulationIndex(float32 M) { for (uint16 i = 0; i < SPWM_POINTS; i++) { float32 duty = 0.5f + 0.5f * M * sinf(2.0f * PI * i / SPWM_POINTS); spwmCmp[i] = (uint16)(TBPRD_VALUE * (1.0f - duty)); } }

这个函数在运行中调用后,下次进入epwm1_isr就会使用新表。需要注意不能在中断正在查表时从主循环改写数组,否则前半周期用新表、后半周期用旧表,输出电压会出现跳变。常见做法是加一个updatePending标志,主循环重建表后置 1,中断统一切换到新表。

4.2 死区时间计算与寄存器配置

F28335 的 ePWM 死区模块由DBCTLDBREDDBFED三个寄存器控制,计数单位是 TBCLK。TBCLK=150MHz 时,一个计数周期约 6.67ns,寄存器值 150 对应 1us 死区。

EPwm1Regs.DBCTL.bit.IN_MODE = DBA_ALL; // A/B 通道输入相同 PWM EPwm1Regs.DBCTL.bit.POLSEL = DB_ACTV_HI; // 互补输出,高有效 EPwm1Regs.DBCTL.bit.OUT_MODE = DB_FULL_ENABLE; EPwm1Regs.DBRED = 150; // 上升沿延迟 1us EPwm1Regs.DBFED = 150; // 下降沿延迟 1us

DBRED控制上升沿延时,DBFED控制下降沿延时,两者一起形成互补信号的死区。如果输出波形不对称,优先检查这两个值是否写反。另外隔离驱动芯片自身的传输延时也要从死区里扣掉,比如驱动芯片延时 200ns,那寄存器值应减少 30,最终实际死区才等于 1us。

开关器件典型死区DBRED/DBFED(150MHz TBCLK)
小功率 MOSFET200ns ~ 500ns30 ~ 75
20A IGBT 模块1us ~ 3us150 ~ 450
大功率 IGBT3us ~ 5us450 ~ 750

死区过大不仅损失输出电压,还会引入低次谐波,电机电流里会有明显的低频脉动。用电流探头看相电流波形,如果过零点出现阶梯状畸变,基本可以断定是死区补偿没做。

4.3 载波比 N 用整数还是小数步进

同步调制要求N = f_c / f_0为整数。SPWM.zip里最简单的做法就是固定 400 点表,载波 20kHz 对应 50Hz。但如果要调 60Hz,再把表长改成 333 就不方便了。我一般用相位累加器做异步调制,表长固定 256,用小数步进逼近目标频率:

float32 phaseAcc = 0.0f; float32 phaseStep = 256.0f * 50.0f / 20000.0f; // 载波 20k,基波 50Hz interrupt void epwm1_isr_async(void) { uint16 idx = (uint16)phaseAcc; EPwm1Regs.CMPA.bit.CMPA = spwmCmp[idx & 0xFF]; phaseAcc += phaseStep; if (phaseAcc >= 256.0f) phaseAcc -= 256.0f; }

idx & 0xFF利用表长 256 做取模,省掉除法。相位累加器不会累积误差,基波频率长期稳定;代价是每个载波周期不总在整点采样,输出频谱里会出现分布较散的边带谐波。对异步电机调速系统来说,这种谐波比同步调制下的集中在开关频率附近要更友好,音频噪声略微发散但不会产生固定拍频。

5. 用示波器和回读验证 SPWM 波形的三个实用技巧

5.1 数脉冲验证时基稳定

直接看 20kHz 的 PWM 脉冲很容易疲劳。用一个计数器把载波分频到 1k 以下,再通过 GPIO 翻转输出,示波器就能稳定观察周期:

volatile uint16 divider = 0; interrupt void epwm1_isr(void) { if (++divider >= 20) { GpioDataRegs.GPBTOGGLE.bit.GPIO34 = 1; divider = 0; } }

GPIO34 的输出周期应该等于 20 个载波周期。如果翻转波形的周期随温度或负载漂移,优先查TBPRD是否被误改,以及系统时钟 PLL 是否在运行中跳变。

5.2 用事件触发看查表连续性

把 ePWM 的归零事件引到一个 GPIO,以此作为示波器触发源,同时观察 PWM 输出。正常 SPWM 波形在正弦峰值附近脉冲最宽,过零附近最窄,相邻载波周期的脉宽变化应连续。如果看到相邻两个周期占空比从接近 0 突然跳到接近 100%,说明查表索引的取模运算写错,或者中断里修改了索引变量。这个技巧比直接抓波形更能暴露代码逻辑错误。

5.3 用 ADC 回读验证调制度线性

相电压经过电阻分压后接到 ADC 输入,在 ePWM 归零中断里同步采样一次。换算公式是U_pk = (adc_code - mid) * Vref / 4096。把 M 分别设为 0.3、0.5、0.7,读出峰值应近似线性增长。如果 M=0.7 的输出比 0.5 没有明显变大,多半是最小脉宽限制削掉了正弦峰值附近的脉冲,需要检查minDuty是否设置过大。

提示:ADC 采样时刻必须放在 PWM 切换完成一个周期之后,直接采桥臂中点的平均值会混入大量开关纹波,验证结果不具备参考价值。工程里DSP2833x_Adc.cDSP2833x_EPwm.c配合使用就是这个目的,把 ADC 触发源设成 ePWM 的归零事件,再由中断同步读取结果。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 11:04:15

燃料电池Simulink建模与仿真技术解析

1. 燃料电池Simulink建模的价值与挑战 燃料电池作为清洁能源技术的代表&#xff0c;其建模与仿真一直是工程研发的关键环节。我在新能源汽车行业工作十年间&#xff0c;亲眼见证了Simulink如何从辅助工具成长为燃料电池系统开发的核心平台。不同于传统的黑箱测试&#xff0c;基…

作者头像 李华
网站建设 2026/9/12 11:02:13

音频转MIDI技术突破:Prism插件实战解析

1. 音频转MIDI革命&#xff1a;Aurally Sound Prism插件深度解析 作为音乐制作领域的老兵&#xff0c;我见证过无数次"音频转MIDI"技术迭代的失望时刻——直到遇见Prism这款真正能用的解决方案。这款由Aurally Sound推出的跨平台插件&#xff0c;首次实现了复杂乐器音…

作者头像 李华
网站建设 2026/9/12 11:00:58

MQTT发布订阅、QoS与遗嘱消息实战解析

1. 这不是教科书里的协议图&#xff0c;而是一套真实设备间“说人话”的通信系统你手头正调试一块EC20 4G模块&#xff0c;想把它连上阿里云IoT平台&#xff1b;或者你在Vue3项目里写MQTT连接逻辑&#xff0c;connect之后死活收不到topic消息&#xff1b;又或者你在RuoYi框架里…

作者头像 李华
网站建设 2026/9/12 11:00:40

AI Agent实时对话意图识别技术解析与实践

1. 项目概述&#xff1a;AI Agent实时对话意图识别的核心价值在智能交互领域&#xff0c;实时对话意图识别系统如同给机器装上了"读心术"。这个我在多个工业级项目中反复验证过的技术&#xff0c;能够将用户零散的语音或文字输入&#xff0c;在300毫秒内转化为结构化…

作者头像 李华