架构14 把实时性拆成了可计算的时序预算,本篇把功能安全 也拆成同一类东西。ISO 26262-6 对软件架构设计的要求(层级化结构、元素有规格说明、依赖最小化、安全机制与异常处理)读起来都对,落到评审现场往往只剩三句话:"我们有看门狗""我们做了冗余""我们按 ASIL D 做的"------没有一句带数字 。本篇把这三句各变成一次可执行实验:同一套扭矩控制回路,做 A(无安全机制)与 B(冗余感知 + 残差去抖 + 降级 + 看门狗)两条架构,开工前先跑 license 探测(沿用"真跑为准"),然后实测:基线 FTTI = 41ms(不是拍脑袋的 100ms) 、B 检测+反应 = 6ms (裕度 35ms)、去抖翻转点 35→40ms 、看门狗检出 54ms (系统预算 100ms)、调度抖动 J=60ms 时 60ms 窗口误报 14 次 、ASIL 分解的结构证据(块 24→66、跨通道边 6 条、共享软件元素 0 )。结论一句话:安全机制 = 落点(哪一层) × 检测时间(实测) × 反应时间(实测) × 预算来源(基线实测或危害分析),少任何一项,它就只是画在框图上的框。
1. 安全机制为什么总是停在框图上
架构评审里,安全机制是最容易"混过去"的部分------因为它听起来天然正确。真实病象有四种:
| 病 | 典型现场 | 病根 |
|---|---|---|
| FTTI 拍脑袋 | "我们留了 100ms 余量很安全" | 没跑过"无安全机制"的基线,不知道危害到底什么时候来 |
| 检测延迟不计价 | 残差阈值和去抖拍到 50ms,理由是"抗干扰" | 去抖本身就是检测延迟,它直接吃掉 FTTI,却从不出现在预算表里 |
| 窗口参数靠经验 | 看门狗窗口 60ms,"行业惯例如此" | 窗口被调度抖动 (下界)和系统预算(上界)夹在中间,两个界都没量过 |
| 分解 ≠ 独立 | 文档写"ASIL D 分解为 B(D)+B(D)" | 拆完没有独立性证据:两条通道共享哪些软件元素、几条跨界边,没人能当场扫出来 |
对症下药就是本篇的四件交付物:基线实测的 FTTI、四机制时间预算总表、窗口可行带、分解独立性的结构扫描。全部由一个脚本现场跑出,不引用任何"我记得"。
2. 被控对象与两条架构:先证明它们功能等价
实验对象是一个 1ms 定步长的扭矩回路:需求 r(1s 起阶跃到 40%)→ P 控制(前馈 + 反馈,Kp=3)→ 限幅 100% → 任务开关(卡死注入)→ 一阶执行器/被控对象(τ=100ms)→ 实际扭矩 x。安全目标(SG,按 ASIL D 处理):防止非预期扭矩 > 60%。
| 模型 | 结构 | 本文记号 |
|---|---|---|
| a15_plain | 只有控制通道 + 传感器1 + 超限判定,无任何安全机制 | A 无安全机制 |
| a15_safe | 与 A 同一份控制通道构件,外加:冗余传感器2(τ=5ms)→ 残差(传感器2 减传感器1 的绝对值)→ 阈值 5% → 去抖 N ms → 锁存 → 降级(切 y2 + 指令限幅 30%);看门狗(心跳 10ms / 窗口 60ms)→ 触发切断 | B 有安全机制 |
门禁先跑等价性 (不等价就没资格比安全机制):无故障时 A/B 逐点一致,maxΔx = 0、maxΔu = 0;同时验证正常工况不误报 ------r 阶跃瞬间传感器2 的一阶滞后让残差冲到 4.341%,仍低于 5% 阈值(余量 13%),锁存不会被正常瞬态误触发。

3. FTTI 是跑出来的,不是说出来的
ISO 26262 里 FTTI(故障容错时间间隔)= 从故障发生到(在没有任何额外安全机制时)出现危害事件的时间 。它必须由危害分析给出,而在架构阶段最硬的来源就是:把安全机制全关掉,跑一遍,看危害什么时候到。
故障注入:t=2.0s 时传感器1 增益失效(y1 = 0.2x)。控制器看到的反馈瞬间从 40% 塌到 8%,于是把油门顶满:

text
A 无安全机制: 危害起始 t=2.041s (故障后 41.0ms) = 实测 FTTI
危害持续 1959ms, 峰值实际扭矩 100.0% (限值 60), 任务输出峰值 100 (饱和)
B 有安全机制: 检测 t=2.005 (+5ms) -> 安全状态 t=2.006 (+6ms), 反应 1ms
峰值 43.5%, 全程未超限, 裕度 = 41 - 6 = 35ms, 降级稳态 30% (跛行)
三个数值得抠:
- FTTI = 41ms :这是扭矩从 40% 沿 τ=100ms 的惯性爬到 60% 所用的时间。它比常见的"经验 100ms"小一倍多------如果你按 100ms 设计检测预算,这个架构从第一天起就不合规;
- 6ms 完成检测+反应:残差在故障当拍就是 32%(0.8×40),去抖 5ms、锁存 1ms,只用掉预算的 15%;
- 降级不是归零 :切到冗余反馈 + 指令限幅后,扭矩稳定在 30% 而不是直接切断------跛行回家也是安全状态,只要它可证明地不超限。这一步同时验证了降级路径真的会被走到,而不是只在文档里。
4. 时间预算总表:四个机制,四个落点,四个数
把上面的实测和看门狗实验(§5)并到一张表------这是本篇的核心交付物之一:
| 机制 | 架构落点 | 故障类 | 检测时间 | 反应时间 | 合计 | 预算来源 | 裕度 | 判定 |
|---|---|---|---|---|---|---|---|---|
| 无 (A) | --- | 传感器失效 | 未检出 | --- | ∞ | 41ms(基线实测) | --- | FAIL:危害持续 1959ms |
| 冗余一致性 + 去抖5ms | 感知层 + 监控层 | 传感器失效 | 5ms | 1ms | 6ms | 41ms(基线实测) | 35ms | PASS |
| 去抖 40ms | 监控层 | 传感器失效 | 40ms | 1ms | 41ms | 41ms | 0 | FAIL:峰值 60.3% 超限 |
| 看门狗 60ms 窗口 | 任务调度层 | 任务卡死 | 54ms | 切断 1ms | 54ms | 100ms(危害分析给定) | 46ms | PASS |
| 无 (A) | --- | 任务卡死 | 未检出 | --- | 1000ms+ | 100ms | --- | FAIL:冻结 40% 且永不告警 |
检测延迟本身是要花 FTTI 的。把去抖从 1ms 扫到 80ms(其余不变),看峰值扭矩什么时候穿限值:
| 去抖 N (ms) | 1 | 5 | 10 | 20 | 25 | 30 | 35 | 40 | 50 | 80 |
|---|---|---|---|---|---|---|---|---|---|---|
| 检出时间 (ms) | 1 | 5 | 10 | 20 | 25 | 30 | 35 | 40 | 50 | 80 |
| 峰值实际扭矩 (%) | 41.2 | 43.5 | 46.3 | 51.4 | 53.8 | 56.1 | 58.2 | 60.3 | 64.1 | 73.4 |
| 超限 (>60%) | 否 | 否 | 否 | 否 | 否 | 否 | 否 | 是 | 是 | 是 |

翻转点落在 35→40ms 之间 ,与基线实测 FTTI=41ms 严丝合缝(40ms 检出 + 1ms 反应时,峰值已经 60.3%)。也就是说:这份架构的去抖上限就是 35ms,再"抗干扰"一点,安全机制自己就变成了危害的帮凶。这条约束应该写进参数表旁边,而不是等路试出问题才回头查。
5. 看门狗:窗口的上界是预算,下界是调度抖动
第二类故障是任务卡死 (t=3.0s 起控制任务停止调度):A 的任务输出冻结在 40%,直到仿真结束 1000ms 内没有任何检出、没有任何告警;B 的看门狗因为心跳停了,计数不再清零,越过窗口即触发并切断:

text
E3 任务卡死@3.0s: A 扭矩冻结 40% 且无任何检出 (持续 1000ms)
B 看门狗 t=3.054 检出 (+54ms), 切断后 x=0.0%
E4 窗口扫描: 20ms->14ms 40ms->34ms 60ms->54ms 80ms->74ms 100ms->94ms (全部 <= 100ms 预算)
窗口的上界 很好理解:检出时间不能超过系统预算(100ms)。但下界 来自另一个方向------任务心跳是被调度器抖动的。固定种子扫 4 秒、心跳周期 10ms + U(-J,+J) 抖动,统计最大心跳间隙与 60ms 窗口的误报次数:
| 抖动半宽 J (ms) | 10 | 30 | 50 | 60 | 75 | 90 | 100 |
|---|---|---|---|---|---|---|---|
| 实测最大心跳间隙 (ms) | 19.98 | 39.54 | 59.97 | 69.69 | 83.93 | 99.58 | 109.57 |
| 60ms 窗口误报次数 (4s) | 0 | 0 | 0 | 14 | 24 | 31 | 31 |
| 可行窗口带 下界, 100ms | 20,100 | 40,100 | 60,100 | 70,100 | 84,100 | 100,100 | 空 → 方案失效 |
三句读法:J=50ms 时窗口 60ms 恰好贴着下界(0 误报但零余量);J=60ms 起 60ms 窗口开始稳定误报(4 秒 14 次)------误报不是"偶尔抽风",是参数与调度不匹配的必然;J=100ms 时可行带为空,说明这个架构里"窗口看门狗"这条路已经走不通,必须换机制(如程序流校验 + 硬件看门狗),而不是继续调窗口 。窗口参数从来不是架构师一个人的决定,它是调度器特性 × 预算的交点,必须两边都拿实测数据来对。
6. ASIL 分解:拆得出去,还要拿得出独立性证据
ISO 26262-9(Clause 5)允许把安全要求(不是安全目标)分解到两个元素上,常用组合:
| 原 ASIL | 允许的分解组合 |
|---|---|
| D | D(D)+QM(D) / C(D)+A(D) / B(D)+B(D) |
| C | C(C)+QM(C) / B(C)+A(C) |
| B | B(B)+QM(B) / A(B)+A(B) |
| A | A(A)+QM(A) |
三条硬规则:括号里的字母表示该元素必须沿用分解前的开发纪律 (QM(D) 也要按 ASIL D 的流程做);分解后的要求必须各自独立地 满足原要求;同质冗余不构成独立 (同一份代码复制两遍,还是同一个 bug)。独立性要靠**依赖失效分析(DFA)**给证据。
本篇用结构扫描给 DFA 的第一块砖------通道划分(ctrl 控制通道 10 块 / sense 感知 9 块 / safety 安全机制 38 块 / plant 被控对象 4 块 / meas 测量 5 块),沿每条连线追溯端口:
text
E5 a15_plain: 块=24 边=36 跨通道边=7
E5 a15_safe : 块=66 边=90 跨通道边=24 (ctrl->safety 3, safety->ctrl 3, 感知/测量跨界 18)
安全机制结构成本: +42 块 / +54 条连线
跨界边明细(安全机制 <-> 控制通道, 共 6 条):
入: Hsw->Usw (切断点) rref->Gcap (限幅参考) rref->Gsw (需求选择)
出: Fsw->Cerr (反馈选择) Gsw->Cerr (需求限幅) Gsw->Cff (前馈限幅)
共享软件元素: 0 共享物理量: 1 (被控对象 x) 定义接口: y1, y2, r

