ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

F´ 自定义成帧协议实现指南:从 Framer/Deframer 组件到 GDS 插件的完整集成

2026/9/15 22:21:20 拓冰建站 浏览量
F´ 自定义成帧协议实现指南:从 Framer/Deframer 组件到 GDS 插件的完整集成 F´ 自定义成帧协议实现指南从 Framer/Deframer 组件到 GDS 插件的完整集成【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime本指南基于 F´F Prime飞行软件框架的官方 How-To 文档系统讲解如何在 F´ 中实现一套自定义通信成帧Framing协议包括飞行软件侧的 Framer、Deframer 组件与可选的 FrameDetector 助手类的设计与实现以及如何将自定义协议以插件形式接入 F´ GDS。读完本文后你将掌握从零搭建一套完整双向成帧协议栈下行成帧 上行解帧 流式数据帧边界检测并在 GDS 侧对接的方法以及集成过程中的常见编译错误排查手段。为什么需要自定义成帧协议现代 F´ 部署默认使用 Svc.ComCcsds 子拓扑CCSDS 协议栈而轻量级的 F Prime Protocol通过Svc.ComFprime提供则是 F´ GDS 原生理解的另一种低开销通信协议。然而部分任务对帧格式有特殊要求——例如使用自定义的起始字Start Word、自定义头部字段、特定校验算法或为了兼容既有地面站而必须实现特定帧结构。此时就需要在 F´ 中实现自己的成帧协议下文统称MyCustomProtocol。一个成帧协议定义的是数据在传输时如何被包裹成帧Framing、接收时如何被校验并解包Deframing。对于双向链路上行 Uplink 下行 Downlink完整实现包含两类飞行软件组件和一个可选的助手类构件类型职责链路方向FramerF´ 组件将载荷数据包裹成帧发送下行飞行软件 → GDSDeframerF´ 组件解析收到的帧、校验并提取载荷上行GDS → 飞行软件FrameDetectorC 助手类非组件在字节流中定位帧的起止边界上行流式传输时如果只是对接已有地面站而无需使用 F´ GDS可以跳过本文的 GDS 部分GDS 集成仅在你希望让 F´ GDS 理解你的新协议时才有必要。[!NOTE] 参考实现Decaf Protocol位于fprime-examples仓库的FlightExamples/CustomFraming与GdsExamples/gds-plugins/src/framing目录。使用参考示例时建议将fprime-examples检出到与当前 F´ 核心安装相同的 release tag可用fprime-util version-check查看当前 F´ 版本。下图展示了 F´ 自带 Framer 组件Svc.FprimeFramer在下行拓扑中的典型连接关系可作为自定义 Framer 连接设计的参考上行方向则是通信组件 → FrameAccumulator → Deframer → Router → 命令分发/文件链路的链路见下图前置知识理解 F´ 子拓扑Subtopology在动手实现自定义成帧协议之前必须先熟悉 F´ 的子拓扑机制。现代 F´ 部署默认携带Svc.ComCcsds子拓扑它提供了一整套标准通信栈含成帧/解帧组件。实现自定义成帧协议通常需要三步理解现有通信子拓扑的结构研读Svc.ComCcsds是如何组织实例与连接的见 ComCcsds.fpp手动引入组件将子拓扑中相关的拓扑代码复制进主拓扑以便按需修改组件连接并删除原来的import Svc.ComCcsds语句替换标准组件将默认的 framer/deframer 实例替换为你的自定义实现。关于子拓扑的完整说明参见 Subtopologies Guide。这是进行自定义成帧实现前必须理解的基础。飞行软件侧实现整体设计三个构件实现自定义成帧协议需要实现Framer一个 F´ 组件负责把载荷数据包装成帧发送Deframer一个 F´ 组件负责拆解收到的帧、提取载荷并校验FrameDetector 助手类可选一个 C 助手类不是F´ 组件当传输层不保证完整报文边界时如 TCP、UART用于在数据流中检测帧的起止。[!TIP] 使用fprime-util new --component创建 framer/deframer 组件时推荐选择被动组件passive component并启用事件events这与Svc.FprimeFramer、Svc.FprimeDeframer的模式一致。以下以假设的MyCustomProtocol协议为例逐步展开。仓库内可直接参照的实现包括 Svc.FprimeFramer、Svc.FprimeDeframer以及fprime-examples仓库的 CustomFraming 示例。实现前的最佳实践与陷阱集中定义协议常量将起始字、头部结构、字段大小等协议常量集中定义在一个专用的.fpp类型文件如Types/Types.fpp中。这样起始字、字段类型、航天器 ID 等常量可以在 framer、deframer 和 frame detector 之间复用避免三处各自硬编码导致不一致。参照 Svc/FprimeProtocol/FprimeProtocol.fpp其中FrameHeader含默认值startWord 0xdeadbeef与FrameTrailer就是这种集中定义。端序约定F´ 默认对所有整型采用**大端序网络字节序**序列化这是Fw::Serializable的定义行为。如果协议要求小端字段必须在序列化时显式指定Fw::Endianness::LITTLE模式或手动调整字节顺序。载荷的序列化/反序列化针对Fw::SerialBufferBase接口进行因此自定义的可序列化类型可以通过serializeTo/deserializeFrom配合兼容缓冲区工作。相关接口可参见 Buffer.hpp 与 Buffer 文档。Deframer 必须设置 APIDDeframer必须在通过dataOut将报文向下游发送之前从帧中提取并设置FrameContext中的 APIDApplication ID。否则 Svc.FprimeRouter 将不知道把报文路由到何处——路由正是由context.get_apid()驱动的。除非你同时实现自定义路由器并自行定义路由规则不推荐且超出本指南范围否则这一步是硬性要求。载荷 vs. 帧结构成帧协议只应定义外层帧结构头部与尾部。内部载荷结构由产生和消费数据的上层协议与应用决定而不是由成帧协议本身决定。第一步定义 Framer 与 Deframer 的 FPP 组件Framer 与 Deframer 组件应通过import方式实现 FPP 接口Svc.Framer 与 Svc.Deframer。Svc.Framer接口定义了以下端口dataIn接收待成帧数据Svc.ComDataWithContext即Fw::BufferComCfg::FrameContextdataOut输出成帧后的数据dataReturnOut成帧完成后将原始Fw::Buffer所有权归还给发送方dataReturnIn接收来自通信驱动解分配回调的缓冲区comStatusIn/comStatusOut接收/转发下游组件的就绪状态Fw.SuccessCondition。Svc.Deframer接口则定义dataIn接收帧数据、dataOut输出解帧后数据、dataReturnOut/dataReturnIn缓冲区所有权归还。完整端口定义见 Framer.fpp 与 Deframer.fpp。在MyCustomFramer.fpp中passive component MyCustomFramer { import Svc.Framer [...] }在MyCustomDeframer.fpp中 Deframer implementation for MyCustomProtocol passive component MyCustomDeframer { import Svc.Deframer [...] }第二步实现 Framer C 组件实现必需的 handler 函数。以仓库中 FprimeFramer.cpp 为参考其dataIn_handlerL26-L74的完整流程是计算帧大小 头大小 数据大小 尾大小→ 通过bufferAllocate_out向 BufferManager 申请帧缓冲区 → 序列化头部设置长度字段→ 序列化载荷数据 → 计算 CRC 并序列化尾部 → 通过dataOut_out发出完整帧 → 通过dataReturnOut_out归还原始数据缓冲区。自定义实现的骨架如下// ...existing code... void MyCustomFramer ::dataIn_handler(FwIndexType portNum, Fw::Buffer data, const ComCfg::FrameContext context) { // TODO: Implement framing logic // 1. Compute total frame size header data trailer // 2. Allocate a frame buffer (e.g. bufferAllocate_out), check validity // 3. Serialize header, payload, and trailer (with checksum) // 4. Emit the frame via dataOut_out // 5. Return the original data buffer via dataReturnOut_out } void MyCustomFramer ::comStatusIn_handler(FwIndexType portNum, Fw::Success condition) { // Pass comStatus through per the Framer Status Protocol. This includes the initial // start-up SUCCESS, per-message statuses, and any recovery SUCCESS after a FAILURE. // See: docs/reference/communication-adapter-interface.md#framer-status-protocol this-comStatusOut_out(portNum, condition); } void MyCustomFramer ::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer data, const ComCfg::FrameContext context) { // TODO: handle return of data ownership // For example, if component required to allocate from a buffer manager, return the buffer to the manager this-bufferDeallocate_out(0, data); } // ...existing code...关于comStatusIn_handler中透传状态的行为FprimeFramer.cpp 的实现是先检查comStatusOut输出端口是否已连接再原样转发。Framer Status Protocol的详细规则初始 SUCCESS、每条消息对应一次状态、FAILURE 后恢复等参见 Communication Adapter Interface 中的协议章节。第三步实现 Deframer C 组件同样实现必需的 handler。以 FprimeDeframer.cpp 为参考其dataIn_handlerL28-L111的完整校验流程值得仔细借鉴最小长度检查缓冲区至少容纳头 尾否则记录InvalidBufferReceived事件并丢弃起始字校验反序列化头部与默认startWord比对不匹配则记录InvalidStartWord并丢弃长度一致性校验期望帧大小 头 长度字段 尾若与缓冲区实际大小不符则记录InvalidLengthReceived并丢弃APID 提取从载荷开头的FwPacketDescriptorType字段读取包描述符若为合法 APID 则写入contextCopy.set_apid(...)这正是Deframer 必须设置 APID这一最佳实践的具体实现尾部校验移动到尾部偏移处反序列化 trailer计算头部载荷的哈希/CRC 并与传输值比对不匹配则记录InvalidChecksum并丢弃提取载荷data.advance(头部大小)跳过帧头、data.setSize(减去尾部大小)去除帧尾然后通过dataOut_out发出解帧后的数据。骨架代码// ...existing code... void MyCustomDeframer ::dataIn_handler(FwIndexType portNum, Fw::Buffer data, const ComCfg::FrameContext context) { // TODO: Implement deframing logic // 1. Validate minimum size (header trailer) // 2. Deserialize and validate frame header (start word, length field) // 3. Extract and set the APID into the FrameContext // 4. Validate the trailer (CRC/checksum) // 5. Advance past header, shrink trailer, emit payload via dataOut_out } void MyCustomDeframer ::dataReturnIn_handler(FwIndexType portNum, Fw::Buffer data, const ComCfg::FrameContext context) { // TODO: handle return of data ownership } // ...existing code...第四步将组件集成到拓扑实现 framer、deframer 以及可选的frame detector 之后需要把它们集成到主拓扑。这通常包括移除现有通信子拓扑的 import 语句如import Svc.ComCcsds手动加入子拓扑中的拓扑代码。可直接参考 ComFprime.fpp将其中大部分代码复制粘贴到自己的拓扑中并按需调整 C phase 代码更新实例将framer和deframer实例替换为自定义实现。Top/Topology.fpp中的示例片段// Remove or comment out the existing subtopology import // import Svc.ComCcsds // Manually add topology code from the subtopology as needed // ... // Replace instances of custom framer and deframer instance framer: MyCustomFramer base id 0x1000 instance deframer: MyCustomDeframer base id 0x2000在 ComFprime.fpp 中可以看到标准子拓扑的完整结构comQueue活动组件按 EVENTS/TELEMETRY/FILE 配置队列深度与优先级、frameAccumulator配置了Svc::FrameDetectors::FprimeFrameDetector、commsBufferManager配置 bins 缓冲区池、deframer、framer、fprimeRouter、comStub。其connections DownlinkL122-L132与connections UplinkL134-L145展示了 framer/deframer 与周边组件的标准连接方式——下行comQueue.dataOut - framer.dataIn、framer.dataReturnOut - comQueue.dataReturnIn、framer.bufferAllocate - commsBufferManager.bufferGetCallee、framer.comStatusOut - comQueue.comStatusIn上行frameAccumulator.dataOut - deframer.dataIn、deframer.dataOut - fprimeRouter.dataIn等。常见集成问题排查移除 CCSDS 子拓扑并加入自定义成帧组件后可能会遇到编译错误缺少头文件如fatal error: Svc/Subtopologies/ComCcsds/ComCcsdsConfig/FppConstantsAc.hpp: No such file or directory从自动生成的DeploymentNameTopologyDefs.hpp文件中删除所有ComCcsds相关内容该文件位于deployment/Top/目录拓扑发生重大变更后可能需要用fprime-util generate --force强制重新生成。未声明的端口枚举如error: Ports_ComPacketQueue has not been declared在DeploymentNameTopologyDefs.hpp中包含生成的端口头文件#include MyDeployment/Top/Ports_ComPacketQueueEnumAc.hpp #include MyDeployment/Top/Ports_ComBufferQueueEnumAc.hpp链接错误如undefined reference to vtable for CustomFraming::MyCustomFrameDetector确保自定义 Detector 通过DEPENDS关键字被添加为 Deframer 或部署的依赖。在 Deframer 的CMakeLists.txt中添加register_fprime_module( [...] DEPENDS CustomFraming_DecafFrameDetector # This is a helper module and cannot be resolved by FPP dependencies alone )第五步可选实现 Frame Detector何时不需要如果通信管理器组件总能收到完整帧就无需帧检测。典型场景内置帧同步的电台Radios with built-in frame synchronization保证消息边界的面向消息传输层协议如 UDP。何时需要如果数据传输是流式的、不保留消息边界则必须实现帧定界机制。典型场景TCP 连接流式无固有消息边界UART/串口连接如 XBee 等 UART 电台。F´ 通过 Svc.FrameAccumulator 组件提供此能力它使用环形缓冲区Utils::CircularBuffer和FrameDetector助手类来识别数据流中的完整帧。[!NOTE]FrameDetector是一个 C 助手类而非 FPP 组件没有.fpp定义由Svc.FrameAccumulator内部使用。其接口定义见 FrameDetector.hpp。FrameDetector::detect()返回三种状态FrameDetector.hpp状态含义对size_out的要求FRAME_DETECTED当前环形缓冲区偏移处存在一帧必须设为该帧大小NO_FRAME_DETECTED当前偏移处不可能有帧如起始字不匹配忽略MORE_DATA_NEEDED可能有帧但数据不足需更多数据必须设为所需数据总量使用Svc.FrameAccumulator需要配置一个自定义 FrameDetectorMyCustomFrameDetector.hpp#include Svc/FrameAccumulator/FrameDetector.hpp class MyCustomFrameDetector : public Svc::FrameDetector { public: Svc::FrameDetector::Status detect(const Types::CircularBuffer data, FwSizeType size_out) const override; };MyCustomFrameDetector.cpp// ...existing code... Svc::FrameDetector::Status MyCustomFrameDetector::detect(const Types::CircularBuffer data, FwSizeType size_out) const { // TODO: Implement frame boundary detection // This can include searching for start words, validating headers, checking lengths, checking CRC and hashes, etc. // Utilities exist for CRC under Utils/Hash, and examples are shown in Svc/Ccsds/Utils or in fprime-examples repo // Refer to the Svc.FrameDetector documentation for details on how to implement this return Svc::FrameDetector::NO_FRAME_DETECTED; } // ...existing code...仓库内有两个可深度参考的现成实现FprimeFrameDetector.cpp先检查头部尾部最小长度不足返回MORE_DATA_NEEDED反序列化并校验起始字0xDEADBEEF依据长度字段计算期望帧大小同时做了防溢出保护再校验尾部 CRCUtils::Hash哈希并比对asBigEndianU32全部通过才返回FRAME_DETECTEDCcsdsTcFrameDetector.cpp校验flagsAndScId令牌、按 TC 协议的帧长 字节数 - 1规则还原长度、用CRC16校验尾部 FECF 字段。随后在 Topology 的 C 代码中配置Svc.FrameAccumulator使用你的自定义 detectorTop/Topology.cpp#include path/to/MyCustomFrameDetector.hpp // ...existing code... MyCustomFrameDetector frameDetector; // ...existing code... frameAccumulator.configure(frameDetector, 1, mallocator, 2048);configure的签名见 FrameAccumulator 文档为configure(FrameDetector detector, FwEnumStoreType allocationId, Fw::MemAllocator allocator, FwSizeType store_size)其中store_size是内部环形缓冲区的容量——该容量必须能容纳你的协议可能出现的最大帧。在 ComFprime.fpp 中可看到官方做法先在configObjectsphase 声明 detector 对象再在configComponentsphase 调用configure(...)最后在tearDownComponentsphase 调用cleanup()释放环形缓冲区内存。FrameAccumulator的processRing循环逻辑FrameAccumulator.cpp也值得理解每收到新缓冲区就进入循环调用detect()NO_FRAME_DETECTED时环形缓冲区向前旋转 1 字节重新检测FRAME_DETECTED时通过bufferAllocate_out申请缓冲区、peek拷贝帧数据、rotate消费数据然后经dataOut_out发出若报告的size_out超过环形缓冲区容量则记录FrameDetectionSizeError事件并丢弃该字节无可用缓冲区时记录NoBufferAvailable事件。F´ GDS 集成要让 F´ GDS 支持自定义协议需要实现一个GDS framing 插件。GDS 插件系统允许用户用自定义代码扩展 GDS 行为对于新的成帧协议需要实现一个继承自FramerDeframer的插件。完整说明参见 How-To Develop a GDS Plugin Guide 与 F Prime GDS Framing Plugin reference。例如在 Python 中from fprime_gds.common.communication.framing import FramerDeframer from fprime_gds.plugin.definitions import gds_plugin gds_plugin(FramerDeframer) class MyCustomFramerDeframer(FramerDeframer): GDS plugin for MyCustomProtocol framing def frame(self, data): # TODO: Implement framing logic return frame def deframe(self, data, no_copyFalse): # TODO: Implement deframing logic return packet, leftover_data, discarded_data def get_name(self): # TODO: Return the protocol name for selection with fprime-gds --framing-selection selection return MyCustomProtocol关键点FramerDeframer的抽象方法为frame(data) - bytes和deframe(data, no_copyFalse) - Tuple[bytes, bytes, bytes]返回(提取的报文, 剩余待解析字节, 丢弃的数据)见 framing.mdgds_plugin(FramerDeframer)装饰器会校验基类合法性、自动生成注册函数、确保所有虚函数已实现详见 develop-gds-plugins.mdframing 是SELECTION 类型插件同一时刻只运行一个由用户通过--framing-selection选择插件运行在 GDS 通信线程内实现不佳或阻塞的实现会拖慢所有通信插件只负责在协议帧与 F´ 数据单元之间做转换不解码 F´ 数据内容。将插件打包并安装到虚拟环境后GDS 即可加载它然后运行fprime-gds --framing-selection MyCustomProtocol小结与参考自定义成帧协议的完整实现路径可以总结为用 FPP 定义实现Svc.Framer/Svc.Deframer接口的组件 → 编写 C handler 实现成帧/解帧逻辑含 APID 提取与校验→ 按需实现FrameDetector并配置FrameAccumulator→ 在拓扑中替换默认子拓扑并解决集成编译问题 → 在 GDS 侧实现 framing 插件对接。整个过程以 Svc.FprimeFramer、Svc.FprimeDeframer 和 Svc.FrameAccumulator 为仓库内的最佳参照实现。进一步阅读F Prime Protocol 协议格式0xDEADBEEF起始字 长度字段 载荷 CRC 的极简帧格式参考Communication Adapter Interface通信适配器接口、ComQueue 协议与 Framer 状态协议的完整规则Framing Plugin 参考GDS framing 插件接口细节Subtopologies Guide子拓扑的设计与使用仓库源码参考Svc/Subtopologies/ComFprime/ComFprime.fpp、Svc/Interfaces/Framer.fpp、Svc/Interfaces/Deframer.fpp、Svc/FrameAccumulator/FrameDetector.hpp。【免费下载链接】fprimeF´ - A flight software and embedded systems framework项目地址: https://gitcode.com/GitHub_Trending/fpr/fprime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考