ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

S7-200 SMART位寻址库:六个子程序封装与实现

S7-200 SMART位寻址库:六个子程序封装与实现 前阵子替客户做产线改造上位机组态软件要通过 Modbus RTU 去读 S7-200 SMART 里几十个分散的位状态。如果只是偶尔读几个位用自带的 MBUS_SLAVE 库也能凑合可对方给的地址表完全是按“从 V0.0 开始数第 N 个位”这种线性位号组织的映射表列得我头皮发麻。后来我干脆自己做了个寻址-库一共六个子程序核心就是两个——一个按位号读一个按位号写批量场景再配上打包和解包。做完这件事最大的感触是只要把位寻址的换算关系搞明白这个库就是一次性投资后续项目直接复用省下的时间远超写库那点功夫。这篇文章就围绕这个库展开讲清楚 S7-200 SMART 的位寻址机制、六个子程序怎么分工、读位和写位子程序的核心算法怎么实现以及我在库封装、指针间接寻址和 Modbus 自由口联动时踩过的坑。适合正在做 200 SMART 通信、协议转换或者想把 V 区按位操作做成可复用库的朋友参考。1. 需求拆解六个子程序要解决什么问题1.1 “读取从V0.0开始的第N个位”是什么场景先说需求是怎么来的。常规写 PLC 程序大家习惯了直接用 V0.0、V10.3、M0.5 这种按字节加位号的方式访问位。一旦涉及上位机或第三方系统情况就变了。对方不会关心你工程里 V0.0 是什么含义他们只拿来一张表上面写着“第 0 号位设备启动”、“第 1 号位设备停止”、“第 2 号位故障”等等几十上百个线性位号。这个位号对应的物理地址就是标题里说的“从 V0.0 开始的第 N 个位”。这类需求在 Modbus 通信场景里尤其常见。S7-200 SMART 自带的 Modbus RTU 从站库保持寄存器可以映射到 V 区但线圈功能默认映射到 Q 区映射地址表和外部系统的位号表对不上。更麻烦的是自由口通信时上位机一帧报文可能就带一个线圈地址PLC 得根据这个地址去读写 V 区的任意一位。这时候一个“给位号 N直接读写从 V0.0 开始第 N 位”的子程序就是刚需。1.2 六个子程序的分工方案标题里写“6 个子”结合上下文这个库应该是一组位操作子程序。我按工程实际用法做了这样的划分你可以根据自己的项目调整BIT_READ按位号读取一个位的状态。输入位号 N输出 0/1。BIT_WRITE按位号写入一个位的状态。输入位号 N 和写入值写入 0/1。BITS_READ8从位号 N 开始连续读 8 个位打包成一个字节返回。做一次性采集最方便。BITS_WRITE8把一个字节的 8 个位从位号 N 开始连续写入 V 区。批量下发的场景会用。BIT_TOGGLE按位号翻转一个位的状态输入位号 N该位自动取反。做按钮自锁逻辑很有用。BIT_AREA_CLR从位号 N 开始连续清零 K 个位。上电初始化时批量复位标志位。总共六个子程序核心是前两个。批量读写、翻转、区域清零这几个从工程角度看也都是高频功能做成一套库调用方只需要管位号和值完全不用关心内部地址换算。这也就是为什么我把这套东西叫“寻址-库”——它把底层寻址逻辑全部封装掉了。2. 位寻址的底层原理位号才是真正的“线性地址”2.1 V区、位地址与位序号之间的换算S7-200 SMART 的 V 区是一整块变量存储区按字节编址每个字节 8 个位。日常写程序时我们用 V0.0、V0.1、V0.7、V1.0 这种“字节地址.位号”的写法是物理视角而“从 V0.0 开始数第 N 个位”则是线性视角。两者之间就是一个简单的换算公式字节偏移 位序号 N ÷ 8 的商位偏移 位序号 N ÷ 8 的余数实际地址 V(字节偏移).(位偏移)举个例子位序号 N1717 除以 8商是 2余数是 1对应地址就是 V2.1。N8 时商是 1余数是 0对应 V1.0。N0 就是 V0.0。这个换算关系是整个库的地基千万不能搞错。为了直观我做了一张对照表方便调试时快速核对位序号 N字节偏移位偏移实际地址000V0.0707V0.7810V1.01721V2.13543V4.381919102397V10239.7最后一行对应 10KB V 区的最后一个位。S7-200 SMART 不同 CPU 的 V 区容量不一样常见的 SR20/ST20 是 10KB大一点的 CPU 有 18KB、20KB、24KB做范围检查时要按实际 CPU 型号来。2.2 为什么说这是 SMART 200 寻址的核心很多人一开始觉得直接在程序里用 V0.0、V1.0 不就行了干嘛要绕一个“位序号”的弯问题在于V0.0 这种地址是静态的不能把“偏移量”塞进去。你不能在运行时根据外部传入的 N 去拼一个 Vx.y 地址。PLC 的梯形图里地址是编译时就确定的。所以必须用间接寻址。S7-200 SMART 的指针是双字32 位格式是 VB0指向 V 区起始地址。拿到位序号 N 之后先算出字节偏移把字节偏移加到基址指针上就能通过指针访问目标字节再用位运算提取或修改目标位。这一套“指针 偏移 掩码”的组合就是 SMART 200 寻址的灵魂。在实际项目里这套东西比想象中常用。比如上位机通过自由口下发一帧命令报文中带一个线圈地址码PLC 收到后要做 Modbus 地址映射报文的地址码通常从 1 开始对应 V0.0 是第 0 号位换算关系又得重新核对一遍。所以把底层寻址封装成库就相当于把最容易出错、最耗时的部分一次性解决。3. 读位与写位子程序的算法与实现3.1 读位算法除法取偏移 移位掩码读位的目标很明确输入位序号 N返回从 V0.0 开始第 N 位的状态。算法分四步用 N 除以 8商作为字节偏移余数作为位偏移。用指针指向 V0.0 所在的字节加上字节偏移得到目标字节地址。读取目标字节。把字节右移位偏移位再和 1 做与运算得到 0 或 1。右移加与 1 是提取某一位状态的标准做法。比如目标字节是 0b01010000位偏移是 4右移 4 位后得到 0b00000101与 1 相与得到 1说明第 4 位是 1。S7-200 SMART 的 STL 核心片段如下// 局部变量表示意 // LW0 : IN 位序号 NINT // L2.0 : OUT 位状态BOOL // LD4 : TEMP 指针 // LD8 : TEMP 除法结果低字商高字余数 // LD12 : TEMP 偏移转换临时 // LB16 : TEMP 字节运算临时 LD SM0.0 ITD LW0, LD8 // 位序号 N 转双整数 DIV 8, LD8 // LW8字节偏移LW10位偏移 MOVD VB0, LD4 // 基址指针V0.0 所在字节 ITD LW8, LD12 // 字节偏移转双字 ADDD LD12, LD4 // 指针 字节偏移 目标字节地址 MOVB *LD4, LB16 // 间接读取目标字节 SRB LB16, LW10 // 右移位偏移位 ANDB 1, LB16 // 与 1 相与保留最低位 LDB LB16, 1 L2.0 // 输出位状态这里有个关键点必须重点提醒S7-200 的 DIV 指令不是直接返回商和余数而是把两个 16 位整数相除得到一个 32 位结果其中低 16 位是商高 16 位是余数。所以 LD8 这个双字里面LW8 是商字节偏移LW10 是余数位偏移。这是和很多其它 PLC 不一样的地方我第一次用的时候在这个坑里卡了半天。3.2 写位算法掩码与字节运算写位比读位多一步因为修改单个位不能直接写必须“读-改-写”否则会覆盖掉同字节里其它位的状态。算法分五步计算字节偏移和位偏移和读位一样。读取目标字节的当前值。生成掩码把 1 左移位偏移位。比如位偏移是 3掩码就是 0b00001000。如果要写 1目标字节和掩码做或运算如果要写 0先把掩码按位取反再和目标字节做与运算。把结果写回目标字节。用生活类比来解释掩码掩码就像一张镂空贴纸镂空的位置正好是要改的那一位。写 1 就像用喷漆喷过整张贴纸只有镂空处能漏下去写 0 正好相反贴纸盖住要写 0 的位置其它地方全被保护起来碰不到。STL 核心片段如下// 局部变量表示意 // LW0 : IN 位序号 NINT // L2.0 : IN 写入值BOOL1置位0清零 // LD4 : TEMP 指针 // LD8 : TEMP 除法结果 // LD12 : TEMP 偏移转换临时 // LB16 : TEMP 当前字节 // LB17 : TEMP 掩码 // LB18 : TEMP 运算临时 LD SM0.0 ITD LW0, LD8 DIV 8, LD8 // LW8字节偏移LW10位偏移 MOVD VB0, LD4 ITD LW8, LD12 ADDD LD12, LD4 MOVB *LD4, LB16 // 读取目标字节当前值 MOVB 1, LB17 SLB LB17, LW10 // 1 左移位偏移位生成掩码 // 网络1写 1 LD L2.0 MOVB LB16, LB18 ORB LB17, LB18 // 原字节 OR 掩码 MOVB LB18, *LD4 // 写回 // 网络2写 0 LDN L2.0 MOVB LB16, LB18 INVB LB18 // 掩码按位取反 ANDB LB18, LB16 // 原字节 AND 取反掩码 MOVB LB16, *LD4 // 写回写 0 分支里取反掩码是关键。掩码 0b00001000 取反后变成 0b11110111和原字节做与运算只有目标位被清 0其它位保持不变。这个“读-改-写”的过程是所有位操作的基本功处理 V 区和 M 区都适用。3.3 批量读、批量写等其余四个子程序怎么做六个子程序里前两个是核心剩下四个本质上是前两个的变体和组合。BITS_READ8从位号 N 开始连续读 8 个位打包成一个字节。最简单的实现是循环调用 BIT_READ 八次但更高效的做法是直接用字节运算。如果位偏移是 0目标字节就是 VB(N/8)直接读取如果位偏移不是 0目标位的 8 位会跨两个字节需要用“右移 左移 或”的方式拼起来。这段逻辑和 CAN 矩阵里处理跨字节位序的思路非常像。BITS_WRITE8把一个字节的 8 个位从位号 N 开始连续写入同样可能跨字节。需要把输入字节拆成高、低两段分别写入前一个字节的高位部分和后一个字节的低位部分中间用一个掩码保护不相关的位。BIT_TOGGLE读取目标字节用掩码做异或运算写回。异或的特性是任何位和 1 异或都会翻转和 0 异或保持不变。一条 XORB 指令就搞定。BIT_AREA_CLR从位号 N 开始连续清零 K 个位。实现上可以先处理头部不完整的字节中间整字节直接置 0尾部再处理剩余进位。K 不大时可以简单循环调用 BIT_WRITE思路清晰代码量小。这四个子程序在批量数据交互、按钮翻转、初始化清零场景里都很有用。特别是 BITS_READ8上位机想一次读 8 个离散状态的时候一个字节就给过去了通信效率提升立竿见影。4. 库封装、参数设计与工程调用4.1 子程序的局部变量表设计做库子程序第一步是把局部变量表设计好。S7-200 SMART 的局部变量表分 IN、IN_OUT、OUT、TEMP 四种类型。IN 是输入参数OUT 是输出结果TEMP 是内部临时变量每次调用都重新分配不保留上次的值。以 BIT_READ 为例变量表设计如下变量名类型用途NIN位序号QOUT位状态PTRTEMP指针DIVRESTEMP除法结果TEMP_DWTEMP偏移转换临时TEMP_BTEMP字节运算临时这里要特别注意几个坑第一BOOL 类型在局部变量表中虽然只占一个位但实际分配时是按字节对齐的所以不要自己去推算 L 区偏移全部用符号名访问编译器会自动分配。第二TEMP 变量不保持。同一个子程序如果在一个扫描周期里被调用两次第二次调用时 TEMP 里的值不会被重置所以 TEMP 必须先赋值再使用不能指望它初始化为 0。这是 S7-200 SMART 最常见的隐性 bug。第三S7-200 SMART 的库子程序不支持指针类型参数所以没办法把“起始字节地址”作为指针参数传进来。这也是为什么标题场景里用“从 V0.0 开始”作为固定基址——这是一个合理且省事的选择。如果确实需要从 VB100 开始可以在子程序内部加一个 WORD 类型的基址偏移参数 BASE核心换算变成总量位号 BASE × 8 N然后再做除法和取余。这个扩展我建议有需要时再加平时别给自己的调用端增加负担。4.2 在 Micro/WIN SMART 中创建和加密库子程序写好、验证通过之后就可以封装成库。在 Micro/WIN SMART 软件里打开“库”菜单选择“创建库”然后按向导添加子程序、填写库名称和版本信息。创建库时有两个关键选项一个是选择“保护”级别。默认是可以查看子程序内容的如果希望别人调用这个库时只能看到接口、看不到内部梯形图或 STL就设置密码保护。这个对商业项目或者给多个同事复用的时候很实用避免核心算法被随意修改。另一个是库的存储路径。生成的库文件是扩展名为 .lib 的文件其它项目通过“添加/移除库”就可以把整组子程序引进来。引进来之后子程序名会自动带后缀比如 BIT_READ 变成 BIT_READ_1这是正常现象因为不同库里的子程序可能重名系统用后缀区分。封装成库后有个明显优势调用方完全不需要关心 V 区寻址、指针、位运算这些底层细节只给 N 和值就行。这就把“懂寻址的人”和“使用库的人”解耦了。团队协作时负责通信对接的同事只管调用接口不用重复踩寻址的坑。4.3 调用库时的几个细节调用自定义库子程序时有几个细节直接影响程序的稳定性。一是库子程序的 EN 使能。调用时 EN 如果不接通子程序什么都不做输出参数保持上一次的值。所以要让库在每次扫描周期都执行就把 EN 接到 SM0.0 上。如果需要条件触发比如只在定时中断里执行就把定时中断的条件接到 EN。二是位号 N 的类型。N 定义为 INT16 位有符号整数理论上最大值是 32767对应 V 区 4KB。如果 V 区超过 4KB 或者位号超过这个范围就要用 DINT 或者调整参数类型。实际上 10KB V 区的总位数是 81920已经
返回列表