
简介面向永磁同步电机PMSM控制开发场景基于TI TMS320F28335 DSP的完整工程源码包适用于电机控制算法研究、F28335外设驱动开发及嵌入式实时系统学习。压缩包共包含364个文件以C源文件、头文件和汇编文件为核心另有CCS工程配置、链接命令、编译生成库文件及输出文件等整体目录层次清晰约1.23MB。目前已有131人下载学习。资源内容贴近工程实践不仅包含PMSM控制所需的PWM、ADC、编码器接口、中断服务等底层驱动示例还涵盖TI官方DSP2833x支持库常用模块如CodeStartBranch、usDelay、ADC_cal、默认中断处理的源文件便于快速理解F28335平台上的系统初始化、外设配置与中断管理。配合描述中提到的SVPWM、矢量控制等算法思路读者可结合代码进一步分析电机控制逻辑的实际实现适合电气工程、自动化及嵌入式方向的工程师和学生进行项目参考或二次开发。1. PMSM 主控工程里F28335 为什么到现在还是首选看到 PMSM-28335-master 这种命名基本就是一套 PMSM 矢量控制工程的主分支整理版PMSM 是控制对象28335 是主控芯片master 表示经过多次合入后能直接编译烧录的版本。拿到手真正要关心的是三件事代码怎么初始化、电流环在哪个中断里跑、编码器零位是怎么标定出来的。F28335 至今还在电机控制领域有大量存量靠的是 150MHz 浮点算力和 ADC、ePWM、QEP 的外设联动TI 当年就是让这些外设围绕电流环设计的。第三方驱动板、例程、参考文档都非常成熟读懂这一颗之后再换到 F2837x、F28004x代码迁移成本也很低。下文按一套主流做法展开先讲 FOC 在 F28335 外设上的映射再给软件框架和完整电流环 ISR最后落到有感零位标定与 FreeMaster 式上位机验证。新手能照着手动调通电流环老手能对照检查参数边界和常见误用。2. F28335 外设怎么承接 FOCADC 触发、QEP 读数和互补 PWM2.1 电流采样为什么必须由 PWM 触发FOC 的第一步是得到两相电流。常见做法是采 ia、ib第三相用 ic -(iaib) 算出来省一路传感器但比「采哪几路」更关键的是「在哪个时刻采」。IGBT 开关瞬间会产生电压尖峰和栅极串扰污染电流信号所以采样点要落在 PWM 载波的谷底或峰顶——这时相电流进入续流平稳段信号最干净。F28335 的 ADC 支持事件触发ePWM 模块又能产生 SOCStart Of Conversion信号两者绑在一起采样时刻就和开关周期严格锁定。初次调试的人最容易犯的错是把 ADC 放在主循环里轮询结果电流波形全是毛刺电流环一闭环就振荡。正确路子是配 ePWM 周期事件触发 ADC在 ADC 中断里完成采样、变换、PI、PWM 更新整个控制链在一个 10kHz 或 16kHz 的节拍里跑完。时间预算上F28335 的 12 位 ADC 单通道转换约 1µs串行扫三相电流再加上母线电压、温度等通道总共大概 5µs 上下对 100µs 的 10kHz 开关周期来说很宽裕。多出来的时间可以跑速度环、通信协议和故障检测。注意F28335 的 ADC 输入阻抗不高电流传感器输出最好经运放缓冲后再进 ADC 引脚采样保持时间也要留足否则高阻抗信号源直接进 ADC 会导致转换值系统性偏小变现出的现象是电流反馈整体比实际值低。触发方式的选择可以参照这张表触发方案配置要点适用场景ePWM 周期事件触发ET 寄存器配 SOC取 CTR0 时刻默认推荐采样点位于载波谷底ePWM 比较事件触发用 CMPB 移动触发点提前或滞后采样采样点需要避开死区或换相瞬间时软件触发ADCTRL2 手动拉高 SOC_SEQ1上电自检、低速调试不建议用于闭环实际项目里绝大多数 10kHz 应用用第一种就够了。比较事件触发通常用于死区补偿或相位校正场景比如把采样点从谷底挪到其他角度思维负担会大一些新手不建议优先尝试。2.2 编码器零位QEP 不是上电就能用的位置环和 dq 变换都需要电气角度而增量编码器只能给出相对角度。F28335 的 QEP 模块解码 A/B/Z 信号内部位置计数器 POSCNT 随电机转动递增递减但仅有计数无法直接用于 FOC——必须先标定编码器零点与电机 d 轴的固定夹角。很多项目调试卡住根因不是控制环写错而是这个偏移没标定或者标定方向搞反了。QEP 初始化本身不复杂典型的 2500 线编码器配置如下// QEP 初始化片段 EQep1Regs.QDECCTL.bit.QSP 0; // 正交解码A/B 相差 90 度 EQep1Regs.QDECCTL.bit.QSRC 0; // 计数源选择0 表示正交模式 EQep1Regs.QPOSMAX 2500 * 4 - 1; // 2500 线编码器4 倍频后每圈 10000 步 EQep1Regs.QEPCTL.bit.PCRM 0; // 位置计数器自由运行 EQep1Regs.QEPCTL.bit.QPEN 1; // 使能 QEP 模块QPOSMAX 2500*4-1的前提是 2500 线编码器加 4 倍频。F28335 的 QEP 对 A/B 的每个跳变沿都计数所以一圈的步数等于线数乘以 4。如果你的编码器是 1024 线这里要改成 1024*4-1后面所有角度换算的分母也同步改。QEP 跑通后POSCNT 换算机械角度的公式是 theta_mech POSCNT / QPOSMAX × 2π。有了机械角度乘以极对数就得到瞬时的电角度但还差一个偏移量。编码器的 Z 信号每圈触发一次标记的只是编码器自身的机械零点跟电机转子的 d 轴没有必然关系。所以即便用 Z 信号做了索引清零装配后也一定要做一次零位标定把「编码器角度零」与「转子 d 轴电角度零」对齐。标定流程放在第 4 章这里先把 POSCNT 读数和换算跑通手动慢慢转电机观察 POSCNT 单调变化、到一圈末正常回绕QEP 部分就算正常了。2.3 互补 PWM 与死区参数逆变器上下桥臂需要互补 PWM。F28335 的 ePWM 在驱动电机时通常配成增减计数模式中心对齐由比较值 CMPA 决定占空比再送进死区发生器产生互补输出。死区不是可有可无的上下管同时导通就是母线直通所以必须保证任何时刻都有一段上下管全关的时间。中心对齐 PWM 的周期寄存器计算是 f_pwm 150MHz / (2 × TBPRD)。注意增减计数是计数器在一个周期内先升后降分母要乘 2// ePWM1 配置ePWM2/3 随后通过同步信号对齐 EPwm1Regs.TBCTL.bit.CLKDIV 0; // 时基时钟不分频取 150MHz EPwm1Regs.TBCTL.bit.HSPCLKDIV 0; // 高速外设时钟不分频 EPwm1Regs.TBCTL.bit.CTRMODE 2; // 增减计数模式 EPwm1Regs.TBPRD 7500; // 150MHz/(2*7500)10kHz EPwm1Regs.CMPA 0; // 占空比初值 0 // 死区 2us150MHz 时钟下对应 300 个计数 EPwm1Regs.DBCTL.bit.IN_MODE 1; // 输入信号取 CMPA 相关边沿 EPwm1Regs.DBCTL.bit.OUT_MODE 3; // 上升沿和下降沿都做延迟 EPwm1Regs.DBRED 300; // 上升沿延迟 300 个时钟 EPwm1Regs.DBFED 300; // 下降沿延迟 300 个时钟TBPRD 7500对应 10kHz 就是这么算出来的。想切到 16kHz把 TBPRD 改成 4687实际频率 16001Hz因为 150MHz/(2×4687)16001。死区 300 个时钟对应 2µs是 IGBT 功率板的常见值MOSFET 驱动板可以降到 1µs 甚至 0.5µs。死区调小的收益是电流谐波降低、低转速力矩平稳代价是直通风险上升只能在硬件可靠的前提下逐步往下试。三个 ePWM 还需要同步ePWM1 配同步输出ePWM2/3 配同步输入三组载波相位才一致ADC 采样点在三相上才能对齐。这里有个容易忽略的细节同步信号到达后每个模块会按自身相位寄存器 TBPHS 装载计数值所以 ePWM2/3 的 TBPHS 必须设成 0否则同步后载波虽然同频但相位有偏移电流采样点的对齐就被破坏了。3. PMSM 软件框架从主程序初始化到电流环 ISR 的完整链路3.1 主程序结构从看门狗到中断向量拿到一份 PMSM-28335 工程先别急着看控制算法把启动流程读一遍。F28335 上电后默认时钟不是 150MHz要由外部晶振经 PLL 倍频上去看门狗默认是开着的不在头几行关掉过一会儿系统会被强制复位。规范的初始化顺序是关看门狗 → 配 PLL → 配外设时钟门控 → 初始化 PIE 中断向量 → 初始化外设 → 挂中断 → 开全局中断。void main(void) { InitSysCtrl(); // 关看门狗PLL 倍频到 150MHz DINT; // 初始化期间关全局中断 InitPieCtrl(); // PIE 控制块上电默认状态 IER 0x0000; // 清中断使能寄存器 IFR 0x0000; // 清中断标志 InitPieVectTable(); // 载入默认中断向量表 InitGpio(); // 电流采样、编码器、继电器引脚 InitAdc(); // ADC 工作在 ePWM 触发模式 InitEQep(); // 编码器接口 InitEPwm(); // 三组互补 PWM含死区 EALLOW; // 开写保护改向量表 PieVectTable.ADCINT1 Adc_ISR; // 电流环挂在 ADC 中断 EDIS; // 关写保护 PieCtrlRegs.PIEIER1.bit.INTx6 1; // 使能 ADCINT1 IER | M_INT1; EINT; // 开全局中断 while (1) { // 非实时任务过压欠压保护、串口/CAN 通信、状态机 } }InitSysCtrl内部除了 PLL还要把 ADC、ePWM、QEP 等外设的时钟门控打开。找不到原因的怪问题里「外设寄存器写了没反应」有相当比例就是漏了 PCLKCR 对应的位。ISR 挂载用的EALLOW/EDIS是 C28x 的写保护机制中断向量表属于受保护寄存器必须先开写保护才能改改完立刻关掉。ADCINT1 在 PIE 中断组 1 的第 6 位对应的使能位就是PIEIER1.bit.INTx6。中断不进时优先查三处全局 IER、PIE 组使能 PIEIER、以及 ISR 末尾有没有清 PIEACK。F28335 要求中断服务程序末尾写PieCtrlRegs.PIEACK.all PIEACK_GROUP1;释放同组其他中断这一句漏了同组后续中断全部卡死现象极其隐蔽。3.2 电流环 ISR 的完整骨架电流环全部工作在 ADC 中断里一个完整的 ISR 骨架如下。这版代码涵盖了采样校准、坐标变换、PI 输出和 SVPWM 更新可以直接抄到工程里再按实际参数修改interrupt void Adc_ISR(void) { float ia, ib, ic; float i_alpha, i_beta, id, iq; float v_alpha, v_beta, vd, vq; float sin_t, cos_t, theta_mech, theta_elec; // 1. 读电流原始值减去运放中点偏移 ia (AdcResult.ADCRESULT0 - ia_offset) * ia_gain; ib (AdcResult.ADCRESULT1 - ib_offset) * ib_gain; ic -ia - ib; // 三相平衡假设 // 2. Clarke 变换 i_alpha ia; i_beta (ia 2.0f * ib) * 0.577350269f; // 1/sqrt(3) // 3. 位置读取 Park 变换 theta_mech (float)EQep1Regs.QPOSCNT / 10000.0f * TWO_PI; theta_elec theta_mech * POLE_PAIRS - theta_offset; sin_t sinf(theta_elec); cos_t cosf(theta_elec); id i_alpha * cos_t i_beta * sin_t; iq -i_alpha * sin_t i_beta * cos_t; // 4. PI 电流环输出限幅到 SVPWM 线性区 Vdc/sqrt(3) vd pi_ctrl(pi_id, id_ref, id); vq pi_ctrl(pi_iq, iq_ref, iq); // 5. 反 Park v_alpha vd * cos_t - vq * sin_t; v_beta vd * sin_t vq * cos_t; // 6. 三相调制 注入零序分量等效 SVPWM svpwm_output(v_alpha, v_beta); // 7. 清中断标志 AdcRegs.ADCTRL2.bit.RST_SEQ1 1; PieCtrlRegs.PIEACK.all PIEACK_GROUP1; }ISR 里藏着几个关键点。ic -ia - ib省一路传感器前提是 ia、ib 采样时刻一致用 F28335 的双采样保持器同时采两路电流时这个假设基本成立。ia_offset是运放中点电压对应的 ADC 原始值ia_gain由采样电阻、放大倍数和 ADC 参考电压决定这两个参数建议上电时做一次自校准不给电机通电把当前读数的平均值当成零点。theta_offset就是第 4 章标定出来的电角度偏移。这里注意极对数换算4 对极电机机械角度转一圈对应 4 圈电角度theta_elec 会以 4×2π 为周期变化sinf/cosf传大角度没问题但做角度差运算时必须做归一化。FPU 跑 sinf/cosf 加全套变换大概几微秒对 10kHz 中断完全够用不必上来就换查表。pi_ctrl用抗积分饱和结构积分器和输出分别限幅限幅值取 SVPWM 线性区对应的最大相电压 Vdc/√3。如果只用输出限幅不管积分器会出现 windup输出已经顶到上限指令方向反转后积分器还要慢慢退回去电流环响应变得迟钝看起来像死区。3.3 SVPWM 更新节奏和比较值装载角F28335 的 ePWM 比较值有影子装载机制ISR 里写 CMPA 不会立即生效而是在配置的装载时刻通常是 CTR0 或周期边界同步生效。这对 FOC 至关重要它保证从 ADC 采样到 PWM 输出之间的延迟是确定的一个载波周期而不是随机抖动。这个确定性延迟是电流环的固有相位滞后也是电流环带宽被限制在开关频率 1/10 左右的原因。SVPWM 的实现不一定要写扇区判断。工程里更常见、更不容易出错的是零序注入法用一个函数完成void svpwm_output(float v_alpha, float v_beta) { float va, vb, vc, vmax, vmin, vzero; float cmpA, cmpB, cmpC; // 反 Park 得到三相正弦电压 va v_alpha; vb -0.5f * v_alpha 0.8660254f * v_beta; vc -0.5f * v_alpha - 0.8660254f * v_beta; // 注入零序分量等效空间矢量调制 vmax fmaxf(va, fmaxf(vb, vc)); vmin fminf(va, fminf(vb, vc)); vzero -0.5f * (vmax vmin); va vzero; vb vzero; vc vzero; // 换算成比较值装载角配在 CTR0 cmpA (0.5f va / Vdc) * 7500.0f; cmpB (0.5f vb / Vdc) * 7500.0f; cmpC (0.5f vc / Vdc) * 7500.0f; EPwm1Regs.CMPA cmpA; EPwm2Regs.CMPA cmpB; EPwm3Regs.CMPA cmpC; }零序注入的数学本质是给三相正弦电压叠加一个共模分量把最大的那一相推到调制边界输出能力由此从 SPWM 的 Vdc/2 提升到 Vdc/√3。代码里的Vdc是实测母线电压不能写死母线电压波动 10% 时占空比会跟着错 10%电流环会出低频纹波。这里把 7500 直接写进函数实际工程建议用宏或全局变量和 TBPRD 保持一致。4. 有感 PMSM 零位标定和电流环参数整定手记4.1 零位标定原理为什么不能只靠 Z 信号有感 FOC 的坐标系变换全部依赖电气角度而这个角度由机械角度乘以极对数再加偏移得到。偏移量来自编码器安装位置与电机转子 d 轴之间的固定夹角每台电机装配后都不一样。把偏移写死在源码里的工程有个副作用换电机必须重新编译。更合理的做法是把它当作标定参数存到 Flash 或 EEPROM上电时读取。标定的原理并不复杂给定子施加一个固定的 d 轴电流矢量转子会转到大直轴与定子磁场对齐的位置这时读取编码器数值就得到了机械角度和电气角度的对应关系。具体操作是给 id 一个正向小电流额定电流的 5%~10%iq 保持 0电压矢量角度固定在 0 度等转子锁死。更稳的做法是正反两次锁死取平均第一次电流矢量锁在 0 度读角度再把电流矢量设为 180 度转子反向转过来锁死再读一次。两次记录的机械角度相差 180 度取平均可以抵消轴承摩擦带来的定位误差。偶数极对数的电机在 180 度内存在两个等效对齐位置如果发现标定结果和上次差 180 度电角度说明转子停在另一个等效点需要用力矩方向测试确认最终值。#define ID_ALIGN_CURRENT 0.8f // 对齐电流约额定电流的 5% #define ALIGN_WAIT_MS 3000 // 等待转子稳定 void CalibrateZeroPosition(void) { float theta_mech_raw; // 1. 输出电压矢量角度 0vd Uvq 0 svpwm_output(ID_ALIGN_CURRENT * R_est, 0.0f); DELAY_MS(ALIGN_WAIT_MS); // 等转子锁死 // 2. 读编码器机械角度 theta_mech_raw (float)EQep1Regs.QPOSCNT / 10000.0f * TWO_PI; // 3. 此时转子 d 轴与电压矢量 0 度对齐 // 偏移 当前电角度 - 0实际为负的机械角乘极对数 theta_offset -theta_mech_raw * POLE_PAIRS; // 4. 归一化到 (-PI, PI] while (theta_offset PI) theta_offset - TWO_PI; while (theta_offset -PI) theta_offset TWO_PI; // 5. 写入参数区 WriteFlash(theta_offset, sizeof(theta_offset)); }ALIGN_WAIT_MS 3000不是随手拍的。转子从初始位置转到对齐位置需要时间机械时间常数通常在几十到几百毫秒给 3 秒是为了确保彻底停稳。对齐电流太小时摩擦力会让转子停在几个位置之间每次标定的 offset 都不一致。判定标准也就明确了重复断电、上电、重标 5 次offset 的一致性应该在 0.5 度电角度以内。注意第 3 步的负号取决于坐标系定义。电机旋向、编码器安装方向不同offset 的符号可能相反这不是代码 bug 而是定义差异。建议在文档里固定约定电机逆时针为正编码器正转时 QPOSCNT 递增。4.2 电流环 PI 初值从 L、R 反推电流环调试不要上来就试凑。PMSM 的 dq 轴电流环可以近似为一阶惯性环节 1/(LsR)用电机参数可以直接算出一组可用初值。下面是常见的小功率伺服电机参数和对应 PI 初值符号含义示例取值R_s相电阻线电阻除以 2 近似0.5 ΩL_s相电感Ld/Lq 取较小值1.2 mHf_swPWM 开关频率10 kHzf_bw电流环目标带宽1 kHzf_sw 的 1/10KpL_s × 2πf_bw1.2e-3 × 6280 ≈ 7.5KiR_s × 2πf_bw0.5 × 6280 ≈ 3140Kp 由电感决定、Ki 由电阻决定这不是巧合。电流环 PI 设计常用极点对消Kp 用来抵消电感造成的相位滞后Ki 保证稳态时消除电阻压降。目标带宽取开关频率的 1/10 是安全值极限可以到 1/5但超过 1/5 后数字采样和 PWM 延迟造成的相位损失会非常明显电流环容易在高频出现共振。实测时把转子卡住给 iq 一个 1Hz 的小幅方波指令看 iq 反馈的跟随情况和超调量。波形振荡就先降 Kp稳态误差大就加 Ki。工程里我习惯把抗饱和直接写进 PI 函数float pi_ctrl(PI_Type *pi, float ref, float fbk) { float err ref - fbk; float out; pi-integrator pi-Ki * err; if (pi-integrator pi-out_max) pi-integrator pi-out_max; if (pi-integrator -pi-out_max) pi-integrator -pi-out_max; out pi-Kp * err pi-integrator; if (out pi-out_max) out pi-out_max; if (out -pi-out_max) out -pi-out_max; return out; }这里积分器和输出分别限幅而不是只对输出限幅。如果只限输出积分器会继续累积出现 windup输出已经给满指令方向反转后要等积分慢慢退回去电流环看起来就像有死区一样迟钝。限幅值out_max取 Vdc/√3 对应的电压值也就是 SVPWM 线性区的上限。4.3 开关频率怎么选10kHz 还是 16kHz看到工程里写的 10kHz 还是 16kHz先别急着改。算一下总预算F28335 在 150MHz 下ADC 转换约 1µsFOC 全套浮点运算约 5~10µs10kHz 的 100µs 周期里占不到 20%16kHz 的 62.5µs 周期里占 20%~30%都还有余量给速度环和通信。所谓「28335 跑不了 16kHz」的说法是不准确的瓶颈不在芯片而在功率管的开关损耗和散热。开关频率还直接影响听觉噪声。10kHz 正好在人耳敏感区容易和机械结构共振16kHz 以上基本不可闻很多伺服板子标称 16kHz 就是图这个。代价是同一负载下开关损耗上升MOS 管温度明显变高。如果你的驱动板散热片不大宁可用 10kHz 跑 80% 额定电流也不要 16kHz 满载硬扛。对无感 FOC 来说开关频率还间接影响低速性能开关频率越高电流采样越密集观测器能提取到的反电动势信息也越多。但无感控制在极低速下失效主要受反电动势幅值限制这不是芯片或开关频率能解决的10kHz 和 16kHz 的差别在低速段并不显著。有感系统则完全不受这个限制电流环带宽和零位精度才是决定低速平稳性的主要因素。5. 用示波器思想和 FreeMaster 式上位机验证 FOC 波形5.1 UART 打点在 FreeMaster 里还原波形调试 FOC 最缺的不是逻辑分析仪而是能把 id、iq、角度这些内部变量同步看出来的手段。FreeMaster 虽然是 NXP 生态的工具但底层就是串口读写内存加上位机绘图接到 F28335 上完全可行——前提是工程里开一路 UART 把变量按固定格式发出来。不需要完整移植它的协议最朴素的二进制帧就够了// 100Hz 发送一帧内容为 id、iq 和电气角度 // 帧格式0xAA 0x55 | id(2B) | iq(2B) | theta(2B) Uint16 pack_id, pack_iq, pack_theta; pack_id (Uint16)((id 10.0f) * 100.0f); // 转成 0~2000 量程 pack_iq (Uint16)((iq 10.0f) * 100.0f); pack_theta (Uint16)((theta_deg 180.0f) * 10.0f); // 0.1 度分辨率 UART_SendByte(0xAA); UART_SendByte(0x55); UART_SendByte(pack_id 8); UART_SendByte(pack_id 0xFF); UART_SendByte(pack_iq 8); UART_SendByte(pack_iq 0xFF); UART_SendByte(pack_theta 8); UART_SendByte(pack_theta 0xFF);id 10.0f是简单的防负值平移* 100.0f对应 0.01A 分辨率如果电流峰值超过 10A把平移量和缩放系数一起改。115200 波特率下每帧 8 字节、100Hz 发送只占约 9.2kbps完全可行。想提高时间分辨率就降到只发一个变量帧率提到 1kHz足够看清电流环的动态过程。5.2 三个必看的验证判据第一个判据是电流正弦度。稳定运行在某转速下iq 反馈应是一条平直的直流分量叠加上细微纹波。如果 iq 波形出现幅值明显的 6 倍频纹波先怀疑死区补偿没做或者 SVPWM 过调制如果纹波频率和转速同步而且幅值不小多半是零位 offset 有偏差。反向用法把零位偏移从 0 开始缓慢调整直到 iq 纹波最小、均值最大这本身就是一种在线微调零位的手段。第二个判据是 d 轴电流。表贴式电机 FOC 里 id_ref0实测 id 应该贴着参考线波动不超过额定电流的 ±2%。如果 id 跟着 iq 一起抖动说明 Park 变换角度不准重点检查极对数换算和 QPOSCNT 的溢出处理。POSCNT 超过 QPOSMAX 后的行为依赖配置电气角度必须先归一化到单圈范围再参与运算直接用原始计数乘极对数会得到错误的锯齿状角度。第三个判据是启停对称性。正转和反转各跑一档转速对比两个方向的 iq 均值、电流纹波和速度稳态误差应该基本对称。如果正转轻负载、反转重负载说明零位标定方向有问题或者死区补偿在反向时方向反了。这个对比测试只要几分钟就能做完却能拦截掉一大类「只在一个方向验证过就交付」的隐患——换向不对称在单方向运行时常被忽视但客户做正反转往复运动时立刻暴露。本文还有配套的精品资源点击获取