☰
Simulink 模型到西门子 PLC 的稳定落地:从仿真到现场的高效转化
2026/10/14 12:31:55 网站建设 项目流程

搞控制的人最舒服的状态是什么?模型在 Simulink 里建好,仿真曲线跑通,然后一键把代码生成到 PLC 里,现场设备的实际数据和仿真几乎重合。但真实情况往往是模型只能留在电脑里,算法要靠手工翻译成 SCL 或者结构化文本,翻译完了还得处理数据类型、时序、初始化一堆问题,调试到天亮都未必对得上。近两年我在几个部署项目里反复做 Simulink 转博图(TIA Portal)的工作,踩了不少坑,也慢慢摸出了一套能稳定落地的流程。这篇就把最重要的核心要点梳理一遍,适合手头已经有 Simulink 模型、准备部署到 S7 系列 PLC 的人,也适合第一次接这种任务、连从哪下手都不知道的新手。我不去讲那些细枝末节,只聊能直接拿来用的东西:模型侧要做什么、代码生成怎么配、博图里怎么导、现场运行怎么调。

1. 整体思路与方案取舍:先分清要不要自动生成

1.1 这个事到底是什么

Simulink 转 PLC,本质上解决一件事:把在 MATLAB 环境里建模、仿真、验证过的控制算法,变成可以在 PLC 上周期执行的代码。PLC 的执行模型和 Simulink 完全不一样。Simulink 里时间可以自由推进,有连续求解器、变步长、事件触发;而 PLC 只有一个固定的循环节拍,物理上只能每隔一定时间扫描一次程序,在这个节拍内把所有任务执行完。所以“转换”这个动作,不只是语法翻译,而是把一种以“模块图 + 时间连续”为核心的计算方式,映射到“周期扫描 + 函数调用”的工业执行环境里。

这个过程绕不开的中间层是代码生成器。Simulink PLC Coder 是官方提供的工具,能生成结构化文本(ST/SCL)代码,也可以生成 C 代码。博图(TIA Portal)这边,对 ST 代码的支持靠的就是 SCL 语言,TIA Portal 可以直接导入外部源文件,把 ST 代码编译成可执行对象。整条链路就是:Simulink 模型 → PLC Coder 生成 ST 代码 → TIA Portal 导入 → 外层封装成功能块 → 由 PLC 周期调用。注意,这里谈的“适配博图”,指的就是 SCL 这一环的兼容性,而不是说 PLC 里能直接跑 Simulink 的二进制模型,这种概念上的区分很多人一开始没意识到,后面就会瞎折腾。

还有一点值得先说清楚:为什么不是直接转成梯形图?梯形图擅长开关逻辑、互锁、电气回路,但你让它去表达一个卡尔曼滤波的状态更新方程,写出来会非常痛苦,而且可读性极差。SCL 是文本语言,能写复杂的数学表达式、数组运算、循环,本质上接近通用编程语言。这也是为什么自动生成路径几乎都会选 ST/SCL 作为输出语言,而不是梯形图。认清这一点,你才不会在工具配置里找半天“梯形图生成选项”。

1.2 自动生成和手写移植怎么选

这是最应该想清楚的问题,也是最容易被忽略的问题。很多人一上来就想着把整个模型全部生成到 PLC,结果生成的代码量巨大、运行周期撑不住,最后又退回手写。我个人的判断标准很简单。

如果算法涉及大量矩阵运算、状态空间求解、观测器、滤波器、预测控制这类数值密集型计算,自动生成几乎是最好的选择。因为手写这类数学逻辑非常容易出错,而且很难保证和仿真结果一致。Simulink 模型本身就是经过验证的算法描述,自动生成相当于把这份描述直接翻译成目标代码,一致性远高于人工转译。

如果控制逻辑就是简单的开关逻辑、顺序控制、布尔判断,那自动生成不仅不划算,还会因为生成的代码结构复杂而拖慢扫描周期。比如一个电机起保停电路,手写 SCL 十行以内,生成出来可能几百行,完全没必要。而且生成的代码内部包含了大量临时变量和辅助函数,资源占用高,纯逻辑控制场景根本吃不消。

还有一个折中方案,我在这几个项目里用得最多:把模型里最核心的算法部分做自动生成,外围的 IO 处理、使能逻辑、故障诊断仍然手写。这样做的好处是,复杂计算交给可靠的生成器,外围逻辑保持人的灵活控制,既保证核心算法不出错,也把代码规模控制在合理范围。

