☰
扫地机器人双脑架构:安全为何不交给Linux,MCU独立守护
2026/10/8 6:03:59 网站建设 项目流程

这年头扫地机器人越来越不像“扫地”的了。以我家那台为例,App里能看实时地图、画虚拟墙、设禁区,语音喊一声就能自己回桩充电,拆开看主控是一块跑Linux的应用处理器,激光雷达、摄像头、Wi-Fi全堆在上面。听起来很美好对吧?但你要是干过这一行,就知道这套方案在“安全”上有个大坑:系统一旦卡死,那些本该保护机器人不跌下楼梯、不撞碎花瓶的逻辑,也跟着一起卡死了。

所以行业里逐渐形成一个共识做法,叫双脑架构。简单说,给扫地机器人装两个“脑子”:一个跑Linux,负责所有需要算力和生态的智能功能;另一个用MCU加RTOS或者干脆裸机,负责所有和人身安全、机器安全强相关的功能。两个脑各管一摊,Linux这边崩了、卡了、重启了,安全脑照样独立值班。

这篇就拆解这套双脑架构:为什么必须这么分,安全脑具体怎么做,Linux侧要注意什么,以及我在实际项目里踩过的排查坑。适合正在做扫地机器人、割草机器人、配送机器人,或者任何“带轮子的Linux设备”的开发者;产品经理如果想知道研发为什么坚持“安全不能交给Linux”,也能看懂。

1. 双脑架构:把“绝不能出错”的边界画清楚

1.1 先按出错后果分类,别按计算量分类

设计双脑架构的第一步,不是选芯片,而是把所有功能按“出错之后的后果”分成两类。这个分类方式和我们熟悉的“按性能需求分类”完全不一样,很多团队会在这里犯第一个错——他们习惯性地把“算力需求高”的功能画到Linux上,“算力需求低”的画到MCU上。但正确的分法是先问一句:这个功能失效的瞬间,人会受伤吗?机器会损坏吗?

以扫地机器人为例,悬崖传感器(用来防止从楼梯边缘跌落)、碰撞条(用来感知撞到墙、家具或小动物)、急停按钮、电机过流与过热保护、电池过放保护,这五种功能如果失效,轻则摔坏机器,重则引发电气风险或者撞伤宠物。它们就是安全关键功能,必须放进安全脑。而SLAM建图、路径规划、App状态同步、语音交互、OTA下载与校验,这些失效最坏的结果是“这次清扫失败了,重新规划一下”,不会直接造成人身或财产伤害,属于非安全关键功能,可以放心交给Linux。

再往下分,还有一类容易被忽略的“半关键”功能,比如贴边清扫时的力度控制、拖布抬起与放下的时机、回桩校准对准。这类功能看起来不影响安全,但如果失控,可能导致机器人把门框顶坏、把电线卷进去。我的建议是,凡是“和控制电机动力输出沾边”的逻辑,都收进安全脑,或者至少由安全脑做最终否决。这不是技术洁癖,而是因为电机的物理输出一旦出错,是唯一能产生实际破坏力的地方,必须捏在最可靠的那颗芯片手里。

1.2 为什么不是一颗高算力SoC一把梭

我在和客户讨论方案时,最常被问到一个问题:现在很多应用处理器是大小核架构,比如A53配一个Cortex-M4,跑Linux的同时还有个实时核,是不是直接用它做安全控制就够了?答案是不建议。首先,同一颗SoC上A核和M核共享电源域、时钟、总线和内存控制器,Linux侧一个错误DMA就可能把共享内存区域写乱,一个驱动把总线锁死,M4核一样遭殃。所谓“独立”,在物理上并没有真正独立。

其次是安全认证的问题。IEC 61508、ISO 13482这类功能安全标准非常看重“独立性”和“确定性”,你如果用同一颗芯片的异构核做隔离,认证时需要向审核员证明两个核之间的干扰已经被完整消除,要多做一堆失效模式分析和测试,成本和时间都上去一大截。所以我做这类项目的原则是:安全脑独立成一颗MCU,电源、时钟、总线、固件存储全部独立,和Linux主控之间只有一条明确的通信链路。物理隔离是最容易证明的隔离。

1.3 双脑典型拓扑与“电机控制权”反转

