
简介这份文档面向工业自动化工程师与TIA博途初学者聚焦于通过AT指令与二维数组实现IO点位映射的编程方法帮助解决大量输入输出信号难以高效管理的问题。资源包内含1个docx文件约522KB以图文步骤形式呈现完整示例。内容涵盖新建项目、创建SCL语言FB块、定义BOOL二维数组、用两层FOR循环遍历赋值以及在全局DB中设置WORD类型输入输出变量、在OB1中调用并连接变量等关键环节并演示输入值16#01、16#02、16#03时数组对应位的变化规律体现西门子PLC高低字节相反的映射规则。目前已有1082人学习适合希望掌握SCL数组映射技巧、提升代码模块化与可维护性的读者参考借鉴。1. 从一堆散点 IO 到一张映射表TIA 博途里 AT 指令加二维数组要解决什么做非标设备的朋友大概率遇到过这种场面一台设备有 32 个气缸、16 个真空吸盘、8 个伺服使能位PLC 程序里全是I0.0、Q0.5这种硬编码地址。换一个机型IO 点位顺序一变整个程序要重写一遍。更麻烦的是 HMI 上要显示第 7 号气缸伸出到位你没法直接拿I0.7去查表因为地址和物理含义之间没有结构化的对应关系。TIA 博途里解决这个问题的常见做法是用 AT 指令把一个字或双字覆盖到一段结构化数据上再用二维数组把工位号 功能位两个维度组织起来。AT 指令负责把连续的 IO 字节映射成有名字的位二维数组负责按工位和功能检索。两者结合程序里就只剩IO_Map[3, 2]这种可读的写法而不是满屏的绝对地址。这套方法适合做多工位、多机型复用的设备也适合需要把 IO 状态批量上传给上位机的场景。下面从 AT 指令的底层机制讲起一路落到 SCL 里怎么查表、怎么排错。2. AT 指令覆盖与二维数组的底层机制2.1 AT 指令在 TIA 博途里到底做了什么AT 指令Address Type在 SCL 和 STL 里都是一个视图叠加操作。它不复制数据而是让同一块存储区同时拥有两种解释方式一种按原始类型比如Word、DWord一种按覆盖后的结构体类型。声明区里写MyWord AT %IW0 : Struct意思是%IW0这个输入字既可以被当作Word读也可以按Struct里的位去访问。关键点在于 AT 覆盖要求两个类型的存储长度完全一致。用Word覆盖一个含 16 个Bool的结构体没问题用Word覆盖一个含 8 个Bool加一个Int的结构体就会编译报错因为后者是 24 位。很多人第一次用 AT 报类型长度不匹配基本都是这个原因。// 在 FB 的静态变量或 FC 的临时变量里声明 MyInputWord AT %IW0 : Word; // 原始视图整个字 IO_Bits AT %IW0 : Struct // 覆盖视图按位拆开 Cyl1_Extend : Bool; Cyl1_Retract : Bool; Cyl2_Extend : Bool; Cyl2_Retract : Bool; // ... 补齐到 16 位 Reserved : Bool; END_STRUCT;逻辑说明MyInputWord和IO_Bits指向同一段物理输入区读IO_Bits.Cyl1_Extend等价于读%I0.0。参数上要注意AT 覆盖的起始地址必须是字节对齐的%IW0、%IW2可以%I0.1不行。覆盖结构体里的Bool顺序按小端方式从低位往高位排第一个Bool对应%I0.0。2.2 为什么用二维数组而不是一维数组或结构体数组一维数组IO_Map[0..31]只能表达第几个点表达不了第几工位的第几个功能。结构体数组Station[1..8].CylExtend可读性好但没法用循环下标去遍历因为成员名不能当索引。二维数组IO_Map[1..8, 1..4]正好卡在中间第一维是工位号第二维是功能号两个维度都能用变量去索引循环里写FOR i : 1 TO 8 DO ... IO_Map[i, j] ...就能批量处理。组织方式可循环索引语义清晰度适合场景一维数组是低单一功能批量点位结构体数组否高工位少、功能固定二维数组是中高多工位多功能的映射表AT 二维数组是高IO 地址与逻辑名解耦2.3 二维数组在 SCL 里的声明与内存布局SCL 里声明二维数组的语法是Array[下限1..上限1, 下限2..上限2] OF 类型。TIA 博途默认下限是 0但做 IO 映射时我一般把下限设成 1因为工位号从 1 开始更符合现场习惯省得到处1、-1转换出错。// 在 DB 块或 FB 静态变量里声明 VAR IO_Map : Array[1..8, 1..4] OF Bool; // 8 工位 x 4 功能 Station_Count : Int : 8; Func_Count : Int : 4; END_VAR逻辑说明IO_Map[1..8, 1..4]在内存里是连续排布的IO_Map[1,1]到IO_Map[1,4]占前 4 位IO_Map[2,1]接着排。参数上Bool数组在 TIA 里会按位打包但跨字节访问时编译器会自动处理不用手动算偏移。如果换成Array[1..8, 1..4] OF Word每个元素占 2 字节总长度就是 64 字节这个长度必须和 AT 覆盖的源类型长度对上。3. 用 AT 指令把 IO 字节映射成二维数组的完整步骤3.1 建立 IO 映射 DB 块并声明 AT 覆盖第一步是建一个全局 DB专门放映射数据。不要直接在 FB 里做 AT 覆盖物理地址因为 FB 可能被多次调用覆盖会冲突。全局 DB 里声明 AT 覆盖%I和%Q区这样整个程序都能引用同一张表。// DB 块 IO_Mapping_DB 的声明部分 VAR // 输入区覆盖32 个输入点按 8 工位 x 4 功能组织 InputBytes AT %IB0 : Array[0..3] OF Byte; InputBits AT %IB0 : Array[1..8, 1..4] OF Bool; // 输出区覆盖32 个输出点 OutputBytes AT %QB0 : Array[0..3] OF Byte; OutputBits AT %QB0 : Array[1..8, 1..4] OF Bool; END_VAR逻辑说明InputBytes和InputBits都覆盖%IB0开始的 4 个字节共 32 位。InputBits[1,1]对应%I0.0InputBits[1,4]对应%I0.3InputBits[2,1]对应%I0.4以此类推。参数上Array[0..3] OF Byte总长 4 字节Array[1..8, 1..4] OF Bool总长 32 位也是 4 字节长度匹配才能编译通过。如果实际输入点超过 32 个把%IB0改成%IB0起始的更多字节同时调整数组上限。3.2 在 SCL 里用循环批量读写映射点位有了这张表读写就变成纯下标操作。下面这段是批量把输入状态同步到输出做直通映射的例子。// FC Map_IO_Scan 里的循环逻辑 FOR #i : 1 TO 8 DO FOR #j : 1 TO 4 DO // 输入位直接映射到输出位实际项目里换成工艺逻辑 IO_Mapping_DB.OutputBits[#i, #j] : IO_Mapping_DB.InputBits[#i, #j]; END_FOR; END_FOR;逻辑说明双层FOR循环遍历 8x4 共 32 个点#i是工位号#j是功能号。参数上循环上限建议用变量Station_Count、Func_Count而不是硬编码 8 和 4这样改机型时只改 DB 里的常量。注意 SCL 里数组下标越界不会在编译期报错运行时才报所以循环上限一定要和数组声明上限一致。3.3 把物理地址和逻辑名解耦的命名约定光有数组还不够现场调试时你得知道IO_Map[3,2]到底是哪个气缸。常见做法是在 DB 的注释里维护一张对照表或者干脆用第二个字符串数组存名字。VAR IO_Name : Array[1..8, 1..4] OF String[16]; END_VAR // 初始化时赋值 IO_Name[1,1] : St1_CylExt; IO_Name[1,2] : St1_CylRet; IO_Name[2,1] : St2_CylExt; // ...逻辑说明String[16]每个元素占 18 字节2 字节头 16 字节内容8x4 共 576 字节放在 DB 里没问题。参数上字符串长度按实际名字最长值设设太大浪费内存设太小会截断。HMI 上要显示点位名时直接读IO_Name[i,j]就行不用在画面里再维护一份。提示AT 覆盖的 DB 块要勾选优化的块访问取消否则绝对地址覆盖会报错。在 DB 属性里把优化的块访问去掉勾选才能用%IB0这种绝对地址。4. 多工位机型切换与映射表动态重排的实战4.1 用配方数据重排二维数组的映射关系同一套程序跑不同机型IO 点位的物理顺序不一样但逻辑功能一样。这时候不要改程序改映射表就行。做法是加一个映射索引二维数组存的是物理点号实际读写时通过索引间接访问。VAR Map_Index : Array[1..8, 1..4] OF Int; // 存物理点号 0..31 Phys_Input : Array[0..31] OF Bool; // 覆盖 %IB0 的 32 个点 END_VAR // 机型 A 的映射工位 1 功能 1 对应物理点 0 Map_Index[1,1] : 0; Map_Index[1,2] : 1; // 机型 B 的映射工位 1 功能 1 对应物理点 5 // Map_Index[1,1] : 5; // 读取时通过索引间接访问 #LogicValue : Phys_Input[Map_Index[#i, #j]];逻辑说明Map_Index存的是物理点号Phys_Input是按物理顺序排的原始位数组。换机型时只改Map_Index的值程序逻辑一行不动。参数上Map_Index的取值范围必须落在Phys_Input的下标范围内否则运行时越界。建议在初始化 FC 里加一段范围校验。4.2 机型切换时校验映射表完整性的 SCL 代码映射表配错了现场表现是某个气缸不动或者乱动排查很费时间。加一段上电校验把重复映射和越界映射提前报出来。// FC Validate_Map 校验映射表 #ErrorFlag : FALSE; FOR #i : 1 TO 8 DO FOR #j : 1 TO 4 DO // 检查越界 IF #Map_Index[#i, #j] 0 OR #Map_Index[#i, #j] 31 THEN #ErrorFlag : TRUE; #ErrorCode : 16#0001; // 越界错误 END_IF; // 检查重复与前面所有点比较 FOR #m : 1 TO #i DO FOR #n : 1 TO 4 DO IF (#m #i OR #n #j) AND (#Map_Index[#m, #n] #Map_Index[#i, #j]) THEN #ErrorFlag : TRUE; #ErrorCode : 16#0002; // 重复映射 END_IF; END_FOR; END_FOR; END_FOR; END_FOR;逻辑说明外层双层循环遍历每个映射项内层双层循环只比较当前项之前的项避免重复比较。参数上#ErrorCode用 16#0001 表示越界、16#0002 表示重复HMI 上直接显示错误码比显示映射错误四个字有用得多。这段校验在上电初始化时调用一次即可不用每个扫描周期跑。4.3 映射表与 HMI 报警联动的参数配置校验出来的错误要能传到 HMI。常见做法是把ErrorFlag和ErrorCode放到一个全局 DB 里HMI 报警组态里关联这个 DB 的变量。报警文本里带上工位号和功能号操作工一看就知道哪一项配错了。错误码含义HMI 报警文本处理动作16#0001映射越界工位 %d 功能 %d 映射越界检查配方数据16#0002重复映射工位 %d 功能 %d 与前面重复检查配方数据16#0003映射表未初始化映射表未加载重新下载配方参数上HMI 报警文本里的%d占位符在 TIA 里用关联变量方式填把#i、#j传到报警触发变量里。注意报警触发变量要用Bool型错误码用Int型单独显示。5. 映射不生效时的排查顺序与几个容易踩的坑5.1 从编译报错到运行时越界的排查路径映射不生效先看编译有没有过。AT 覆盖报错最常见的是类型长度不匹配回去数一下覆盖结构体的总位数和源类型位数是否一致。编译过了但运行不对按这个顺序查先确认 DB 的优化的块访问已取消再确认 AT 覆盖的起始地址和实际接线一致最后用监控表强制几个点看InputBits有没有跟着变。// 临时调试把映射表状态写到诊断数组 FOR #i : 1 TO 8 DO FOR #j : 1 TO 4 DO Diag_DB.Map_Status[#i, #j] : IO_Mapping_DB.InputBits[#i, #j]; END_FOR; END_FOR;逻辑说明把映射后的位状态复制到诊断 DB监控表里直接看Map_Status就能判断是映射没生效还是后续逻辑有问题。参数上诊断 DB 不要用 AT 覆盖用普通数组避免引入新的覆盖问题。5.2 二维数组下标从 0 还是 1 开始的取舍TIA 博途默认数组下限是 0但做 IO 映射时下限设 1 更顺手。混用 0 和 1 是新手最容易犯的错DB 里声明Array[0..7, 0..3]SCL 里写IO_Map[1,1]访问的是第二个工位第二个功能不是第一个。统一约定所有映射相关数组下限一律设 1在 DB 注释里写清楚团队里谁也别破例。注意AT 覆盖的数组下限必须和覆盖源类型的下标逻辑一致。Array[0..3] OF Byte覆盖%IB0时[0]对应%IB0[1]对应%IB1。如果改成Array[1..4] OF Byte[1]对应%IB0偏移关系变了覆盖结构体里的位顺序也要跟着调。5.3 用监控表和强制表验证映射正确性的具体操作验证映射对不对最快的方法是监控表里同时放物理地址和映射后的逻辑名。比如放%I0.0和IO_Mapping_DB.InputBits[1,1]强制%I0.0为 TRUE看InputBits[1,1]是否跟着变。变了说明 AT 覆盖生效没变说明覆盖地址或长度有问题。批量验证时用强制表把%IB0整个字节强制成16#FF监控InputBits的 8 个点应该全为 TRUE。参数上强制表操作要谨慎强制输入点可能影响现场设备动作建议在设备停机状态下做。验证完记得取消所有强制TIA 里取消全部强制按钮在强制表工具栏上。如果强制后监控表里值不变检查监控表的刷新周期是不是设得太长改成 100ms 再试。本文还有配套的精品资源点击获取