选自动生成之前,要做一次可行性审查:模型是否离散化、采样时间是否合理、数据类型是否已收敛、生成代码有没有用到 PLC 不支持的动态内存、指针、递归调用。这些检查最好在动手前完成,否则配置做到一半才发现模型根本不适合生成,前面功夫全白费。

2. 模型侧准备与代码生成配置:决定成败的第一步

2.1 模型离散化与采样时间的设定

PLC 是周期执行,模型必须工作在离散状态。这一点怎么强调都不过分。如果在模型里用了连续积分环节或者连续求解器,生成的代码即便编译通过,运行起来也会因为缺少真实积分环境而出现各种怪异现象。比如明明是个稳定的闭环,一到 PLC 里就震荡;或者某个状态变量不断累积漂移,几分钟后直接饱和。所以第一步就是检查模型里的所有积分环节,全部改成离散版本。常用的做法是用零阶保持器离散化,或者在 Simulink 里把求解器设置为固定步长离散求解器,先仿真确认行为不偏差太大,再进入代码生成流程。

采样时间的设定是另一个关键。理论上采样时间要满足控制带宽的需求,至少比被控对象中最高频的动态过程快 5 到 10 倍。比如你的对象动态响应最快是 100ms 级别,采样步长保守起见就取 10ms 到 20ms。PLC 侧还要考虑扫描周期。如果 PLC 的循环中断周期是 10ms,模型采样周期最好不要设成 25ms 这种非整数倍关系,否则会产生相位抖动。工程上常见做法是采样步长取 PLC 扫描周期的整数倍,比如 1 倍、2 倍、5 倍,这样每次调用时机都是确定的,不会累积偏移。

还有一个细节:固定步长求解器的类型选择。离散模型最稳妥的是选“单任务离散”(SingleTasking)模式,或者把求解器类型直接选为离散。如果在配置里还保留连续求解器,代码生成时会自动插入一些连续求解器的支持代码,这些代码在 PLC 里根本用不上,只会白白增加体积和计算开销。我见过有人生成的代码里带了完整的 ODE 求解器辅助函数,原因就是模型里残留了连续模块。

2.2 PLC 代码生成的几项关键配置

配置这一步,不同人用的工具版本不一样界面可能略有差异,但逻辑基本一致。我一般会走这么几个步骤。

第一步,在模型的 Configuration Parameters 里选择 PLC Coder 作为代码生成目标,目标平台选择和你实际部署的 PLC 类型匹配,这里就是选 S7 系列相关支持。

第二步,语言选择结构化文本 ST。这里要确认一下生成模式:有些版本会默认生成 C 代码,而不是 ST,需要手动切过去。切过去以后,代码生成报告里的输出文件后缀就会变成 .scl 或 .st。

第三步,函数封装方式。通常选生成一个 step 函数和一个 initialize 函数。推荐关闭“生成单独文件”的选项,让代码整体打包,方便导入 TIA Portal。因为如果拆成多个小文件,导入博图时要逐个处理,文件之间如果有相互引用,编译顺序稍不对就报错。

第四步,代码优化级别。优化要打开,可以减少代码体积和局部变量,但不要开到最高级。最高级可能会改变某些内部计算的顺序,在数值敏感算法里会产生微小偏差,比如一个矩阵连乘展开以后,浮点累加顺序变了,结果就差几个单位。对控制算法来说,这种差随迭代会累积,非常不好排查。

第五步,注释和文档选项。生成的代码里默认带一堆注释,不影响执行,但导入博图后代码体积会明显变大。建议把注释量降到最低,尤其是那些“Generated by Simulink”之类的头部注释。真正运行的时候没人看这些,PLc 内存才是硬约束。

2.3 数据类型和接口的预处理

代码生成之前,模型里所有和外部交互的信号,都应该明确数据类型和维度。我见到最多的问题就是 double 类型。Simulink 里默认数据类型是 double,也就是双精度浮点,64 位。S7 系列 PLC 里的浮点数是 REAL,单精度,32 位。这个映射关一旦处理不好,仿真结果和现场数值对不上是必然的,不是差一点,而是能差出一个数量级。

我的做法是,建模阶段就把关键信号的数据类型显式设置成 single,然后在仿真时也用 single 跑。这样仿真时看到的精度表现,就是将来 PLC 里实际运行的精度表现,没有任何意外。Code Generation 本身的自动映射也有,但我从来只把它当作兜底,而不是依赖。