双脑架构的真实拓扑并不复杂。安全脑MCU直连悬崖传感器、碰撞条、急停按钮、电机驱动板的使能和PWM引脚;Linux主控则连接激光雷达、摄像头、IMU、Wi-Fi模块、扬声器和麦克风。安全脑和Linux主控之间用一条UART或SPI链路互发心跳与状态帧。

这里面有一个特别重要的设计反转:电机PWM和使能信号的最终控制权,必须握在安全脑手里,而不是Linux手里。Linux想前进、后退、转弯,不能直接去拉电机驱动,而是通过通信链路向安全脑发一条“运动请求”,安全脑根据当前安全状态决定是否执行。也就是说,Linux是“打报告”,安全脑是“拍板”。这个反转的意义在于,即使Linux侧完全疯掉——不管是软件bug还是被OTA升级搞挂——它也只能“胡言乱语”地向安全脑发消息,安全脑只要盯好心跳和状态机,就能保证电机不会在危险场景下乱转。

2. 为什么安全永远不能交给Linux

2.1 实时性:平均延迟会骗人

Linux是通用分时操作系统,内核里跑着大量内核线程、驱动中断、内存回收和文件系统线程,调度器的核心目标是“公平”,而不是“保证某个任务在绝对时间点前完成”。在一个负载正常的系统上,一个高优先级实时线程从收到中断到真正执行电机反转,平均可能只要几百微秒,听起来很理想。但安全系统看的是最坏情况,不是平均情况。

当Linux在做OTA升级、内核在刷Dirty Page、文件系统在等待flash写入、GPU在编译着色器、Wi-Fi驱动正在加载固件的时候,一次调度延迟飙到几十毫秒甚至上百毫秒完全正常。而扫地机器人从楼梯边缘跌落的临界时间,通常在几十到几百毫秒之间。一次卡顿,就足够让机器人完成坠落。有人会说“我加PREEMPT_RT内核补丁”,PREEMPT_RT确实能把大部分内核临界区的抢占延迟降到微秒级,但它仍然不是硬实时,页面分配器锁、某些驱动屏蔽中断、总线stall都会带来不可预知的长尾延迟。安全系统不允许“绝大多数时候没问题”,它要求的是“所有时刻都确定”。

2.2 确定性:同样输入,必须给出同样时序

嵌入式安全工程里有个词叫确定性。意思是同样的输入、同样的状态,系统行为在时间轴上是可以复现的。裸机上写一个超级循环,读传感器、跑状态机、输出PWM,每一轮的执行时间基本是固定的,你可以用示波器量出来,对于失效分析来说这是巨大的优势。Linux则完全不同,它内部有太多异步活动在竞争CPU、锁、缓存和I/O带宽,同一个分支这次跑2毫秒,下次可能跑30毫秒。

这种不确定性对SLAM、路径规划完全无所谓,但对安全逻辑是致命的。你没法向审核员或者向你自己证明“这行代码一定在跌落前执行完”。安全状态机的核心就是可控的时间递进,没有确定性,状态机就是空中楼阁。这也是为什么安全工程师普遍对“在Linux里写一个看门狗线程”抱有极大怀疑:你连这个线程什么时候能拿到CPU都无法保证,凭什么把它当作保护装置?

2.3 复杂度带来的灾难性故障面

Linux内核有上千万行代码,加上驱动、C库、动态链接器、文件系统,任何一个环节出问题都可能让整个系统崩溃或挂起。我做故障复盘时见过太多例子:Wi-Fi驱动把DMA描述符写错导致内核panic、malloc返回NULL但代码没检查导致段错误、OTA升级把rootfs写坏导致无限重启、SD卡在读取过程中被拔出导致文件系统hang住。这些故障在普通Linux设备上最多是“重启一下”,在扫地机器人上,如果你把安全逻辑也放在Linux里,那就意味着安全逻辑和这些故障绑定在了一起。

更隐蔽的是局部故障。很多时候Linux并不是完全死掉,而是某个子系统hung住,比如一块驱动锁死了一个CPU核,或者某个内核线程陷入死循环。系统表面上还活着,App还能连上,但实际上你的安全线程已经长时间拿不到调度机会。这种“半死不活”的状态比完全崩溃更难排查,也更容易让安全功能失效。

2.4 启动、升级、文件系统:安全不能依赖“系统健康”

