文章目录

ST多任务处理:在ST中配置和处理不同优先级的任务

发布于 2026-03-18 19:54:34 · 浏览 20 次 · 评论 0 条

在结构化文本(ST)编程环境中实现多任务处理,核心在于将不同响应速度、执行频率和安全等级的控制逻辑,分配到相互独立、优先级明确的任务槽中。这并非简单地“写多个程序”,而是通过PLC运行时系统的任务调度机制,让高优先级任务(如紧急停机、高速脉冲输出)不受低优先级任务(如历史数据记录、HMI画面刷新)阻塞,确保关键控制动作在确定时间内完成。

以下是以主流支持IEC 61131-3标准的PLC(如倍福TwinCAT、贝加莱Automation Studio、罗克韦尔Studio 5000 Logix Designer)为背景,手把手配置ST多任务的实操指南。所有操作均基于标准功能块与系统变量,不依赖厂商私有扩展。


一、理解任务层级:周期性任务 ≠ 同步任务 ≠ 事件任务

PLC中的“任务”本质是调度单元,每个任务绑定一个执行条件(触发源)和一个执行周期(或触发事件)。三类基础任务类型必须严格区分:

  1. 周期性任务(Cyclic Task)
    按固定时间间隔重复执行,例如 1 ms10 ms100 ms。适用于运动控制、PID调节、I/O扫描等对时间确定性要求高的场景。

  2. 同步任务(Synchronous Task)
    与硬件时钟(如编码器Z相、主轴位置信号)严格对齐,在指定硬件事件发生时立即启动。常用于电子齿轮、飞剪定位等需微秒级相位同步的应用。

  3. 事件任务(Event-Driven Task)
    由软件变量状态变化触发(如 StartButton = TRUE),或由中断服务程序(ISR)置位的标志触发。适用于单次动作、故障响应、手动模式切换等非周期性操作。

⚠️ 注意:同一ST程序段不可同时被多个任务调用。若需复用逻辑,必须封装为功能块(FB)或函数(FC),并通过实例化方式在不同任务中调用。


二、配置任务:以TwinCAT 3为例的完整流程

以下步骤在TwinCAT 3 Engineering(v4.12+)中验证有效,其他平台逻辑一致,仅界面路径略有差异。

  1. 打开任务配置视图
    在Solution Explorer中,右键点击 PLC Project → Tasks,选择 Add Task

  2. 创建高优先级周期任务(Task_High)

    • Name: Task_High
    • Execution Mode: Cyclic
    • Cycle Time: 1 ms
    • Priority: 31(最高级,数值越大优先级越高;范围通常为0–31)
    • Assigned Program: MAIN_High(新建POU,类型为Program,语言为ST
  3. 创建中优先级周期任务(Task_Medium)

    • Name: Task_Medium
    • Execution Mode: Cyclic
    • Cycle Time: 10 ms
    • Priority: 20
    • Assigned Program: MAIN_Medium
  4. 创建低优先级事件任务(Task_Low)

    • Name: Task_Low
    • Execution Mode: Event-Driven
    • Trigger Variable: g_bLogRequest(全局布尔变量,由HMI或另一任务置位)
    • Priority: 5
    • Assigned Program: MAIN_Low

✅ 验证要点:

  • 所有任务的Priority值互不相同;
  • Cycle Time必须为系统支持的最小分辨率整数倍(如TwinCAT支持125 μs步进,故1 ms合法,1.3 ms非法);
  • Trigger Variable必须为BOOLUINT类型,且在项目中已声明为GLOBALRETAIN

三、编写任务专属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),防止重复执行;
  • 不在低任务中调用WAITDELAY,避免阻塞整个事件队列。

四、任务间安全通信:禁止直连,必须经由同步机制

多个任务读写同一变量时,若无保护,会导致值覆盖、逻辑错乱。正确做法是使用以下三种同步机制之一:

机制 适用场景 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,中任务收到后者后才更新下一批数据。全程无共享内存操作。


五、调试与验证:确认任务真正按预期运行

不能仅凭“程序能编译”判断多任务配置成功。必须通过以下三步验证:

  1. 查看任务调度时间戳
    在TwinCAT中启用Task Monitor,观察各任务实际执行周期偏差(Jitter)。合格标准:

    • Task_High的Jitter ≤ 10%周期(即1 ms任务Jitter ≤ 100 μs);
    • 若出现Overrun告警,说明任务内代码超时,需优化算法或拆分逻辑。
  2. 监测变量访问冲突
    在变量在线监视窗口中,右键点击g_rSetpointBreak on Write,观察是否被非预期任务修改。若Task_Low意外写入,则说明变量权限失控。

  3. 注入扰动测试响应性
    强制将Task_Medium周期改为100 ms,同时监控Task_HighfbAxisCtrl.Velocity输出是否仍保持1 ms更新节奏。若输出卡顿,则证明高任务未真正脱离中任务影响。


六、常见错误及修正对照表

错误现象 根本原因 修正方法
高任务执行周期忽长忽短 中任务内含WAITDELAY阻塞调度器 将延时逻辑移至事件任务,用定时器功能块替代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 ms50 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拖垮毫秒级控制环。每一次任务划分,都是在用代码重写物理世界的时序契约。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文