做一个表贴式永磁同步电机(SPMSM)的直接转矩控制(DTC)仿真模型,比我想象中更能打。大多数人学电机控制都是FOC起步:坐标变换、PI调节器、SVPWM,一套组合拳下来,电流波形确实干净,但整个链路也相当厚重。后来我因为项目需要,把手头一个FOC模型换成DTC思路时,才真正意识到这套被称为“bang-bang控制”的路线有多直接——没有电流环,不搞调制,只靠磁链和转矩两个滞环比较器去推开关表,居然能把转矩响应速度做到比FOC还快。这篇文章就把我在Simulink里从零搭建SPMSM的DTC仿真模型的完整过程写出来,包括数学推导、开关表逻辑、参数整定,以及几个让我当时折腾到半夜的坑。
如果你正准备做DTC仿真,或者只想把“磁链圆”和“转矩纹波”这两个概念落到实际波形上,这篇可以直接当作参考。我会尽量把每一步都讲清楚,尤其是那些说明书里不会写、只有实际跑模型才会撞上的细节。
1. 为什么表贴式PMSM的DTC模型值得从FOC思路里跳出来
1.1 DTC和FOC的本质差异在哪里
FOC的本质是把电流当成被控对象:转子磁场定向之后,id和iq完全解耦,iq就是转矩电流,再用两个PI环把电流“喂”到指定值。整个控制链路是“转矩指令→iq指令→电流PI→d/q电压→SVPWM→逆变器”,每一层都有自己的动态,所以转矩响应再快也要等电流环反应一拍。
DTC直接把定子磁链幅值和电磁转矩当作被控对象。控制器每周期估算一次磁链和转矩,拿它们和给定值比较,误差进入两个滞环比较器,输出一个“磁链要增还是要减”“转矩要增还是要减”的组合标志,然后通过开关表选出本次周期最合适的电压矢量。没有PI,没有电流环,非线性 bang-bang 控制的代价是转矩纹波偏大,但换来的是极快的动态响应。
如果控制对象换成表贴式永磁同步电机,还有一个结构性优势:SPMSM的d轴电感和q轴电感相等,电磁转矩公式退化成 Te = 1.5·p·ψf·iq。这意味着定子磁链和转子磁链之间的夹角(负载角)可以直接决定转矩大小。DTC通过选择电压矢量控制定子磁链的旋转速度和幅值,本质就是在快速操纵这个负载角,所以表贴式PMSM是DTC最容易发挥优势的场景之一。
1.2 仿真模型能帮你验证什么
写DTC仿真模型之前,先明确一个态度:这个模型的主要价值不是“复现厂家参数表”,而是把控制逻辑的因果链跑通。你在仿真里最值得观察的三件事:
- 定子磁链轨迹是否接近圆形;
- 转矩给定突变时,估算转矩能不能在几个采样周期内跟上;
- 磁链幅值是否被滞环控制在设定带宽内。
这三个现象直接反映了开关表、扇区计算、滞环逻辑是否正确。相比FOC仿真里全是一堆PI参数,DTC模型的调试对象非常集中,反而更容易定位问题。
2. 磁链估算与开关表:先把控制逻辑的数学底子打牢
2.1 电压模型与磁链初值
DTC不需要转子位置传感器参与磁链估算(这正是它能往无速度传感器方向扩展的原因)。在静止αβ坐标系下,定子磁链用电压模型计算:
ψα = ∫(uα − Rs·iα)dt
ψβ = ∫(uβ − Rs·iβ)dt
这个式子看上去简单,但有两个隐藏问题。第一个是积分纯零漂:直流偏置和误差会被积分越积越大,仿真里不太明显,到了实物板上几乎必须改用带低通滤波的改进型积分器。第二个是初值:如果你把积分器初始条件设成0,而电机初始定子磁链实际上等于永磁磁链,启动瞬间估算磁链和真实磁链严重不一致,控制器会猛打“增大磁链”指令,冲击电流能飙到一个很吓人的数值。
我的处理是:在Simulink的Integrator模块里直接把ψα初值设为ψf,ψβ初值设为0。这样做的前提是转子初始位置为0,也就是d轴和α轴对齐,仿真里可以手动把PMSM模块的初始机械角度设为零。需要注意,如果你改了转子初始角度,初值也要跟着改,否则启动冲击照样存在。
2.2 转矩估算的αβ表达
转矩估算是DTC里另一个关键计算,不需要知道 d/q 轴电流,直接用αβ分量就能算:
Te = 1.5·p·(ψα·iβ − ψβ·iα)
这里p是极对数。这个表达式在静止坐标系下成立,正好和电压模型的磁链估算共用同一组αβ电流电压测量值。
在Simulink里实现时,我建议不要把这个式子直接用一堆Gain和乘法器搭出来,而是放进一个MATLAB Function里,输入ψα、ψβ、iα、iβ,输出Te。这样不仅代码清晰,后续如果想加入滤波、补偿逻辑,改起来也方便。注意函数输入信号的单位要保持一致:Simscape等物理域信号统一转成国际单位后,磁链是Wb、电流是A、转速是rad/s,算出来的转矩单位才是N·m。
2.3 六扇区划分与经典开关表
DTC选择电压矢量的依据,是定子磁链当前所在扇区。把αβ平面按60度一个扇区切分成6个区域,然后根据磁链误差和转矩误差的组合,从6个非零矢量和2个零矢量里挑一个。
我用的扇区定义是:V1(100)位于0度,V2(110)位于60度,V3(010)位于120度,V4(011)位于180度,V5(001)位于240度,V6(101)位于300度。扇区1覆盖0~60度,扇区2覆盖60~120度,以此类推。磁链角度用atan2(ψβ, ψα)算出弧度后,再换算到0~360度范围。这里必须注意MATLAB的atan2返回范围是-π到π,负角度要加2π,否则扇区会跳变。
扇区计算的MATLAB Function代码:
function sector = fcn(theta) thetaDeg = theta * 180 / pi; thetaDeg = mod(thetaDeg, 360); % 保证在0~360 sector = floor(thetaDeg / 60) + 1; if sector > 6 sector = 6; end end扇区定下来之后,就轮到开关表。经典DTC表的形式是:行对应磁链标志和转矩标志的四种组合,列对应扇区1~6,格子里放电压矢量编号。我使用的表和大多数人看到的论文一致:
| 状态 | 扇区1 | 扇区2 | 扇区3 | 扇区4 | 扇区5 | 扇区6 |
|---|---|---|---|---|---|---|
| 磁链增、转矩增 | V2 | V3 | V4 | V5 | V6 | V1 |
| 磁链增、转矩减 | V6 | V1 | V2 | V3 | V4 | V5 |
| 磁链减、转矩增 | V3 | V4 | V5 | V6 | V1 | V2 |
| 磁链减、转矩减 | V5 | V6 | V1 | V2 | V3 | V4 |
注意,这张表对应的扇区定义和矢量排列必须和上面的扇区计算严格一致,否则磁链圆会变成多边形。经常会看到有人从论文里抄了一张表,但扇区边界定义不一样,结果怎么调都调不出圆轨迹,问题就出在这种看似不起眼的约定上。
3. Simulink模型分层搭建:从三相桥到开关脉冲的完整链路
3.1 主电路:直流母线、三相桥和电机模型
我搭建时把整个模型分成了清晰的三层:主电路层、测量层、控制层。主电路层用Simulink物理域模块:直流电压源给一个310V的直流母线,Universal Bridge三相全桥输出接PMSM电机模型。电机选表贴式结构,也就是在模块参数里把Ld和Lq设成相同的数值。
这里有几个版本差异导致的坑:PMSM电机模块在不同Simulink版本里位置和名称差别很大,有的在Simscape/Electrical/Specialized Power Systems下面,有的叫Permanent Magnet Synchronous Machine。你在库里搜PMSM就能找到,但连接方式略有不同。我建议先用Universal Bridge内部的Measurement输出测三相电流,再用Voltage Measurement测电机端电压,这样保证采集信号和主电路共地关系清楚。
PMSM模块参数我用的是一组接近1.5kW小电机的数据:
| 参数 | 数值 |
|---|---|
| 额定功率 | 1.5 kW |
| 极对数 | 4 |
| 定子电阻 Rs | 0.958 Ω |
| d轴电感 Ld | 5.25 mH |
| q轴电感 Lq | 5.25 mH |
| 永磁磁链 ψf | 0.1827 Wb |
| 额定转速 | 1500 rpm |
| 转动惯量 J | 0.003 kg·m² |
| 直流母线电压 | 310 V |
3.2 测量、Clarke变换和信号转换
主电路里跑的是物理信号,控制器里跑的是普通Simulink信号。中间必须用PS-Simulink Converter和Simulink-PS Converter做转换,少一个都会报黄色的链接错误,这是刚接触物理域模块最容易卡住的地方。
电流电压信号转成普通信号后,下一步做Clarke变换。三相变两相静止坐标的公式用等幅值变换:
uα = (2/3)·(ua − 0.5·ub − 0.5·uc)
uβ = (2/3)·(0.5·√3·ub − 0.5·√3·uc)
这个变换放在MATLAB Function里很简单,但要注意如果三相电压信号是从Universal Bridge的测量端出来的,已经是线电压还是相电压要确认。PMSM模块和逆变器共地的情况下,电机相电压可以直接用三个Voltage Measurement分别测A、B、C相对直流母线负端,这样Clarke变换输入就是相电压。
3.3 磁链积分器、滞环和开关表怎么接
磁链估算这块,我用两个Simulink积分器模块,分别对α轴和β轴做积分。输入端是uα−Rs·iα和uβ−Rs·iβ。积分器初始条件按前面说的设置。积分器输出ψα、ψβ后,用幅值和角度计算模块得到磁链幅值和角度。
滞环比较器我建议用MATLAB Function而不是Relay模块。Relay本身很好用,但它在连续时间模型里容易产生高频抖动,而且和后面开关表的状态衔接不如代码直观。我用一个Function实现两个滞环,内部用persistent变量保存上一周期状态。
function [psiFlag, torFlag] = dtc_hysteresis(errPsi, errTor, bandPsi, bandTor) persistent lastPsiFlag lastTorFlag; if isempty(lastPsiFlag) lastPsiFlag = 0; lastTorFlag = 0; end if errPsi > bandPsi lastPsiFlag = 1; % 磁链偏低,需要增磁 elseif errPsi < -bandPsi lastPsiFlag = 0; % 磁链偏高,需要减磁 end if errTor > bandTor lastTorFlag = 1; % 转矩偏低,需要增矩 elseif errTor < -bandTor lastTorFlag = 0; % 转矩偏高,需要减速 end psiFlag = lastPsiFlag; torFlag = lastTorFlag; end输入errPsi是磁链给定减去磁链幅值,errTor是转矩给定减去估算转矩。bandPsi和bandTor就是滞环半带宽。
开关表放在另一个MATLAB Function里,输入psiFlag、torFlag、sector,输出三个上桥臂开关信号。下面是代码,注意idx的计算方式:
function [SwA, SwB, SwC] = dtc_switch_table(psiFlag, torFlag, sector) table = [2 3 4 5 6 1; % 磁链增、转矩增 6 1 2 3 4 5; % 磁链增、转矩减 3 4 5 6 1 2; % 磁链减、转矩增 5 6 1 2 3 4]; % 磁链减、转矩减 idx = (1 - psiFlag) * 2 + (1 - torFlag) + 1; v = table(idx, sector); switch v case 1, SwA = 1; SwB = 0; SwC = 0; % V1(100) case 2, SwA = 1; SwB = 1; SwC = 0; % V2(110) case 3, SwA = 0; SwB = 1; SwC = 0; % V3(010) case 4, SwA = 0; SwB = 1; SwC = 1; % V4(011) case 5, SwA = 0; SwB = 0; SwC = 1; % V5(001) case 6, SwA = 1; SwB = 0; SwC = 1; % V6(101) otherwise, SwA = 0; SwB = 0; SwC = 0; end end三个开关信号送入Universal Bridge控制端时,注意桥臂逻辑是“上管导通下管关断”还是互补输出。大部分Universal Bridge模型只要给上管信号,下管自动取反,但也有配置需要手动互补。如果你发现电流波形上半周和下半周严重不对称,优先查这里。
3.4 控制层的整体信号流
整个控制层从输入到输出很清晰:三相电压电流→Clarke→估算器→磁链幅值和转矩计算→滞环→开关表→桥臂信号。把这几个Function和积分器连起来时,最需要注意的是离散化方式。我建议整个仿真用固定步长,步长设1e-6秒,控制器里的MATLAB Function可以按每一步执行,但为了体现DTC“每周期采样一次”的本质,可以在Function前面加一个采样保持模块,或者把控制器整体放在一个定时子系统里。
如果你偷懒直接用变步长求解器,很多结果也能看,但开关事件会让求解器频频缩小步长,仿真速度慢到想砸键盘。固定步长是正路。
4. 滞环带宽、采样周期与母线电压:仿真稳定性的三道门槛
4.1 滞环带宽到底怎么整定
DTC的滞环带宽是控制品质的核心。磁链带宽如果设太宽,磁链幅值波动会直接耦合进转矩,造成转矩纹波叠加;如果设太窄,磁链稍有偏离就会触发状态翻转,开关频率急剧升高。我在这组参数下试过,磁链半带宽取0.02Wb左右比较合适,额定磁链0.1827Wb对应波动约±11%,电流质量可以接受。
转矩带宽的设定更考验手感。半带宽1.5N·m在1.5kW电机上大约对应15%额定转矩波动,动态响应很激进,但稳态纹波明显。你可以先跑一版全带宽2N·m,把模型跑通,再慢慢收紧到1N·m。每收一次,留意示波器里的估算转矩波形:
- 带宽大时,转矩波形锯齿幅度大,开关频率偏低;
- 带宽小时,锯齿幅度小,但功率管开关动作明显变密。
这个折衷是DTC天然的特性,没有免费的午餐。
4.2 采样周期和控制频率的配合
DTC的控制频率就是开关表的刷新频率。理论上,控制频率越高,转矩纹波越小,但受限于功率管的开关损耗,实际工程一般取5kHz到20kHz。我做仿真时把控制采样周期设成1e-5秒,也就是10kHz刷新率,和固定步长1e-6配合,控制器每10个仿真步长刷新一次开关状态。
这里有个实现细节:如果控制器函数在每一步都执行,而电机时间常数又很短,你会看到开关表输出几乎每个步长都在跳,波形一团乱麻。解决方法是把滞环和开关表函数的更新包在一个周期为1e-5的采样保持逻辑里,也就是给这些Function的输入信号加一个采样保持模块,或者直接把它们放在一个受周期触发的子系统内。这样仿真波形干净,也更接近真实控制器的离散采样行为。
4.3 直流母线电压和转速范围的匹配
很多人建完DTC模型,随便给个母线电压和转速指令,然后发现高速时磁链圆严重畸变。原因很简单:电压矢量能改变磁链的速率是有限的,反电动势接近母线电压时,有效电压矢量对磁链的调整能力会被削弱,控制器基本进入饱和状态。
以310V母线、1.5kW电机为例,最大相电压基波幅值大约在0.637倍母线电压,也就是接近197V。额定反电动势在1500rpm时对应大约132V左右(具体看电机参数),所以额定转速附近还有一定裕量。如果你把转速指令拉到3000rpm,磁链圆肯定撑不住,模型会变得非常不稳定。这个约束不是Simulink的锅,是DTC本身的物理边界。
5. 波形异常排查链路:磁链圆、转矩纹波和启动冲击三连问
5.1 磁链圆的形状到底透露了什么信息
模型跑通之后,第一个要看的是示波器里ψβ对ψα的XY图。正常情况下应该是一个接近圆形的轨迹,外圈有滞环造成的锯齿状毛刺。如果形状明显变成六边形或者朝向某个定子电压矢量的方向凸起,基本可以认定开关表或者扇区计算有问题。
我调试时遇到最典型的一种情况:磁链圆在某个扇区交界处出现一个向内凹陷的小尖角。后来排查发现是扇区计算里mod函数在角度接近360度边界时,floor函数把角度归到扇区1而不是扇区6,导致最后一步选错了矢量。解决方案就是在扇区计算函数末尾加一句if sector > 6的保护,把边界值强制拉回扇区6。
磁链圆心不在原点上,则是积分器初值或直流偏置问题。仿真里常见的是启动初值给错,圆心整体偏移了一个ψf的量。这个问题在启动冲击部分还会展开讲。
5.2 转矩纹波偏大时,先别急着加滤波器
转矩纹波偏大,很多人的第一反应是给估算转矩加低通滤波,结果把DTC的快速动态也一起滤没了。正确的排查顺序是:
- 先看磁链滞环带的实际翻转频率,是否明显低于预期控制频率。如果是,说明带宽太大或者母线电压不足;
- 再看零矢量占比。低速轻载时,有效电压矢量会把磁链很快推出去,转矩又下降不够快,所以开关表被迫频繁切换零矢量,纹波在低速时特别扎眼;
- 最后才考虑是不是控制采样频率不够。
零矢量的选择本身也有讲究。经典表里我没有专门安排零矢量,但在实际调试中,如果转矩需要减小时磁链还没到需要减磁的程度,用零矢量比用反向矢量更合理。这时可以做一个简单优化:让V0=000和V7=111按控制周期交替使用,避免逆变器某一个桥臂长时间悬空,电流波形也会平滑不少。这个改动不用大动开关表,效果立竿见影。
5.3 启动阶段冲击电流怎么处理
启动冲击是DTC仿真常见的“第一印象”问题。现象是仿真刚一开始,电流直接从零跳到几十安,转矩波形像被踹了一脚。原因我在第2.1节提过:积分器初值不对,控制器误以为磁链非常小,于是持续输出“增磁”矢量。
解决分两步。一步是把磁链估算积分器初值按转子初始位置给对,ψα初值设为ψf,ψβ初值设为0。另一步是让转矩给定不要阶跃,加一个斜坡或者让它跟踪一个从零上升的参考信号。我一般用Signal Builder给一个0到5N·m、斜坡时间0.05秒的转矩指令。你会发现冲击电流明显变小,磁链圆从启动第一个周期就在额定半径附近,而不是从一个点逐步扩出来。
如果你的负载是一个恒定的机械负载,还可以在机械端口加一个阶跃负载转矩,观察DTC的动态补偿能力。这也是验证DTC“瞬时转矩控制”特性的经典做法。
5.4 Simulink建模时几个容易忽略的隐藏坑
最后列几个我在搭建过程中实际踩过的坑,虽然看起来琐碎,但足以让一个模型折腾一整天:
- 电机模块和逆变器之间的物理信号连接如果不共地,电压测量会报错或者数值完全不对;
- Universal Bridge默认的理想开关参数在某些版本下会产生高次谐波振荡,检查Snubber电路设置,必要时候把它设成极大值,近似开路;
- MATLAB Function里输入信号维数不匹配是最常见的错误,比如把三相电流当成一维向量输入,但函数内部用了矩阵运算,维数很容易错;
- 固定步长求解器下,如果模型里同时存在连续积分器和离散采样保持,要检查积分器的采样时间是否继承了全局步长,必要时显式设为连续采样时间;
- 转速反馈如果是用rad/s,而机械负载模块用的是rpm或r/min,力矩乘以半径之类的换算也要检查。仿真控制系统经常出现“看上去转得很稳,但转速差了60倍”这种低级但又难查的错误。
6. 跑通之后的边界与扩展:纯DTC模型的下一步
6.1 估算器不能一直用纯积分
仿真里纯积分为了方便,但如果你要把这套DTC模型往半实物或实测方向推,必须尽早把磁链估算器里的纯积分换成带限积分器。纯积分在实际离散代码里对直流偏置和采样零漂非常敏感,积分几分钟之后磁链估算就会漂移。常见的改法是低通滤波加幅值相位补偿,或者一阶惯性网络来近似积分。这一步不会改变DTC的控制结构,只是把估算器做扎实。
我做仿真时习惯同时观测一组“参考磁链”和“估算磁链”:在电机模型内部提取真实定子磁链,输出和控制器的估算值做对比。如果两条曲线在动态过程中偏离明显,说明估算器设计有问题,而不是控制器有问题。这个对比习惯比任何一项单独调试都有用。
6.2 从经典DTC到SVM-DTC和占空比DTC
经典DTC跑顺之后,再往下走通常有两条路。一条是SVM-DTC:将磁链和转矩误差通过PI或查表生成参考电压矢量,再用空间矢量调制输出。这样做保留了DTC的转矩动态,但开关频率固定了,纹波大幅下降。另一条是占空比DTC:在一个控制周期内插入零矢量和有效矢量的比例,通过计算最优占空比来减少转矩纹波。这条路线改起来小,不用上SVPWM,适合在现有仿真模型上增量验证。
我个人更推荐先把占空比DTC做出来,因为它对原模型的改动最小,而且能让你对“零矢量作用时间”有更深的理解。之后再考虑SVM-DTC,那已经不是入门级别,而是往高性能控制器方向走了。
6.3 我建议的推进顺序
如果你完全从零开始,我给的建议顺序是:先复现一个低速轻载工况,比如500rpm、5N·m,把磁链圆和转矩纹波调顺;再往上提速,观察母线电压对磁链圆的影响;然后加转矩阶跃,验证动态响应;最后再回头研究零矢量优化和估算器改进。每一步只动一个变量,通过示波器记录波形,比对前后差异。
等你把经典DTC彻底跑明白,再回头看FOC,你会有一种很奇妙的感觉:FOC像精雕细琢的线性系统,DTC像大刀阔斧的开关逻辑。两者没有绝对的优劣,但DTC这套“简单结构却极其依赖逻辑正确性”的思路,确实能逼着你把电机模型的物理本质理解得更透。至少在搭建这版SPMSM的DTC仿真模型之后,我对定子磁链电压矢量、负载角这些概念的理解,比之前只看FOC框图时扎实多了。