- 嵌入式
- 系统编程
【免费下载链接】fprime
F´ - A flight software and embedded systems framework
导读:
Fw::Time是 F´(F Prime)飞行软件与嵌入式系统框架中用于在系统内传递时间戳(time stamp)的标准端口,遥测(telemetry)、事件(events)以及其他需要时间信息的组件都依赖它。本文将围绕 Fw/Time/docs/sdd.md 的设计文档,结合仓库内端口定义、Fw::Time数据类型实现、配置项与单元测试,完整解析该端口的作用、Fw::Time类的内部结构与运算语义,以及它如何通过序列化在组件间流转。读完本文,你将掌握Fw::Time端口的定义方式、时间基准(Time Base)与时间上下文(Time Context)的配置含义、时间戳序列化格式,以及时间比较/加减运算的边界行为,能够在自己的 F´ 组件中正确使用或扩展这一机制。
1. 端口定位与核心职责
根据 Fw/Time/docs/sdd.md 的说明:
The
Fw::Timeport is used to pass time stamps around the system, either for telemetry and events or other uses.
Fw::Time端口用于在系统各处传递时间戳——无论是遥测数据、事件(EVENT)报文,还是其他需要时间信息的用途。它是 F´ 中时间信息流通的标准管道:负责提供时间的组件(如Svc::Time)通过该端口对外输出当前时间,遥测/事件采集链路则通过该端口获取时间戳并打入下行报文。
1.1 FPP 端口定义
端口在 FPP 语言中的定义位于 Fw/Time/Time.fpp:
module Fw { type Time @ Time port port Time( ref $time: Fw.Time @< The U32 cmd argument ) }关键点:
- 端口名与数据类型同名,均为
Time,归属Fw模块; - 端口通过引用(
ref)传递一个Fw.Time类型的参数$time,即调用方传入一个Fw::Time对象引用,由被调用方填充/读取; - 该端口没有返回值(无
return子句),时间值完全依靠引用参数带出,这与后续Svc::Time组件中timeGetPort的同步调用模式一致。
2. 端口设计上下文(Design)
2.1 端口图
设计文档给出的端口框图如上图所示(图片源文件为 Fw/Time/docs/img/TimeBDD.jpg),其含义可以解读为:
- 左侧
<<PortType>> Time(命名空间Fw)描述了端口类型Time,其关联操作return() : Time表明该端口最终返回一个Fw::Time类型的值; - 右侧
<<DataType>> Time是端口使用的数据类型,明确标注其来源头文件为Fw/Time/Time.hpp、命名空间为Fw,与左侧端口引用的Time类型完全对应。
2.2 端口使用的可序列化类型:Fw::Time
设计文档指出:
Fw::Timeis a class that stores the time base (project specific), seconds and microseconds of the time stamp.
即Fw::Time是一个类,存储了时间戳的时间基准(time base,项目相关)、秒与微秒三部分信息。在仓库实现中,Fw/Time/Time.hpp 进一步表明它继承自Fw::Serializable,是一个标准的可序列化对象,内部还额外维护了一个时间上下文(time context)字段,用于记录如重启计数、节点编号等用户自定义信息。
3. Fw::Time 数据类型源码级剖析
3.1 内部成员与内存布局
Fw/Time/Time.hpp 定义了其序列化后的尺寸常量:
enum { SERIALIZED_SIZE = sizeof(FwTimeBaseStoreType) + sizeof(FwTimeContextStoreType) + sizeof(U32) + sizeof(U32) };四个成员分别为(见 Fw/Time/Time.hpp):
| 成员 | 类型 | 含义 |
|---|---|---|
m_seconds | U32 | 秒部分 |
m_useconds | U32 | 微秒部分 |
m_timeBase | TimeBase(枚举) | 时间基准,由系统/项目定义 |
m_timeContext | FwTimeContextStoreType(U8) | 用户可设值,如重启计数、节点号等 |
在默认配置(FW_USE_TIME_BASE = 1、FW_USE_TIME_CONTEXT = 1)下,时间戳序列化占用2 + 1 + 4 + 4 = 11字节;若在 config/FpConfig.hpp 中关闭时间基准或时间上下文特性,序列化体积可相应缩减。
3.2 时间基准与存储类型定义(config/FpConfig.hpp)
时间基准枚举与配套存储类型定义在 config/FpConfig.hpp:
enum TimeBase { TB_NONE, //!< No time base has been established TB_PROC_TIME, //!< Indicates time is processor cycle time. Not tied to external time TB_WORKSTATION_TIME, //!< Time as reported on workstation where software is running. For testing. TB_DONT_CARE = 0xFFFF //!< Don't care value for sequences. If FwTimeBaseStoreType is changed, value should be changed }; #define FW_CONTEXT_DONT_CARE 0xFF //!< Don't care value for time contexts in sequences typedef U16 FwTimeBaseStoreType; typedef U8 FwTimeContextStoreType;需要理解的关键语义:
TB_NONE:尚未建立时间基准,Fw::Time的默认构造状态即为此值(Time()构造时将m_timeBase置为TB_NONE);TB_PROC_TIME:处理器周期时间,不绑定外部时钟,适合嵌入式环境下的相对计时;TB_WORKSTATION_TIME:软件运行所在工作站的系统时间,主要用于主机/桌面测试场景;TB_DONT_CARE与FW_CONTEXT_DONT_CARE:序列(sequence)等场景中表示"不关心"的占位值,用于指令序列文件中的时间匹配;FwTimeBaseStoreType(U16)与FwTimeContextStoreType(U8)是时间基准与上下文的存储类型,保证其在序列化与跨平台传输时尺寸确定。
此外 config/FpConfig.hpp 提供了两个特性开关,直接影响Fw::Time的序列化行为与比较逻辑:
#ifndef FW_USE_TIME_BASE #define FW_USE_TIME_BASE 1 //!< Whether or not to use the time base #endif #ifndef FW_USE_TIME_CONTEXT #define FW_USE_TIME_CONTEXT 1 //!< Whether or not to serialize the time context #endif从 Fw/Time/Time.cpp 的实现可以看到:当FW_USE_TIME_BASE关闭时,反序列化会将m_timeBase强制置为TB_NONE;当FW_USE_TIME_CONTEXT关闭时,上下文被强制置 0——这说明这两个特性开关既控制序列化字节流,也决定了对端解析后的默认值。
3.3 构造与赋值 API
Fw/Time/Time.hpp 提供了多组构造与设置方法,覆盖从"仅秒/微秒"到"基准+上下文+秒/微秒"的完整组合:
Time(); // 默认构造:0 秒、0 微秒、TB_NONE、上下文 0 Time(U32 seconds, U32 useconds); // 仅秒与微秒(基准为 TB_NONE) Time(TimeBase timeBase, U32 seconds, U32 useconds); Time(TimeBase timeBase, FwTimeContextStoreType context, U32 seconds, U32 useconds); void set(U32 seconds, U32 useconds); // 覆盖设置,保留原基准与上下文 void set(TimeBase timeBase, U32 seconds, U32 useconds); void set(TimeBase timeBase, FwTimeContextStoreType context, U32 seconds, U32 useconds);配套的读取接口为getSeconds()、getUSeconds()、getTimeBase()、getContext()。set(U32, U32)这类"只传秒/微秒"的重载会保留对象已有的基准与上下文,而不是重置为默认值,这一点在复用时间对象时应特别注意。
3.4 序列化与反序列化
Fw::Time作为Fw::Serializable的派生类,实现了serialize/deserialize(见 Fw/Time/Time.cpp),其字节流顺序为:
- (可选,
FW_USE_TIME_BASE)时间基准FwTimeBaseStoreType; - (可选,
FW_USE_TIME_CONTEXT)时间上下文FwTimeContextStoreType; - 秒
U32; - 微秒
U32。
每一步都检查SerializeStatus,任一环节失败即立即返回错误状态。正是因为实现了这套标准序列化接口,Fw::Time才能被嵌入遥测通道(Tlm)、事件(Log)等报文缓冲区,随下行数据一起传回地面。
3.5 比较运算:基准不同则不可比
Fw::Time实现了==、!=、>、<、>=、<=全部比较运算符,底层统一委托给静态方法Time::compare(Fw/Time/Time.cpp):
- 比较结果类型为
Time::Comparison枚举:LT = -1、EQ = 0、GT = 1、INCOMPARABLE = 2; - 在
FW_USE_TIME_BASE开启时,若两个时间基准不同,返回INCOMPARABLE; - 在
FW_USE_TIME_CONTEXT开启时,若上下文不同,同样返回INCOMPARABLE; - 仅当基准与上下文一致时,才按"先比秒、后比微秒"的字典序给出
LT/EQ/GT。
因此,在业务代码中跨基准(例如把TB_PROC_TIME与TB_WORKSTATION_TIME的时间戳直接比较)会得到"不可比"结论,这是设计上防止语义错乱时间比较的刻意约束。
3.6 时间算术:add / sub / add(seconds, useconds)
Fw/Time/Time.cpp 提供了三种时间运算:
static Time add(const Time& a, const Time& b):两个时间相加,要求基准与上下文相同(否则触发FW_ASSERT断言);微秒部分相加后若>= 1000000则向秒进位(并断言结果微秒uSeconds < 1999999,防止溢出);static Time sub(const Time& minuend, const Time& subtrahend):被减数减减数,断言被减数不小于减数;若微秒不够减,则从秒借 1(等价于微秒 +1000000 再减),正确实现跨秒借位;void add(U32 seconds, U32 useconds):就地增加秒与微秒,同样处理微秒进位,并断言m_useconds < 1999999。
这些运算在测量任务执行耗时、计算时间差等场景中非常实用,其借位/进位逻辑已被单元测试覆盖(见第 5 节)。
3.7 零时间与全局常量
- 静态工厂方法
static Time zero(TimeBase timeBase = TB_NONE)返回"基准可指定、秒/微秒/上下文全为 0"的时间对象; - 全局常量
extern const Time ZERO_TIME;(定义于 Fw/Time/Time.cpp)是默认构造的零时间,Fw::ZERO_TIME可直接用于"时间是否已初始化"的判断——例如 Fw/Time/test/ut/TimeTest.cpp 中验证了非默认构造的时间与ZERO_TIME不相等、默认构造的时间与ZERO_TIME相等。
4. 在系统中的应用:Svc::Time 与遥测/事件链路
Fw::Time端口在实际部署中由时间服务组件对外提供。仓库中的 Svc/Time/Time.fpp 给出了标准用法:
passive component Time { ... sync input port timeGetPort: Fw.Time ... }Svc::Time组件暴露名为timeGetPort的同步输入端口,端口类型正是Fw.Time。外部组件(如遥测通道Svc::TlmChan、事件记录器Svc::ActiveLogger、命令调度器等)在生成遥测或事件报文时,通过该端口同步获取当前时间戳并填入报文。整套链路可以概括为:
- 时间源(
Svc::Time及其平台实现,如Svc::LinuxTime)维护并持续更新时间; - 需要时间戳的组件通过
Fw::Time端口同步调用timeGetPort,传入Fw::Time引用参数; - 时间服务填充引用对象的秒/微秒/基准/上下文后返回;
- 调用方将
Fw::Time序列化进 Tlm/Log 报文缓冲区,随遥测或事件下行到地面系统。
这一"端口传递引用 + 对象可序列化"的组合,正是Fw::Time在整个框架中通用性强的根本原因——它不绑定任何特定组件,任何模块都可以声明并使用该端口获取或转发时间戳。
5. 单元测试验证
仓库的单元测试位于 Fw/Time/test/ut/TimeTest.cpp,覆盖了该数据类型的全部核心行为:
| 测试用例 | 验证内容 |
|---|---|
InstantiateTest | 构造Time(TB_NONE, 1, 2)后,基准、上下文、秒、微秒字段取值正确 |
MathTest | 相等/不等/大于/小于/大于等于比较;add求和(1000s1000us + 4000s2000us = 5000s3000us);常规减法;微秒借位减法(2s1us - 1s999999us = 0s2us) |
CopyTest | 拷贝构造、赋值运算符、set方法均能完整复制四个字段 |
ZeroTimeEquality | 非零时间与Fw::ZERO_TIME不等;默认构造时间与Fw::ZERO_TIME相等 |
其中MathTest对微秒进位(加法)与借位(减法)的断言,正是对 Fw/Time/Time.cpp 中进位/借位分支的直接验证,可作为理解Fw::Time时间运算语义的最佳示例。
6. 变更记录
设计文档末尾给出了本模块的变更历史(见 Fw/Time/docs/sdd.md):
| Date | Description |
|---|---|
| 6/19/2015 | Initial Version |
小结与实战要点
- 端口形态:
Fw::Time是无返回值、以ref引用方式传递Fw.Time参数的端口,FPP 定义见 Fw/Time/Time.fpp; - 数据类型:
Fw::Time存储"时间基准 + 时间上下文 + 秒 + 微秒"四个字段,序列化尺寸与字段取舍受 config/FpConfig.hpp 中的TimeBase枚举、FW_USE_TIME_BASE、FW_USE_TIME_CONTEXT控制; - 运算语义:比较运算要求基准与上下文一致,否则返回
INCOMPARABLE;add/sub内置微秒进位/借位逻辑,均以断言保护; - 接入方式:参照 Svc/Time/Time.fpp 声明
sync input port timeGetPort: Fw.Time,即可在自己的组件中接收系统时间戳; - 验证手段:阅读并运行 Fw/Time/test/ut/TimeTest.cpp 可快速理解构造、比较、算术与零时间语义。
如需进一步了解时间在遥测与事件报文中的落地方式,可继续阅读 Fw/Tlm 与 Fw/Log 目录下的相关实现与文档。
- 嵌入式
- 系统编程
【免费下载链接】fprime
F´ - A flight software and embedded systems framework
相关推荐
F´ 框架中的 Fw::Time 端口:时间戳传递、Fw::Time 数据类型与序列化机制详解
F´ 框架中的 Fw::Time 端口:时间戳传递、Fw::Time 数据类型与序列化机制详解 导读 Fw::Time 端口是 NASA F´(F Prime)
嵌入式系统编程F´ (F Prime) 事件日志端口深度解析:Fw::Log 与 Fw::LogText 端口的设计、序列化与使用指南
F´ F Prime 事件日志端口深度解析:Fw::Log 与 Fw::LogText 端口的设计、序列化与使用指南 导读 本文聚焦 F´(F Prime)飞行
嵌入式系统编程F´ 框架中的 Fw::Time 与 Fw::TimeInterval:时间戳与时间间隔端口的深度解析
F´ 框架中的 Fw::Time 与 Fw::TimeInterval:时间戳与时间间隔端口的深度解析 本文围绕 F´(F Prime)飞行软件与嵌入式系统框架
嵌入式系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考