跳到正文
Learn-LAD
PLC AI 实战/写一段 SCL:从需求到编译 0 错误 0 警告

写一段 SCL:从需求到编译 0 错误 0 警告

一段输送带分拣需求:让智能体通过 TiaLink 在你的 TIA Portal 工程里写 SCL,导入、编译到 0 错误 0 警告,再读回核对。

入门 第 1 课 30 分钟 TiaLink

学完你能:独立走完「读工程 → 写需求 → 生成 SCL → 导入编译 → 读回核对」,并说清哪些环节不能交给 AI。

概述

本课用一段「输送带分拣」需求,完整走一遍:智能体读工程 → 生成 SCL 功能块 → 通过 TiaLink 导入博途 → 编译到 0 错误 0 警告 → 读回核对 → 保存。

本课流程已在 TiaLink 1.1.0.0 + TIA Portal V19 + S7-1200(CPU 1214C,V4.6) 上实测跑通:最终编译 0 错误 0 警告,读回接口与需求一致。实测中撞到的两个真实坑(计时器调用漏 PT、输出未初始化)都写进了下面的实际过程和「常见坑」——这恰好是本课最想教的东西:AI 写的代码能不能用,由编译器和读回核对说了算,不由 AI 自称。

本课按一次真实运行来讲:你只输入一段需求,其余都是 agent 的工作。下面会告诉你它中间做了什么、哪里可能需要找你、最后你得到什么。

口径:AI 是辅助,改动必须人工复核。安全功能、联锁与急停逻辑不交给 AI。

你将学会

  • 把 TiaLink 配成 MCP 服务,让 Claude Code / Cursor / VS Code 这类客户端直接操作博途;
  • 知道一次真实交互里用户要做什么(其实只有输入需求 + 少量确认),agent 会做哪些工作;
  • 工程里没有 PLC 设备(空项目)时,agent 会先建一台 CPU——否则导入直接失败;
  • 看懂 agent 的「导入 → 编译 → 修错」循环,以及两个高频真实报错:计时器调用漏 PT、输出未初始化;
  • 用「验证清单」抽查结果:编译 0 错误 0 警告、接口一致、已保存。

前置条件

  • Windows 机器,已安装 TIA Portal V19(TiaLink 当前基于 V19 与 Openness 构建);
  • 新建一个空项目:在 TIA Portal 里新建一个项目,名字随意(例如 SorterDemo),不要在正在用的工程上练。空项目里没有设备也没关系——本课过程会建一台 CPU;没有带 PLC 软件的设备时,导入会直接失败(实测报错:No device with PLC software was found in the project.);
  • TiaLink:在 TiaLink 页面 下载并启动,连接当前博途工程;建议启用 API Token。启动后先看健康状态:页首状态条(或让智能体做一次健康检查)应显示已连接和项目名;如果健康检查里有白名单一项不通过,TIA 会在写入时弹授权窗——那一下只能人工点确认,可以先跑一次 TiaLink.Helper.exe 把白名单补上;
  • 一个支持 Streamable HTTP MCP 的客户端(Claude Code、Cursor、VS Code 等),MCP 端点为 http://localhost:8080/mcp。

步骤

真实使用只有一次输入:你把需求说清楚。之后读工程、建 CPU、写 SCL、导入、编译修错、读回核对、保存,都是 agent 自己做的。需要你的地方基本只有两处:它先报告后改动时你点个头,以及最后抽查看结果。

第一步:配好 TiaLink 与 MCP

动手写需求之前,只做两件一次性的事(之后每次用都不再需要):

  1. 在博途那台机器上启动 TiaLink,打开目标工程。页首状态条会自动检测(也可以在客户端里让 agent 做一次健康检查)。

