3. 系统架构设计:架构设计原则、模块划分、接口定义、数据流设计

好,咱们进入第三章。系统架构设计,说白了就是给座舱显示系统搭骨架。你想想看,一个飞行员在天上,几百个参数、几十个页面、好几路视频信号,全得靠这套系统稳稳当当跑起来。骨架要是歪了,后面再怎么填肉都白搭。

我个人习惯,做架构设计前先问自己三个问题:这东西要飞多少年?坏了怎么修?下一代怎么改?这三个问题想透了,架构的大方向就不会偏。

3.1 架构设计原则

我在项目中遇到过最头疼的事,就是架构设计时只盯着功能,没考虑维护。结果系统交付后,每次升级都像在拆炸弹。所以,我总结了四条铁律:

  • 高内聚、低耦合:每个模块只管自己的事。显示模块别去碰数据采集的活,数据采集模块也别操心画面怎么画。说白了,各扫门前雪。
  • 模块化与可替换性:任何一个模块坏了,拔下来换新的就能用。我见过有些系统,换个显示驱动得重新编译整个软件栈,那叫一个酸爽。
  • 可扩展性:今天支持两路视频输入,明天要加一路怎么办?架构设计时就得留好接口。别到时候为了加个功能,把整个系统推倒重来。
  • 安全性优先:座舱系统不是手机App。死机了可以重启,飞机上死机了可没机会重启。所以,关键路径上的模块必须做冗余设计。

核心原则总结:

架构设计不是为了好看,是为了好改、好修、好升级。记住这九个字,比背一百条原则都管用。

3.2 模块划分

模块怎么切?我习惯按功能域来切。一个典型的座舱显示系统,我通常会分成这么几块:

模块名称 核心职责 关键约束
数据采集模块 从ARINC 429、AFDX等总线收数据 实时性要求高,延迟<10ms
数据处理模块 数据校验、格式转换、告警逻辑 不能丢数据,不能误告警
图形渲染模块 生成PFD、ND、EICAS等画面 帧率稳定在30fps以上
人机交互模块 处理按键、旋钮、触控输入 响应时间<50ms
健康管理模块 自检、故障上报、BIT测试 不能影响主功能运行
配置管理模块 加载机型配置、用户偏好 支持热加载,不重启系统

嗯,这里要注意。模块不是越细越好。我见过有人把数据处理模块拆成八个子模块,结果接口定义写了三百页,开发人员看了直接想转行。我的建议是:一个模块的代码量控制在1-3万行之间,太大了难维护,太小了接口爆炸。

我的小技巧:

画模块图的时候,用不同颜色标注模块的安全等级。红色的是关键模块(比如告警逻辑),黄色的是重要模块(比如显示渲染),绿色的是普通模块(比如配置管理)。这样一眼就能看出哪些地方需要重点保护。

3.3 接口定义

接口定义是架构设计里最磨人的活。为什么?因为接口一旦定下来,后面改起来成本极高。我曾经在一个项目里,就因为接口定义时少考虑了一个参数,导致后面三个模块都得跟着改,光回归测试就多花了两周。

接口定义我建议遵循「三明确」原则:

  1. 明确数据格式:是用XML还是二进制?是大端还是小端?浮点数精度保留几位?这些都得写清楚。别指望靠默契,默契在航空系统里是最不可靠的东西。
  2. 明确时序约束:数据多久更新一次?超时了怎么办?我习惯在接口定义里加上timeoutretry策略。比如数据采集模块如果连续3个周期没收到数据,就得给健康管理模块发告警。
  3. 明确错误处理:接口调用失败了怎么办?返回什么错误码?调用方该怎么处理?这些必须在接口文档里写死。

举个例子,数据采集模块和数据处理模块之间的接口,我通常会这么定义:

// 数据采集模块 -> 数据处理模块
typedef struct {
    uint32_t    timestamp;      // 时间戳,单位微秒
    uint16_t    source_id;      // 数据源ID
    uint8_t     data_type;      // 数据类型:0=气压高度,1=空速,2=航向
    uint8_t     data_status;    // 数据状态:0=有效,1=无效,2=超时
    float       data_value;     // 数据值
    uint8_t     checksum;       // 校验和
} DataPacket_t;

你看,这个结构体里我特意加了一个data_status字段。为什么?因为数据采集模块可能收到无效数据,但数据处理模块需要知道这个数据是「有效但异常」还是「根本就没收到」。这两个情况处理逻辑完全不同。

避坑指南:

我曾经在一个项目里,接口定义时没考虑字节对齐的问题。结果A模块用#pragma pack(1)打包,B模块用默认对齐方式解包,数据全乱了。从那以后,我每个接口定义都会明确标注对齐方式。

3.4 数据流设计

数据流设计,说白了就是搞清楚数据从哪来、到哪去、中间经过谁。我习惯画数据流图,但我不画那种花里胡哨的UML图,我画的是「谁给谁什么数据」的简单框图。

一个典型的座舱显示系统数据流是这样的:

  1. 传感器数据(大气数据计算机、惯性导航系统等)通过ARINC 429总线进入数据采集模块
  2. 数据采集模块把原始数据打包成标准格式,发给数据处理模块
  3. 数据处理模块做校验、滤波、告警判断,然后把处理后的数据发给图形渲染模块
  4. 图形渲染模块根据数据生成画面,输出到显示屏。
  5. 同时,健康管理模块从各个模块收集健康状态,一旦发现异常,立刻通知数据处理模块触发告警。

这里有个关键点:数据流不能有环。你想想看,如果数据处理模块的数据又回流到数据采集模块,那不就成死循环了吗?我见过一个项目,就是因为数据流设计时没注意,导致系统跑着跑着CPU占用率飙到100%,最后发现是数据在几个模块之间来回传,根本停不下来。

数据流设计的三个检查点:

  • 检查数据流向:是不是单向的?有没有意外的环路?
  • 检查数据速率:上游产数据的速度,下游吃得消吗?要不要加缓冲队列?
  • 检查数据冗余:关键数据有没有备份路径?主路径断了怎么办?

说到数据速率,我建议在架构设计阶段就做一次带宽估算。比如,ARINC 429总线速率是100Kbps,一条总线上挂了20个参数,每个参数32位,每秒更新50次。算一下:20 × 32 × 50 = 32Kbps,占总线带宽的32%。看起来没问题,但如果再加10个参数,就变成48Kbps,接近50%了。这时候就得考虑用两条总线,或者降低某些参数的更新频率。

嗯,数据流设计还有一个容易被忽略的点:数据优先级。告警数据必须比普通参数优先处理。我习惯在数据包里加一个priority字段,数据处理模块根据优先级决定处理顺序。这样即使系统负载高,也能保证关键数据不被耽误。

好了,系统架构设计这块就聊到这儿。记住,架构设计不是一次性的工作,它需要在整个项目周期里不断迭代。但核心骨架一旦定下来,就别轻易动。动一次,伤筋动骨一百天。