还有三个工程问题。第一是启动时间,Linux从上电到用户态安全服务就绪,通常要几秒到十几秒。如果机器人刚上电就被人拎起来,或者从充电座上滑下来,这段启动窗口里你希望谁保护它?安全脑能做到上电后几十毫秒内进入工作状态。第二是OTA升级,升级过程中rootfs被写坏是常见问题。如果安全功能依赖Linux,升级失败等于同时摧毁了安全功能。第三是文件系统损坏、磁盘只读、临时目录写满,这些Linux老毛病对一个“每天都在运动并可能被断电的设备”来说非常现实,而安全脑固件只需要在出厂时写入一次,运行期间只有固定的一块flash区域,完全没有文件系统这种脆弱层。

2.5 安全认证:审核员只认确定性与独立性

最后说一点和商业直接相关的。如果产品要出海外或者进入高端渠道,功能安全认证基本绕不开,比如IEC 61508、ISO 13849,以及针对个人护理机器人、服务机器人的ISO 13482。审核员关注的核心就两件事:你为安全功能提供了一套独立、确定、可验证的实现,并且你能证明它在故障条件下会进入安全状态。如果安全功能跑在一个通用Linux系统上,你需要把操作系统的所有故障模式、所有驱动行为、内存损坏对安全逻辑的影响全部解释清楚,这几乎是不可能完成的工作量。但用一颗MCU跑裸机或者简单RTOS,状态机清晰,每一行指令的执行路径都可分析,时序可以直接测量,认证的难度和成本会直线下降。与其说“Linux做不了安全”,不如说“安全工程需要可辩护的证据,Linux给不了”。

3. 安全脑怎么做:从选型到状态机

3.1 选型:够用就行,关键是独立

安全脑的算力不需要强。扫地机器人这种场景,我常用Cortex-M0+级别的MCU,比如STM32G0、GD32E230,成本低、外设够用、功耗低。如果产品定位高端或者要走功能安全认证,可以考虑带硬件ECC和双核锁步的工业级MCU,比如TI Hercules系列、STM32H5系列。选型时的核心不是“性能”,而是“独立”:安全脑要有自己的电源轨、自己的晶振、自己的固件flash,最好和Linux侧完全分开。我看到过一些设计,为了省成本让安全脑和Linux主控共用一颗LDO,结果Linux侧过流直接让安全脑掉电,这是典型的“省出安全问题”。

软件上,裸机超级循环加定时器中断是我的首选,因为安全状态机本身不复杂,外设少,裸机最好分析、最好验证。如果任务确实多,比如还要管电池管理、多个传感器通信,选FreeRTOS这类小内核RTOS也完全可以。但有一条硬性要求:无论选什么,安全脑上不要跑任何通用操作系统,更不要跑Linux。安全脑不需要“方便”,需要的是“每一件事都可预期”。

3.2 传感器直连:安全链路不能过Linux

悬崖传感器、碰撞条、急停按钮,这些输入必须直接接到安全脑的GPIO或ADC上。我见过一个反面案例:碰撞条信号先触发Linux的中断,Linux再通过UART通知MCU停车,结果一次Wi-Fi下载导致中断响应变慢,机器人顶着柜子推了两秒才停。这就是把安全链路放在了错误的位置。正确做法是,传感器信号进安全脑,安全脑在收到触发后的下一个控制周期内直接切断电机使能,或者强制进入减速状态。整个过程不依赖Linux的任何代码,连“通知Linux”都只是附带动作,而不是前置条件。

在硬件层面还有几个细节值得注意。传感器信号线不要和电机驱动线绑在同一个线束里,大电流换向时的感性干扰很容易灌到信号线上,导致误触发。信号输入端加一个简单的RC低通滤波,或者用施密特触发器做整形,能减少很多抖动问题。在软件层面,安全脑的传感器扫描周期要做到固定,比如每1ms扫描一次跌落检测,每5ms扫描一次碰撞条,这个周期要在注释里写清楚,并且用实际测量确认,不要靠估计。

3.3 心跳协议:安全脑盯着Linux,Linux别盯着安全脑