页首状态条:已连接 TiaLink(项目:test7,模式:attached)
课程页按你设置的地址检测到 TiaLink(实测截图)

  1. 在客户端的 MCP 配置里加一个 Streamable HTTP 服务,URL 指向 /mcp 端点。地址填你实际的 TiaLink 网关地址:TiaLink 与博途同机部署;MCP 客户端在另一台机器时,填博途那台机器的局域网地址,端口以 TiaLink 启动配置为准。

先在本页顶部点「设置」填一次(存在本机,示例库页面也用同一个地址),下面的 URL 会跟着更新,点「复制」直接贴进客户端配置:

MCP
{
  "mcpServers": {
    "tialink": {
      "type": "http",
      "url": "http://localhost:8080/mcp",
      "headers": { "Authorization": "Bearer <你的 TiaLink API Token>" }
    }
  }
}

看到什么:客户端列出 TiaLink 的工具(名字以 tialink_ 开头,覆盖健康检查、读工程、导入、编译、导出、保存等)。工具数量随 TiaLink 版本变化,不必对数字,能看到这组前缀就对了。

第二步:你输入的(全部内容)

把下面这段发给 agent(需求部分可以换成你自己的):

帮我在当前博途工程里实现一个功能块,并直接做到可交付:

需求:写一个 FB FB_Sorter(SCL),输入:Start(Bool)、SensorA(Bool)、SensorB(Bool)、Reset(Bool);输出:DiverterA(Bool)、DiverterB(Bool)、Running(Bool)。Start 上升沿后 Running 置位;SensorA 触发后 DiverterA 输出 2s;SensorB 触发后 DiverterB 输出 2s;两路互锁;Reset 复位所有输出。定时器用命名的 IEC 定时器实例(TON_TIME,声明在 Static 区)。

要求:

  1. 先只读工程,告诉我你看到了什么;如果工程里没有 PLC 设备,先查订货号建一台(先 dry-run 再创建);
  2. 实现这个块并导入工程;自己编译并读取编译结果,报错就修,重复到 0 错误 0 警告,不要让我手工粘贴输出;
  3. 读回块核对需求(接口、时序、互锁、复位),输出「需求 → 实现 → 结论」对照表;
  4. 清理导入留下的多余外部源文件,最后保存工程并确认没有未保存修改;
  5. 全程只动这个块和必要的新建设备,不要修改工程里的其他对象;涉及安全、联锁与急停的部分提醒我人工复核。

发完这一条,你等着看结果就行。下面是它实际会做的事(本次实测过程)。

agent 实际做了什么:读工程,发现是空项目

它先确认 TiaLink 已连接、项目名和写入权限,然后只读工程。新建空项目的回报是「没有 PLC 设备 / 设备树是空的」——这是预期,不是失败。

空项目导入报错:没有 PLC 设备时导入会直接失败
读完工程后它会告诉你:这样导入会失败,需要先建 CPU

agent 实际做了什么:查订货号、建一台 CPU

它自己查订货号、挑最新固件版本,先 dry-run 报告将要创建的设备,再创建。

创建 CPU 的结果
查订货号 → 创建 S7-1200(PLC_1)

这里可能会找你确认一次:如果 agent 严格按「先报告后改动」的习惯办事,它会问你要不要建这台 CPU;回复「建」即可。想省这一步,可以在需求里预先授权(上面的提示词已经写了「先查订货号建一台」)。

创建后工程里出现 PLC_1 和 Main (OB1),它会重新读一遍确认,再继续。

agent 实际做了什么:写第一版 SCL

它从需求生成块接口和逻辑——接口与需求逐条对应,定时器是 Static 区的命名实例:

SCL
1FUNCTION_BLOCK "FB_Sorter"2VAR_INPUT3   Start : Bool;4   SensorA : Bool;5   SensorB : Bool;6   Reset : Bool;7END_VAR8VAR_OUTPUT9   DiverterA : Bool;10   DiverterB : Bool;11   Running : Bool;12END_VAR13VAR14   PulseA : TON_TIME;15   PulseB : TON_TIME;16END_VAR17BEGIN18   IF #Start AND NOT #Reset THEN19      #Running := TRUE;20   END_IF;2122   #PulseA(IN := #Running AND #SensorA AND NOT #PulseB.Q,23           PT := T#2s);24   #DiverterA := #PulseA.Q;2526   #PulseB(IN := #Running AND #SensorB AND NOT #DiverterA,27           PT := T#2s);28   #DiverterB := #PulseB.Q;2930   IF #Reset THEN31      #Running := FALSE;32      #PulseA(IN := FALSE);33      #PulseB(IN := FALSE);34      #DiverterA := FALSE;35      #DiverterB := FALSE;36   END_IF;37END_FUNCTION_BLOCK

看起来完全符合需求,也能生成块——但编译不过。下面才是它真正花时间的地方。

agent 实际做了什么:导入 → 编译 → 自己修到 0 错误 0 警告

它把块导入工程并编译。第一轮:2 错误 2 警告。然后它自己读取编译输出、定位原因、修改、重新导入,重复这个过程,直到 0/0。你不需要粘贴任何报错。

第一版 SCL:2 错误 2 警告
第一版编译结果:PT 缺失与输出未初始化

这两个报错就是本课最值得记住的地方:

  • [Error] Parameter 'PT' has to be used. —— 计时器每次调用都必须带 PT,包括复位分支里那句 #PulseA(IN := FALSE);。正确做法是每扫描周期只调用一次计时器,把复位条件并进 IN:#PulseA(IN := #RunningState AND #SensorA AND NOT #PulseB.Q AND NOT #Reset, PT := T#2s);,输出再用 #PulseA.Q AND NOT #Reset 兜底。
  • [Warning] The parameter '#Running' might not be initialized. —— 输出在读取前没有被确定赋值。用一个 Static 状态变量(RunningState : Bool := FALSE;)承载状态,最后 #Running := #RunningState; 无条件赋值,警告消失。
  • 编译消息的语言跟随 TIA 安装语言(中文 TIA 会给中文消息),agent 不能按消息文本匹配,要看结构化结果和消息里的位置/对象名。

修复后的完整 SCL(实测,编译 0 错误 0 警告):

SCL
1FUNCTION_BLOCK "FB_Sorter"2VAR_INPUT3   Start : Bool;4   SensorA : Bool;5   SensorB : Bool;6   Reset : Bool;7END_VAR8VAR_OUTPUT9   DiverterA : Bool;10   DiverterB : Bool;11   Running : Bool;12END_VAR13VAR14   PulseA : TON_TIME;15   PulseB : TON_TIME;16   StartEdge : R_TRIG;17   RunningState : Bool := FALSE;18END_VAR19BEGIN20   #StartEdge(CLK := #Start);21   IF #Reset THEN22      #RunningState := FALSE;23   ELSIF #StartEdge.Q THEN24      #RunningState := TRUE;25   END_IF;2627   #PulseA(IN := #RunningState AND #SensorA AND NOT #PulseB.Q AND NOT #Reset, PT := T#2s);28   #DiverterA := #PulseA.Q AND NOT #Reset;2930   #PulseB(IN := #RunningState AND #SensorB AND NOT #DiverterA AND NOT #Reset, PT := T#2s);31   #DiverterB := #PulseB.Q AND NOT #Reset;3233   #Running := #RunningState;34END_FUNCTION_BLOCK

修复后:0 错误 0 警告
修复后编译成功

agent 实际做了什么:读回核对、清理、保存

最后它把块从工程里读回,逐项核对需求并输出「需求 → 实现 → 结论」对照表;顺手清掉中间迭代留下的外部源文件;然后保存工程,确认没有未保存修改。

读回核对结果
读回接口与块状态 → 保存

你得到什么

  • 工程里多了 FB FB_Sorter(以及为了装下它而建的 CPU),编译 0 错误 0 警告,且已保存;
  • 一份「需求 → 实现 → 结论」对照表和关键修改摘要;
  • 打开博途能看到块和接口,随时可以按「验证清单」抽查。

