PLC 的核心:循环扫描
PLC(可编程逻辑控制器)从诞生之初就与通用计算机走了两条路。通用计算机追求吞吐与灵活,PLC 追求的是确定性:给定同样的输入,在同样的时刻给出同样的输出。为此它采用了一套看起来“笨”但极其可靠的执行模型——循环扫描。一个扫描周期分为三个阶段:输入采样,把所有输入端子的状态一次性读进输入映像区;程序执行,从上到下把用户程序完整跑一遍;输出刷新,把输出映像区的结果一次性送到输出端子。
理解 PLC 的关键,是接受“程序不是随时响应,而是周期性、按顺序、从头到尾执行一遍”这件事。所有看起来反直觉的现象,都能从这个模型里找到解释。
扫描周期通常在 1 到 10 毫秒之间,程序越长、指令越复杂,周期越长。这个数字直接决定了系统能捕捉到多窄的脉冲信号:如果某个传感器的输出只持续 0.5 毫秒,而扫描周期是 5 毫秒,那么这个信号很可能被完整地跳过。这也是为什么急停、安全门这类安全信号不会交给普通 PLC 程序去处理,而是走独立的安全回路或安全 PLC。
- 扫描周期程序执行一遍所需时间,是 PLC 的“心跳”,也是所有实时性指标的基准。
- 映像区输入输出都先写进内存映像,程序运算的是映像而不是硬件端口,这才保证了同一周期内输入状态的一致性。
- 看门狗监视扫描周期,一旦程序跑飞导致周期超限,立即切到安全状态并报错。
梯形图:从继电器回路继承的表达法
梯形图(Ladder Diagram)的图形长得像电气控制柜里的继电器回路:左边一条母线,右边一条母线,中间是并联与串联的触点,末端接线圈。这不是刻意的形式主义,而是为了让上世纪熟悉继电器柜的电工能够平滑地迁移到编程环境——同一个逻辑,画的还是那张图,只是触点从物理接触变成了内存位。
IEC 61131-3 标准定义了五种编程语言:梯形图(LD)、功能块图(FBD)、结构化文本(ST)、指令表(IL)和顺序功能图(SFC)。实际工程中常见的是混合使用:设备级的联锁与顺序控制用梯形图,看得见逻辑、方便现场维护;涉及运算、字符串、数组、状态机的部分用结构化文本,表达力更强。下面的实践建议来自产线维护的真实教训。
- 把“主控流程”和“设备动作”分成不同的程序块,主控只负责发出启动、停止、复位这类指令。
- 每个输出线圈只在一处被赋值。多处赋值是后期调试时最难排查的故障来源。
- 所有联锁条件写在动作之前的串联回路上,而不是在动作之后再补一个“如果异常则关闭”。
- 给每个程序块加注释,写清它的输入条件、输出结果与对应的机械部位。
一个能上机的例子:启保停与故障锁存
“启保停”是工业控制里最基础的回路:按下启动按钮,接触器吸合并用自身常开触点保持;按下停止按钮,回路断开。加上热继电器过载保护后,就构成了最小的电机控制逻辑。下面用符合 IEC 61131-3 的结构化文本写成一个功能块,可以直接在支持 ST 的 PLC 工程中调用。
(* 电机启停控制:IEC 61131-3 结构化文本 *)
FUNCTION_BLOCK FB_MotorCtrl
VAR_INPUT
StartBtn : BOOL; (* 启动按钮,常开触点 *)
StopBtn : BOOL; (* 停止按钮,常闭触点,正常时为 TRUE *)
Overload : BOOL; (* 热继电器,过载时为 TRUE *)
ResetBtn : BOOL; (* 故障复位,上升沿有效 *)
END_VAR
VAR_OUTPUT
Run : BOOL; (* 接触器输出 *)
Fault : BOOL; (* 故障指示 *)
END_VAR
VAR
FaultLatch : BOOL; (* 故障锁存位 *)
END_VAR
(* 故障优先:先处理锁存,再处理运行 *)
IF Overload THEN
FaultLatch := TRUE; (* 过载置位并锁存 *)
ELSIF ResetBtn AND NOT Overload THEN
FaultLatch := FALSE; (* 仅在故障已消失时允许复位 *)
END_IF;
Fault := FaultLatch;
IF FaultLatch OR StopBtn THEN
Run := FALSE; (* 故障或停止优先于启动 *)
ELSIF StartBtn THEN
Run := TRUE; (* 保持:本周期未断开则继续运行 *)
END_IF;
END_FUNCTION_BLOCK
这段代码里有三个值得反复咀嚼的设计决定。第一,故障优先级高于启动,任何一个周期里只要 Overload 为真,Run 一定是 FALSE,不存在“抢在保护之前启动”的可能性。第二,故障必须锁存,因为热继电器在电机停机后会自然冷却复位,如果不锁存,操作员一按启动就会再次过载,形成反复冲击。第三,复位需要人工确认,这是安全设计的基本要求:自动复位的故障很容易被忽略,直到事故发生。
把功能块实例化使用即可:Motor1(StartBtn := I0.0, StopBtn := I0.1, Overload := I0.2, ResetBtn := I0.3);,随后用 Motor1.Run 驱动输出线圈。这种“把逻辑封装成可复用块”的习惯,在一条产线上有二十台同型号电机时,能把程序量压到原来的三分之一。
上位机与 PLC 的三种通信路径
PLC 负责现场控制,上位机负责数据汇聚、界面展示与业务流转,两者之间必须有稳定的数据通道。工程上可选的主干路径有三条,各有明确的适用边界。
- 串口 Modbus RTU:成本最低,适合单台设备或老设备改造,缺点是速率低、一条总线上的从站数量有限,通常不超过 32 个。
- 以太网 Modbus TCP:现场最通用,带宽足够,可以直接跑在产线环网里,端口固定 502。多数中小型 PLC 都原生支持。
- 厂商协议或 OPC UA:西门子 S7 协议、三菱 MC 协议等能读取更丰富的信息;OPC UA 则在语义与安全上更进一步,适合接入上层平台。
- 数据类型PLC 里一个 REAL 占两个寄存器,上位机必须按同样的字节序还原,否则读出来的转速会是天文数字。
- 轮询频率上位机轮询周期必须明显大于 PLC 扫描周期,否则通信任务会挤占程序执行时间。
- 写权限只读的点位严禁开放写接口,一次误写设定值就可能造成批量报废。
上位机程序骨架:轮询、重连与写入
上位机程序最常见的写法是“定时器 + 读寄存器”,但这种写法在生产环境里几乎一定会出问题:网络抖动或 PLC 重启后,客户端会停在半开连接上,读操作一直超时却永远不重连,界面上的数据从此定格。下面这段骨架把连接管理、失败计数与主动重建都写清楚了,可以直接作为采集模块的底座。
import time
from pymodbus.client import ModbusTcpClient
PLC_IP, PLC_PORT = "192.168.1.10", 502
POLL_INTERVAL = 0.5 # 采集周期(秒),需明显大于 PLC 扫描周期
MAX_FAIL = 3 # 连续失败阈值,超过则重建连接
def connect():
while True:
client = ModbusTcpClient(PLC_IP, port=PLC_PORT, timeout=1)
if client.connect():
print("PLC 连接成功")
return client
print("连接失败,3 秒后重试")
time.sleep(3)
def main():
client = connect()
fails = 0
while True:
rr = client.read_holding_registers(address=0, count=8, slave=1)
if rr.isError():
fails += 1
print(f"读取失败 {fails}/{MAX_FAIL}")
if fails >= MAX_FAIL:
client.close() # 主动断开,避免半开连接假死
client = connect()
fails = 0
else:
fails = 0
# 约定:转速以 10 倍整数存储,读回后除以 10 还原工程量
speed = rr.registers[0] / 10.0
print(f"当前转速 {speed:.1f} rpm")
time.sleep(POLL_INTERVAL)
if __name__ == "__main__":
main()
关于写操作,有三条经验规则值得写进团队的开发规范:写之前先读一次确认当前状态,避免盲目覆盖;写之后回读一次确认生效,把“写成功但没生效”这种情况暴露出来;所有写操作记录操作人、时间与旧值新值,出问题时这是唯一的追溯依据。
报警与数据记录设计
报警和记录是上位机最容易被做糙、又最影响使用体验的两个模块。做糙的典型表现是:设备稍微抖动就刷屏报警,操作员最终形成“报警麻木”;历史库里每天写入几千万条不变的数据,查询慢得无法接受。
- 分级:把报警分为提示、警告、故障三级,只有故障级才需要声光提醒与记录追责,提示级可以只在界面上做颜色变化。
- 死区与去抖:给模拟量设置死区(例如温度偏离设定值 0.5 度以上才报警),给开关量设置持续时间(持续 200 毫秒以上才确认),可以过滤掉九成以上的无效报警。
- 确认与复位分离:操作员点击“确认”只表示看到了,不代表故障消失;故障消失后单独提供复位入口,两个状态分别记录。
- 变化存储优先:模拟量在变化超过阈值时写入、开关量在变化时写入,再叠加一个低频的心跳点,既省空间又不丢信息。
- 统一时间源:所有记录都带采集时间戳,且各采集节点统一对时,否则跨设备的数据对齐无从谈起。
历史库选型上,若只是单机几万点的记录,成熟的时序数据库已经足够;真正需要提前设计的是数据保留策略——原始数据保留多久、降采样后的趋势数据保留多久、报警记录是否永久保留,这三件事必须在项目初期和业主确认清楚。
联调与交付清单
项目最后阶段的联调质量,往往决定了这套系统上线后要返工几次。把下面的清单作为交付前的固定动作,可以避免大部分“上线即救火”的场面。
- IO 点表核对:逐个点确认物理地址、信号类型、量程与工程量换算,特别关注模拟量的量程上下限。
- 通信地址表核对:寄存器地址、数据类型、字节序与从站地址表三方一致,并与上位机点表逐条比对。
- 报警阈值确认:与工艺工程师共同确认每一级阈值的取值依据,写进文档而不是只留在程序里。
- 离线模拟测试:用仿真器把通信断开、把数值置为边界值,验证上位机的重连、报警与记录行为。
- 安全边界复查:急停、安全门、光栅等安全信号必须是硬件回路或安全 PLC,不能依赖普通程序逻辑。
- 版本与备份:PLC 程序、上位机程序、触摸屏工程、点表文档四份文件同步归档,并记录版本号与变更说明。
做完这些,一个能稳定运行的产线控制系统才算真正交付。PLC 编程的门槛在于把工艺需求翻译成确定性的逻辑,上位机开发的门槛在于把不稳定的网络与硬件抽象成稳定的数据流——这两件事都不难,难的是长期保持一致与可维护。