微信算帐机器人定制 CODESYS 应用实践 — 上下位机闭环
这是工业自动化中非常典型的架构:上位机(PC/HMI/SCADA)负责监控、参数下发、数据可视化;下位机(PLC/控制器运行 CODESYS Runtime)负责实时逻辑控制、PID 闭环、运动控制。两者通过工业通信协议构成完整闭环。下面从架构到代码实现完整讲透。
---
一、架构总览
┌──────────────────────────────────────────────────────────┐
│ 上位机 (上位) │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ CODESYS │ │ HMI / │ │ SCADA / Python │ │
│ │ IDE │ │ WebVisu │ │ / 上位机软件 │ │
│ │ (开发调试)│ │ (可视化) │ │ (数据采集监控) │ │
│ └────┬────┘ └────┬─────┘ └────────┬─────────┘ │
│ │ │ │ │
│ └──────────────┼───────────────────┘ │
│ │ │
└───────────────────────┼───────────────────────────────────┘
│
通信协议层
┌──────────┼──────────┐
│ │ │
OPC UA Modbus TCP EtherCAT
(语义化) (简单通用) (实时总线)
│ │ │
┌───────────┼──────────┼──────────┼────────────────────────┐
│ │ │ │ 下位机 (下位) │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────┐ │
│ │ CODESYS Runtime │ │
│ │ (IEC 61131-3 运行时) │ │
│ │ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ PLC 逻辑 (ST/LD/FBD) │ │ │
│ │ │ PID 闭环控制 │ │ │
│ │ │ 运动控制 (MC) │ │ │
│ │ └──────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ I/O 驱动 │ │ │
│ │ │ EtherCAT Master │ │ │
│ │ │ Modbus TCP/Ser │ │ │
│ │ │ OPC UA Server │ │ │
│ │ └──────────────────────┘ │ │
│ └──────────────────────────────┘ │
│ │ │
│ ┌─────────┴──────────┐ │
│ │ 现场设备 │ │
│ │ 传感器/执行器/电机 │ │
│ └────────────────────┘ │
└──────────────────────────────────────────────────────────┘
---
二、CODESYS 是什么
| 维度 | 说明 |
|------|------|
| 全称 | Controller Development System |
| 开发方 | 德国 3S(CODESYS)软件集团 |
| 标准 | 遵循 IEC 61131-3 国际标准 |
| 编程语言 | LD(梯形图)、FBD(功能块图)、ST(结构化文本)、SFC(顺序功能图)、IL(指令表)、CFC(连续功能图) |
| 核心能力 | PLC 逻辑 + 运动控制 + HMI 可视化 + 安全控制 + Web 远程监控 + 冗余 |
| 硬件独立性 | 同一套软件可编程不同厂商的 PLC/控制器 |
| 架构 | IDE(开发环境)+ Runtime(运行时,部署在目标硬件上) |
---
三、上下位机角色划分
| 维度 | 上位机 | 下位机 |
|------|--------|--------|
| 硬件 | PC / 工业平板 / 触摸屏 | PLC / 运动控制器 / 嵌入式控制器 / 工业 PC |
| 软件 | CODESYS IDE / HMI 软件 / SCADA / Python | CODESYS Runtime |
| 职责 | 开发调试、参数下发、数据可视化、历史存储、报警管理 | 实时逻辑控制、PID 闭环、运动控制、I/O 采集 |
| 实时性 | 秒级 / 百毫秒级 | 毫秒级 / 微秒级 |
| 通信角色 | OPC UA Client / Modbus Master | OPC UA Server / Modbus Slave |
---
四、通信协议选择
| 协议 | 特点 | 适用场景 | CODESYS 支持 |
|------|------|----------|:---:|
| OPC UA | 语义化信息模型、带类型/时间戳/质量码、跨厂商标准 | 大量带语义 tag、工业互联网、MES/SCADA 对接 | ✅ 内置 Server |
| Modbus TCP | 简单、寄存器地址、无类型语义 | 简单数据交换、老旧设备兼容 | ✅ Master/Slave |
| EtherCAT | 实时总线、微秒级周期 | 伺服运动控制、高速 I/O | ✅ Master |
| CANopen | CAN 总线、设备协议 | 工程机械、车辆 | ✅ Master/Slave |
| PROFINET | 西门子生态 | 西门子设备对接 | ✅ 需授权 |
| EtherNet/IP | 罗克韦尔生态 | AB 设备对接 | ✅ Scanner/Adapter |
选型建议:
需要语义化 tag 管理?
├── 是 → OPC UA(PLC 做 Server,上位机做 Client)
└── 否 → 需要实时运动控制?
├── 是 → EtherCAT(伺服/IO 模块)
└── 否 → Modbus TCP(简单够用)
---
五、实战:OPC UA 上下位机闭环
5.1 下位机(CODESYS PLC)配置
Step 1:创建工程
CODESYS IDE → File → New Project
→ Device: CODESYS Control Win V3 (或对应硬件)
→ PLC PRJ → 添加 POU
Step 2:开启 OPC UA Server
在设备树中添加 OPC UA Server:
Device → 右键 → Add Device → Fieldbus → OPC UA → OPC UA Server
Step 3:定义变量(ST 语言)
// ── 全局变量表 ──
{attribute 'OPC.UA.DA' := '1'} // 暴露给 OPC UA
VAR_GLOBAL
// 上位机下发的设定值
fSetTemp : REAL := 25.0; // 目标温度
fSetPressure : REAL := 1.5; // 目标压力
bStart : BOOL := FALSE; // 启动信号
bStop : BOOL := FALSE; // 停止信号
// 上位机读取的反馈值
fActTemp : REAL := 0.0; // 实际温度
fActPressure : REAL := 0.0; // 实际压力
fActRPM : REAL := 0.0; // 实际转速
iRunStatus : INT := 0; // 运行状态码
sAlarmMsg : STRING(80); // 报警信息
END_VAR
Step 4:PID 闭环控制(ST 语言)
// ── PID 温度闭环控制 ──
FUNCTIONBLOCK FBTempControl
VAR_INPUT
fSetpoint : REAL; // 目标温度
fActual : REAL; // 实际温度(传感器采集)
bEnable : BOOL; // 使能
bAuto : BOOL; // 自动/手动模式
fManualOut : REAL; // 手动输出(0~100%)
END_VAR
VAR_OUTPUT
fOutput : REAL; // 输出(0~100%,控制加热器 PWM)
END_VAR
VAR
pidTemp : FB_PID; // CODESYS 内置 PID 功能块
fError : REAL;
END_VAR
// PID 参数
pidTemp(
KP := 2.0, // 比例系数
TN := 10.0, // 积分时间(秒)
TV := 2.0, // 微分时间(秒)
SET_POINT := fSetpoint,
ACTUAL := fActual,
AUTO := bAuto,
MANUAL := fManualOut,
Y => fOutput // 输出 0~100%
);
IF NOT bEnable THEN
fOutput := 0.0;
END_IF
Step 5:主程序
PROGRAM PLC_PRG
VAR
fbTemp1 : FB_TempControl;
fbPress1: FB_PressureControl;
iState : INT := 0; // 状态机
END_VAR
// ── 状态机 ──
CASE iState OF
0: // 待机
IF bStart THEN
iState := 10;
bStart := FALSE;
END_IF
10: // 运行 - 温度闭环
fbTemp1(
fSetpoint := fSetTemp,
fActual := fActTemp,
bEnable := TRUE,
bAuto := TRUE,
fManualOut := 0.0
);
// 压力闭环
fbPress1(
fSetpoint := fSetPressure,
fActual := fActPressure,
bEnable := TRUE,
bAuto := TRUE,
fManualOut := 0.0
);
// 状态反馈
iRunStatus := 10;
IF bStop THEN
iState := 20;
bStop := FALSE;
END_IF
20: // 停止
fbTemp1(bEnable := FALSE);
fbPress1(bEnable := FALSE);
iRunStatus := 20;
iState := 0;
END_CASE
// ── 报警检测 ──
IF fActTemp > 80.0 THEN
sAlarmMsg := '温度超限!';
iState := 20;
ELSIF fActPressure > 5.0 THEN
sAlarmMsg := '压力超限!';
iState := 20;
ELSE
sAlarmMsg := '';
END_IF
Step 6:下载到下位机
Online → Login → Download
→ PLC 开始运行,OPC UA Server 在端口 4840 监听
5.2 上位机(Python OPC UA Client)
from opcua import Client
import time
连接下位机 OPC UA Server
client = Client("opc.tcp://192.168.1.100:4840")
client.connect()
── 获取变量节点 ──
方式 A:通过 NodeID 直接获取
nodesettemp = client.getnode("ns=4;s=Application.PLCPRG.fSetTemp")
nodeacttemp = client.getnode("ns=4;s=Application.PLCPRG.fActTemp")
nodestart = client.getnode("ns=4;s=Application.PLC_PRG.bStart")
nodestatus = client.getnode("ns=4;s=Application.PLC_PRG.iRunStatus")
nodealarm = client.getnode("ns=4;s=Application.PLC_PRG.sAlarmMsg")
── 下发设定值 ──
nodesettemp.set_value(25.0) # 设置目标温度
nodestart.setvalue(True) # 启动
── 订阅反馈值(变化时回调)──
def ontempchanged(node, val, data):
print(f"[反馈] 实际温度: {val:.1f}°C")
def onalarmchanged(node, val, data):
if val:
print(f"[报警] {val}")
sub = client.create_subscription(500, handler=None) # 500ms
sub.subscribedatachange(nodeacttemp, ontempchanged)
sub.subscribedatachange(nodealarm, onalarm_changed)
── 主循环:监控 + 记录 ──
while True:
status = nodestatus.getvalue()
temp = nodeacttemp.get_value()
print(f"[状态] {status} | 温度 {temp:.1f}°C")
time.sleep(1)
5.3 上位机(CODESYS WebVisu 可视化)
如果用 CODESYS 自带的 WebVisu,不需要额外软件:
工程中添加 Visualization Manager
添加 Visualization 对象
拖拽控件:
文本框绑定 fActTemp(显示实际温度)
输入框绑定 fSetTemp(下发目标温度)
按钮绑定 bStart / bStop
指示灯绑定 iRunStatus
报警条绑定 sAlarmMsg
WebVisu → 启动 Web Server
浏览器打开 http://<PLC_IP>:8080
---
六、实战:Modbus TCP 上下位机闭环
6.1 下位机配置
Device → 右键 → Add Device → Fieldbus → Modbus → Modbus TCP Slave Device
→ 配置 IP: 192.168.1.100, Port: 502
→ 添加 Modbus Channel:
Holding Register 0: fSetTemp (REAL, 2 registers)
Holding Register 2: fActTemp (REAL, 2 registers)
Coil 0: bStart
Coil 1: bStop
6.2 上位机(Python Modbus Client)
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient("192.168.1.100", port=502)
client.connect()
写设定值(REAL 占 2 个寄存器)
import struct
temp_bytes = struct.pack('>f', 25.0)
regval = struct.unpack('>HH', tempbytes)
client.writeregisters(0, list(regval))
启动
client.write_coil(0, True)
读实际温度
result = client.readholdingregisters(2, 2)
raw = struct.pack('>HH', result.registers[0], result.registers[1])
act_temp = struct.unpack('>f', raw)[0]
print(f"实际温度: {act_temp:.1f}°C")
client.close()
---
七、PID 闭环控制完整实现
CODESYS 提供 FBPID(或 CTRLPID)功能块,也可以手写:
// ── 手写位置式 PID ──
FUNCTIONBLOCK FBPID_Custom
VAR_INPUT
fSetpoint : REAL; // 设定值
fActual : REAL; // 实际值
fKp : REAL := 2.0; // 比例
fKi : REAL := 0.1; // 积分
fKd : REAL := 0.5; // 微分
fOutMin : REAL := 0.0; // 输出下限
fOutMax : REAL := 100.0; // 输出上限
tCycle : TIME := T#100ms; // 控制周期
END_VAR
VAR_OUTPUT
fOutput : REAL; // 输出
END_VAR
VAR
fError : REAL;
fLastError: REAL;
fIntegral : REAL;
fDeriv : REAL;
ton : TON; // 定时器
END_VAR
// 周期触发
ton(PT := tCycle, IN := TRUE);
IF ton.Q THEN
ton(IN := FALSE);
// PID 计算
fError := fSetpoint - fActual;
fIntegral := fIntegral + fError * TIMETOREAL(tCycle);
fDeriv := (fError - fLastError) / TIMETOREAL(tCycle);
fOutput := fKp fError + fKi fIntegral + fKd * fDeriv;
// 输出限幅
fOutput := LIMIT(fOutMin, fOutput, fOutMax);
// 抗积分饱和
IF fOutput >= fOutMax AND fError > 0 THEN
fIntegral := fIntegral - fError * TIMETOREAL(tCycle);
ELSIF fOutput <= fOutMin AND fError < 0 THEN
fIntegral := fIntegral - fError * TIMETOREAL(tCycle);
END_IF
fLastError := fError;
END_IF
---
八、通信协议对比
| 维度 | OPC UA | Modbus TCP | EtherCAT |
|------|--------|------------|----------|
| 语义化 | ✅ 带名字/类型/时间戳 | ❌ 只有寄存器地址 | ❌ 过程数据映射 |
| 类型安全 | ✅ 强类型 | ❌ 需手动解析 | ✅ ESI 描述 |
| 订阅模式 | ✅ 变化通知 | ❌ 轮询 | ✅ 周期同步 |
| 实时性 | 毫秒级 | 十毫秒级 | 微秒级 |
| 配置复杂度 | 中 | 低 | 高 |
| 跨厂商 | ✅ 标准 | ✅ 标准 | ✅ 标准 |
| 适用 | 上位机 ↔ PLC | 简单数据交换 | PLC ↔ 伺服/IO |
---
九、典型部署形态
形态 1:CODESYS 软PLC(PC-based)
工业 PC(运行 CODESYS Control for Linux/Windows)
├── 上位机:同一 PC 或远程 PC 上的 WebVisu/SCADA
├── 下位机:CODESYS Runtime(软 PLC)
├── 通信:本地内存共享 / OPC UA
└── I/O:EtherCAT 总线连接远程 IO 模块
形态 2:CODESYS + 独立 PLC
上位机 PC(CODESYS IDE + WebVisu)
↕ OPC UA / Modbus TCP
下位机 PLC(汇川 AC 系列 / 倍福 CX / Wago PFC 等)
↕ EtherCAT / Modbus
现场设备(伺服/传感器/执行器)
形态 3:HMI 一体化(HMI 即 PLC)
威纶通 cMT X / 倍福 CP 系列
├── HMI(触摸屏可视化)
├── PLC(CODESYS Runtime 内嵌)
└── 远程 IO(EtherCAT/Modbus)
---
十、调试与排坑
| 问题 | 原因 | 解法 |
|------|------|------|
| OPC UA 连不上 | 端口未开放/防火墙 | 检查 4840 端口、关闭防火墙或加白名单 |
| 变量在 OPC UA 中看不到 | 未加 {attribute 'OPC.UA.DA'} | 在变量声明前加属性标记 |
| Modbus 读到乱码 | 字节序不对 | 尝试 Big-endian / Little-endian / Word Swap |
| PID 输出震荡 | Kp 太大或周期太短 | 降 Kp、增大积分时间、调大控制周期 |
| EtherCAT 通信超时 | 拓扑/线缆/从站配置 | 检查拓扑顺序、线缆质量、ENI 文件 |
| WebVisu 打不开 | Web Server 未启动 | 工程中添加 WebVisu 模块,下载后访问 8080 |
| 下位机重启后参数丢失 | 变量未持久化 | 变量加 PERSISTENT 或 RETAIN 关键字 |
参数持久化:
VAR_GLOBAL PERSISTENT
fSetTemp : REAL := 25.0; // 重启后保持上次值
fSetPressure : REAL := 1.5;
END_VAR
---
十一、完整闭环数据流
上位机设定值 ──OPC UA/Modbus──► 下位机 PLC
│
▼
PID 闭环计算
│
▼
PWM/模拟量输出 ──► 加热器/电机/阀门
│
▼
传感器采集实际值
│
▼
实际值反馈 ◄──OPC UA/Modbus── 下位机 PLC ──► 上位机显示/记录/报警
核心就一条链:上位机下发设定值 → 下位机 PID 闭环 → 输出到执行器 → 传感器采集反馈 → 上位机监控显示。CODESYS 的优势在于 PLC 逻辑、PID 控制、HMI 可视化、通信协议全部在一个平台里完成,不需要拼凑多个软件。