还有一个容易被忽略的地方,是模拟量输入输出的范围。Simulink 里你随便定义一个 0 到 100 的百分比信号,PLC 侧对应的可能是 0 到 27648 的整数编码。这类缩放关系必须在模型侧用 Gain 模块提前处理,而不是部署到 PLC 以后再手动换算。为什么坚持在模型侧处理?因为模型侧处理完之后,仿真结果和现场实际值可以直接对得上,排查问题少绕一大圈。如果你让 PLC 侧做线性转换,仿真输出和现场实际之间永远隔着一层换算,每次调试都要同时看两边,效率极低。

3. 工具链与目标平台适配:搞清楚你手上的“博图”是什么

3.1 博图版本与 SCL 支持差异

同一个“博图”名称下面,不同版本的 SCL 编译能力差别不小。较新版本对复杂数组、结构体、字符串处理的支持明显更好,编译速度和诊断信息也完善得多。早期版本有时会出现对某个 ST 语法报“不支持的表达式”,但同样一段代码换到新版上就能正常编译。所以做这类项目,我强烈建议先把博图版本确认清楚,再决定模型里怎么组织代码。如果手头的博图版本比较旧,那模型侧就要尽量避免生成过于前沿的语法结构,比如避免在内部代码里使用复杂的嵌套 FOR 循环或动态数组访问。

这里有个教训:有一次我把目标平台配置成了某个比较新的 PLC 型号,生成的 ST 代码里嵌了一段针对长数组的初始化指令。导入到现场那台版本偏旧的 TIA Portal 里,编译直接挂掉。后来只能回到 Simulink 里把数组拆成标量接口,重新生成一遍,才勉强通过。所以,版本适配不是到了博图侧才想的,模型生成配置阶段就要把现场环境一起考虑进去。

3.2 S7-1200 与 S7-1500 的资源边界

S7 系列里最常见的是 1200 和 1500 两个子系列,它们的资源边界差异很大。S7-1500 的浮点运算能力强,内存也大,适合跑带有大量矩阵运算或状态观测器的模型。S7-1200 是小中型 PLC,CPU 主频有限,内存也小,跑比较大的生成代码会非常吃力。我有一个测算习惯:先把生成的 ST 代码行数拿到,再估算里面需要多少字节的静态变量区。一般来说,超过几千行带浮点运算的代码,就不太建议往 S7-1200 里塞了,硬塞进去要么扫描周期暴涨,要么编译时报内存不足。

如果项目还没定 PLC 型号,我的建议是:只要模型里有超过几十个状态变量的算法,优先选 1500 系列,留足余量。不要为了省几百块硬件成本,让后面的算法优化和代码瘦身占用大量调试时间。硬件选型省下来的钱,远远不够覆盖现场调试的人工成本。

3.3 ST 代码与 C 代码的选择

除了 ST 代码,PLC Coder 也能生成 C 代码。很多人会在这一步纠结。我直接给结论:在 S7 系列 PLC 上,标准路径就是 ST/SCL,不用考虑 C 代码。原因很简单,常规 S7 CPU 没有开放 C 运行时环境,C 代码不能直接作为 PLC 程序运行,除非设备本身是带操作系统的工业 PC 或者软 PLC 方案,那就不是传统意义上的博图项目了。ST 代码则是 TIA Portal 原生支持的语言之一,导入、编译、下载都是顺畅的。

所以,路径选择其实是平台决定的,而不是个人偏好。如果你确认将来要部署到基于 PC 的软 PLC 或带实时系统的控制器上,再考虑 C 代码的路径也不迟。但在普通的 S7 项目里,谈 C 代码只会绕远路。工具链的适配就到此为止,明白这些边界之后,再进博图做集成会顺很多。

4. 博图适配与集成落地:代码只是开始

4.1 生成的代码长什么样

用 PLC Coder 生成的 ST 代码,结构上一般包含两个核心函数:一个是初始化函数,负责把内部状态变量设置成初始值;另一个是 step 函数,也就是周期执行的主计算函数。step 函数的输入参数对应模型中的输入端,输出参数对应模型中的输出端。函数内部通常会有很多临时变量,代码结构看起来像一个比较大的 SCL 程序块。下面是个简化的结构示意:

// 示意结构,实际代码会比这个多很多 FUNCTION_BLOCK FB_ModelWrapper VAR_INPUT u : REAL; // 模型输入 END_VAR VAR_OUTPUT y : REAL; // 模型输出 END_VAR VAR x : ARRAY[0..9] OF REAL; // 模型内部状态 tmp : REAL; // 临时变量 END_VAR BEGIN // --- 这里粘贴 Step 生成代码 --- // y := ...; // tmp := ...; // 状态更新逻辑 END_FUNCTION_BLOCK

在 TIA Portal 里,你不会直接把这段代码当成主程序运行,而是先建一个 FB(功能块),在 FB 的声明区里定义好输入、输出和静态变量,然后把生成的 step 函数代码整体复制到 FB 的代码区。这个 FB 就是模型的外壳,上层 OB 只需要周期调用这个 FB 就行。初始化函数可以在 FB 的初始化部分调用一次,或者用 FB 静态变量的默认值来实现。这样外层结构非常清晰:输入输出全部暴露在 FB 的接口上,想监控哪个变量都方便,查问题的时候不用去翻一大堆内部局部变量。

4.2 TIA Portal 导入与外层功能块封装

在博图里导入 ST 代码,有两条路。第一条是用“外部源文件”功能,在项目树里右键添加新源文件,选 .scl 后缀,把生成的代码贴进去,编译完成后可以生成一个功能块。这个方式适合代码量大、结构完整的工程。第二条是手动处理:新建一个 FB,把变量声明区先按代码里的输入输出补齐,再把代码粘贴到实现区,然后编译。如果代码规模不大,我更喜欢第二种,因为变量声明和注释都掌握在自己手里,排查问题顺手。

无论走哪条路,有三件事必须注意。

变量命名冲突是高频问题。生成的代码内部变量名可能带有特殊前缀,博图不保证完全兼容,遇到报错就改变量名,但建议在生成前就在 Simulink 侧做一个全局命名前缀设置,统一加个短前缀,比如 mdl_,能避免大多数冲突。

数组下标习惯也要统一。博图数组可以自定义上下界,我一般统一用 LOWER_BOUND 0 来对齐 C/ST 习惯,这样从代码里复制过来的数组访问逻辑不用改。如果 Simulink 侧的数组默认是 1 基索引,生成的代码里可能会差一个下标,这种错误编译能通过,但运行结果完全不对,非常隐蔽。

FB 的调用方式同样要注意。封装好的 FB 不能直接往 OB1 里一放就完事,还要确定调用位置。建议单独建一个技术功能块专门负责周期调用,便于运行监控。直接把算法 FB 散落在 OB1 的各个角落,后面想测量这段代码的执行时间都很难操作。

4.3 轮询调用与周期设计

PLC 运行模型算法,绝对不能直接挂在 OB1 上。OB1 的扫描周期和程序复杂度强相关,同一段代码在不同扫描周期里执行到的位置可能完全不同,时序不稳定对算法来说是灾难。我的做法是建一个循环中断 OB,设置固定中断周期,比如 10ms,再在中断里调用封装好的 FB。这样模型的计算频率是精确可控的,不受主程序其他逻辑影响。

循环中断的周期要和模型采样步长保持一致性。如果模型采样时间是 20ms,而 PLC 循环中断是 10ms,那 step 函数每执行一次需要等待两个中断周期,逻辑上也能跑,但复杂状态下容易出现状态滞后和时序抖动。更好的做法是循环中断周期等于模型采样步长,或者采样步长是中断周期的整数倍。如果是整数倍关系,step 函数外层要加一个计数逻辑,只有计数满时才真正执行 step。这个执行标志位可以作为 FB 的输出暴露出来,方便监控。

实际部署时还要留一个性能余量参数:step 函数的平均执行时间占循环中断周期最好控制在 50% 以内。如果占太高,后续模型迭代加功能或者现场负载增加时,很容易超时触发 PLC 的循环超时报警。这个参数建议在上线前就测好,别等到现场出了问题再抓瞎。

5. 部署中的常见问题与排查技巧实录

5.1 导入编译阶段的经典报错

我把实际过程中碰到的问题整理成一个速查表,方便对照:

现象原因处理办法
导入时提示语法不支持生成了特定平台扩展语法,或源文件编码不符合预期检查生成选项,把平台相关选项全部取消,确认编码为 UTF-8
函数名重复step 函数名与博图已有对象重名生成前在代码接口里配置函数命名前缀
数组越界多维数组拍平后下标错位统一数组定义上下界,用 LOWER_BOUND 0
数据类型不匹配double 被映射为 REAL 后,某些接口还要和 DINT 对接在模型侧显式把接口转换成需要的类型

