在结构化文本(ST)编程环境中实现多任务处理,核心在于将不同响应速度、执行频率和安全等级的控制逻辑,分配到相互独立、优先级明确的任务槽中。这并非简单地“写多个程序”,而是通过PLC运行时系统的任务调度机制,让高优先级任务(如紧急停机、高速脉冲输出)不受低优先级任务(如历史数据记录、HMI画面刷新)阻塞,确保关键控制动作在确定时间内完成。
以下是以主流支持IEC 61131-3标准的PLC(如倍福TwinCAT、贝加莱Automation Studio、罗克韦尔Studio 5000 Logix Designer)为背景,手把手配置ST多任务的实操指南。所有操作均基于标准功能块与系统变量,不依赖厂商私有扩展。
一、理解任务层级:周期性任务 ≠ 同步任务 ≠ 事件任务
PLC中的“任务”本质是调度单元,每个任务绑定一个执行条件(触发源)和一个执行周期(或触发事件)。三类基础任务类型必须严格区分:
-
周期性任务(Cyclic Task)
按固定时间间隔重复执行,例如1 ms、10 ms、100 ms。适用于运动控制、PID调节、I/O扫描等对时间确定性要求高的场景。 -
同步任务(Synchronous Task)
与硬件时钟(如编码器Z相、主轴位置信号)严格对齐,在指定硬件事件发生时立即启动。常用于电子齿轮、飞剪定位等需微秒级相位同步的应用。 -
事件任务(Event-Driven Task)
由软件变量状态变化触发(如StartButton = TRUE),或由中断服务程序(ISR)置位的标志触发。适用于单次动作、故障响应、手动模式切换等非周期性操作。
⚠️ 注意:同一ST程序段不可同时被多个任务调用。若需复用逻辑,必须封装为功能块(FB)或函数(FC),并通过实例化方式在不同任务中调用。
二、配置任务:以TwinCAT 3为例的完整流程
以下步骤在TwinCAT 3 Engineering(v4.12+)中验证有效,其他平台逻辑一致,仅界面路径略有差异。
-
打开任务配置视图
在Solution Explorer中,右键点击PLC Project → Tasks,选择Add Task。 -
创建高优先级周期任务(Task_High)
- Name:
Task_High - Execution Mode:
Cyclic - Cycle Time:
1 ms - Priority:
31(最高级,数值越大优先级越高;范围通常为0–31) - Assigned Program:
MAIN_High(新建POU,类型为Program,语言为ST)
- Name:
-
创建中优先级周期任务(Task_Medium)
- Name:
Task_Medium - Execution Mode:
Cyclic - Cycle Time:
10 ms - Priority:
20 - Assigned Program:
MAIN_Medium
- Name:
-
创建低优先级事件任务(Task_Low)
- Name:
Task_Low - Execution Mode:
Event-Driven - Trigger Variable:
g_bLogRequest(全局布尔变量,由HMI或另一任务置位) - Priority:
5 - Assigned Program:
MAIN_Low
- Name:
✅ 验证要点:
- 所有任务的
Priority值互不相同;Cycle Time必须为系统支持的最小分辨率整数倍(如TwinCAT支持125 μs步进,故1 ms合法,1.3 ms非法);Trigger Variable必须为BOOL或UINT类型,且在项目中已声明为GLOBAL或RETAIN。
三、编写任务专属ST程序:分工原则与防冲突设计
每个任务对应一个独立程序(POU),但它们共享同一全局变量空间。因此,变量访问必须遵循“谁产生、谁负责、谁保护”原则。
(1)高优先级任务:执行实时控制(MAIN_High.st)
PROGRAM MAIN_High
VAR
fbAxisCtrl: MC_MoveVelocity; // 运动控制功能块
fbPID: PID_Controller; // 自定义PID功能块
g_rSetpoint: REAL; // 全局设定值(由中任务更新)
g_rProcessValue: REAL; // 全局过程值(由硬件采集)
g_bEmergencyStop: BOOL; // 全局急停标志
g_bAxisEnable: BOOL; // 全局轴使能
END_VAR
// 紧急停止优先于一切:检测到急停,立即禁用轴并清零指令
IF g_bEmergencyStop THEN
g_bAxisEnable := FALSE;
fbAxisCtrl.Velocity := 0.0;
fbPID.bEnable := FALSE;
ELSE
// 仅当使能时执行闭环控制
IF g_bAxisEnable THEN
fbPID.rSP := g_rSetpoint;
fbPID.rPV := g_rProcessValue;
fbPID(bEnable := TRUE);
fbAxisCtrl.Velocity := fbPID.rMV;
fbAxisCtrl(bExecute := TRUE);
END_IF;
END_IF;
🔑 关键点:
- 急停逻辑使用组合逻辑硬联锁(
IF g_bEmergencyStop THEN ...),不依赖任何延时或中间变量;- PID输出直接驱动
fbAxisCtrl.Velocity,避免跨任务传递中间值引入抖动;- 所有运算在
1 ms内完成,可通过TcSysTime测量实际执行时间。
(2)中优先级任务:协调与计算(MAIN_Medium.st)
PROGRAM MAIN_Medium
VAR
g_rSetpoint: REAL; // 与高任务共享
g_rProcessValue: REAL; // 与高任务共享
g_bEmergencyStop: BOOL; // 与高任务共享
g_bAxisEnable: BOOL; // 与高任务共享
stRecipe: RecipeStruct; // 配方结构体(含温度、压力等参数)
fbAlarm: AlarmHandler; // 报警管理功能块
END_VAR
// 每10ms读取一次现场传感器(避免高频采样占用高任务)
g_rProcessValue := ReadAnalogInput(CHANNEL_0);
// 根据配方自动调整设定值(示例:升温阶段设为80℃,恒温阶段设为120℃)
CASE stRecipe.nPhase OF
1: g_rSetpoint := 80.0;
2: g_rSetpoint := 120.0;
3: g_rSetpoint := 0.0;
END_CASE;
// 检测超限并触发报警(非立即停机,交由高任务响应)
fbAlarm(
bActive := (g_rProcessValue > 130.0),
sMessage := 'TEMP_OVER_LIMIT',
nLevel := 2
);
🔑 关键点:
ReadAnalogInput()调用放在中任务,避免高任务因ADC转换延迟失步;- 设定值
g_rSetpoint为只写(由中任务写入),高任务为只读——消除竞态;- 报警触发不直接控制输出,仅置位报警标志,由高任务的
IF g_bEmergencyStop THEN统一裁决是否停机。
(3)低优先级任务:人机交互与日志(MAIN_Low.st)
PROGRAM MAIN_Low
VAR
g_bLogRequest: BOOL; // 事件触发变量(全局)
g_stLogEntry: LogEntryStruct; // 日志结构体
fbFileWrite: FILE_WRITE; // 文件写入功能块
sLogPath: STRING := 'C:\Logs\ctrl_log.txt';
END_VAR
// 仅当g_bLogRequest为TRUE时执行一次写入,然后自动复位
IF g_bLogRequest THEN
g_stLogEntry.dtTime := CURRENT_TIME();
g_stLogEntry.sMsg := CONCAT('Setpoint=', REAL_TO_STRING(g_rSetpoint));
fbFileWrite(
sFileName := sLogPath,
sData := STRUCT_TO_STRING(g_stLogEntry),
bExecute := TRUE
);
// 写入完成后立即清除请求标志
g_bLogRequest := FALSE;
END_IF;
🔑 关键点:
- 使用
STRUCT_TO_STRING()将结构体转为可读文本,避免二进制写入导致日志不可查;g_bLogRequest采用边沿触发式清零(写入后立刻赋FALSE),防止重复执行;- 不在低任务中调用
WAIT或DELAY,避免阻塞整个事件队列。
四、任务间安全通信:禁止直连,必须经由同步机制
多个任务读写同一变量时,若无保护,会导致值覆盖、逻辑错乱。正确做法是使用以下三种同步机制之一:
| 机制 | 适用场景 | ST代码示意 |
|---|---|---|
| 全局变量 + 读写分离 | 设定值、状态标志等单向数据流 | 中任务写g_rSetpoint,高任务只读——无需额外代码 |
| 临界区保护(CRITICAL_SECTION) | 需原子读-改-写(如计数器累加) | ENTER_CRITICAL_SECTION(); Cnt := Cnt + 1; LEAVE_CRITICAL_SECTION(); |
| 邮箱(Mailbox)机制 | 复杂数据包传递(如配方参数组) | 调用MBX_Send()发送结构体,接收端在目标任务中MBX_Receive() |
✅ 推荐首选方案:读写分离 + 布尔握手信号
例如,中任务准备就绪后置位g_bDataReady := TRUE,高任务检测到该信号后复制数据,再置位g_bDataTaken := TRUE,中任务收到后者后才更新下一批数据。全程无共享内存操作。
五、调试与验证:确认任务真正按预期运行
不能仅凭“程序能编译”判断多任务配置成功。必须通过以下三步验证:
-
查看任务调度时间戳
在TwinCAT中启用Task Monitor,观察各任务实际执行周期偏差(Jitter)。合格标准:Task_High的Jitter ≤ 10%周期(即1 ms任务Jitter ≤ 100 μs);- 若出现
Overrun告警,说明任务内代码超时,需优化算法或拆分逻辑。
-
监测变量访问冲突
在变量在线监视窗口中,右键点击g_rSetpoint→Break on Write,观察是否被非预期任务修改。若Task_Low意外写入,则说明变量权限失控。 -
注入扰动测试响应性
强制将Task_Medium周期改为100 ms,同时监控Task_High的fbAxisCtrl.Velocity输出是否仍保持1 ms更新节奏。若输出卡顿,则证明高任务未真正脱离中任务影响。
六、常见错误及修正对照表
| 错误现象 | 根本原因 | 修正方法 |
|---|---|---|
| 高任务执行周期忽长忽短 | 中任务内含WAIT或DELAY阻塞调度器 |
将延时逻辑移至事件任务,用定时器功能块替代WAIT |
| 急停后轴仍微动 | g_bEmergencyStop被中任务周期性覆写为FALSE |
在高任务中增加锁存:IF g_bEmergencyStop THEN g_bAxisEnable := FALSE; END_IF;,且中任务永不写该变量 |
| 日志文件写入失败 | 低任务尝试在Task_High运行时写硬盘(硬件资源冲突) |
改用环形缓冲区+DMA传输,或仅在Task_Low中调用FILE_WRITE一次 |
| 多个任务同时调用同一FB实例 | 实例数据被覆盖(如两个PID共用同一fbPID地址) |
为每个任务单独声明实例:fbPID_High: PID_Controller; fbPID_Med: PID_Controller; |
七、性能边界提醒:何时不该用多任务
多任务不是万能解药。以下情况应拒绝使用,改用单任务内部分时复用:
- 控制对象少于3个,且周期差异小于10倍(如
10 ms与50 ms); - PLC CPU利用率已超70%,新增任务将导致整体Jitter超标;
- 项目需通过IEC 61508 SIL2认证——多任务调度需额外验证,单任务更易取证。
此时,可在单一10 ms任务中,用计数器分频执行不同逻辑:
VAR
uiCounter: UINT := 0;
END_VAR
uiCounter := uiCounter + 1;
IF uiCounter >= 1 THEN // 每10ms执行
// 主逻辑
END_IF;
IF uiCounter >= 5 THEN // 每50ms执行(5×10ms)
// 次要逻辑
uiCounter := 0;
END_IF;
该方案无任务切换开销,确定性更高,且符合安全认证要求。
任务优先级配置不是设置几个数字,而是对控制逻辑时间属性的精确建模。把急停放在Priority 31,不是因为它“重要”,而是因为从检测到动作必须≤1 ms;把日志写入放在事件任务,不是为了“省事”,而是避免硬盘IO拖垮毫秒级控制环。每一次任务划分,都是在用代码重写物理世界的时序契约。

暂无评论,快来抢沙发吧!