安全脑和Linux之间最基础的通信就是心跳。典型设计是Linux侧一个高优先级进程,每100ms向安全脑发一帧心跳,帧里包含一个单调递增的序号、Linux当前状态、电池电量、error code。安全脑内部维护一个超时计数器,如果连续超过500ms没有收到合法心跳,就判定Linux侧异常,进入预设的安全状态。安全状态可以配置:如果机器人正在清扫,立刻停车并打开指示灯;如果正在回桩,降速并原地待命;如果正在充电,切断充电继电器。

这里有个容易忽略的细节:心跳帧一定要带序号,并且安全脑要校验序号严格递增,否则就会出现“数据链路拥塞导致旧帧被重放,安全脑以为Linux还活着”的假象。另外,安全脑自身也要有硬件看门狗,一旦安全脑的主循环被某个外设阻塞超过设定时间,看门狗强制复位,复位后必须默认进入安全状态。也就是说,安全脑绝对不能“死得安安静静”,它要么正常工作,要么以进入安全状态的方式死。

3.4 状态机与失效安全:出事时怎么“安全地死”

安全脑的核心是一个状态机。我常用的状态有这些:INIT(上电自检)、IDLE(待机)、CLEAN(正常清扫)、RETURN(回桩)、FAULT(故障)、EMERGENCY(急停或跌落中)。每个状态定义了允许的电机输出和对Linux请求的响应策略。比如EMERGENCY状态下,电机使能被硬件切断,机器人只能依靠制动或者反向短刹车物理减速,任何来自Linux的“继续前进”请求都被忽略。

失效安全原则是这套状态机的灵魂:发生任何不确定的故障时,默认进入安全状态,而不是保持当前状态继续跑。例如跌落传感器在机器人已经悬空时触发,安全脑要立刻切断轮子输出并执行反向制动,把机器尽量拉回边缘;急停按钮按下后,所有自动恢复路径都关闭,必须人工干预才能退出。这个“出事宁可停错,也不能不停”的设计理念,和做互联网产品时“尽量保持服务可用”的思路是完全相反的,做嵌入式安全的人必须切换过来。

注意:失效安全不是“尽力停车”,而是“必须进入一个可预期的安全状态”。随便乱停和不停一样危险,比如在楼梯边缘乱停车也可能造成二次跌落。

3.5 安全脑与Linux通信:接口与消息格式

通信接口我用UART最多,3根线(TX、RX、GND),波特率115200或更高,加CRC校验和心跳。UART结构简单,故障模式容易分析,比I2C和SPI更适合两个独立系统之间的长连接。如果你有电磁兼容性顾虑,中间加一个数字隔离器或者光耦,成本也不高。

消息格式建议做成固定长度的二进制结构体,不要用什么JSON解析。固定结构体解析零依赖、零动态内存、时序上也可预测。一个参考结构体可以是:

typedef struct { uint16_t magic; /* 帧头,固定为0xAA55 */ uint8_t msg_type; /* 消息类型 */ uint16_t seq; /* 单调递增序号 */ uint8_t linux_state; /* Linux状态 */ uint8_t battery; /* 电池电量百分比 */ uint16_t error_code; /* 错误码 */ uint32_t config; /* 配置请求 */ uint16_t crc; /* CRC校验 */ } linux_to_safety_t;

整个帧20字节左右,100ms一帧,串口负载非常低,但信息量足够安全脑做决策。安全脑回复的帧可以包含当前安全状态、电机状态、传感器状态和故障码,Linux侧记录日志时要用到这些信息。

4. Linux侧怎么做才不拖后腿

4.1 Linux侧该干什么

在双脑架构里,Linux侧的任务非常清晰:把那些“失败成本低但算力要求高”的功能做到极致。具体来说就是SLAM建图、路径规划、激光与视觉融合、语义地图、App通信、OTA下载与校验、语音交互。这些功能在Linux上开发有巨大的生态优势,OpenCV、Cartographer、ROS/ROS2、各种深度学习推理框架都能直接用,不用在MCU上痛苦地移植算法。

但Linux侧一定要记住自己的角色边界:它只是一个“功能提供者”,不是一个“安全决策者”。我在项目里经常跟软件同事强调一句话:Linux侧可以提建议,安全脑才有决定权。比如Linux算出前方有障碍物,建议减速,那么它发一条减速请求给安全脑;真正让轮子慢下来,是安全脑在确认状态安全后执行的。这样一来,Linux算法出错最多是“请求不合理”,而安全脑可以用状态机再过滤一次。

