2. MotoPlus程序结构与执行机制

MotoPlus 程序并不是“从上到下跑一遍就结束”的普通 C 程序,它运行在 YRC1000 控制柜的实时环境中,与伺服控制、I/O 扫描、示教器通信等系统任务共享 CPU。理解 MotoPlus 的执行机制,核心是理解三件事:任务以什么形式存在、由谁在什么时候启动、以及多个任务之间如何被调度。只有把这三件事弄清楚,才能写出既满足功能、又不干扰机器人实时运动控制的程序。

一、MotoPlus 的两种基本执行体:任务与中断

MotoPlus 提供两类可并发的执行体。第一类是任务(Task),它由系统调度器按优先级抢占式调度,拥有独立的栈空间,可以阻塞、延时、等待信号量,适合处理周期性逻辑、通信、数据处理等“可以等一等”的工作。第二类是中断处理程序(Interrupt Handler),它由特定硬件或系统事件触发,运行在中断上下文,优先级高于所有任务,必须极短、不能阻塞、不能调用可能引起挂起的 API。

任务通过 mpTaskStart 创建并启动,其原型大致如下:

LONG mpTaskStart(
    LONG (*task)(LONG),   // 任务入口函数
    LONG priority,        // 优先级
    LONG stackSize,       // 栈大小(字节)
    LONG option,          // 选项
    LONG *taskId,         // 返回的任务ID
    LONG param);          // 传给任务的参数

入口函数必须返回 LONG,参数与返回值都是 32 位整数。任务一旦启动,就进入就绪队列,等待调度器按优先级分配 CPU。

二、优先级:数字越小,优先级越高

MotoPlus 的优先级是一个整数,数值越小表示优先级越高。系统任务通常占用较高的优先级区间,用户任务应使用较低的优先级,避免与系统任务争抢 CPU。一个常见的做法是把用户任务分成三档:

  • 高优先级用户任务:用于对时序敏感、但又不适合放进中断的短周期逻辑,例如每 4 ms 一次的轨迹状态采样。
  • 中优先级用户任务:用于常规控制与通信,例如与上位机交换数据、解析指令。
  • 低优先级用户任务:用于日志、文件写入、界面刷新等“慢活”。

优先级不是越高越好。如果用户任务优先级设得过高,可能挤占系统任务的时间片,导致示教器响应变慢甚至运动控制异常。因此,能用低优先级完成的事,就不要用高优先级。

三、启动方式:从系统启动到任务运行

MotoPlus 程序的启动通常有两种路径。第一种是随控制柜上电自动启动:把编译生成的 .out 文件放入控制柜的指定目录,并在系统设置中登记为自动启动程序,控制柜上电后由系统加载并调用入口函数。第二种是由示教器或外部信号触发启动:程序先被加载,但入口函数中不立即创建任务,而是等待某个启动条件(如通用 I/O 信号、示教器操作)满足后再调用 mpTaskStart。

无论哪种方式,入口函数本身运行在一个由系统创建的主任务上下文中。因此,入口函数应当尽快完成初始化并返回或进入主循环,不要在入口函数里做长时间阻塞操作。

四、主循环与中断处理的分工

典型的 MotoPlus 程序结构是“一个主循环 + 若干中断”。主循环负责周期性轮询和状态机推进,中断负责对时间要求最苛刻的事件做出即时响应。下面这张图展示了这种结构:主循环任务按固定周期运行,中断可以在任意时刻打断任务,处理完后返回被中断的位置继续执行。

MotoPlus 任务与中断执行结构 控制柜上电 加载 .out 程序 入口函数 main 初始化 + 创建任务 mpTaskStart 创建任务 分配优先级与栈 进入就绪队列 等待调度器分配 CPU 主循环任务(周期性执行) 读取机器人状态 位置 / 速度 / I/O 逻辑判断与计算 状态机 / 数据处理 输出控制 / 通信 写 I/O / 发数据 延时等待 mpTaskDelay(4ms) 循环回到起点 中断处理(事件触发) 外部 I/O 中断 急停 / 传感器信号 定时器中断 固定周期触发 通信接收中断 数据到达通知 打断 返回 优先级:中断 > 高优先级任务 > 中优先级任务 > 低优先级任务(数值越小优先级越高)

从图中可以看到,主循环是一个“读取—计算—输出—延时”的闭环,而中断是横向插入的。中断处理程序应当只做最必要的动作,例如置一个标志位、把数据放入缓冲区,然后把耗时的处理留给任务去做。这种“中断只做通知、任务负责处理”的分工,是保证实时性的关键。

五、一个可参考的任务划分示例

假设我们要实现一个“根据外部传感器信号,动态调整机器人抓取位置”的 MotoPlus 程序。可以这样划分:

执行体类型优先级周期/触发职责
传感器采集任务任务中每 4 ms读取传感器值,写入共享缓冲区
位置计算任务任务中每 8 ms根据传感器值计算目标位置偏移
通信任务任务低每 20 ms与上位机交换状态与指令
急停中断中断最高信号触发置急停标志,通知任务处理

这里的关键是:共享缓冲区必须用信号量或互斥量保护,因为采集任务和计算任务可能同时访问它。MotoPlus 提供 mpSemTake / mpSemGive 等 API 来完成同步。如果忽略这一点,就可能出现读到“半更新”数据的问题。

六、主循环的节奏控制

主循环不能写成“死循环里全速跑”,否则会占满 CPU,影响其他任务。正确做法是在循环末尾调用 mpTaskDelay() 主动让出 CPU。例如:

while (1) {
    read_sensor();
    compute_offset();
    write_output();
    mpTaskDelay(4);   // 延时 4 ms,让出 CPU
}

这里的 4 ms 是示例值,实际应根据控制周期要求确定。延时太短会浪费 CPU,太长则响应变慢。一般建议让主循环的周期略小于最关心的外部事件间隔,留出余量。

提示:中断处理程序里不要调用 mpTaskDelay、mpSemTake 等可能引起阻塞的 API。中断里只做标志置位、数据入队等短操作,把复杂处理交给任务。
注意:优先级数值越小优先级越高,这一点与很多 RTOS 的习惯相反。设置优先级时务必确认,避免把低优先级任务设成“高优先级”,导致系统任务被挤占。

总结来说,MotoPlus 程序结构的核心是“任务 + 中断”的分工:任务负责周期性的、可等待的逻辑,中断负责即时的、短促的响应;优先级决定谁先拿到 CPU,启动方式决定程序何时进入运行状态,主循环则提供稳定的执行节奏。把这四者配合好,程序才能在完成功能的同时,不干扰机器人本身的实时控制。