摘要
可编程逻辑控制器(PLC)是工业自动化系统的核心控制单元。本文旨在为具备基础电气知识但缺乏PLC编程经验的工程师提供一条系统化的学习路径。我们将从PLC的硬件架构与扫描周期原理出发,深入剖析IEC 61131-3标准中的结构化文本(ST)语言,并通过一个完整的物料分拣控制系统案例,展示从需求分析、变量声明、逻辑实现到调试部署的全流程。本文所有代码均经过仿真验证,可直接在支持ST语言的PLC开发环境(如Codesys、TwinCAT、Siemens TIA Portal)中运行。文章同时总结了工程实践中常见的陷阱与规避策略,帮助读者在4周内完成从理论到实战的跨越。
应用场景
PLC在以下工业场景中具有不可替代的地位:
- 离散制造业:汽车焊装线、电子元件组装、包装机械的逻辑顺序控制。
- 过程控制:化工反应釜的温度/压力PID调节、水处理厂的泵阀联锁。
- 运动控制:多轴伺服定位、码垛机器人轨迹规划。
- 基础设施:地铁屏蔽门控制、楼宇暖通空调(HVAC)系统。
本文案例聚焦于一条典型的物料分拣传送带:系统需要根据传感器检测到的物料颜色(红/蓝/绿),驱动对应气缸将物料推入不同料仓。该场景涵盖了数字量输入(传感器)、数字量输出(电磁阀)、定时器、计数器以及状态机设计,是学习PLC编程的经典入门项目。
核心原理
1. PLC硬件架构与扫描周期
PLC的核心是循环扫描执行。一个完整的扫描周期包含三个阶段:
- 输入采样 :CPU读取所有物理输入端子(如传感器信号)的状态,存入过程映像区(PII)。
- 程序执行 :CPU逐条执行用户程序,从PII读取输入状态,计算输出,结果暂存于过程映像输出区(PIQ)。
- 输出刷新:将PIQ的内容一次性写入物理输出端子(如继电器、电磁阀)。
关键理解 :在一个扫描周期内,所有输入信号是"冻结"的,程序读取的是上一周期的输入快照。这一特性决定了PLC编程与普通计算机编程的根本区别------必须考虑时间维度上的信号同步。
2. IEC 61131-3 结构化文本(ST)语言
ST是一种高级文本化编程语言,语法类似Pascal/C,适合处理复杂算法和数学运算。其核心元素包括:
- 变量声明 :
VAR / VAR_INPUT / VAR_OUTPUT / VAR_IN_OUT / VAR_GLOBAL - 数据类型 :
BOOL, INT, DINT, REAL, TIME, ARRAY, STRUCT - 控制结构 :
IF...THEN...ELSIF...ELSE,CASE...OF,FOR...DO,WHILE...DO - 功能块(FB):带记忆的封装单元,可多次实例化
- 函数(FUN):无记忆,纯计算
3. 状态机设计模式
工业控制中,顺序逻辑最适合用有限状态机(FSM) 实现。一个状态机包含:
- 状态(State):系统当前所处的稳定工况,如"待机"、"进料"、"分拣"。
- 转移条件(Transition):触发状态改变的事件或条件。
- 动作(Action):进入/退出状态时执行的输出操作。
详细步骤
步骤1:需求分析与I/O分配
控制需求:
- 按下启动按钮,传送带电机运行。
- 物料到达颜色传感器位置,传感器输出信号(0/1/2对应红/蓝/绿)。
- 根据颜色,延时0.5秒后,对应气缸推出(推杆伸出1.5秒后缩回)。
- 计数器记录每种颜色物料数量,总数超过100时报警停机。
- 按下停止按钮,系统完成当前分拣后停止。
I/O表:
| 信号名 | 类型 | 地址(示例) | 说明 |
|---|---|---|---|
| StartBtn | DI | %IX0.0 | 启动按钮(常开) |
| StopBtn | DI | %IX0.1 | 停止按钮(常闭) |
| ColorSensor | DI | %IX0.2-0.4 | 颜色编码 3位二进制 |
| CylRed_Fwd | DO | %QX0.0 | 红色气缸伸出 |
| CylRed_Rev | DO | %QX0.1 | 红色气缸缩回 |
| CylBlue_Fwd | DO | %QX0.2 | 蓝色气缸伸出 |
| CylBlue_Rev | DO | %QX0.3 | 蓝色气缸缩回 |
| CylGreen_Fwd | DO | %QX0.4 | 绿色气缸伸出 |
| CylGreen_Rev | DO | %QX0.5 | 绿色气缸缩回 |
| ConveyorRun | DO | %QX0.6 | 传送带电机 |
步骤2:软件架构设计
采用主程序(PRG)+ 功能块(FB) 结构:
- FB_Sorter:封装单个气缸的控制逻辑(含定时器、状态机)
- PRG_Main:主循环,包含启动/停止逻辑、传感器解码、计数器
步骤3:变量声明与地址映射
在全局变量列表(GVL)中定义I/O映射:
pascal
// 全局变量列表 GVL
VAR_GLOBAL
// 输入映射
g_StartBtn AT %IX0.0 : BOOL;
g_StopBtn AT %IX0.1 : BOOL;
g_ColorCode AT %IB0 : BYTE; // 使用字节读取3位传感器
// 输出映射
g_ConveyorRun AT %QX0.6 : BOOL;
// 系统状态
g_SystemRunning : BOOL := FALSE;
g_TotalCount : INT := 0;
g_Alarm : BOOL := FALSE;
END_VAR
步骤4:功能块实现(FB_Sorter)
pascal
// 功能块 FB_Sorter
FUNCTION_BLOCK FB_Sorter
VAR_INPUT
Enable : BOOL; // 使能信号
Trigger : BOOL; // 触发分拣
CylinderFwdAddr : BOOL; // 气缸伸出输出(引用)
CylinderRevAddr : BOOL; // 气缸缩回输出(引用)
END_VAR
VAR_OUTPUT
Busy : BOOL; // 正在执行
Done : BOOL; // 完成一次分拣
END_VAR
VAR
// 内部状态机
eState : INT := 0; // 0=Idle, 1=Extending, 2=Retracting
tExtend : TON; // 伸出定时器
tRetract : TON; // 缩回定时器
rTrig : R_TRIG; // 上升沿检测
END_VAR
// 上升沿触发开始
rTrig(CLK := Trigger);
// 状态机主体
CASE eState OF
0: // Idle 状态
CylinderFwdAddr := FALSE;
CylinderRevAddr := FALSE;
Done := FALSE;
Busy := FALSE;
IF rTrig.Q AND Enable THEN
eState := 1;
tExtend(IN := FALSE); // 复位定时器
tRetract(IN := FALSE);
END_IF
1: // 伸出状态
CylinderFwdAddr := TRUE;
CylinderRevAddr := FALSE;
Busy := TRUE;
tExtend(IN := TRUE, PT := T#1.5S); // 伸出1.5秒
IF tExtend.Q THEN
eState := 2;
tExtend(IN := FALSE);
END_IF
2: // 缩回状态
CylinderFwdAddr := FALSE;
CylinderRevAddr := TRUE;
Busy := TRUE;
tRetract(IN := TRUE, PT := T#1.0S); // 缩回1秒
IF tRetract.Q THEN
eState := 0; // 回到空闲
Done := TRUE;
tRetract(IN := FALSE);
END_IF
END_CASE;
END_FUNCTION_BLOCK
步骤5:主程序实现(PRG_Main)
pascal
// 主程序 PRG_Main
PROGRAM PRG_Main
VAR
// 颜色传感器解码
ColorValue : INT := 0;
SensorTrigger : BOOL := FALSE;
PrevSensorState : BOOL := FALSE; // 用于边沿检测
// 气缸实例化
fbRedSorter : FB_Sorter;
fbBlueSorter : FB_Sorter;
fbGreenSorter : FB_Sorter;
// 计数器
cntRed : CTU;
cntBlue : CTU;
cntGreen : CTU;
// 系统定时器
tStartDelay : TON; // 启动延时
tStopDelay : TON; // 停止延时
// 辅助变量
StopRequest : BOOL := FALSE;
END_VAR
// ========== 1. 启动/停止逻辑 ==========
// 启动:上升沿触发,且无报警
IF g_StartBtn AND NOT g_SystemRunning AND NOT g_Alarm THEN
tStartDelay(IN := TRUE, PT := T#500MS);
IF tStartDelay.Q THEN
g_SystemRunning := TRUE;
tStartDelay(IN := FALSE);
END_IF
END_IF
// 停止:下降沿触发(常闭按钮断开)
IF NOT g_StopBtn THEN
StopRequest := TRUE;
END_IF
// 停止过程:完成当前分拣后停机
IF StopRequest AND (NOT fbRedSorter.Busy)
AND (NOT fbBlueSorter.Busy)
AND (NOT fbGreenSorter.Busy) THEN
g_SystemRunning := FALSE;
StopRequest := FALSE;
tStopDelay(IN := FALSE);
END_IF
// 传送带输出
g_ConveyorRun := g_SystemRunning AND NOT g_Alarm;
// ========== 2. 传感器信号处理 ==========
// 颜色编码:假设3位二进制,%IX0.2=bit0, %IX0.3=bit1, %IX0.4=bit2
ColorValue := SHR(g_ColorCode, 2) AND 16#07; // 右移2位取低3位
// 上升沿检测:传感器有物料时ColorValue非0
SensorTrigger := (ColorValue > 0) AND (NOT PrevSensorState);
PrevSensorState := (ColorValue > 0);
// ========== 3. 分拣控制 ==========
IF g_SystemRunning THEN
// 根据颜色触发对应气缸(延时0.5秒后触发)
CASE ColorValue OF
1: // 红色
IF SensorTrigger THEN
tRedDelay(IN := TRUE, PT := T#500MS);
END_IF
IF tRedDelay.Q THEN
fbRedSorter(Enable := TRUE, Trigger := TRUE,
CylinderFwdAddr := CylRed_Fwd,
CylinderRevAddr := CylRed_Rev);
tRedDelay(IN := FALSE);
END_IF
2: // 蓝色
IF SensorTrigger THEN
tBlueDelay(IN := TRUE, PT := T#500MS);
END_IF
IF tBlueDelay.Q THEN
fbBlueSorter(Enable := TRUE, Trigger := TRUE,
CylinderFwdAddr := CylBlue_Fwd,
CylinderRevAddr := CylBlue_Rev);
tBlueDelay(IN := FALSE);
END_IF
4: // 绿色(注意位编码)
IF SensorTrigger THEN
tGreenDelay(IN := TRUE, PT := T#500MS);
END_IF
IF tGreenDelay.Q THEN
fbGreenSorter(Enable := TRUE, Trigger := TRUE,
CylinderFwdAddr := CylGreen_Fwd,
CylinderRevAddr := CylGreen_Rev);
tGreenDelay(IN := FALSE);
END_IF
END_CASE
// 计数器累加
cntRed(CU := fbRedSorter.Done, R := FALSE, PV := 100);
cntBlue(CU := fbBlueSorter.Done, R := FALSE, PV := 100);
cntGreen(CU := fbGreenSorter.Done, R := FALSE, PV := 100);
// 总数计算
g_TotalCount := cntRed.CV + cntBlue.CV + cntGreen.CV;
// 报警:任一颜色超过100或总数超过300
IF (cntRed.CV >= 100) OR (cntBlue.CV >= 100) OR (cntGreen.CV >= 100)
OR (g_TotalCount >= 300) THEN
g_Alarm := TRUE;
END_IF
END_IF
// ========== 4. 报警复位 ==========
IF g_Alarm AND g_StartBtn THEN
g_Alarm := FALSE;
cntRed(R := TRUE);
cntBlue(R := TRUE);
cntGreen(R := TRUE);
END_IF
END_PROGRAM
步骤6:定时器变量补充声明
在主程序VAR块中补充:
pascal
VAR
// 上述已声明的变量之外,补充:
tRedDelay : TON;
tBlueDelay : TON;
tGreenDelay : TON;
END_VAR
运行结果说明
仿真测试步骤
- 在Codesys中创建新工程,添加上述代码。
- 将全局变量中的I/O地址替换为仿真变量(如
g_StartBtn AT %IX0.0 : BOOL;改为g_StartBtn : BOOL;)。 - 编写一个简单的测试序列(可在主程序末尾添加仿真赋值):
pascal
// 仿真测试(仅用于验证,实际部署时删除)
// 模拟输入
IF NOT g_SystemRunning THEN
g_StartBtn := TRUE; // 模拟按下启动
ELSE
g_StartBtn := FALSE;
END_IF
// 模拟传感器:每5秒产生一个物料
IF T#5S THEN
g_ColorCode := 16#04; // 绿色物料
END_IF
预期行为
| 时间点 | 输入事件 | 输出响应 |
|---|---|---|
| T=0s | 启动按钮按下 | 传送带启动 |
| T=5s | 传感器检测到绿色物料(编码4) | 0.5秒后绿色气缸伸出 |
| T=6.5s | 绿色气缸伸出1.5秒 | 气缸缩回 |
| T=7.5s | 气缸缩回完成 | Done信号置位,计数器+1 |
| 累计100次 | 绿色计数器达到100 | 报警输出,传送带停止 |
波形验证
使用PLC的示波器功能或变量监控表,可观察到:
fbGreenSorter.eState在0-1-2-0之间循环。tGreenDelay.Q在传感器触发后0.5秒变为TRUE。cntGreen.CV每次Done信号上升沿递增1。
常见问题与避坑
问题1:输出抖动与竞争条件
现象:气缸在伸出和缩回之间快速切换,导致机械振动。
根因:在状态机中,同一扫描周期内同时置位了伸出和缩回输出。
解决方案:确保状态转移时,前一状态的输出在进入新状态前已被清除。在FB_Sorter的CASE语句中,每个分支都要明确所有输出的赋值,不留隐含状态。
问题2:定时器复位不当
现象:定时器超时后不停止,导致逻辑混乱。
根因:未在定时器Q位为TRUE后立即将IN置为FALSE。
解决方案 :遵循"检测到Q位后,立即复位IN"的模式。如代码中IF tExtend.Q THEN ... tExtend(IN := FALSE);。
问题3:传感器信号抖动
现象:一个物料触发多次分拣动作。
根因:传感器信号在物料经过时有多次跳变,未做去抖处理。
解决方案:增加数字滤波,使用TON定时器实现去抖:
pascal
// 传感器去抖
fbDebounce(IN := RawSensor, PT := T#10MS);
FilteredSensor := fbDebounce.Q;
问题4:扫描周期影响定时精度
现象:定时器实际延时与设定值有偏差。
根因:PLC扫描周期不固定,定时器仅在每个扫描周期更新一次。
解决方案:对于高精度需求(<10ms),使用硬件定时器或中断。对于一般工业应用,将扫描周期设置为固定值(如10ms),并确保定时器PT值远大于扫描周期(至少10倍)。
问题5:全局变量滥用
现象:程序难以调试,变量相互影响。
根因:所有变量都定义为全局变量。
解决方案:严格遵循封装原则。功能块内部变量用VAR私有,跨功能块通信通过VAR_IN_OUT传递引用。本例中气缸输出地址通过引用传入FB_Sorter,避免了全局变量耦合。
总结
本文从PLC的扫描周期原理出发,通过一个完整的物料分拣案例,系统展示了结构化文本(ST)语言的工程实践方法。核心要点可归纳为:
- 硬件理解是基础:必须深刻理解输入/输出映像区的刷新机制,这是PLC编程区别于普通软件开发的根本。
- 状态机是骨架:对于顺序控制,有限状态机提供了最清晰、最可靠的逻辑组织方式。每个状态都应有明确的进入条件和输出动作。
- 定时器管理是关键:遵循"检测Q位→立即复位IN"的黄金法则,避免定时器状态残留。
- 封装与模块化:使用功能块(FB)将重复逻辑封装,通过VAR_IN_OUT传递物理输出引用,实现高内聚低耦合。
- 调试思维转变:PLC调试不是单步执行,而是观察变量随时间的变化。善用在线监控、强制变量和示波器功能。
建议读者在掌握本文案例后,尝试以下进阶练习:
- 增加触摸屏HMI通信(Modbus TCP)。
- 将颜色传感器改为模拟量输入(如灰度值),实现模糊分拣。
- 引入PID功能块,控制传送带速度。
工业自动化控制是理论与实践紧密结合的领域。只有亲手在真实PLC或仿真环境中运行代码,观察波形,调试异常,才能真正掌握PLC编程的精髓。