4.2 串口驱动与心跳线程:把故障面压到最小

Linux侧与安全脑的接口驱动,我建议用SoC自带的独立UART,不要用USB转串口。独立UART的设备节点固定、驱动简单、没有USB协议的额外延迟和断连风险。在Linux内核配置里,把这个UART的中断优先级调高,关闭它和其他外设共享中断。用户态的心跳线程用SCHED_FIFO实时调度策略,给它足够的栈空间,并把它做成一个独立守护进程。如果守护进程崩溃了,用systemd或init脚本自动拉起,但安全脑那边的判断逻辑不要依赖“自动拉起成功”,它只管超时停车就对了。

关于Linux内核要不要打PREEMPT_RT补丁,我的看法是:双脑架构下安全脑已经接管了安全,Linux侧不需要为了安全去追求硬实时,但为了心跳稳定和传感器数据采集平滑,PREEMPT_RT仍然有正面价值。你可以把心跳线程和SLAM主线程都跑在实时优先级下,减少被调度延迟拉长的时间。但不要指望PREEMPT_RT能替代安全脑,它优化的是“体验质量”,不是“安全保证”。

4.3 定制发行版与日志:别拿桌面系统直接上

如果是量产产品,Linux侧应该用Yocto或Buildroot定制一个最小发行版,只保留必要的内核模块、驱动和服务。桌面发行版做过多的后台服务,比如包管理daemon、桌面组件、自动更新检查,这些东西每一个都可能成为崩溃源,而且在嵌入设备上完全没用。裁剪掉它们不仅减少内存占用,还能显著减小安全脑误判的诱因——很多心跳超时其实都是系统卡在某个无关的服务上导致的。

日志策略也很重要。Linux侧日志要写到flash的一个环形缓冲区域,不能无限制增长。日志内容建议包含:心跳发送的record、安全脑回传的状态帧、每个故障时刻的上下文(当前速度、当前模式、传感器值)。一旦安全脑进入FAULT,Linux侧要能把最近的日志迅速导出,供研发分析。没有日志,机器人在客户家里摔了,你只能靠猜,那是最痛苦的事。

4.4 联调顺序:先做故障注入,再跑功能

联调时一定要先做故障注入,再做功能测试。我会准备一套故障注入清单:人为杀掉心跳进程;把rootfs写满;格式化flash分区;模拟OTA升级过程中断电;给Linux主控降压到临界值;拔掉SD卡。每一条都观察安全脑是否正确超时停车,并记录从故障发生到安全停车的时间。这个时间最好控制在几百毫秒内,并且要稳定可复现。

功能测试反而可以稍后做。因为如果故障注入期间安全脑表现不稳,说明架构设计本身还有漏洞,这时候花再多时间去调SLAM算法也是白费。我做过一个割草机器人项目,第一次做故障注入时发现rootfs损坏后心跳直接断掉,安全脑停车没问题,但停车后不会自动退出FAULT,必须人工干预。这个行为是有意保留的:在系统状态不确定时,宁可让人来恢复,也不能让机器自己乱跑。

5. 常见问题与排查技巧实录

5.1 安全脑误触发

现象是机器人在平地上跑着突然停车,FAULT灯亮。最常见的诱因是跌落传感器在深色地毯、黑色瓷砖或者透明玻璃上误判,因为这些表面的反射特性很像“没有地面”。排查方法看安全脑日志里记录的触发源,如果是跌落传感器,第一件事是调整传感器阈值,第二件事是评估加装第二路冗余传感器的成本。注意不要因为误报率高就粗暴地加长滤波时间,滤波延迟必须小于实际跌落时间,否则失效安全就失效了。

还有一个我在多次测试中确认的规律:误触发大多发生在机器人高速清扫、经过高反光地面或者门槛边缘时。所以状态机里可以加一个“确认窗口”——跌落信号必须持续比如30ms以上才被确认为跌落,而不是第一帧就急停。这个30ms不是拍脑袋定的,是通过实测跌落过程的关键时间窗口确定的,每个机型都要重新测。

5.2 Linux侧还活着,但安全脑判定超时

