简介:这份资源面向电力电子与电机控制方向的研究生、工程师及科研人员,提供NPC三电平逆变器在PLECS中的完整仿真方案,重点解决控制算法与仿真环境联调的问题。压缩包共80个文件,约863KB,以C与H源码、tlog与log日志、obj与pdb编译中间文件、vcxproj工程配置及plecs模型、dll动态库为主,构成从控制程序到仿真调用的完整链路。其中PLECS模型负责逆变器拓扑与参数配置,VS工程编译生成的DLL承载SPWM调制、SVPWM、DSOGI_SPLL锁相环、Clarke/Park变换、PID调节及ADC采样等控制逻辑,通过DllHeader接口与仿真交互。已有187人学习,适合希望理解DLL调用机制、验证三电平调制策略并优化开关损耗与谐波表现的读者参考,可在此基础上快速复现仿真、对比控制效果并排查联调问题。
1. 从一份被退回的毕设说起:为什么NPC三电平逆变器的仿真非要走DLL这条路
去年帮某高校电气工程专业的一位同学看毕设,题目是NPC三电平逆变器的控制与仿真。他最初的做法很典型:在PLECS里用C-Script模块把SVPWM算法硬写了一遍,结果跑出来的线电压波形在换流点附近全是毛刺,中点电位像喝醉了酒一样来回漂。导师看了一眼就说,控制算法和主电路耦合太紧,换一个调制策略就得重画一遍模型,这活儿没法往下做。
这个场景其实点出了很多人在做NPC三电平逆变器仿真时的通病:把控制逻辑和功率拓扑揉在一个仿真环境里。NPC三电平逆变器本身有12个开关管、6个钳位二极管、上下两个母线电容,主电路已经够复杂了,如果控制算法再用图形化模块搭,调试起来就是一场灾难。更现实的问题是,很多高校实验室的PLECS授权是教学版,C-Script模块有代码长度限制,稍微复杂一点的算法根本塞不进去。
所以这套方案的核心思路就一句话:把控制程序从仿真模型里剥出来,用VS写成独立的DLL,PLECS通过DLL模块调用。这样做的好处是控制算法可以用C语言完整实现,编译成二进制后仿真调用,既绕开了PLECS脚本模块的限制,又让控制代码可以单独调试、单独版本管理。适合正在做电力电子方向毕设或课设的同学,也适合需要快速验证三电平调制策略的从业者。
2. 需求拆解与技术选型:为什么是PLECS加DLL而不是纯Simulink
2.1 三电平逆变器仿真的三个硬需求
先把这个项目的需求说清楚。NPC三电平逆变器仿真不是跑个波形就完事,它至少要满足三个条件。
第一是拓扑精度。三电平的钳位二极管不是摆设,它决定了每个桥臂能输出三种电平状态:正母线电压、中点电压、负母线电压。仿真模型必须能准确反映这12个开关管的导通组合,以及每种组合下电流流过哪些钳位路径。用两电平的模型改一改就上,中点电位根本对不上。
第二是控制算法的可替换性。做毕设的人通常要对比不同调制策略,比如载波层叠PWM和空间矢量SVPWM,或者要改中点电位平衡的控制系数。如果每次改算法都要动仿真模型,时间全耗在拖模块上了。控制程序独立成DLL之后,改算法只需要在VS里改C代码、重新编译,PLECS那边连模型都不用重新打开。
第三是仿真速度。三电平逆变器的开关频率通常在5kHz到20kHz,仿真步长要压到微秒级才能看清换流过程。PLECS本身是解算电力电子系统的专用工具,比通用仿真器快不少,但如果控制部分用解释型脚本,每个步长都要解释执行一遍,速度会被拖垮。编译成DLL之后是本地机器码,调用开销小得多。
2.2 PLECS与DLL的接口机制
PLECS调用外部DLL靠的是DLL模块。这个模块在PLECS的库里有现成的,配置项不多但每个都要填对。
核心接口是一个C函数,PLECS在每个仿真步长调用它一次。函数签名通常是这样的:
// PLECS DLL模块的标准入口函数 // 参数说明: // t: 当前仿真时间,单位秒 // inputs: 输入信号数组指针,存放从PLECS模型传进来的采样值 // outputs: 输出信号数组指针,存放要返回给PLECS模型的控制量 // nInputs: 输入信号个数 // nOutputs: 输出信号个数 // userData: 用户自定义数据指针,可用于存放控制器状态 void plecs_dll_step(double t, const double *inputs, double *outputs, int nInputs, int nOutputs, void *userData) { // 从inputs里取出三相电流、三相电压、上下母线电容电压 double ia = inputs[0]; double ib = inputs[1]; double ic = inputs[2]; double vdc1 = inputs[3]; // 上母线电容电压 double vdc2 = inputs[4]; // 下母线电容电压 // 调用SVPWM核心算法,计算三相占空比 // 这里省略具体算法实现,后面章节展开 double da, db, dc; svpwm_calc(ia, ib, ic, vdc1, vdc2, &da, &db, &dc); // 把占空比写回outputs,PLECS模型用这些值驱动开关管 outputs[0] = da; outputs[1] = db; outputs[2] = dc; }这个函数在每个仿真步长被调用,输入是PLECS模型里测到的电流电压,输出是算出来的占空比。PLECS那边拿到占空比之后,通过比较器生成12路PWM信号去驱动开关管。
注意:DLL模块的输入输出顺序必须和PLECS模型里配置的端口顺序完全一致,错一个位置整个控制就反了。我一般会在代码开头用注释把顺序写死,改模型的时候对照着看。
2.3 为什么不用Simulink的S-Function
有人会问,Simulink也有S-Function可以调C代码,为什么非要PLECS。这里有个实际考量:PLECS的电力电子解算器对开关事件的捕捉更准。三电平逆变器在一个开关周期里有多次换流,Simulink用变步长解算器跑起来经常漏掉窄脉冲,PLECS的固定步长加事件检测机制更适合这种场景。而且PLECS的模型文件小,发给导师看的时候不用附带一堆MATLAB路径配置。
3. 总体架构设计:主电路、控制DLL与信号接口的三层划分
3.1 主电路模型的搭建要点
主电路部分在PLECS里搭,结构分三块:直流母线、NPC桥臂、输出滤波。
直流母线用两个电容串联,中间引出中点。电容值不用太纠结,通常取1mF到3mF之间,仿真是理想器件,电容值主要影响中点电位波动的幅度。两个电容的初始电压要设成相等,各为母线电压的一半,否则仿真一开始中点就是偏的。
NPC桥臂是重点。每个桥臂有4个IGBT和2个钳位二极管。PLECS的IGBT模型用理想开关加反并联二极管就行,不用上详细损耗模型,毕设阶段关注的是波形和算法,不是效率。钳位二极管的接法要仔细:上桥臂的两个IGBT中间接一个二极管到中点,下桥臂同理。接反了仿真直接报错。
输出接三相星形负载,阻感负载比较常见,电阻取10欧姆、电感取5mH左右,功率因数接近0.9。如果要模拟并网,就在输出加一个三相电压源和电感,但毕设阶段用阻感负载足够说明问题。
3.2 控制DLL的模块划分
控制程序在VS里写,建议分成三个源文件,不要全塞在一个main.c里。
第一个文件放SVPWM核心算法。三电平的SVPWM比两电平复杂,要先判断参考矢量落在哪个大扇区,再判断哪个小扇区,然后查表得到基本矢量的作用时间。这部分代码大概200行左右,是整个DLL里最核心的部分。
第二个文件放中点电位平衡控制。三电平特有的问题就是中点电位会漂,需要根据上下电容电压的差值调整小矢量的作用时间。通常用一个PI调节器,输入是vdc1减vdc2,输出是一个修正量,叠加到小矢量的占空比上。
第三个文件放接口函数和状态管理。plecs_dll_step函数放在这里,负责从inputs取数、调用算法、把结果写回outputs。另外还要处理一些状态变量,比如PI调节器的积分项,这些变量要存在userData指向的结构体里,不能在函数里用static局部变量,否则多个DLL实例会互相干扰。
3.3 信号接口的定义
PLECS模型和DLL之间的信号传递要提前定好。输入信号我一般设7个:三相电流ia/ib/ic、上下母线电压vdc1/vdc2、还有两个备用通道可以接参考电压的幅值和频率。输出信号设3个:三相占空比da/db/dc。
占空比的范围要约定好。PLECS里的比较器通常接受0到1之间的占空比,所以DLL输出的值要归一化到这个区间。如果算出来的占空比超过1或者小于0,说明参考矢量超出了线性调制区,这时候要么限幅要么报错,看你的仿真目的。
// 占空比限幅函数,防止过调制导致仿真异常 // 输入:原始占空比指针,上下限 // 输出:限幅后的值直接写回原地址 void clamp_duty(double *duty, double lower, double upper) { if (*duty > upper) { *duty = upper; // 超过上限就拉到上限 } else if (*duty < lower) { *duty = lower; // 低于下限就拉回下限 } // 在范围内则不变 }这个限幅函数看着简单,但不加的话仿真很容易崩。三电平的调制比超过1.15之后波形就乱了,限幅至少能保证仿真跑完。
4. 关键模块实现:SVPWM算法、中点平衡与DLL编译配置
4.1 三电平SVPWM的扇区判断与矢量作用时间
三电平SVPWM的核心是把参考电压矢量分解成基本矢量的组合。三电平有27个基本矢量,比两电平的8个多得多。实际实现的时候不用把27个都列出来,按大扇区和小扇区两级判断就行。
大扇区判断用参考矢量的角度。把360度分成6个60度的扇区,每个大扇区里再根据参考矢量的幅值和角度分成4个小扇区。小扇区的判断稍微麻烦一点,通常用几个线性不等式来划分。
// 三电平SVPWM扇区判断 // 输入:alpha-beta坐标系下的参考电压v_alpha, v_beta // 输出:大扇区号sector(1-6),小扇区号sub_sector(1-4) void svpwm_sector_judge(double v_alpha, double v_beta, int *sector, int *sub_sector) { // 第一步:大扇区判断,用角度 double angle = atan2(v_beta, v_alpha); if (angle < 0) angle += 2 * M_PI; // 转到0到2pi *sector = (int)(angle / (M_PI / 3)) + 1; // 每60度一个扇区 // 第二步:小扇区判断,用幅值和角度组合 // 这里用简化的边界条件,实际项目里要查表 double v_ref = sqrt(v_alpha * v_alpha + v_beta * v_beta); double v_dc = 400.0; // 母线电压,根据实际设置 double m = v_ref / (v_dc / sqrt(3)); // 调制比 if (m < 0.5) { *sub_sector = 1; // 低调制比区域 } else if (m < 0.866) { *sub_sector = 2; // 中调制比区域 } else { *sub_sector = 3; // 高调制比区域 } // 实际还有第4个小扇区,在特定角度范围内 }这段代码是简化版,实际项目里小扇区的判断要更细致。但思路就是这样:先粗判大扇区,再细判小扇区,然后根据扇区号查表得到基本矢量的作用时间。
作用时间的计算涉及伏秒平衡。每个采样周期内,参考矢量等于几个基本矢量按时间加权平均。三电平的方程比两电平多一个中点矢量,解方程的时候要注意中点电流的影响。
4.2 中点电位平衡的PI调节器实现
中点电位漂移是三电平的固有毛病。原因很简单:当某相输出中点电平时,中点会有电流流过,如果三相的中点电流不对称,上下电容的充放电就不平衡,电压就偏了。
解决办法是调整小矢量的作用时间。小矢量分正小矢量和负小矢量,它们对中点电流的方向相反。通过改变正负小矢量的时间分配,可以控制中点电流的平均值。
// 中点电位平衡PI调节器 // 输入:上电容电压vdc1,下电容电压vdc2 // 输出:小矢量时间分配的修正量delta // 状态变量存在结构体里,避免用static typedef struct { double integral; // 积分项累积值 double kp; // 比例系数 double ki; // 积分系数 double out_max; // 输出限幅上限 double out_min; // 输出限幅下限 } PI_Controller; double midpoint_balance_pi(PI_Controller *pi, double vdc1, double vdc2) { double error = vdc1 - vdc2; // 电压偏差 double p_term = pi->kp * error; // 比例项 pi->integral += pi->ki * error; // 积分项累加 // 积分限幅,防止饱和 if (pi->integral > pi->out_max) pi->integral = pi->out_max; if (pi->integral < pi->out_min) pi->integral = pi->out_min; double output = p_term + pi->integral; // PI输出 // 输出限幅 if (output > pi->out_max) output = pi->out_max; if (output < pi->out_min) output = pi->out_min; return output; }PI参数怎么调?我一般先把ki设成0,只调kp,让中点电压的波动幅度降到母线电压的5%以内,然后再慢慢加ki,把稳态误差消掉。kp太大中点电压会振荡,ki太大响应会变慢。通常kp取0.01到0.05,ki取1到10,具体看母线电容和负载大小。
4.3 VS工程配置与DLL编译
VS里新建一个动态链接库工程,平台选x64,因为PLECS通常是64位的。配置类型选动态库.dll,不要选静态库。
导出函数要用extern "C"和__declspec(dllexport)修饰,否则C++的名称修饰会让PLECS找不到入口。
// 导出函数声明,必须加extern "C"防止名称修饰 extern "C" __declspec(dllexport) void plecs_dll_step(double t, const double *inputs, double *outputs, int nInputs, int nOutputs, void *userData) { // 函数体 }编译的时候注意运行库选项。PLECS加载DLL的时候,如果DLL依赖的C运行时库版本和PLECS不一致,会加载失败。稳妥的做法是选多线程调试DLL(/MDd)或者多线程DLL(/MD),不要选静态链接。
编译完成后,在PLECS的DLL模块里指定生成的.dll文件路径。PLECS会在仿真开始时加载这个DLL,如果路径不对或者依赖缺失,PLECS会弹窗报错,错误信息通常能看到缺哪个dll。
提示:每次改完C代码重新编译之后,PLECS里要重新加载模型或者重启仿真,否则PLECS可能还在用旧版本的DLL。这个坑我踩过好几次,改了代码跑出来的波形和没改一样,查了半天才发现是DLL没更新。
5. 避坑与常见问题排查:从波形异常到DLL加载失败
5.1 线电压波形出现尖刺
现象:仿真跑出来的线电压在电平切换的地方有很窄的尖刺,幅值能到母线电压的1.5倍。
原因:通常是死区时间没设对。PLECS的IGBT模型可以设死区,如果死区太小,上下管直通,仿真里会出现瞬时短路。如果死区太大,输出电压会有畸变。另一个可能是仿真步长太大,换流过程没被准确捕捉。
解决:死区时间设成2微秒左右,仿真步长压到1微秒以下。如果还有尖刺,检查钳位二极管的参数,反向恢复时间设成0,仿真是理想器件,不需要模拟反向恢复。
5.2 中点电位持续偏移不回来
现象:上下电容电压的差值越来越大,PI调节器好像没起作用。
原因:先检查PI的输出有没有正确叠加到小矢量的作用时间上。常见错误是符号搞反了,本来应该增加正小矢量时间,结果减了,偏差反而被放大。另一个可能是PI的输出限幅太窄,修正量不够。
解决:把PI的kp临时调大,看中点电压有没有反应。如果有反应但方向反了,就把输出乘-1。如果没反应,检查小矢量时间分配的代码逻辑,确认修正量确实写进了占空比。
5.3 DLL加载失败
现象:PLECS启动仿真时弹窗报错,提示无法加载DLL或者找不到入口点。
原因:三个常见原因。一是DLL编译成了32位,PLECS是64位,位数不匹配。二是导出函数没加extern "C",函数名被C++修饰了,PLECS按原名找不到。三是DLL依赖的运行时库在目标机器上缺失。
解决:确认VS工程平台是x64。确认导出函数声明加了extern "C"和__declspec(dllexport)。用Dependency Walker或者VS自带的dumpbin工具查看DLL的导出符号,确认函数名是plecs_dll_step而不是被修饰过的名字。
5.4 仿真速度突然变慢
现象:仿真跑着跑着速度骤降,原来1秒仿真只要10秒,现在要1分钟。
原因:通常是DLL里出现了死循环或者浮点异常。比如除零操作,或者atan2的参数都是0。PLECS调用DLL的时候如果函数不返回,整个仿真就卡住了。
解决:在DLL里加保护,除法之前判断分母是否接近0,atan2之前判断两个参数是否同时为0。另外可以在DLL里加一个计数器,每调用10000次往文件里写一行日志,这样卡住的时候能知道大概在哪个阶段出的问题。
5.5 输出波形和理论对不上
现象:占空比算出来是对的,但PLECS里的输出电压幅值不对。
原因:检查PLECS模型里比较器的载波幅值。DLL输出的占空比是0到1,PLECS的比较器要把这个值和载波比较。如果载波是三角波,幅值设成了-1到1,那占空比0.5对应的输出就和预期不一样。
解决:统一量纲。要么DLL输出0到1的占空比,PLECS载波用0到1的锯齿波;要么DLL输出-1到1的调制波,PLECS载波用-1到1的三角波。两种方式都行,但不能混。
6. 验收与答辩:怎么证明这套仿真真的跑通了
6.1 验收指标与测试用例
仿真跑通不等于方案合格。答辩的时候导师不会只看波形好不好看,他会问几个关键指标。我一般建议按下面这张表准备测试用例。
| 测试项 | 测试条件 | 合格标准 | 观察位置 |
|---|---|---|---|
| 线电压波形 | 调制比0.8,阻感负载 | 五电平阶梯清晰,无尖刺 | 输出线电压示波器 |
| 相电压波形 | 同上 | 三电平阶梯,台阶幅值等于母线电压一半 | 输出相电压示波器 |
| 中点电位波动 | 同上 | 波动幅度小于母线电压的5% | 上下电容电压差 |
| 输出电流THD | 同上 | 小于3% | 电流FFT分析 |
| 动态响应 | 负载突变50% | 恢复时间小于10ms | 输出电压包络 |
| DLL调用耗时 | 统计每个步长平均耗时 | 小于仿真步长的10% | DLL内部计时 |
这张表里的数值不是死的,根据你的母线电压和开关频率可以调整。但每一项都要有明确的合格线,答辩的时候直接拿数据说话。
6.2 答辩时怎么讲清楚DLL调用的价值
很多同学答辩的时候只讲波形,不讲架构。导师听完不知道你到底做了什么。我建议用三句话把DLL调用的价值讲清楚。
第一句:控制算法和主电路解耦。主电路在PLECS里搭,控制算法在VS里写,两边通过标准接口通信。改算法不用动模型,改模型不用动算法。
第二句:算法可以单独验证。DLL里的SVPWM函数可以写一个main函数单独跑,输入模拟的电压电流,输出占空比,用printf打出来看。不用每次都启动PLECS。
第三句:仿真速度可控。编译后的DLL是机器码,比PLECS脚本快一个数量级。同样的仿真时长,DLL方案能跑更小的步长,波形更细。
6.3 一个我踩过的坑
第一次做这个方案的时候,我把PI调节器的积分项写成了static局部变量。单次仿真没问题,后来想同时跑两组不同参数的仿真做对比,发现第二组的结果受第一组影响。查了半天才想起来static变量在DLL加载期间一直存在,多个仿真实例共享了同一个积分项。
从那以后我每次写DLL,所有需要保持的状态变量都塞进userData指向的结构体里,在DLL初始化的时候分配内存,在仿真结束时释放。PLECS的DLL模块支持初始化函数和终止函数,把malloc和free放在那里,就不会出现状态串扰的问题。
这套NPC三电平逆变器的PLECS仿真加DLL控制方案,从主电路搭建到控制算法实现再到联调验收,整个流程走下来大概需要两到三周。核心难点不在PLECS建模,也不在C语言编程,而在两者的接口对齐和调试方法。把接口定义清楚,把状态管理做干净,剩下的就是调参数和看波形了。希望帮到你。
本文还有配套的精品资源,点击获取