别让 Linux 去做硬实时,也别让 MCU 背复杂业务:边缘 AI 系统到底该怎么分工?
栏目:嵌入式 × 边缘视觉
摘要
在边缘 AI 项目里,Linux 板卡跑模型、MCU 管执行器,看起来像一条天然分界线。真正进入联调后,事情通常没有这么简单:为了快速验证,Linux 端开始直接输出 PWM;为了少设计几个通信字段,MCU 又接下目标筛选、任务状态和异常判断。单个模块往往都能运行,但系统一旦遇到推理长尾、通信中断、进程重启或传感器异常,两端就会对“设备现在该做什么”给出不同答案。
这篇文章讨论一个更可操作的判断方法:任务应该放在 Linux 还是 MCU,不应按“哪边写得快”来决定,而要看它的截止时间、故障后果、状态归属与更新方式。目标是让 Linux 保持算法和业务的扩展能力,同时让 MCU 独立守住执行时序与底层安全。
图 1:任务归属先看时间边界和故障后果,再看算力与开发便利性。
一、分工的起点,不是算力,而是任务失败后的后果
很多讨论从“Linux 性能强、MCU 性能弱”开始,这会把问题带偏。Linux 板卡确实更适合图像、模型、网络、文件系统和复杂软件库;MCU 则更容易直接管理定时器、PWM、编码器、限位和看门狗。但分工的第一依据,不应是性能排名,而是任务错过截止时间后会发生什么。
目标检测偶尔晚几十毫秒,通常表现为跟踪不够平滑、结果更新变慢;电流保护、驱动使能或限位处理晚几十毫秒,后果可能是执行机构越界、驱动保护触发,甚至留下不可恢复的机械风险。两者都是“计算任务”,却不应采用相同的执行环境和失效策略。
因此,适合优先留在 MCU 的,往往是电流、速度、位置等快速闭环,编码器与高频传感器采样,PWM 或脉冲输出,限位与故障输入,通信超时后的安全动作,以及上电时默认禁止执行器的逻辑。它们的共同点不是代码短,而是必须在明确的时间边界内完成。
Linux 的普通调度并不以最坏时延保证为目标。实时内核、优先级和 CPU 亲和性可以显著降低抖动,在经过测量和架构验证后也能承担一部分实时任务;但这不能替代底层独立的安全闭环。工程上应区分两件事:平均运行很快,和最坏情况下仍能按时完成,并不是同一个结论。
二、Linux 应该承担什么:复杂、可演进、允许受控降级的能力
Linux 端的价值在于把复杂的信息组织成可更新的任务:视频采集和解码、目标检测与跟踪、多传感器融合、路径或行为决策、界面、网络、日志、远程配置、模型升级和业务流程,都更适合放在这一侧。它们依赖动态内存、第三方库、文件系统和多线程,迭代频率也高。
但 Linux 不应把自己的进程稳定性假定为物理系统的最后保障。一个推理服务重启、磁盘写入变慢或网络线程阻塞,不应让电机保持上一条危险输出。更稳妥的做法是由 Linux 形成目标和约束,由 MCU 在本地闭环内执行,并将真实执行状态回传。
图 2:Linux 侧组织感知、策略与任务;MCU 侧完成本地闭环、限幅和安全处置。两端通过明确的命令与状态接口衔接。
这张图要表达的不是“Linux 只负责 AI、MCU 只负责控制”这样的简化划分。中间的运动控制层需要结合具体项目判断:如果轨迹生成频率较低、依赖视觉语义或任务逻辑,可以在 Linux;若它直接影响快速闭环稳定性或必须在通信异常时继续工作,就应下沉到 MCU 或由 MCU 做本地约束。
三、上位机应发送目标与约束,不应远程操纵每一次底层输出
早期 Demo 常见的接口很直接:Linux 给 MCU 发送一组 PWM 数值和方向位。它看似省事,却把执行器的连续控制责任转移给了 Linux。通信一抖动,MCU 就只能继续保持旧值、立即清零,或猜测上位机的意图;执行器的实际位置、负载、限位和故障状态也很难进入闭环。
更适合长期维护的接口,是让 Linux 下发参考量和边界,例如目标位置、目标速度、最大加速度、控制模式、有效时间和安全等级。MCU 读取编码器与本地传感器,在自己的周期内生成实际输出,并在约束不满足时拒绝或降级命令。
例如,Linux 可以给出“进入位置跟踪模式、目标角度为某值、最大速度受限、该指令在 100ms 内有效”。它不需要也不应逐个计算 PWM 周期。MCU 则负责限幅、轨迹约束、闭环计算、限位检查和超时停车。这样即使 Linux 的模型更新频率只有十几 Hz,底层控制仍可以稳定运行在更高频率。
图 3:Linux 保存“希望系统做什么”,MCU 保存“系统实际处于什么状态”。关键物理状态只能有一个最终拥有者。
这里最容易被忽略的是状态所有权。Linux 可以维护任务期望、目标 ID、参考量和策略模式;MCU 应拥有电机使能、编码器位置、实际速度、故障码、限位状态和当前控制状态。两边都缓存状态没有问题,但不能把两份缓存都当作最终事实。否则通信恢复、服务重启或手动接管之后,很容易出现双重真相。
四、接口语义比“能收发数据”更重要
串口、CAN 或以太网只解决数据能否到达,不能自动定义数据到达后是否仍然有效。面向执行机构的协议至少应让 MCU 能回答:这条命令来自哪一版协议?是不是新的?在什么时间产生?还能使用多久?请求的模式与边界是什么?数据是否完整?
下图给出的是基于常见嵌入式通信结构整理的精简示意,不是某一项目的完整源码。它刻意把 seq、timestamp_ms、valid_for_ms 与 ack_seq 放在接口中:这些字段让“收到一帧”变成“收到一条可验证的控制请求”。
图 4:基于源码结构整理的精简示意。命令必须携带时效与序号,状态必须回报确认序号、实际状态和故障信息。
协议的队列语义也要提前约定。连续参考量通常应采用“最新值覆盖旧值”:旧的速度或位置目标排队执行,只会把系统带向过期意图。急停、故障切换和模式确认则属于高优先级事件,需要确认与重试。参数配置更接近版本化事务,应明确生效版本和确认结果。日志则不能抢占控制路径,更不能让一次格式化输出拖慢实时循环。
五、不同频率的任务,不能混在同一条循环里
视觉模型、策略判断、通信、控制和保护的工作频率天然不同。模型可能受视频帧率与推理时间约束;任务决策随模型更新或状态变化而运行;MCU 的闭环和硬件保护则需要更稳定、更高频的调度。工程问题通常不在于某个数字是否够大,而在于不同频率的任务是否被错误地串进了一条阻塞链路。
图 5:视觉、策略和控制可以以不同节奏运行;Linux 健康状态丢失后,MCU 仍应依据本地超时和安全状态机完成受控处置。
实践中,不必把模型频率、决策频率和控制频率强行做成整数倍。更重要的是把数据年龄、有效期和降级动作设计清楚:视觉结果过期时,策略是否保持、缓慢停止还是切换到搜索;Linux 心跳消失时,MCU 是否先减速、再禁止使能;MCU 回报故障时,Linux 是否立即停止发送普通目标并进入故障界面。每一个答案都应写进状态机和接口约定,而不是靠联调时的临时判断。
六、一个可落地的排查顺序
当项目里 Linux 和 MCU 的责任已经混在一起时,不必先大规模重写。可以先按下面的顺序拆解。
先列出所有会改变执行机构的任务,分别标注它们的截止时间、输入来源和失效后果。再确认每个物理状态的唯一拥有者:位置、速度、使能、限位、故障码和控制模式,最终以谁为准。随后把 Linux 到 MCU 的接口从低层输出收敛为目标、约束、有效期和模式,并让 MCU 对过期、非法或不安全命令有独立的处理路径。
最后再做故障演练,而不是只在正常路径上测速:暂停 Linux 推理服务、阻断通信、让模型输出停止更新、重启上位机、模拟编码器异常,观察 MCU 是否能回到可解释的安全状态,也观察 Linux 是否能准确显示“实际发生了什么”。这类实验往往比继续微调模型延迟更早暴露系统边界问题。
总结
Linux 与 MCU 的合理分工,不是把功能按模块名称切开,而是把责任按时间边界和故障后果切开。Linux 适合组织视觉、模型、任务、网络和策略;MCU 适合保持快速闭环、执行约束和底层安全。两者之间以目标、约束、时效和状态确认通信,才能让系统在算法更新、通信波动和服务异常时仍保持可解释、可恢复的行为。
对于一个正在从 Demo 走向现场运行的边缘 AI 项目,这条边界越早写清楚,后续增加模型、传感器、任务模式和远程功能时,系统越不容易把复杂度堆到错误的一端。
关注“嵌视工程笔记”,后续继续拆解视觉、嵌入式控制与边缘 AI 在同一系统里如何对齐接口、时间和状态。