
1. 从代号到工程Apollo-11 到底在做什么第一次看到“Apollo-11”这个代号很多人脑子里蹦出来的可能是那段著名的登月历史。但在我们这行尤其是做嵌入式、航天仿真、导航算法或者高可靠系统的人眼里Apollo-11 更像是一个符号——它代表着一类极端苛刻的工程场景算力极其有限、通信带宽窄得可怜、任何一个微小错误都可能让整个任务归零。我最早接触这个代号是在一个做飞行控制仿真的小项目里当时团队想复现一套“低算力条件下的自主导航与决策流程”内部代号就借用了 Apollo-11。后来发现圈子里用这个代号做项目的人还真不少有做历史计算机复原的有做容错架构演练的也有做教学演示平台的。所以这篇博文我不打算聊历史八卦而是把 Apollo-11 当作一个工程原型来拆解。它本质上是一个“在资源极度受限、环境高度不确定的前提下完成复杂序列决策与实时控制”的系统。核心关键词就三个制导、导航与控制外加一个隐藏关键词——容错。这套东西放到今天依然有极强的参考价值。比如你做无人机飞控、做车载自动驾驶的降级策略、做工业机器人的安全回路甚至做一套高可靠的后台任务调度系统都能从 Apollo-11 的架构思路里挖到东西。这篇文章适合谁看如果你是刚入行的嵌入式或控制方向工程师想理解“为什么老系统那么稳”那这篇能帮你建立一套从传感器到执行器的完整链路认知。如果你是有一定经验的开发者正在设计一套不允许宕机的系统那里面关于冗余、优先级、异常恢复的细节应该能直接抄作业。如果你只是对航天工程好奇那也没问题我会尽量用生活化的类比把原理讲透。全文会围绕一个模拟项目展开所有代码和参数都是基于常见工程实践补全的你可以直接拿去改吧改吧用在自个儿的 Demo 里。2. 核心架构拆解为什么它非得这么设计2.1 算力只有今天千分之一怎么保证不出错Apollo-11 那个年代导航计算机的算力大概相当于现在一个计算器。主频不到 1 MHz内存用磁芯存储几十 KB 的级别。你拿今天随便一个单片机的资源去比都觉得寒酸。但就是在这种条件下它要完成姿态解算、轨道修正、着陆雷达数据处理、发动机指令输出还要跟地面通信。这就逼出了一套极其克制的设计哲学能算一次绝不算两次能存常量绝不存变量能靠硬件绝不靠软件。我后来在做一个低功耗传感器节点的项目时就借鉴了这个思路。当时用的是一颗 8 位单片机RAM 只有 512 字节要跑一个简易的卡尔曼滤波。如果按教科书上的矩阵运算写光中间变量就爆了。后来我把状态转移矩阵里的常数全部展开成标量表达式把浮点运算改成定点 Q 格式把三角函数用查表加线性插值替代最后硬是塞进去了。这个经历让我深刻理解到资源受限不是让你降低功能而是逼你重新思考哪些计算是真正必要的。Apollo-11 的制导计算机里很多系数是提前算好烧进去的飞行中只做查表和简单插值。姿态控制用的不是复杂的自适应算法而是经典的 PID 加前馈补偿参数在地面反复调好。这种“地面算好、天上执行”的分工在今天看来依然很聪明。你如果做边缘计算也可以借鉴把重负载的模型训练放在云端端侧只做推理和轻量级后处理。2.2 冗余不是堆硬件而是分层隔离很多人一提高可靠就想到“双机热备”“三模冗余”。Apollo-11 确实有冗余但它的冗余是分层的不是简单堆数量。我把它拆成三层传感器层冗余惯性测量单元有多套但并不是同时全开。主惯导负责常规导航备用惯导只在特定阶段激活或者用来交叉校验。这样既省电又减少故障点。计算层冗余主计算机负责制导和导航另一台辅助计算机负责姿态控制显示和备份指令生成。两者通过特定的数据总线交换关键状态字而不是全量同步。决策层冗余关键动作比如发动机点火、着陆雷达开机需要多个条件同时满足并且有独立的硬件互锁电路。软件指令只是“请求”最终执行由硬件逻辑确认。这种分层隔离的好处是故障不会跨层传播。传感器坏了计算层还能靠历史数据外推一段时间主计算机挂了辅助计算机能接管基本姿态控制软件跑飞了硬件互锁能防止误点火。我在设计一个工业网关的看门狗电路时就用了类似思路软件喂狗是一层硬件看门狗是一层外部电源监控芯片又是一层三层各自独立任何一层失效都不会让整个设备死掉。2.3 任务阶段化把复杂问题切成可管理的片段Apollo-11 的飞行过程被切成了非常明确的阶段发射、入轨、地月转移、环月、着陆、月面活动、上升、返回。每个阶段有独立的制导模式、导航滤波参数和故障预案。这种阶段化设计的好处是每个阶段只需要关心当前最相关的状态量不用在一个巨大的状态空间里做全局优化。我后来做无人机自主航线飞行时也用了同样的套路。起飞阶段只关心高度和姿态巡航阶段关心航向和地速降落阶段关心下降率和水平位置。每个阶段切换时重置滤波器的过程噪声矩阵调整控制器的积分限幅。这样代码逻辑清晰调试也方便。如果你一上来就想用一个统一的模型覆盖所有阶段参数会非常难调而且一个阶段的异常容易污染其他阶段。注意阶段切换的判据一定要用“硬条件软条件”双重确认。硬条件比如高度、速度阈值软条件比如持续时间、状态一致性。单靠一个条件容易误触发。3. 制导与导航的实操细节从传感器到指令3.1 惯性测量单元的数据预处理Apollo-11 用的惯性测量单元包括陀螺仪和加速度计。原始数据噪声大温漂明显必须做预处理。我复现时用的是一块常见的六轴 IMU采样率 1 kHz通过 SPI 读取。预处理步骤大概是这样零偏校准上电后静止采集 2000 个样本计算均值和标准差把均值作为零偏扣除。标准差用来判断传感器是否正常如果太大说明振动或故障。温度补偿IMU 内部有温度传感器零偏随温度变化可以用一阶线性模型拟合。我实测下来零偏温度系数大概在 0.05 °/s/°C 左右不补的话长时间导航会漂得亲妈都不认识。低通滤波用二阶巴特沃斯低通截止频率 50 Hz滤掉高频振动。截止频率的选择要权衡太低会引入相位滞后影响控制环路稳定性太高则滤波效果不够。坐标变换把机体坐标系下的角速度和加速度转到导航坐标系。这一步需要姿态四元数而姿态又是由角速度积分来的所以是个闭环。实际实现时我用的是互补滤波陀螺积分得到高频姿态加速度计测重力方向得到低频姿态两者加权融合。代码大概长这样// 简化版互补滤波实际项目要加磁力计和更复杂的补偿 float alpha 0.98f; float dt 0.001f; // 陀螺积分 gyro_angle_x gyro_x * dt; gyro_angle_y gyro_y * dt; // 加速度计解算 accel_angle_x atan2f(accel_y, accel_z) * RAD_TO_DEG; accel_angle_y atan2f(-accel_x, sqrtf(accel_y*accel_y accel_z*accel_z)) * RAD_TO_DEG; // 互补融合 angle_x alpha * (angle_x gyro_x * dt) (1 - alpha) * accel_angle_x; angle_y alpha * (angle_y gyro_y * dt) (1 - alpha) * accel_angle_y;这段代码看着简单但有几个坑我踩过atan2f的输入顺序别搞反否则横滚和俯仰会互换alpha的取值跟采样率有关1 kHz 下 0.98 差不多如果你用 100 Hz得重新算加速度计在剧烈运动时测的不是重力所以融合权重最好动态调整比如用加速度模长与重力加速度的偏差来判断可信度。3.2 导航滤波器的状态量与观测方程Apollo-11 的导航滤波器本质上是一个扩展卡尔曼滤波状态量包括位置、速度、姿态误差、陀螺零偏、加速度计零偏。我复现时简化了一下状态量取 15 维位置3 维速度3 维姿态四元数4 维实际用误差四元数3 维陀螺零偏3 维加速度计零偏3 维观测方程根据可用传感器切换。地面测控阶段可以用雷达测距测速自主阶段主要靠 IMU 递推加星光敏感器修正。我做的模拟项目里用了一个虚拟的“高度计”和“星敏感器”作为观测源噪声模型按高斯白噪声加随机游走处理。过程噪声矩阵 Q 的调参是个体力活。我的经验是先根据传感器数据手册的噪声密度和零偏不稳定性算出理论值然后在这个基础上放大 2 到 5 倍作为初始值。实际跑的时候看新息序列观测残差是否白化。如果新息有趋势说明 Q 太小滤波器太信任模型如果新息频繁跳变说明 Q 太大滤波器太信任观测。实操心得调 Q 和 R 的时候先把 R 固定为传感器手册给出的观测噪声方差然后只调 Q。这样收敛快而且物理意义明确。等 Q 差不多了再微调 R。3.3 制导律的选择与参数整定制导律决定了“往哪飞、怎么飞”。Apollo-11 在不同阶段用了不同的制导律地月转移用的是瞄准线制导着陆段用的是重力转弯制导。我复现时着陆段用了一个简化的比例导引加高度速度剖面跟踪。高度速度剖面是核心。简单说就是根据当前高度查表得到一个期望下降速度。高度越高允许下降越快接近地面时下降速度必须压到很低。这个剖面通常用多项式拟合比如# 高度-速度剖面示例单位米米/秒 def desired_velocity(altitude): if altitude 1000: return -50.0 # 高速下降 elif altitude 100: # 线性过渡 return -50.0 (altitude - 100) * (50.0 - 5.0) / (1000 - 100) elif altitude 10: return -5.0 (altitude - 10) * (5.0 - 1.0) / (100 - 10) else: return -1.0 # 最终着陆速度然后制导律输出加速度指令def guidance_accel(altitude, velocity): v_des desired_velocity(altitude) error v_des - velocity # PD 控制 accel kp * error kd * (error - last_error) / dt return accelkp和kd的整定我一般先用 Ziegler-Nichols 方法粗调然后根据仿真结果微调。着陆段对超调非常敏感所以kd要给足阻尼比最好在 0.9 以上。另外加速度指令一定要限幅否则在高度突变时会产生不切实际的指令。4. 容错与异常处理那些教科书不写的实战经验4.1 传感器故障的检测与隔离Apollo-11 的传感器冗余不是简单投票而是基于模型的故障检测。我复现时用了一个“解析冗余”的方法用 IMU 的加速度积分得到速度跟高度计微分得到的速度做对比如果偏差超过阈值且持续一定时间就判定其中一个传感器故障。具体实现时我维护了一个“故障计数器”。每个周期如果残差超过 3 倍标准差计数器加一如果低于阈值计数器减一但不小于零。当计数器超过 10就触发隔离逻辑。隔离后系统切换到备用传感器或者降级使用模型外推。这个方法的坑在于阈值不能设死。飞行阶段不同振动水平不同残差的标准差也在变。我后来改成自适应阈值用最近 100 个周期的残差计算滑动标准差阈值设为 3 倍当前标准差。这样在振动大的阶段不会误报在平稳阶段又能检测小故障。4.2 计算机重启后的状态恢复Apollo-11 的计算机有过载重启的案例重启后需要快速恢复关键状态。我复现时设计了一个“状态快照”机制每 100 ms 把关键状态位置、速度、姿态、模式字写入非易失存储。重启后从快照恢复然后用 IMU 数据外推补上丢失的时间。这里有个细节快照写入不能太频繁否则影响实时性也不能太稀疏否则恢复误差大。我实测下来100 ms 是个不错的折中。另外快照数据要加校验和防止写入过程中掉电导致数据损坏。恢复时如果校验失败就退回到上一个有效快照或者进入安全模式。注意状态恢复后滤波器的协方差矩阵不能直接恢复要适当放大表示对恢复状态的不确定性。我一般把位置和速度的方差放大 10 倍姿态方差放大 5 倍让滤波器快速收敛。4.3 通信中断的应对策略Apollo-11 跟地面通信会中断尤其是绕到月球背面的时候。我复现时模拟了通信中断场景地面指令停止下发遥测数据停止上传。系统需要自主运行一段时间。我的策略是通信正常时地面定期上传“指令序列”和“参数更新”星上缓存起来。通信中断后系统按缓存的指令序列执行同时用星上传感器维持导航。如果中断时间超过预设阈值比如 30 分钟系统自动进入“安全保持”模式停止高功耗设备降低控制频率等待通信恢复。这个思路在今天的物联网设备上也很实用。比如一个偏远地区的环境监测站网络不稳定就可以让设备缓存一段时间的采集计划和上报策略断网时按计划执行联网后再补传数据。5. 常见问题与排查技巧实录5.1 姿态解算发散的几种原因姿态解算发散是新手最常遇到的问题。我总结了几种典型情况现象可能原因排查方法解决方案横滚角缓慢漂移陀螺零偏未补偿静止时观察角速度输出重新校准零偏加温度补偿俯仰角高频抖动加速度计噪声大看原始数据频谱加强低通滤波或降低融合权重偏航角随机游走磁力计受干扰对比磁力计与陀螺积分磁力计做椭圆拟合校准或改用星光/视觉角度突然跳变三角函数象限错误检查 atan2 输入符号用四元数代替欧拉角避免万向锁我踩过最坑的一次是加速度计安装方向跟机体坐标系差了一个 90 度导致俯仰和横滚完全反了。当时调了一整天最后拿水平仪一比才发现。所以装配后第一件事就是做六面校准每个面静止采集数据确认符号和轴向。5.2 滤波器不收敛的调试步骤卡尔曼滤波不收敛表现是新息越来越大或者状态估计明显偏离真值。我的调试步骤一般是检查观测方程拿一组已知状态的数据手动算一遍观测预测值跟实际观测对比。如果差得离谱说明方程写错了。检查雅可比矩阵扩展卡尔曼滤波的雅可比矩阵容易推错尤其是姿态部分。我一般用数值微分验证对每个状态量加一个小扰动看观测变化跟解析雅可比对比。检查 Q 和 R 的量级如果 Q 太小滤波器会过度信任模型导致新息累积如果 R 太小滤波器会过度信任观测导致估计跳变。我一般先把 Q 设大一点让滤波器快速跟踪然后再慢慢收紧。检查时间同步IMU 和观测传感器的时间戳如果没对齐会导致观测残差有系统性偏差。我一般用硬件触发同步或者用插值对齐时间戳。实操心得调滤波器的时候把新息序列画出来。正常的新息应该是零均值白噪声。如果有周期性波动说明模型有未建模的动态如果有阶跃说明有未检测到的故障或模式切换。5.3 实时性不足的优化手段Apollo-11 的计算机要在有限周期内完成所有计算实时性优化是必须的。我复现时也遇到了计算超时的问题。优化手段大概有这些定点化把浮点运算改成定点速度提升明显。但要注意溢出和精度损失。我一般用 Q15 格式动态范围够用精度也还行。查表代替实时计算三角函数、指数、开方这些提前算好表运行时查表加插值。表的大小和插值阶数要权衡我一般用 256 点表加线性插值精度足够。减少不必要的状态更新不是所有状态都需要每个周期更新。比如陀螺零偏可以每 10 个周期更新一次中间保持。中断优先级管理把最紧急的任务比如电机控制放在最高优先级中断导航解算放在低优先级用 DMA 搬运数据减少 CPU 占用。我实测下来经过这些优化一个 15 维的扩展卡尔曼滤波可以在 1 ms 内跑完留给控制律的时间还有富余。6. 从 Apollo-11 到现代系统哪些思路可以直接搬6.1 分层架构在自动驾驶中的应用Apollo-11 的分层冗余思路在今天的自动驾驶里几乎原样复现。感知层有多套传感器摄像头、毫米波雷达、激光雷达计算层有主控和备份控制器决策层有安全监控芯片。我参与过一个 L2 级别的辅助驾驶项目就借鉴了 Apollo-11 的“阶段化”设计高速巡航、跟车、换道、泊车每个阶段有独立的控制参数和故障预案。阶段切换用硬条件车速、车道线置信度加软条件持续时间、驾驶员状态双重确认。这种设计的好处是每个阶段的测试用例可以独立编写回归测试也方便。你不需要在一个巨大的状态空间里穷举只需要保证每个阶段的边界条件覆盖到位。6.2 资源受限下的算法选择现在很多边缘设备算力虽然比 Apollo-11 强但依然有限。比如一个智能摄像头要在本地做目标检测和跟踪。我的经验是模型选型比模型优化更重要。与其把一个大型网络压缩到极致不如直接选一个轻量级网络然后针对场景做微调。Apollo-11 当年也是这么干的不追求通用最优只追求当前任务够用。另外混合精度是个好办法。关键路径用浮点非关键路径用定点或半精度。我在一个无人机视觉项目里把特征提取用半精度匹配和位姿解算用浮点整体功耗降了 30%精度几乎没损失。6.3 故障注入测试的必要性Apollo-11 的软件在开发过程中做了大量的故障注入测试模拟传感器卡死、数据跳变、计算机重启、通信中断。我后来做任何高可靠系统都会搭一个故障注入测试台。最简单的做法是在传感器数据读取函数里加一个“故障模式”开关可以强制返回固定值、随机值、或上次的值。然后跑一遍完整任务流程看系统能不能正确检测并恢复。这个测试台帮我发现过很多隐藏 bug。比如有一次陀螺仪数据卡死在某个值故障检测逻辑因为阈值设得太宽过了 5 秒才报警。后来我把检测逻辑改成“连续 10 个周期方差为零”就触发响应时间缩短到 100 ms。注意故障注入测试一定要在真实硬件上跑仿真环境容易忽略时序和硬件互锁的问题。我一般先在仿真里跑通逻辑再在硬件上做长时间拷机测试。7. 我个人在实际操作中的体会做这类系统最深的体会是简单可靠比先进复杂更重要。Apollo-11 用的算法今天看都是教科书级别的经典方法没有深度学习没有自适应控制但组合起来就是能完成任务。我见过太多项目为了追求“技术先进性”堆了一堆复杂算法结果调试周期无限拉长最后稳定性还不行。另一个体会是测试用例的设计比代码本身更花时间。一个导航模块代码可能就几百行但测试用例要覆盖各种边界条件、故障模式、阶段切换写下来上千行。但这是值得的因为航天、工业这些领域一次失败的成本太高了。最后分享一个小技巧给每个关键变量加“合理性检查”。比如位置不能超过某个范围速度不能突变姿态角不能跳变。一旦检查失败就触发降级或安全模式。这个检查逻辑很简单但能拦住大部分低级错误。我在一个项目中就因为加了这个检查提前发现了一个坐标系转换的 bug避免了试飞时的炸机。这个内容后续还可以这样扩展把制导律换成模型预测控制看看在同样算力下能不能跑或者把导航滤波器改成无迹卡尔曼滤波对比一下收敛速度和精度再或者把整个系统容器化用软件在环的方式做持续集成测试。这些都是我接下来想尝试的方向。