
CODESYS 用 ModbusFB 库实现 Modbus TCP Server(双向读写)
问题:设备映射的 Modbus TCP Server 是”单向”的
最开始我是在 CODESYS 的设备连接里直接添加了一个 Modbus TCP Server 设备,然后在里面做寄存器映射。跑起来之后发现一个问题:
- 映射通道会把数据单向覆盖:这些寄存器只能由上位机发起读写;
- PLC 侧的程序对这些映射变量只能读,就算你在程序里给它赋值,下一轮还是会被映射关系覆盖掉。
也就是说,这是一条”上位机 → 映射区”的单行道,PLC 自己没有真正的写权限。
而在西门子那边做 Modbus TCP 服务器(比如 S7-1200/1500 的 MB_SERVER)时,我的习惯是:上位机和 PLC 程序都能对同一块 DB 读写,PLC 写的状态量上位机能看到,上位机下的设定值 PLC 也能拿到。CODESYS 设备映射这种”单向覆盖”的行为完全达不到我的预期。
问了一圈免费的 AI,回答都大而化之,结论基本一致:设备映射这条路走不通,要用第三方库(ModbusFB)自己编程搭一个 Server。后来用智谱的 GLM 拿到了一套完整可行的方案,我这边在 CODESYS 上实操验证通过,整理出来给大家参考。
思路一句话概括:用 ModbusFB 库的 ServerTCP 功能块,把普通的全局变量数组”声明”成 Modbus 四张表的数据区。数据区就是普普通通的 GVL 变量,PLC 程序随便读写,上位机通过 Modbus TCP 也可以随便读写——天然双向。
📦 添加 ModbusFB 库
- 在应用的项目树双击打开「库管理器」
- 点「添加库」
- 搜索库 →
ModbusFB - 选中
ModbusFB→ 确定

ModbusFB 是 CODESYS 自带库仓库(CODESYS Store / 随安装包的库)里就能搜到的库,不需要额外下载。
🔧 新建 POU 程序
- 右键应用(Application)→ 添加对象 → POU
- 名称填
ModbusTCP,类型选「程序(Program)」 - 添加

📬 新建任务
不建议直接挂在 MainTask 里跑——Modbus 响应最好有自己固定周期的任务,不受主程序扫描时间波动影响。
- 右键「任务配置器」→ 添加对象 → 任务
- 命名
Task_ModbusTCP,周期建议 10~50ms - 添加

💽 任务里调用程序
- 右键任务 → 添加程序调用
- 点输入助手的「…」按钮
- 选中
ModbusTCP程序 - 确认

📝 添加数据类型和全局变量
Modbus 里数据就分四张表,先把概念对齐,后面配置才不糊:
| 表 | 功能码 | 方向 | 典型用途 |
|---|---|---|---|
| 保持寄存器 Holding Registers | FC03 读 / FC06、FC16 写 | 双向 | 设定值、参数(上位机写,PLC 读) |
| 输入寄存器 Input Registers | FC04 只读 | PLC → 上位机 | 温度、压力等运行数据 |
| 线圈 Coils | FC01 读 / FC05、FC15 写 | 双向 | 启停、开关量下发 |
| 离散输入 Discrete Inputs | FC02 只读 | PLC → 上位机 | 故障、运行状态信号 |
对应地建一个数据类型 + 一组全局变量:
1. 新建数据类型(DUT)
右键「应用」→ 添加对象 → DUT,命名 test_modbus:
1 | TYPE test_modbus : |
浮点数据没法直接走一个寄存器,常规做法是 PLC 写 REAL 变量,再用
WORD_TO_...拆成高低字放进 WORD 区,或者像上面这样把 REAL 数组直接指给一段寄存器区(一个 REAL = 2 个寄存器)。拆字时注意和上位机约定好字节序(ABCD / CDAB / BADC / DCBA),这是联调时最容易对不上的地方。
2. 新建全局变量列表(GVL)
命名 ModbusDB(也叫”数据区”,它是 PLC 程序和 Modbus 客户端共享的同一块内存):
1 | VAR_GLOBAL |
test_modbus里两个数组加起来是 10 + 20 = 30 个寄存器(REAL 占 2 个),这决定了后面保持寄存器分区的uiNumDataItems要填 30。分区数量必须 ≤ 数据区实际大小,否则客户端越界访问会触发异常应答。
✍ 编写 ModbusTCP 调用程序
1. 声明变量
1 | PROGRAM ModbusTCP |
分区(TableSection)的意义是允许把一段连续的 Modbus 地址拆成几块内存来放,比如 0..19 放设定值、20..99 放参数,各指到不同数组。这里我每种表只用一块分区。
2. 主程序
1 | // ---------- 1. 构建数据模型(只需做一次,必须在使能之前)---------- |
几个关键点解释一下:
- 数据模型只在
xEnable上升沿被库读取一次。所以第 1 步建模型必须发生在使能之前,而且运行中途改pStartAddr、改数量都是不生效的——要改就得先xEnable := FALSE再重新使能。这也是用xModelInit一次性初始化的原因。 ADR()指过去的就是普通变量。PLC 程序里对ModbusDB.xxx的读写和上位机的 Modbus 读写操作的是同一块内存,谁写都行,这正是设备映射做不到的双向。uiDataItemSize是最坑的一个参数:WORD/寄存器类填 16,BOOL 数组填 8(按”每元素 8 bit”算,不是 1)。填错了表现为客户端读到的数据错位。pSections指的是分区数组第一个元素的地址,多分区时连续元素依次排开即可。
💡联调建议
- 先用 Modbus Poll / QModMaster 之类的客户端工具连
PLC的IP:502,扫一遍 0~29 保持寄存器,确认能读到数; - 客户端写一个寄存器,在 PLC 里监控
ModbusDB,确认值变了——双向通了,剩下的就是正常的 PLC 编程了; - 如果读回来全是异常应答,优先查三件事:分区数量 vs 数组实际大小、uiDataItemSize 填没填对、字节序约定。
🎉大功告成
- 标题: CODESYS 用 ModbusFB 库实现 Modbus TCP Server(双向读写)
- 作者: LinJiefeng
- 创建于 : 2026-11-05 21:30:44
- 更新于 : 2026-11-06 09:22:24
- 链接: https://blog.icsteam.cn/2026/11/05/208_codesysModbusTcpServer/
- 版权声明: 本文章采用 CC BY 4.0 进行许可。