这个故障很隐蔽。排查思路是先用逻辑分析仪抓串口,确认Linux侧的UART引脚上到底有没有发出心跳帧。我遇到过的情况是Linux侧串口FIFO满的时候丢数据,安全脑连续几个超时周期都没收到有效帧。解决办法是把Linux侧串口设置为raw模式、关闭回显和流控干扰,并在用户态检测串口发送缓冲区是否经常堆积,必要时提高心跳频率到50ms,这样偶发丢帧不会影响安全脑判断。如果你用了DMA接收,还要检查DMA环形缓冲有没有因为其他驱动抢内存而长时间得不到搬运。

另外要注意地线问题。电机大电流瞬时变化会把信号地的电位抬高,导致UART波形畸变、安全脑收到乱码。排查时看安全脑收到的帧CRC错误率是不是突然升高。解决办法是加强主控板与驱动板之间的共地,或者用数字隔离器隔离通信链路。这类问题在原理图阶段就要考虑,等样机出来再改会非常痛苦。

5.3 安全脑周期性复位

如果安全脑每隔一段时间复位一次,多半不是外部干扰,而是安全脑自己的看门狗在复位它,说明安全脑主循环被某个操作阻塞了。最常见的是安全脑用I2C读某个传感器时,总线被卡住,代码又没有设置超时,于是一个指令周期被无限拉长。我的改进方式是:安全脑代码里所有外设操作一律带超时,任何函数不允许出现“死等”;主循环的每次迭代时间要固定,用定时器统计,超过上限就主动喂狗失败、触发复位,让复位后进入安全状态。

还有一个容易踩的坑:安全脑复位后,如果Linux侧还在跑,心跳恢复需要一段时间,此时Linux可能发来一堆“继续清扫”的请求。安全脑复位后必须重新走INIT状态,做一次完整自检,再决定是否接受Linux的请求,不能因为收到了旧消息就直接进入CLEAN状态。

5.4 故障速查表

现象可能原因排查方法
安全脑停车但Linux没死心跳通信丢帧、UART模式错误、FIFO溢出抓串口波形,改用raw模式,提高心跳频率
Linux升级后安全脑频繁FAULTOTA包改写了安全脑配置项安全脑配置与Linux升级包分离,固化版本号
安全脑定时复位I2C/SPI读卡死、看门狗喂狗失败所有外设读取加超时,查看复位原因寄存器
机器人跌落后轮子还在转电机PWM控制权在Linux侧架构整改:电机PWM使能改由安全脑硬件控制
回桩时安全脑复位充电电极接触瞬间电压扰动增强电源电容,接触稳定后再闭合充电继电器

5.5 排查时的两个小技巧

第一,安全脑的FAULT状态一定要通过指示灯或者蜂鸣器明确表达出来,不要默默停车。客户看到机器停在原地不动,如果没有任何状态提示,大概率会觉得“这机器坏了”,然后反复重启,反而掩盖了真正的故障原因。第二,量产固件里一定要保留详细的安全日志,哪怕占一点flash空间。你永远不知道客户家里的复现条件是怎样的,而日志里的传感器值、时间戳、状态切换记录,往往是定位问题的唯一线索。

我在实际项目里还有一条经验:虽然是双脑架构,但安全脑固件的版本管理要和Linux侧的版本完全解耦。安全脑固件不能出现在OTA升级包里,更不能被Linux侧的升级脚本顺手擦写。它应该有自己的独立烧录通道,并且每次功能安全回归测试都只针对安全脑固件版本进行。这个解耦能防止一次普通升级把安全脑也弄挂了,而安全脑一旦被弄挂,整个机器就变成了一个没有刹车的车。


最后说点实际的。我做了很多年带轮子的Linux设备,最深的一个体会是:安全系统的设计原则不是“尽量做强”,而是“把出事的概率和出事的后果压到物理上最小”。双脑架构不是什么高深算法,它就是一个朴素的隔离思想——把绝对不能出错的部分,从随时可能崩溃的部分中拿出来。如果你正在设计一款扫地机器人或者类似产品,也遇到“安全逻辑要不要放Linux”的争论,我建议你先问自己一句:如果这颗Linux SoC下一秒死机,我的机器人会怎样?如果答案里有“它可能继续撞墙”或者“它可能掉下楼梯”,那你就需要一颗独立的安全脑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询