表格里每一条都可以展开。就拿第一类来说,我遇到过生成的 ST 文件里带了一些由代码生成工具额外加上的平台宏定义,博图转译的时候直接报“未知标识符”,最后只能把代码里无关的部分删掉再编译。所以导入之前,先打开源文件扫一眼,是很有必要的习惯。不要指望一键导入永远顺利,多留出十分钟做检查,能省出一个晚上的排查时间。

数组越界这类问题,最典型的场景是 Simulink 侧用了一个二维矩阵接口,生成到 ST 里变成二维数组。如果模型里对矩阵的索引方式用的是 C 语言习惯(从 0 开始),而你在博图里不小心把数组下限定义为了 1,那每一个访问都会错位,运行结果完全不可理喻。排查时可以先把所有数组下限改成 0,如果现象立刻消失,那就是索引约定不一致。

5.2 运行阶段的偏差、时序与资源问题

编译通过不代表运行就对。最典型的问题是仿真曲线和现场曲线对不上,不是差一星半点,而是趋势都对不上。这时候先不要怀疑代码生成有 bug,90% 的概率是采样时间不一致,或者模型侧和 PLC 侧的数据类型精度不一致。我的排查顺序是:先确认循环中断周期和采样步长,再检查所有接口数据的类型和缩放,最后再对比关键中间变量的数值。中间变量对比这一招最有用,它能帮你在模型内部定位到第一个产生偏差的位置,而不是在输出端猜原因。

第二个高频问题是 CPU 占用过高。生成代码虽然看起来是一堆文本,但里面常常有大量嵌套循环和矩阵计算。S7-1200 这类小型 PLC 跑起来尤其吃力。建议在上线前做一个性能估算,把 step 函数平均执行时间控制在循环中断周期的 50% 以内。如果超了,优先优化模型里的矩阵运算,比如用直接公式替代复杂的矩阵求逆,而不是去优化代码结构本身。

第三个问题是状态初始化。生成的初始化函数只在 FB 首次调用时执行一次,如果中途修改过模型并重新下载 PLC 程序,老的状态可能残留,导致输出跳变。解决办法是在 FB 里加一个“模型复位”输入,或者用启动 OB 里的触发逻辑,把初始化函数强制调用一次。这个细节很多文档不会写,但现场基本都会遇到,提前设计好能少很多麻烦。

5.3 一套我实测好用的推进流程

最后分享一个自己反复用的推进顺序。拿到模型后,我不会先去做代码生成,而是先把模型里所有可能引起部署问题的地方标记出来,包括连续积分环节、int 型接口、上升沿检测模块、维度不明确的信号。然后把任务分成两类:一类需要在模型侧修改,另一类可以在 PLC 侧通过外层封装解决。这样就不会在工具配置里反复返工。

具体流程大概是:模型离散化检查 → 采样周期确定 → 接口数据类型规范化 → 代码生成 → 导入博图 → FB 封装 → 循环中断部署 → 空载仿真对比 → 连接 IO 实机验证。每一步都有对应的输出物,比如采样周期确定后就写进项目文档,这样后续迭代时有据可查。尤其是代码生成那一步,建议先生成一个最简单的测试模型,比如一个增益模块加一个积分器,把整个链路从生成到博图运行完整跑通,再换成真实模型。很多人一上来直接拿完整模型去生成,到最后一步出问题,定位范围会非常大。

我个人习惯在每一步切换前都做一次“目标环境对照”:问自己,这段逻辑在 PLC 里会以什么频率执行、有什么资源限制、和模型里的仿真假设是否一致。把这个问题想清楚,比记住任何具体配置项都重要。工具只是执行者,真正决定成败的是对两个平台差异的理解。

我自己在这几轮项目里最深的体会是:Simulink 转 PLC 这件事最磨人的不是工具链,而是心态。总觉得自动生成就应该是一键完成,却忘了 PLC 和 Simulink 是两种执行模型,一个时间自由,一个严格周期。把模型离散化、把数据类型显式收敛、把采样时间对齐,这三件事做到位,后面基本是水到渠成。顺便分享一个最后的小技巧:在上线前,把生成代码里的所有常量调出来和 Simulink 模型里的参数逐个核对一遍,经常会有意外收获。我至少碰到过两次,模型中某处还是默认的 0.5、1.0,而实际工程参数应该是 0.05、10 的情况。这类问题不会报错,但会让现场的曲线非常难看,多留这一步,能帮你省掉大量排查时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询