第1章:UA(用户应用)设计

各位同学,今天我们来聊聊ARINC 661标准里最核心的部分——UA设计。说白了,UA就是座舱显示系统的“大脑”,它负责处理飞行数据,然后告诉CDS该怎么画。

我刚开始接触ARINC 661时,总觉得UA和CDS的边界很模糊。后来做了几个项目才明白,这个边界一旦划清楚,整个系统设计就顺了。

1.1 UA的职责

UA的全称是User Application,用户应用。它的职责可以概括为三件事:

  • 数据采集与处理:从飞机总线(比如ARINC 429、AFDX)获取原始数据,进行必要的计算和转换
  • 状态管理:维护显示系统需要的各种状态变量,比如当前高度、航向、告警状态
  • 与CDS通信:按照ARINC 661协议,把处理好的数据封装成标准消息,发给CDS

记住一个原则:UA只管“有什么数据”,不管“数据怎么画”。画的事情交给CDS。

我在项目中遇到过一位同事,非要在UA里写绘图逻辑,结果CDS升级时两边都要改,折腾了整整两周。嗯,从那以后我定了个规矩:UA里绝对不出现任何与像素、坐标相关的代码。

1.2 UA与CDS的交互流程

UA和CDS的交互,其实就是一个“请求-响应”的循环。你想想看,飞行员操作屏幕,或者飞机状态发生变化,UA都需要及时响应。

典型的交互流程是这样的:

  1. CDS发送事件:飞行员触摸屏幕,CDS把触摸事件打包成ARINC 661消息,发给UA
  2. UA处理事件:UA解析消息,更新内部状态,可能还会触发一些逻辑(比如切换页面)
  3. UA发送更新:UA把需要更新的控件数据封装成消息,发回给CDS
  4. CDS渲染:CDS收到消息后,更新对应的控件显示

这个循环每秒钟可能发生几十次。我建议你在设计时,给每个交互步骤打上时间戳,方便后期排查性能问题。

个人经验:我曾经在一个项目中,UA和CDS之间的通信延迟超过了100ms,结果飞行员反映屏幕“卡顿”。后来发现是UA端的数据封装太慢,优化后降到了20ms以内。

1.3 UA端的数据封装与解析

这部分是实操的重点。ARINC 661定义了一套标准的消息格式,UA需要严格按照这个格式来封装和解析数据。

消息的基本结构是这样的:

// ARINC 661消息头
typedef struct {
    uint16_t messageId;     // 消息ID
    uint16_t length;        // 消息总长度
    uint32_t timestamp;     // 时间戳(可选)
} A661MessageHeader;

// 控件更新消息
typedef struct {
    A661MessageHeader header;
    uint32_t widgetId;      // 控件ID
    uint16_t parameterId;   // 参数ID
    uint8_t  data[];        // 参数值
} A661SetParameterMessage;

封装数据时,我习惯这样做:

  • 先确定要更新哪个控件(widgetId)
  • 再确定要更新哪个参数(parameterId)
  • 最后把参数值按标准格式填入data字段

举个例子,更新一个高度显示控件:

// 封装高度数据
void packAltitudeData(uint32_t widgetId, float altitude) {
    A661SetParameterMessage msg;
    msg.header.messageId = 0x0102;  // SetParameter消息ID
    msg.header.length = sizeof(msg) + sizeof(float);
    msg.widgetId = widgetId;
    msg.parameterId = 0x0001;       // 高度参数ID
    memcpy(msg.data, &altitude, sizeof(float));
    
    // 发送给CDS
    sendToCDS(&msg, msg.header.length);
}

解析CDS发来的事件消息,步骤正好反过来:

// 解析触摸事件
void parseTouchEvent(uint8_t* buffer, uint32_t length) {
    A661EventMessage* event = (A661EventMessage*)buffer;
    
    // 检查消息ID
    if (event->header.messageId != 0x0201) {
        // 不是事件消息,忽略
        return;
    }
    
    // 获取事件类型和参数
    uint16_t eventType = event->eventType;
    uint32_t widgetId = event->widgetId;
    
    // 根据事件类型处理
    switch (eventType) {
        case TOUCH_PRESS:
            handlePress(widgetId);
            break;
        case TOUCH_RELEASE:
            handleRelease(widgetId);
            break;
        default:
            break;
    }
}

注意:数据封装时,字节序(Endianness)是个容易踩坑的地方。ARINC 661默认使用大端序,但有些嵌入式平台是小端序。我曾经因为这个bug,排查了整整一天才发现是字节序搞反了。

最后,我给大家一个避坑指南:

  • 消息ID别搞混:每个消息类型都有固定的ID,建议用枚举定义,别用魔数
  • 长度字段要准确:length字段包含消息头本身,别算错了
  • 数据类型要匹配:CDS端期望什么类型,UA就发什么类型,别把float当int发

好了,UA设计的基础内容就这些。下一章我们会深入UA的内部状态机设计,到时候再聊。