需要你动手的地方(很少)

  • 确认建 CPU:agent 先报告后改动时会问一次;在提示词里预先授权就可以省掉;
  • TIA 授权弹窗:TiaLink 白名单没配好时,第一次写入 TIA 会弹授权窗,只能人工点确认(先跑一次 TiaLink.Helper.exe 可以免掉);
  • 最后抽查:按「验证清单」看编译结果、接口与逻辑是否与需求一致;安全、联锁与急停部分必须人工评审。

可选:想逐步监督时的分步指令

正常使用不需要;如果你想在每一步停下来看,可以用这些指令:

想介入的点 发给 agent 的话
先只读 只读,不要修改。告诉我设备清单、块清单、变量表和现有逻辑。
建 CPU 工程里没有 PLC 设备。先查订货号,先 dry-run 报告,再建一台。
只写需求 把上面的需求先只给我 SCL 源码和接口说明,不要导入。
导入编译 把 FB_Sorter 导入并编译,自己读取结果修到 0 错误 0 警告,不要让我贴输出。
读回保存 读回 FB_Sorter 核对需求并输出对照表,清理多余外部源文件后保存。

验证清单

  • 健康检查显示已连接,项目名与博途一致,允许写入;
  • 设备清单里至少有一台带 PLC 软件的设备(本课过程会自动补上);
  • 智能体复述/读回的 I/O 与实际接口一致;
  • 编译报告 0 错误 0 警告;
  • 每个定时器都是 Static 区的命名实例,且每次调用都带 PT;
  • 读回核对逐条打勾:接口区、时序、互锁、复位;块处于一致状态;
  • 导入过程留下的多余外部源文件已清理(或明确保留哪一个);
  • 保存后没有未保存修改(博途里也不报未保存)。

常见坑

  • 工程没打开就动手:先做健康检查,没连上工程时任何写入都会失败;TiaLink 不能替你打开工程。
  • 空项目/没有 PLC 设备就直接导入:会失败:No device with PLC software was found in the project. 先补一台 CPU(agent 可以代劳)。
  • TIA 授权弹窗:prerequisites 里白名单不通过时,第一次写入会让 TIA 弹授权窗,只能人工确认;先跑一次 TiaLink.Helper.exe 可以免掉。
  • 计时器漏 PT:Parameter 'PT' has to be used.——每次调用都要给 PT,复位分支别写 #PulseA(IN := FALSE);,改成把复位条件并进 IN。
  • 输出未初始化:The parameter '...' might not be initialized.——状态用带初值的 Static 变量,输出最后无条件赋值。
  • 导出被挡:未编译一致的块不能导出(会提示先编译)。先编译,或让智能体导出时先编译。
  • 整机编译混入旧错误:整机编译会把工程里既有错误一起报出来;定位自己块的问题时,让智能体只编译这一个块。
  • 外部源文件累积:每轮导入都会在工程里留下一个外部源文件(重名会自动加后缀),多轮迭代后项目里一堆脚本。每轮结束后让智能体清理,只保留最后一个或全部删掉。
  • 只导入不保存:导入成功不等于保存,改动要显式保存,否则关掉工程就丢。
  • 越界:安全逻辑、联锁与急停必须人工评审;AI 生成的代码永远按「待验证」对待。

下一步

  • 用示例库里的 输送带分拣 做同样的练习:先读懂它的逻辑,再让智能体重写一版并核对行为是否一致;
  • 想让智能体先理解一整套老工程,看下一课《读懂一套没注释的博途老工程》(待上线);
  • 工具:TiaLink 下载与说明——本课所有动作都由它落到博途,工程数据不出你的机器。

本课工具

TiaLink

把 TIA Portal 接到你的智能体:导入、编译、读回核对、PLCSIM 仿真。本地运行,工程数据不出厂。

打开 TiaLink