1. 安全冗余基础:为什么需要冗余?冗余的定义与分类
各位同学好,我是老张。今天咱们聊聊路径规划里一个绕不开的话题——安全冗余。
说实话,我刚开始做路径规划那会儿,觉得冗余就是“多此一举”。系统跑得好好的,干嘛要搞那么多备份?直到有一次,我在一个AGV项目里栽了个大跟头。
那是个仓储物流项目,AGV在货架间穿梭。某天下午,一个激光雷达突然被掉落的纸箱遮挡了半边视野。按说系统应该能处理,但偏偏那台车的路径规划模块没有做传感器冗余。结果呢?车直接撞上了货架,货架上的零件撒了一地。老板脸都绿了。
从那以后,我养成了一个习惯:任何路径规划系统,第一件事就是问自己——如果这个模块挂了,车还能安全停下来吗?
1.1 为什么需要冗余?
说白了,冗余就是给系统买保险。你想想看,路径规划系统面对的是一堆不确定因素:
- 传感器会坏——激光雷达被遮挡、摄像头过曝、IMU漂移
- 计算会出错——CPU过热降频、内存溢出、算法bug
- 通信会断——CAN总线干扰、WiFi丢包、线缆松动
- 执行器会卡——电机堵转、刹车失灵、转向机构卡死
任何一个环节出问题,都可能导致路径规划失败。轻则原地趴窝,重则撞人撞物。
核心观点:冗余不是浪费,而是用可控的成本,换取系统在故障下的可接受行为。
我个人的经验是,冗余设计要回答三个问题:
- 系统最怕什么故障?(单点失效分析)
- 故障发生后,系统要维持什么功能?(安全降级策略)
- 冗余的成本是否可接受?(性价比权衡)
小技巧:我建议你在项目初期就画一张“故障-影响-冗余”对照表。把每个关键模块可能出的故障列出来,再写清楚故障后果,最后决定要不要加冗余。这张表能帮你省下不少后期返工的功夫。
1.2 冗余的定义
冗余,英文叫Redundancy。在工程领域,它的定义是:在系统中引入额外的资源或功能,使得当部分组件失效时,系统仍能维持全部或部分功能。
注意,这里有个关键点——冗余不是简单的“多一份备份”。真正的冗余,要求备份组件和主组件在功能上独立,在失效模式上不相关。
举个例子:你给AGV装了两个激光雷达,一个在车头,一个在车尾。如果两个雷达用的是同一型号、同一批次、同一固件版本,那它们很可能因为同一个原因同时失效(比如固件bug)。这种冗余,我称之为“假冗余”。
避坑指南:我曾经在一个项目中,给AGV配了双激光雷达,结果两个雷达的电源线走的是同一个保险丝。保险丝一烧,两个雷达同时掉线。嗯,这就是典型的“冗余设计没做彻底”。从那以后,我要求所有冗余组件的电源、通信、安装位置都必须物理隔离。
1.3 冗余的分类
在路径规划领域,冗余通常分为四类。我按自己习惯的顺序来讲:
1.3.1 硬件冗余
这是最直观的冗余方式——多装一套硬件。
常见的做法有:
- 传感器冗余:激光雷达+摄像头+超声波,多传感器融合
- 控制器冗余:双MCU或双CPU,主备切换
- 执行器冗余:双电机驱动、双刹车系统
- 通信冗余:CAN总线+以太网双通道
硬件冗余的优点是可靠性高,缺点是成本高、体积大、重量重。
我记得有个项目,客户要求AGV在单传感器失效时仍能完成路径规划。我们最后方案是:主激光雷达+备用激光雷达+超声波阵列。成本直接翻了一倍,但客户验收时很满意。
1.3.2 软件冗余
软件冗余,说白了就是用不同的算法或代码实现同一功能。
举个例子:
// 主路径规划算法:A*算法
Path planPathAStar(Map map, Pose start, Pose goal) {
// A*实现
}
// 备用路径规划算法:Dijkstra算法
Path planPathDijkstra(Map map, Pose start, Pose goal) {
// Dijkstra实现
}
// 仲裁逻辑:比较两个结果,取一致的部分
Path arbitratePaths(Path path1, Path path2) {
if (path1 == path2) return path1;
else return fallbackPath(); // 降级到安全路径
}
软件冗余的好处是成本低,只需要多写代码。但坏处也很明显——如果两个算法用了同一个有bug的库,那冗余就失效了。
我的建议:做软件冗余时,尽量用不同的实现团队、不同的编程语言、不同的第三方库。比如主算法用C++写,备用算法用Python写。这样能最大程度避免共性失效。
1.3.3 信息冗余
信息冗余,指的是在数据层面增加额外的校验或备份信息。
常见的例子:
- CRC校验:在通信数据包中加入循环冗余校验码
- 奇偶校验:内存中的错误检测
- 多版本数据:同时保存路径规划结果和原始传感器数据
- 时间戳+序列号:防止数据重放或乱序
在路径规划中,我常用的一种信息冗余是“路径点校验”:
// 路径规划结果包含冗余信息
struct PathPoint {
float x, y; // 坐标
float curvature; // 曲率
uint32_t crc32; // 该点的CRC校验
};
// 执行前校验
bool validatePathPoint(PathPoint pt) {
uint32_t calcCrc = computeCRC32(pt.x, pt.y, pt.curvature);
return calcCrc == pt.crc32;
}
信息冗余的优点是几乎不增加硬件成本,但会增加计算开销和通信带宽。
1.3.4 时间冗余
时间冗余,就是用时间换可靠性。说白了,就是“多算几遍”。
常见做法:
- 多次采样取平均:传感器数据多次读取后滤波
- 重复计算:同一路径规划算法跑两遍,对比结果
- 超时重试:通信失败后等待一段时间再试
- 看门狗定时器:程序卡死时自动复位
举个例子,我在AGV项目中用过“三次握手”式的路径确认:
// 时间冗余:路径规划结果确认三次
bool confirmPath(Path path, int maxRetries = 3) {
for (int i = 0; i < maxRetries; i++) {
Path recheck = planPath(currentPose, path.goal);
if (isPathSimilar(path, recheck, threshold)) {
return true; // 两次结果一致,确认通过
}
delay(50); // 等待50ms后重试
}
return false; // 三次都不一致,拒绝执行
}
时间冗余的代价是增加了系统延迟。在高速移动的机器人上,50ms的延迟可能意味着几十厘米的误差。所以,时间冗余要谨慎使用。
注意:时间冗余不是万能的。如果系统存在设计缺陷,算一万遍也是错的。我曾经见过一个项目,路径规划算法里有个除零bug,每次算到某个特定场景就崩溃。时间冗余只会让系统崩溃得更快。所以,时间冗余只能对抗随机故障,不能对抗系统性故障。
1.4 四种冗余的对比
| 冗余类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 硬件冗余 | 可靠性最高,物理隔离 | 成本高,体积大 | 安全关键系统(如无人车、AGV) |
| 软件冗余 | 成本低,灵活 | 共性失效风险 | 算法验证、功能安全 |
| 信息冗余 | 几乎零硬件成本 | 增加计算和带宽开销 | 通信链路、数据存储 |
| 时间冗余 | 实现简单 | 增加延迟 | 低速系统、非实时场景 |
1.5 我的设计原则
做了这么多年路径规划,我总结了几条冗余设计的原则:
- 先分析,后设计——别一上来就堆冗余。先做FMEA(失效模式与影响分析),找到真正的单点失效。
- 多样性优于数量——两个不同型号的传感器,比三个同型号的传感器更可靠。
- 冗余要独立——电源、通信、安装位置都要独立,否则就是假冗余。
- 冗余要有仲裁——多份数据来了,谁说了算?必须设计仲裁逻辑。
- 冗余要可测试——你设计的冗余,能不能在测试中验证?如果测不了,那就是摆设。
最后说一句:冗余不是越多越好。每增加一份冗余,系统复杂度就上升一个台阶。而复杂度,往往是安全性的最大敌人。所以,恰到好处的冗余,才是最好的冗余。
下一章,我会讲硬件冗余的具体实现——双激光雷达的安装、标定和故障切换策略。到时候我会分享一个我踩过的坑,保证让你印象深刻。
好,今天就到这里。有问题随时交流。