F´ 地面接口(Ground Interface)架构解析与自定义扩展实践指南
【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime
本文以 F´(F Prime)飞行软件框架的地面接口为核心,系统拆解其"航天器侧"的 uplink/downlink 双链路与 framing/driver 双层结构,并结合仓库源码说明如何为现有框架添加自定义线协议与自定义 ByteStream 驱动。读完本文,你将掌握 F´ 地面接口各组件(驱动、帧累积器、成帧器/解帧器、路由器)的职责与端口契约,能够在不替换整个地面接口的前提下完成针对性适配,例如对接 COSMOS、OpenMCT 等地面系统。
Ground Interface 总体架构
F´ 的地面接口分为两个部分:航天器侧(spacecraft side)与地面侧(ground side)。本文聚焦航天器侧的适配——最常见的工程模式是改造 F´ 飞行软件以对接某种已有的地面系统(如 OpenC3 COSMOS、NASA OpenMCT 等)。得益于模块化的分层设计,大多数项目只需要做局部适配,而无需整体替换地面接口。
在最基本的形式下,F´ 地面系统由uplink(上行)与downlink(下行)两侧组成,每一侧又分为framing(成帧)与driver(驱动)两层:
- Uplink:处理来自接口远端(地面站)的数据,将其解包为 F´ 数据类型并路由到整个 F´ 系统;
- Downlink:处理发往远端接口的数据;
- Framing:负责在数据与字节缓冲区之间做序列化/反序列化(成帧与解帧);
- Driver:负责向硬件读写字节。
注意:本指南中称其为"驱动层",许多项目中把它叫作"电台(radio)层"或"通信层"。该层的功能就是向某个硬件读写字节,只要硬件能收发字节,其具体形态(TCP、UART、总线等)对本层而言并不重要。
框架的分层设计使得成帧协议可以独立拆出、快速适配。此外,每一级处理都需要分配内存,因此使用者还应当参考 缓冲区池管理指南,为驱动、成帧/解帧、路由各环节规划好缓冲区来源。
标准 F´ 组件处理两类数据:com buffers与raw buffers。Com buffers 承载标准的 F´ 条目(如事件、遥测、命令),raw buffers(Fw::Buffer)承载任意原始字节(如文件数据)。因此地面接口必须同时处理这两类数据。通信硬件通常只传输字节、对数据本身的性质一无所知,地面接口的目标就是把各种 F´ 数据翻译成一段字节序列,并保证其在接口另一侧可以被完整重建。
Driver 层:硬件通信管理
驱动负责管理硬件通信。它既可以是简单的硬件接口(如 TCP 或 UART),也可以是相当复杂的构造(如电台、航天器总线)。从 F´ 的角度看,驱动有两个职能:提供输入数据与处理输出数据。
注意:通常项目使用单个驱动同时处理输入与输出;但如果上下行需要不同的行为,也可以使用两个驱动(例如 UDP 下行追求速度、TCP 上行保证可靠性)。
所有驱动都实现一个接收成帧器数据的输入端口——驱动应把输入数据写入其管理的硬件。驱动通过recv输出端口递交收到的数据,该端口通常由一个读线程支撑。接收方使用完毕后,通过驱动的recvReturnIn端口归还缓冲区所有权。
发送数据(Sending Data)
要向驱动发送数据,需把一个Fw::Buffer传入驱动的 send 输入端口,缓冲区包裹的数据会被推送到硬件。同步驱动(实现Drv.ByteStreamDriver)直接从 send 调用返回Drv.ByteStreamStatus枚举值之一,且调用方保留缓冲区所有权:
ByteStreamStatus.OP_OK:发送成功;ByteStreamStatus.SEND_RETRY:后续重传很可能会成功;ByteStreamStatus.OTHER_ERROR:发生错误,数据未发送,重试可能成功。
异步驱动(实现Drv.AsyncByteStreamDriver)在 send 时接管缓冲区所有权,并通过其sendReturnOut回调端口同时返回状态与缓冲区。
接收数据(Receiving Data)
驱动通常有一个内部任务,收到数据时调用recv输出端口。接收端口传递一个Fw::Buffer与一个Drv.ByteStreamStatus状态:
ByteStreamStatus.OP_OK:接收正常,缓冲区包含有效数据;ByteStreamStatus.RECV_NO_DATA:接收正常,但没有数据;ByteStreamStatus.OTHER_ERROR:接收失败,缓冲区没有有效数据。
当接收组件处理完缓冲区后,通过驱动的recvReturnIn端口把所有权归还给驱动。
源码中的驱动契约:ByteStreamDriver / AsyncByteStreamDriver
地面接口对驱动的兼容性要求,定义在 Drv/Interfaces 目录的 FPP 接口文件中。同步接口 ByteStreamDriver.fpp 定义如下四个端口:
output port ready: Drv.ByteStreamReady output port $recv: Drv.ByteStreamData guarded input port $send: Drv.ByteStreamSend guarded input port recvReturnIn: Fw.BufferSend- ready(输出):驱动无参调用该端口,表示其已就绪、可以收发数据;
- recv(输出):驱动调用该端口并携带一个
Drv.ByteStreamStatus与一个Fw::Buffer,用于递交收到的数据; - send(输入):客户端调用该端口并传入
Fw::Buffer以发送数据;状态同步返回,调用方保留缓冲区所有权; - recvReturnIn(输入):客户端通过该端口归还通过
recv收到的缓冲区所有权。
异步接口 AsyncByteStreamDriver.fpp 则把同步的$send替换为async input port $send: Fw.BufferSend,并新增output port sendReturnOut: Drv.ByteStreamData,通过该回调返回发送状态与缓冲区所有权。
从源码结构看,同一文件还为驱动客户端定义了配套的ByteStreamDriverClient接口(drvConnected、drvReceiveIn、drvReceiveReturnOut、drvSendOut),供成帧/通信栈一侧连接使用。发送与接收状态的完整语义可进一步查阅 ByteStreamDriverModel 设计文档:其中明确了同步与异步两种版本下缓冲区所有权的流转规则,并列出框架内实现该模型的组件——Drv::TcpClient、Drv::TcpServer、Drv::Udp 与 Drv::PosixUartDriver。
Uplink:上行链路组件链
Uplink 处理收到的数据,解包 F´ 数据类型并将其路由到更大的 F´ 系统。典型组态下,解出的 com buffers 被送往命令调度器(command dispatcher),raw buffers 被送往文件上行组件(file uplink)。Uplink 由多个组件串联实现:
- Svc.FrameAccumulator:从驱动处累积字节,直到检测出一个完整帧;
- 实现 Svc.DeframerInterface 端口接口的组件:把帧解包为 F´ 数据类型。F´ 自带多种协议实现:
- Svc.FprimeDeframer:用于轻量级的 F´ 协议;
- Svc.Ccsds 包:包含 CCSDS TC 与 Space Packet 协议实现;
- 实现 Svc.RouterInterface 端口接口的路由组件:把解包后的 F´ 数据类型路由到其目的地(命令调度器、文件上行等)。F´ 自带 Svc.FprimeRouter 实现 F´ 数据类型的路由。
FrameAccumulator:从字节流中提取完整帧
Svc.FrameAccumulator 把输入的一串Fw::Buffer累积进一个环形缓冲区(Utils::CircularBuffer),并借助Svc::FrameDetector检测完整帧。每个新缓冲区到达后,组件会循环调用detect(),依据返回值处理:
NO_FRAME_DETECTED:环形缓冲区头部没有有效帧(如起始字不匹配),组件将环形缓冲区旋转一个字节后再次检测,直到缓冲区耗尽;FRAME_DETECTED:当前头部存在完整帧,组件分配新Fw::Buffer拷贝帧数据,连同最近一次dataIn调用的ComCfg::FrameContext一起从dataOut发出,随后旋转环形缓冲区移除已提取的数据;MORE_DATA_NEEDED:数据不足,组件释放输入Fw::Buffer并等待下一个缓冲区。
上行帧无需与缓冲区边界对齐,一个帧可以跨越一个或多个缓冲区。若检测器报告的帧尺寸超过内部累积缓冲容量,组件会记录告警事件并继续寻找新帧。值得注意的是,FrameDetector是一个 C++ 辅助类而非 FPP 组件,F´ 通信协议的标准实现是Svc::FrameDetectors::FprimeFrameDetector;自定义协议场景下可自行实现该类的detect()虚函数(见下文自定义成帧协议一节)。
Deframer:解帧并校验
Svc.FprimeDeframer 在dataIn端口收到 F´ 帧后,依据 F´ 协议帧规范 校验缓冲区是否为有效帧(缓冲区足够容纳头部与尾部、以 F´ 起始字开头、长度与帧头长度字段一致、CRC 与帧头+载荷计算值一致),然后通过修改缓冲区的长度与数据指针偏移来剥离头部与尾部,把载荷从dataOut发出。任一校验失败都会丢弃该帧,并通过dataReturnOut归还输入缓冲区所有权。该组件不支持单帧内拼接多个数据包。
Router:路由 F´ 数据包
Svc.FprimeRouter 接收Svc.ComDataWithContext类型的数据包,依据上下文中的 APID(由解帧栈根据包类型设置)做同步端口调用完成路由:FW_PACKET_COMMAND发往commandOut、FW_PACKET_FILE发往fileOut,未知包类型发往unknownDataOut,可连接项目自定义组件实现扩展路由。文件与未知包缓冲区不经拷贝直接透传,接收方处理完后必须通过fileBufferReturnIn归还,路由器才会把原始缓冲区还给解帧器;路由器内部用FprimeRouterCfg.BufferContextTableSize容量大小的表维护"缓冲区→上下文"关联,以在归还时恢复原始FrameContext(如vcId)。表满或查找失败时发出告警事件并以空上下文归还缓冲区。典型组态中,commandOut连接 Svc.CmdDispatcher,fileOut连接 Svc.FileUplink。
Downlink:下行链路组件
Downlink 接收 F´ 数据,用支持所需协议的字节将其包装成帧,然后交给驱动发送。Downlink 由一个实现Svc.FramerInterface端口接口的组件实现,F´ 自带两种协议的实现:
- Svc.FprimeFramer:实现 F´ 轻量协议;
- Svc.Ccsds 包:包含 CCSDS TM 与 Space Packet 协议实现。
以 Svc.FprimeFramer 为例,其内部处理流程为:收到数据包后分配一个尺寸为数据包大小 + F´ 帧头 + 帧尾的新outBuffer,序列化 F´ 起始字(0xDEADBEEF)与长度 token,再序列化数据包数据,计算并序列化 CRC32 校验和,最后把outBuffer从dataOut发出(所有权转交接收方),同时通过dataReturnOut把输入数据包所有权归还给原发送方(通常是 Svc.ComQueue)。其comStatusIn/comStatusOut端口遵循 Framer Status Protocol 透传Fw.SuccessCondition状态,包括启动时的初始SUCCESS、逐消息状态以及故障后的恢复SUCCESS。
添加自定义线协议(Wire Protocol)
如需引入自定义协议(如私有 CCSDS 变体、自定义遥测格式等),请遵循完整的 How-To 实现成帧协议指南。要点摘录如下:
- 现代 F´ 部署默认通过
Svc.ComCcsds子拓扑使用 CCSDS 协议,轻量级的 F´ 协议(Svc.ComFprime)是 F´ GDS 可理解的低开销替代方案;双向实现需要同时实现 framer(下行)与 deframer(上行),流式传输(TCP、UART)下通常还需一个可选的 FrameDetector 辅助类; - 创建组件时推荐使用被动组件并启用事件(
fprime-util new --component); - 在 FPP 中通过
import Svc.Framer与import Svc.Deframer让自定义组件实现对应接口,随后实现dataIn_handler、dataReturnIn_handler(以及 framer 的comStatusIn_handler)等 C++ 处理函数; - 需在 deframer 中提取并设置
FrameContext中的 APID,否则 Svc.FprimeRouter 无法确定包的路由去向; - 集成时删除
import Svc.ComCcsds、手动把子拓扑代码复制进主拓扑并替换framer/deframer实例;若出现缺失头文件或未声明的端口枚举等编译错误,需要清理<deployment>/Top/下自动生成的TopologyDefs.hpp并执行fprime-util generate --force,自定义 FrameDetector 还需在CMakeLists.txt中用DEPENDS声明依赖; - 若需要 F´ GDS 支持自定义协议,可实现继承
FramerDeframer的 GDS 插件(详见 GDS 插件开发指南 与 F´ GDS 成帧插件参考),并在虚拟环境中打包安装后通过fprime-gds --framing-selection MyCustomProtocol选择协议。
添加自定义驱动(Custom Driver)
要与本地面接口兼容,驱动必须实现 Drv/Interfaces 中定义的字节流驱动接口之一:Drv.ByteStreamDriver(同步发送)或Drv.AsyncByteStreamDriver(异步发送)。驱动可以根据需要增加任何其他端口、事件、遥测或其他 F´ 构造。同步接口定义的端口如下:
output port ready: Drv.ByteStreamReady output port $recv: Drv.ByteStreamData guarded input port $send: Drv.ByteStreamSend guarded input port recvReturnIn: Fw.BufferSend- ready(输出):驱动无参调用该端口,表示已就绪、可收发数据;
- recv(输出):驱动调用该端口并携带
Drv.ByteStreamStatus与Fw::Buffer,提供收到的数据; - send(输入):客户端调用该端口并传入
Fw::Buffer发送数据;状态同步返回,调用方保留缓冲区所有权; - recvReturnIn(输入):客户端通过该端口归还经
recv收到的缓冲区所有权。
异步接口则把同步的$send替换为async input port $send: Fw.BufferSend,并新增output port sendReturnOut: Drv.ByteStreamData,通过该端口返回发送状态与缓冲区所有权(实现见 AsyncByteStreamDriver.fpp)。
从源码结构看,仓库中的 Drv::TcpClient、Drv::TcpServer、Drv::Udp 与 Drv::PosixUartDriver 都是基于同步字节流模型的具体驱动实现,可作为编写自定义驱动(例如封装新型电台、CAN 或空间总线)时的参照范本;其底层套接字封装位于 Drv/Ip 目录(IpSocket、TcpClientSocket、TcpServerSocket、UdpSocket等)。
总结
F´ 地面接口通过"上行/下行 × 成帧/驱动"的清晰分层,把通信协议的适配成本降到最低:驱动层只需要满足Drv.ByteStreamDriver或Drv.AsyncByteStreamDriver的端口契约,成帧/解帧层则通过Svc.Framer/Svc.Deframer接口与Svc.FrameAccumulator、Svc.FprimeRouter等标准组件即插即用。对大多数项目而言,无需整体替换地面接口——只需根据本文所述端口契约新增一个自定义驱动、或按照 自定义成帧协议指南 实现协议组件,即可把 F´ 飞行软件接入目标地面系统。
【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考