这张图就是"安全机制在架构里的落点"的标准画法:上半区四层各自标出机制与它接管的位置,右上把每个机制的检测/反应时间画在同一根时间轴上(并标出两条预算线),右下是分解关系与独立性证据。三个区域缺一个,这张图就退化成框图。
对扫描结果的诚实解读:控制通道与安全机制的块集合零重叠、只有 6 条显式跨界边 ,这是"结构上可分解"的证据;但共享物理量 1 个(被控对象)意味着传感器2 与传感器1 仍共享同一被测量 ,供电/时钟/存储/核这类共因必须另做 DFA 才能宣称独立------结构扫描是独立性论证的起点,不是终点。
7. 落地检查单:10 项,每项都是实测值
| # | 类别 | 判据 | 本篇实测 | OK/NG |
|---|---|---|---|---|
| CK01 | 门禁 | 无故障时两架构等价 maxΔ ≤ 1e-9 |
maxΔx = 0 |
OK |
| CK02 | 门禁 | 正常工况残差 < 阈值(不误报) | 4.341 < 5 | OK |
| CK03 | 门禁 | 基线(无保护)确实产生危害,否则实验无效 | 41.0ms 穿越,持续 1959ms | OK |
| CK04 | 时间 | 检测+反应 ≤ 实测 FTTI | 6 ≤ 41(裕度 35) | OK |
| CK05 | 降级 | 反应后不超限且进入安全状态 | 峰值 43.5 < 60,稳态 30 | OK |
| CK06 | 参数 | 去抖 < 翻转点(35→40ms) | 5ms | OK |
| CK07 | 看门狗 | 检出 ≤ 系统预算 | 54 ≤ 100 | OK |
| CK08 | 参数 | 窗口 > 最大心跳间隙(无误报) | J=10:60 > 19.98;J=60:60 < 69.69 → 误报 14 次 | NG(参数与调度绑定) |
| CK09 | 分解 | 控制通道与安全机制共享软件元素 = 0 | 0(跨通道边 6 条全为显式接口) | OK |
| CK10 | 成本 | 安全机制的结构成本已登记 | +42 块 / +54 边 | OK |
没有满分流程:CK08 故意留一条 NG------看门狗窗口不是"设了就行",它必须和目标 ECU 的调度实测绑定,这一条要在架构决策记录里挂责任方。检查单的意义不是凑绿灯,是逼出"哪类风险我们接受、由谁签字"。
8. 方法论:三条可迁移的结论
- 预算先分两类,再谈够不够:基线实测 FTTI(关掉安全机制跑出来的 41ms)与危害分析给定预算(100ms)是两种来源、两种置信度,必须在表里分开标注;把两者混成一个"100ms 余量",会掩盖"危害 41ms 就到"这个事实;
- 安全机制的四元组缺一不可 :落点(哪一层)、检测时间(实测)、反应时间(实测)、预算来源。本篇每个机制都填满了这四格,评审时任何一格空着,就说明它还停留在框图阶段------去抖 35ms 上限、窗口 60ms 下界,都是这套四元组逼出来的约束;
- 参数是被两端夹出来的 :去抖被"抗干扰需求"和 FTTI 夹,窗口被"调度抖动"和"系统预算"夹。架构师的工作不是拍一个中间值,而是把两端都量出来,把可行区间写进架构文档;可行区间为空(如 J=100ms 时的窗口看门狗)就是架构级红灯,该换机制而不是调参数。
9. 本篇的可复现性
- 环境探测:
a15_probe.m/a15_probe2-4.m(license(Simulink)=1, R2025b;比较器块路径在 R2025b 已并入Relational Operator,Mux 移至Signal Routing)------沿用"真跑为准",不假设工具可用; - 模型:
a15_plain.slx/a15_safe.slx(1ms 定步长,控制通道构件逐块同一实现); - 脚本:
a15_gen.m(建模 + 等价性 + 故障注入 + 去抖扫描 + 卡死/看门狗 + 窗口/抖动扫描 + 结构扫描 + 5 张图一体,mark()全程打点); - 日志与结果:
a15_gen.log、a15_res.mat(本文所有数字均可回读核对); - 统计口径:FTTI = 基线 A 中
x > 60%首次出现时刻 − 故障注入时刻;检测时间 = 锁存标志首次置位 − 故障时刻;抖动扫描固定种子rng(7),心跳10ms + U(-J,+J),取 4s 内最大间隙与间隙 > 60ms计数;结构扫描按块名通道归属统计,连线经PortHandles.Inport → Line → SrcBlockHandle逐条追溯; - 边界如实声明:ISO 26262 条文以正式标准文本为准,本篇只做"架构要求的可执行化";抖动是合成分布,真实项目须替换为实测调度轨迹;结构扫描不替代 DFA,硬件度量(SPFM/LFM)与 HSI 类要求不在本篇范围。
10. 下篇预告
架构16:架构守护------让架构不被实现"腐化"的一致性检查(架构模型↔实现模型追溯、规则检查脚本化:把本篇检查单里 CK08 这类"必须随项目绑定"的项,变成 CI 里自动跑的规则)。
本篇脚本、日志、模型与图随文开源;「软件架构设计实战」专栏与「基于MATLAB的AUTOSAR软件开发指南」互为前后篇------架构定结构,前者定实现,合起来才是完整的车载软件工程闭环。