飞控日志系统记录设计方案
type
status
date
slug
summary
tags
category
icon
password
1 概述2 需求分析2.1 当前日志功能2.2 新版本日志功能3 功能性能指标3.1 功能指标3.2 性能指标4 方案设计4.1 软件设计方案4.1.1 总体设计方案4.1.1.1 系统初始化4.1.1.2 数据采集与uORB主题订阅4.1.1.3 数据存储到ULog文件4.1.1.4 扩展日志功能(暂不添加)4.1.1.5 文件管理与系统退出4.1.2 软件流程设计4.1.2.1 系统初始化4.1.2.2 数据采集与uORB主题订阅4.1.2.3 数据存储到ULog文件4.1.2.4 扩展日志功能(暂不添加)4.1.2.5 文件管理与系统退出4.2 技术关键点设计4.2.1 任务调度处理4.2.2 ULog 数据类型4.2.3 uORB 编译工作流4.2.4 ULog 日志生成4.2.5 ULog 日志结构4.2.5 ULog 日志解析4.2.4 可用的第三方解析器5 功能性能指标符合性分析6 附录1:ULog日志记录数据类型表(待补充)
1 概述
该文档为旋翼代码工程日志记录功能开发设计方案文档,对旋翼日志记录功能的功能方案进行详细设计。
2 需求分析
2.1 当前日志功能
在现有的旋翼日志系统中,日志记录是直接写入CSV文件的。具体流程是:系统启动后,日志模块会开启一个独立的线程,在线程内执行下面的内容:
日志模块会首先检查是否有足够的存储空间,并在SD卡上创建一个新的日志目录和文件。日志模块通过直接写入CSV文件来记录飞控系统中的各种数据,包括飞行状态(如:
vehicle_status
)、电池状态(如:battery_status
)、GPS位置等信息。数据记录使用特定的函数(如:log_write_csv
)将数据格式化为CSV格式,并以设定的频率写入文件。这种日志方法的一个主要问题在于频繁的文件操作,例如每次写入时需要打开和关闭文件,这可能会影响性能。另外,由于日志是通过文件系统操作来实现的,在极端情况下(例如存储设备已满或损坏),数据可能会丢失。更重要的是,这种日志记录方式只能在飞行结束后通过文件读取,缺乏实时性。
2.2 新版本日志功能
为了改善当前的日志系统,计划引入基于ULog的日志记录机制,以替代直接的CSV文件写入。ULog是一种二进制日志格式,设计用于高效记录飞行数据并支持丰富的后期分析。
新的日志系统需要提高数据记录的实时性,通过uORB机制实现实时数据发布,使得其他模块可以在飞行过程中即时订阅并获取系统状态信息。这种数据发布方式不仅消除了等待飞行结束后读取文件的需求,同时也支持系统在飞行过程中基于日志数据进行快速响应,从而增强了系统的灵活性和实时处理能力。
3 功能性能指标
通过借鉴PX4 Autopilot的日志系统以及ulog_cpp开源日志库的设计,下述功能和性能指标表格为基于ULog的日志记录系统提供了明确的开发方向和衡量标准,实现了高效和实时的日志记录功能。
3.1 功能指标
表 2 功能指标
序号 | 功能指标 | 描述 | 目标 |
1 | 实时数据记录 | 通过uORB机制实时发布日志数据,使其他模块能即时订阅系统状态信息 | 日志数据发布0.1秒内即可被系统其他模块访问 |
2 | 数据存储格式 | 使用ULog二进制格式记录数据,支持高频率数据写入并兼容后期数据分析 | 使用ULog格式记录数据,使用100Hz及更高频率的数据采集 |
3 | 系统模块化与扩展 | 采用uORB发布-订阅机制,允许多个模块订阅相同的日志数据 | 支持系统中增加订阅的模块数不低于20个的同时仍保持稳定的数据发布 |
4 | 支持多个日志主题 | 支持记录多个uORB主题的数据,如 vehicle_statue 、sensor_combined 等 | 支持记录不少于50个uORB主题的数据 |
5 | 自动存储管理 | 支持基于时间戳或会话编号的文件命名,并自动管理存储文件数量以适应存储空间的变化 | 在存储空间不足时自动清理最早文件,最多保留100个日志文件 |
6 | 分析工具兼容性 | 记录的ULog文件可以与Flight Review、PX4 Tools等工具兼容 | 生成的ULog文件可直接使用Flight Review分析,并支持通过ulog2csv工具转换为CSV格式 |
3.2 性能指标
表 3 性能指标
序号 | 性能指标 | 描述 | 目标 |
1 | 数据写入效率 | 在ULog格式下高效记录数据,以满足高频率飞行数据采集需求。 | 在100Hz记录频率下,数据写入效率达到95%以上,确保数据不会丢失。 |
2 | 系统资源占用 | 优化日志记录对CPU、内存等资源的使用,确保对飞控系统性能的影响最小化。 | CPU使用率增幅控制在5%以内,内存使用增幅不超过20MB。 |
3 | 数据发布延迟 | 通过uORB低延迟传输日志数据,确保满足实时性需求。 | uORB数据传输延迟不超过20ms,订阅模块能及时获取数据。 |
4 | 文件写入速度 | 支持高效的二进制写入操作,确保数据能够及时写入ULog文件中。 | 记录频率达到1000条/秒,写入操作不会导致飞行器延迟响应。 |
5 | 存储空间管理 | 在存储空间不足时实现文件清理功能,保证长时间飞行的数据记录能力。 | 当可用存储空间小于100MB时自动清理最早日志文件,并保留至少10%可用空间。 |
6 | 数据可靠性 | 使用ULog的中央存储机制,确保日志数据在存储设备已满或异常情况下保持可靠。 | 在存储空间不足、数据写入受限的情况下,保证重要数据优先保留,避免丢失关键信息。 |
4 方案设计
4.1 软件设计方案
4.1.1 总体设计方案
图 1 总体设计方案
本设计方案旨在改进当前日志系统,使其基于uORB和ULog机制实现高效的数据记录和实时性数据传输。新系统将取代传统的CSV写入方式,通过uORB进行数据发布和订阅,并使用ULog格式进行日志存储,该系统主要分为以下几个核心模块:
4.1.1.1 系统初始化
系统启动时,首先进行参数初始化,包括日志记录频率、文件存储路径等。然后,系统会检查存储设备的可用空间,确保有足够的存储容量进行数据记录。日志系统还会在存储设备上创建新的日志目录,并以当前时间或会话编号命名,用于存储ULog日志文件。
4.1.1.2 数据采集与uORB主题订阅
在日志记录线程启动后,系统将基于配置文件或参数选择需要记录的uORB主题,如vehicle_status、sensor_combined等。系统通过uORB订阅所选主题,以实现实时数据的采集。每当有新的数据发布到uORB主题,日志系统会通过回调函数立即获取数据,并将其存入内部缓存中。
4.1.1.3 数据存储到ULog文件
当日志记录线程检测到ULog文件尚未打开时,将自动创建并打开新的ULog文件。在数据采集过程中,系统会将缓存中的数据批量写入ULog文件。ULog采用二进制格式,能够支持高频率数据记录并减少文件存储空间占用,确保数据记录的稳定性和高效性。
4.1.1.4 扩展日志功能(暂不添加)
如果启用了扩展日志记录功能,系统将启动一个独立的扩展日志线程,用于记录更多的特定数据主题或高频数据。扩展日志线程与主日志线程独立运行,可以根据需求配置不同的数据记录频率和存储格式,从而实现更加灵活的数据记录方案。
4.1.1.5 文件管理与系统退出
日志记录过程中,系统会持续监测存储设备的剩余空间。当空间不足时,系统会自动清理最早的日志文件,以保证后续数据的记录。当主线程退出时,系统将关闭所有打开的ULog文件并停止日志记录,确保所有数据成功写入存储设备。
4.1.2 软件流程设计
在本设计方案中,软件流程设计是实现高效日志记录和实时数据传输的关键。以下是软件流程的主要步骤:
4.1.2.1 系统初始化
该功能确保所有必要的存储资源和配置参数已经到位
图 2 系统初始化流程图
- 检查存储设备:系统启动时,首先对存储设备进行检查,以确保有足够的空间记录日志数据。如果存储设备不可用,系统将报告错误并退出,以防止因存储问题导致的数据丢失。
- 加载系统参数:在确认存储设备可用后,系统加载日志记录所需的参数配置。这些参数包括日志记录频率、日志文件的存储路径、以及日志记录的模式(例如自动启动或手动触发等)。
- 检查日志目录:系统会验证日志目录是否已存在。如果不存在,系统将自动创建一个新的日志目录,用于存储日志文件。
- 日志目录命名:在成功创建或确认日志目录存在后,系统会根据当前时间或会话编号对日志目录进行命名。这样可以确保每次飞行的日志文件存储在独立的目录中,便于数据管理和后期分析。
- 创建ULog文件名:接下来,系统为ULog日志文件生成一个文件名,通常基于时间戳,以确保文件名的唯一性。这样可以避免文件覆盖和数据丢失问题。
- 分配内存和缓存资源:系统为日志记录分配必要的内存空间和缓存资源,以支持高频数据记录需求。这一步确保在实际记录过程中有足够的资源进行高效的数据写入和存储。
- 设置日志记录频率和模式:最后,系统根据加载的参数配置设定日志记录频率和模式。设定完成后,系统初始化过程结束,进入日志记录线程,开始数据采集和记录。
4.1.2.2 数据采集与uORB主题订阅
图 3 数据采集与uORB主题订阅流程图
- 获取配置的uORB主题列表:系统根据初始化时加载的配置文件或参数,获取需要记录的uORB主题列表。这些主题代表了系统中不同类型的传感器数据和飞行状态信息,如vehicle_status、sensor_combined等。
- 初始化主题订阅:系统为每个主题创建订阅实例,确保可以实时接收这些主题上发布的数据。通过uORB的发布-订阅机制,系统可以在主题更新时立即获取数据,从而保证数据的实时性。
- 进入数据采样循环:系统进入数据采样循环,开始持续监测和获取已订阅主题上的数据。该循环在日志系统运行期间不断重复,以确保实时数据采样。
- 检测主题是否有新数据:系统在每次循环中检查各个主题是否有新的数据更新。如果有主题更新了数据,说明有新的传感器数据或飞行状态需要记录。
- 采集新数据:当检测到某个主题有新数据发布时,系统立即采集该数据。这样可以确保所有重要的飞行信息在被发布到uORB主题上时,能够立即被日志系统捕捉到。
- 缓存数据:采集到的新数据会暂时存入系统的缓存中,以便后续的批量写入操作。这种做法可以减少频繁的文件写入操作,优化数据存储性能。
- 检查是否达到写入频率:系统检查是否达到了预设的写入频率。只有在满足频率要求时,数据才会从缓存中写入到文件中。这种设计可以有效控制数据存储的频率,防止系统资源被过度消耗。
- 将数据存入缓存区:如果达到写入频率,系统会将当前采集的所有数据存入缓存区,准备批量写入到ULog文件中。否则,数据将继续在缓存中等待,直到下一个满足写入频率的时刻。
4.1.2.3 数据存储到ULog文件
图 4 数据存储到ULog文件
- 开始数据采集:系统开始数据的采集过程。
- 是否启用日志记录:判断是否启用了日志记录功能,如果未启用,则继续数据采集而不记录日志。
- 打开ULog文件:如果日志记录启用,打开对应的 ULog 文件,用于存储数据。
- 初始化日志数据结构:为即将记录的数据初始化 ULog 日志的结构和格式。
- 从uORB主题订阅数据:从飞控系统的 uORB 中订阅相应的主题数据。
- 序列化数据为ULog格式:将采集到的数据按照 ULog 格式进行序列化,以符合 ULog 文件的存储格式。
- 是否需要缓存:判断是否需要将数据缓存,以优化写入效率。若缓存,则先存入缓存区,后续写入;否则直接写入文件。
- 写入ULog文件:将序列化后的数据写入 ULog 文件。
- 日志记录是否结束:判断日志记录是否结束。如果没有结束,则继续从 uORB 主题订阅数据。如果结束,则关闭 ULog 文件并结束整个流程。
- 关闭ULog文件:当日志记录结束时,关闭 ULog 文件,释放资源。
4.1.2.4 扩展日志功能(暂不添加)
图 5 扩展日志功能(暂不添加)
- 启用扩展日志功能:启动扩展日志功能,用于记录额外的数据。
- 扩展日志已启用?:检查是否已经启用了扩展日志功能。如果未启用,系统会等待扩展日志的启动。
- 创建扩展日志线程:如果启用了扩展日志功能,创建一个独立的扩展日志线程来处理日志记录。
- 初始化扩展日志数据结构:为扩展日志准备数据结构,确保数据能够正确记录。
- 从uORB主题订阅扩展数据:订阅 uORB 中额外的主题数据,这些数据将被记录到扩展日志中。
- 序列化数据为ULog格式:将订阅到的扩展数据序列化为 ULog 格式,确保数据符合日志格式要求。
- 是否需要缓存?:判断是否需要缓存扩展数据,如果需要,数据会先存入缓存区,稍后再写入。
- 将扩展数据缓存:在需要缓存的情况下,数据会暂时存放在缓存区。
- 写入扩展日志文件:将序列化后的扩展数据写入扩展日志文件。
- 扩展日志记录结束?:判断扩展日志记录是否结束。如果没有结束,则继续订阅和记录数据。如果结束,则关闭日志文件。
- 关闭扩展日志文件:当扩展日志记录结束时,关闭文件并释放相关资源。
- 结束扩展日志线程:结束扩展日志的线程,完成所有记录任务。
- 扩展日志功能结束:扩展日志功能完成,流程结束。
4.1.2.5 文件管理与系统退出
图 5 文件管理与系统退出
- 日志系统启动:日志系统启动并准备记录日志。
- 日志系统是否运行中?:检查日志系统是否已经运行。如果未运行,系统等待日志系统的启动。
- 检查可用存储空间:检查系统中剩余的存储空间,确保日志能够正常写入。
- 存储空间充足?:判断存储空间是否足够。如果不够,系统会发出存储空间不足的警告。
- 发送存储空间不足警告:当存储空间不足时,向用户发出警告信息,提醒存储空间不足。
- 创建日志文件目录:如果存储空间充足,系统会为本次日志创建新的文件目录。
- 打开日志文件:在文件目录下创建并打开日志文件,准备记录数据。
- 写入日志数据:将日志数据写入到已经打开的日志文件中。
- 日志系统是否需要关闭?:判断日志系统是否需要关闭。如果不需要关闭,则继续写入日志数据。
- 关闭日志文件:当日志系统需要关闭时,首先关闭日志文件,确保数据写入完毕。
- 释放文件句柄:释放文件句柄,确保系统资源得到有效释放。
- 关闭扩展日志线程:关闭扩展日志的线程,结束所有日志记录的操作。
- 系统退出并清理资源:系统退出,并释放所有相关的资源,确保系统清理干净。
- 日志系统退出完成:日志系统退出完成,整个文件管理和系统退出流程结束。
4.2 技术关键点设计
4.2.1 任务调度处理
任务调度处理是实时操作系统VxWorks中的关键功能。本节将详细阐述如何将原有的PX4 autopilot中基于POSIX pthread的任务调度方案替换为VxWorks环境的实现方案,包括具体设计和更换的步骤与方法。
- 1:任务创建与删除设计
在设计中,我们使用VxWorks的
taskSpawn
函数来替代POSIX的pthread_create
实现任务的创建。taskSpawn
允许我们为每个任务设定独立的优先级、堆栈大小以及任务入口函数,与此同时,也可以在创建时直接指定任务选项(如任务是否浮点支持等)。为保证资源释放,在任务结束时调用taskDelete
替代POSIX的pthread_cancel
或pthread_join
,从而在VxWorks环境中实现自动资源清理。这样,设计方案确保了任务的创建与销毁过程在VxWorks环境中的平稳过渡。- 2:任务优先级设置设计
VxWorks任务优先级设置通过
taskSpawn
的优先级参数完成初始优先级设定,并在需要时使用taskPrioritySet
动态调整优先级。相较于POSIX中需要通过pthread_attr_t
或pthread_setschedparam
进行较为复杂的优先级调整,设计方案直接利用VxWorks的优先级设置机制,以0-255的范围精确控制任务优先级,并通过在关键任务前调用taskPrioritySet
提升其优先级,确保高优先级任务及时响应。这一设计可以避免任务阻塞或延迟,提高任务调度的实时性。- 3:任务状态管理设计
在VxWorks中,任务的多状态管理(READY、SUSPEND、PEND、DELAY等)使得替换POSIX线程状态更加高效。在任务运行过程中,通过
taskSuspend
和taskResume
实现对任务的挂起和恢复,替代POSIX中的pthread_cond_wait
和pthread_cond_signal
功能。具体设计中,将使用taskSuspend
实现对无用或非优先级任务的挂起,当需要恢复任务时,通过taskResume
迅速重新激活任务,以达到资源节省和任务管理优化的目的。此设计方案简化了状态管理,增强了任务调度的控制力。- 4:任务同步机制设计
为了保证任务间同步,设计方案采用VxWorks的信号量和事件标志来替代原有POSIX的
pthread_mutex
和pthread_cond
等同步机制。例如,将VxWorks的semTake
和semGive
替换pthread_mutex_lock
和pthread_mutex_unlock
,保证互斥访问。同时,为了实现条件通知,将事件标志eventSend
应用于重要信号传递,替代POSIX的条件变量。此替换设计不仅简化了代码结构,还能在高负载情况下提升任务同步的可靠性。- 5:任务时间控制设计
VxWorks的
taskDelay
为任务的时间控制提供了简洁的实现方法,取代了POSIX的nanosleep
或定时器的复杂实现。在设计中,将通过taskDelay
实现任务的延迟控制,确保非实时任务在等待期间不占用系统资源,从而减少系统调度压力。同时,taskDelay
的使用确保了每个任务可独立定义时间片,从而实现更精确的任务执行控制,满足系统的实时性需求。4.2.2 ULog 数据类型
在 ULog 中使用了与 C 语言类似的基础数据类型。这些数据类型在日志文件中以小端模式存储,即低字节位(LSB)存放在最低的内存地址。以下是几种常用数据类型的定义以及一个数组示例。
- 基本数据类型
在这个示例中,我们定义了一个结构体
sensor_combined_s
,包含了 uint64_t timestamp
、float
数组(用于存储陀螺仪和加速度计数据),以及对齐字节(_padding0
和 _padding1
)。这些字段在 ULog 文件中按照顺序存储,以确保数据对齐并避免未定义的行为。注意:由于C语言的结构体在不同平台上的内存对齐策略可能有所不同,ULog在记录数据时通常需要考虑内存对齐(padding)的问题。为了确保跨平台的一致性,ULog在日志文件中存储数据时会显式地去除这些对齐字节,以保证每个字段的数据紧凑排列。例如,在上述结构体中,
float gyro_rad[3]
后面会有一个对齐字节 _padding0
以便接下来的 float accel_m_s2[3]
能够在合适的内存地址上对齐。因此,日志模块在写入或读取数据时需要正确地处理这些对齐字节,确保数据的一致性。- 数组类型
在日志中,数组会按照定义的长度依次存储,每个
float
类型占用 4 个字节,总共存储 5 * 4 = 20 字节。重要提示:字符串(
char[length]
)在日志文件中不包含结束符 \\0
,因此需要确保字符串在使用时不会越界访问。4.2.3 uORB 编译工作流
移植原PX4-Autopilot工程中的CMake编译工作流至Makefile脚本中,在编译前调用Python脚本从msg文件中读取数据,并根据已提供的em模板文件自动生成相应的C++头文件,使得飞控系统能够处理各类消息。以下为详细描述:
- 消息文件配置
在项目的源代码目录中的一个msg子目录(路径为:
lkd_kfs_110_vip/modules/uORB/msg
),其中存放所有uORB消息定义的.msg文件。每个文件包含一个消息的结构定义,例如传感器数据、控制指令等。这些文件是uORB框架内模块之间交换数据的基础。- 生成uORB头文件
- 脚本解析传入的命令行参数,指定生成头文件的操作。
- 使用
msg_loader
模块加载消息定义,提取每个字段的信息。 - 通过em模板引擎处理模板文件,将字段信息填充到模板中,以生成每个消息类型的C++头文件。
使用
px_generate_uorb_topic_files.py
脚本,根据消息文件(msg文件)生成对应的uORB头文件。该脚本通过em模板引擎将消息定义转换为C++代码,用于描述消息的结构和类型。具体步骤如下:命令如下(假设源码位于
$SOURCE_DIR
,消息文件位于$SOURCE_DIR/modules/uORB/msg
,并且当前位于$SOURCE_DIR
,生成的文件将存放在$SOURCE_DIR/module/uORB/topics
中):- 生成uORB压缩字段文件
- 脚本首先解析输入的JSON文件,提取每个消息的字段定义和依赖关系。
- 使用深度优先搜索的方式,按照依赖关系对字段进行排序,以确保嵌套类型优先生成。
- 使用
heatshrink_encode
模块对字段进行压缩,并生成对应的C++代码,其中包括用于解压缩的函数。 - 最终生成的文件包括压缩后的字段定义和辅助函数,用于在运行时进行解压缩。
为了优化消息占用的带宽和存储,使用
px_generate_uorb_compressed_fields.py
脚本生成压缩的uORB消息字段的C++源文件和头文件。该脚本解析上一步生成的JSON文件,并通过heatshrink压缩算法对消息字段进行压缩,以减少占用空间。具体方法如下:命令如下(假设源码位于
$SOURCE_DIR
,消息文件位于$SOURCE_DIR/modules/uORB/msg
,并且当前位于$SOURCE_DIR
,生成的文件将存放在$SOURCE_DIR/modules/uORB/topics_sources
中):- 生成microcdr头文件
- 脚本解析传入的命令行参数,指定生成microcdr头文件的操作。
- 使用em模板引擎处理模板文件,生成与消息相关的microcdr头文件,以支持数据的高效传输和序列化。
使用
px_generate_uorb_topic_files.py
脚本,根据消息文件生成与microcdr相关的头文件,这些头文件用于支持微型序列化和反序列化操作,通常用于高效的数据传输。具体步骤如下:命令如下(假设源码位于
$SOURCE_DIR
,消息文件位于$SOURCE_DIR/modules/uORB/msg
,并且当前位于$SOURCE_DIR
,生成的文件将存放在$SOURCE_DIR/modules/uORB/ucdr
中):- 生成uORB源文件
- 脚本解析传入的命令行参数,指定生成源文件的操作。
- 使用
msg_loader
模块加载消息定义,并通过em模板引擎将字段信息填入模板文件中,生成对应的C++源文件。 - 生成的源文件实现了对每个消息类型的操作函数,例如消息的发布与订阅,以支持PX4系统中的模块间通信。
最后,使用
px_generate_uorb_topic_files.py
脚本,根据消息文件生成对应的uORB源文件。这些源文件实现了消息发布、订阅以及消息数据的读取与写入功能。具体步骤如下:命令如下(假设源码位于
$SOURCE_DIR
,消息文件位于$SOURCE_DIR/modules/uORB/msg
,并且当前位于$SOURCE_DIR
,生成的文件将存放在$SOURCE_DIR/modules/uORB/topics_sources
中):4.2.4 ULog 日志生成
日志模块负责收集系统状态、传感器数据等信息,并将其记录为
ULog
格式的日志文件。在PX4系统中。通过移植PX4 Autopilot的日志系统设计,飞控订阅uORB消息来获取系统各个模块的状态更新。具体过程如下:- 订阅uORB消息:
日志模块通过uORB的API来订阅相关的消息主题,这些主题对应在项目中定义的
.msg
文件。通过订阅,日志模块能够实时接收与飞控系统相关的数据更新。PX4 Autopilot使用
orb_subscribe()
函数来订阅消息主题。例如:订阅后,日志模块通过调用
orb_check()
函数来检测消息是否更新,如果有更新则使用orb_copy()
函数来读取消息内容。回调函数处理是通过一个循环不断检查消息更新,然后对收到的数据进行处理并写入日志。例如:
- 读取和解压缩消息格式:
日志模块使用
MessageFormatReader
类来读取uORB的压缩消息格式。在MessageFormatReader::readMore()
函数中,日志模块通过heatshrink
解码器对压缩的消息格式进行解压缩,从而获得完整的消息字段定义。MessageFormatReader
通过读取orb_compressed_message_formats()
返回的压缩数据,对其进行解码,以获取原始的消息定义,这样日志模块可以对所有字段进行正确的处理。- 处理消息数据:
当日志模块订阅的消息有更新时,日志模块会调用
MessageFormatReader
的相关方法来读取具体的消息字段数据。解压缩后的消息数据被存储在日志模块的内部缓冲区中,通过调用MessageFormatReader::readNextField()
等函数,可以逐一解析各个字段,并将这些数据写入日志文件中。- 日志写入:
日志模块会将解压缩后的数据按照指定的格式写入到日志文件中。日志文件通常采用
ULog
格式,以便于后续的分析和调试。在写入过程中,日志模块会根据消息字段的数据类型和内容调用适当的写入函数,将数据按照预定的二进制格式进行存储,以提高日志的写入效率并减少存储空间的占用。例如:通过这样的方式,所有的消息数据都会被高效地记录到日志文件中。
- 错误处理和恢复:
在消息读取和写入过程中,如果遇到缓冲区不足或数据格式不匹配等问题,日志模块会记录错误信息,例如使用
LOG_ERR
宏打印错误日志,并尝试进行恢复。在解压缩过程中,如果遇到解码错误,MessageFormatReader
会将状态设置为失败,停止进一步的处理,以防止无效数据导致系统的不稳定。例如,在缓冲区容量不足时,日志模块会输出如下错误:同时,日志模块会根据情况清空缓冲区或重新分配资源,确保系统能够继续运行并尽可能保留已有的有效数据。
4.2.5 ULog 日志结构
ULog 文件的结构可以分为三部分:头部(Header)、定义部分(Definitions)和数据部分(Data)。每个部分都有其特定的功能和格式。如下图所示:
图6 ULog 文件结构
在实现ULog日志文件写入的过程中,我们仿照了PX4 Autopilot的设计,确保日志文件符合ULog的二进制格式规范。日志文件的结构分为多个部分,包括文件头、格式信息、参数信息、消息数据以及文件的关闭操作。以下为各个写入内容的具体实现描述:
- ULog日志文件创建与文件名生成 (
get_log_file_name
)
在
get_log_file_name
函数中,根据当前的时间戳或文件编号生成日志文件名称。文件名称的生成方式包括基于时间戳的命名格式,如/fs/microsd/log/2024-11-01/12_34_56.ulg
,确保日志文件具有唯一性。此外,还为文件指定了.ulg
后缀,以表示其为ULog格式文件。PX4 Autopilot使用如下函数创建日志文件:
- ULog 文件头信息 (
write_header
)
ULog文件头是日志文件的开端,定义了文件的基础信息。文件的头部是固定大小的(16 字节),包含ULog文件的标识符“ULog”和版本号(当前为
1
),以及日志的初始时间戳。写入文件头信息可以帮助ULog日志读取器识别文件格式,确保文件是ULog兼容的。PX4 Autopilot使用如下函数创建文件头信息:
ULog文件头部示例如下:
- 定义数据格式 (
write_formats
)
ULog日志文件中包含数据格式描述,定义了日志文件中将使用的消息格式。例如,PX4 autopilot会定义包含传感器数据的格式,包括字段名称和数据类型,如
timestamp
、gps_position
、attitude
等。此信息使用ULog格式的ulog_message_format_s
结构写入,以便日志解析器知道如何解码每条数据记录。PX4 Autopilot使用如下函数定义数据格式:
ULog文件数据格式如下:
这个定义表示了一个
sensor_data
消息,包含两个字段:温度(temperature
)和压力(pressure
),它们的数据类型都是 float
。- 写入系统和参数信息 (
write_info
)
系统和参数信息写入是ULog日志中重要的一部分,包括系统版本信息、硬件信息以及参数设置。
write_info
函数用于写入单项系统信息,如硬件型号、固件版本、操作系统版本等。还可以记录关键的控制参数,如飞控板中的attitude_control_gain
等,以便在日志分析中了解飞行器的配置。PX4 Autopilot使用如下函数写入系统和参数信息:
目前支持的预定义如下表所示:
键 | 描述 | 值示例 |
char[value_len] sys_name | 系统名称 | "PX4" |
char[value_len] ver_hw | 硬件版本(板卡) | "PX4FMU_V4" |
char[value_len] ver_hw_subtype | 板卡子版本(变体) | "V2" |
char[value_len] ver_sw | 软件版本(git标签) | "7f65e01" |
char[value_len] ver_sw_branch | git分支 | "master" |
uint32_t ver_sw_release | 软件版本(见下文) | 0x010401ff |
char[value_len] sys_os_name | 操作系统名称 | "Linux" |
char[value_len] sys_os_ver | 操作系统版本(git标签) | "9f82919" |
uint32_t ver_os_release | 操作系统版本(见下文) | 0x010401ff |
char[value_len] sys_toolchain | 工具链名称 | "GNU GCC" |
char[value_len] sys_toolchain_ver | 工具链版本 | "6.2.1" |
char[value_len] sys_mcu | 芯片名称和修订版本 | "STM32F42x, rev A" |
char[value_len] sys_uuid | 车辆唯一标识符(如MCU ID) | "392a93e32fa3"... |
char[value_len] log_type | 日志类型(如未指定则为完整日志) | "mission" |
char[value_len] replay | 重放模式下重放的日志文件名 | "log001.ulg" |
int32_t time_ref_utc | UTC时间偏移(以秒为单位) | -3600 |
- 写入参数 (
write_parameters
)
write_parameters
函数将日志记录时的系统参数写入日志文件。参数记录可帮助分析不同配置对系统行为的影响,如飞控增益、PID参数和传感器偏移量等。PX4 Autopilot使用如下函数写入参数信息:
- 数据写入 (
write_message
)
write_message
函数用于将实际的数据记录写入ULog日志中。每条数据消息包含时间戳、消息类型和具体的传感器或控制数据。例如,通过ULogMessage
结构体可以记录每个时间点的传感器数据(如GPS位置、姿态信息等)。此函数确保了数据的准确写入,并通过对齐确保数据结构的正确性。PX4 Autopilot使用如下函数写入数据消息:
数据部分包含了实际的日志记录内容,它使用前面定义的消息格式来记录每一条数据,其格式如下:
- ULog文件关闭 (
stop_log_file
)
stop_log_file
函数用于在记录结束时关闭ULog文件,确保所有数据已写入文件,避免数据丢失。关闭文件之前会清空缓冲区并写入文件末尾的必要信息,保证文件的完整性。4.2.5 ULog 日志解析
ULog 解析器必须具备处理未来文件格式和未知消息的能力,还必须在日志文件不完整或包含无法解析的标志位时做出正确处理。我们通过具体的场景来展示解析器的功能。
- 未知消息处理
假设日志文件中包含一个解析器尚未支持的消息类型。
在这种情况下,解析器应当忽略此消息,并输出警告信息。日志解析过程不会因此中断。
- 文件中断处理
日志文件在传输过程中可能会中断,导致某条消息不完整。
解析器应当丢弃未完成的消息,并继续解析后续完整的消息,而不会崩溃或报错。
- 处理附加数据
某些日志文件可能会在记录结束后附加数据,例如硬故障崩溃信息。
解析器应读取
appended_offsets
指定的偏移位置,并将附加数据视为正常的日志数据进行解析。4.2.4 可用的第三方解析器
为了更好地理解和使用 ULog 格式,已有多个开源和商业工具提供了支持。这些工具能够帮助开发者更高效地解析、分析和可视化 ULog 日志。
- pyulog(Python 实现)
pyulog
是一个 Python 库,能够方便地读取和解析 ULog 文件。通过 ULog
类,开发者可以轻松获取日志中的所有消息。- FlightPlot(Java 实现)
FlightPlot 是一个开源的 Java 应用程序,能够对 ULog 文件进行可视化操作。用户可以通过图形界面选择不同的日志数据并生成时间序列图。
- PlotJuggler(C++ 实现)
PlotJuggler 是一个 C++/Qt 应用,支持从 ULog 文件生成图表和时间序列数据。开发者可以使用这个工具进行复杂的数据分析和展示。
5 功能性能指标符合性分析
表 6 需求符合性分析
序号 | 需求 | 设计说明 | 符合性说明 |
1 | 日志文件正常生成至指定目录下 | 使用TFTP服务下载日志信息正常且全部可以解析出来 | |
2 | 使用PlotJuggler查看日志信息 | 正确解析已添加的全部字段 | |
3 | 使用PX4 Flight Review网页工具查看日志信息 | 正确解析已添加的全部字段 | |
4 | 使用ulog2csv工具转换为csv格式文件 | 正确解析已添加的全部字段并转换为csv格式文件 | |
5 | 已有的消息类型兼容PX4日志记录程序 | ㅤ | ㅤ |
6 附录1:ULog日志记录数据类型表(待补充)
注:
- 数据类型为uORB结构体类型,该结构体中存在大量的未使用字段,目前日志记录程序只对下表中的参数进行记录(代码中已注释的部分未添加到表格中)。
- 日志记录参数的类型参考uORB结构体的类型,表中展示的日志记录参数可通过第三方ULog解析程序解析。
表 1 ULog日志记录数据类型表
序号 | 数据类型 | 日志记录参数 | 日志说明 | 机型适用条件 |
1 | vehicle_status | main_state, nav_state, arming_state, failsafe, condition_landed, load | 主状态,导航状态,解锁状态,失控保护标志,降落检测标志,cpu占用 | 通用 |
2 | battery_status | voltage_filtered_v, current_a, fmu_bat_voltage_filtered_v, payload_voltage_v | 动力电压,动力电流,执行机构电压,机载设备电压 | 通用 |
3 | vehicle_gps_position | fix_type, lon, lat, alt, vel_n_m_s, vel_e_m_s, vel_d_m_s, eph, epv, satellites_used, heading_form_gps | 定位类型,经度,纬度,海拔高度,北向速度,东向速度,垂向速度,eph,epv,定位星数,双天线航向 | 通用 |
4 | rc_channels | channels[0], channels[1], channels[2], channels[3], channels[4], channels[5], channels[6], channels[7], channel_count, signal_lost, rssi | rc通道1,rc通道2,rc通道3,rc通道4,rc通道5,rc通道6,rc通道7,rc通道8,通道数,rc丢失标志,rssi | 通用 |
5 | sensor_combined | gyro_rad_s[0], gyro_rad_s[1], gyro_rad_s[2], accelerometer_m_s2[0], accelerometer_m_s2[1], accelerometer_m_s2[2], magnetometer_ga[0], magnetometer_ga[1], magnetometer_ga[2], baro_pres_mbar, baro_alt_meter, baro_temp_celsius, differential_pressure_filtered_pa | x轴角速度,y轴角速度,z轴角速度,x轴加速度,y轴加速度,z轴加速度,x轴磁场强度,y轴磁场强度,z轴磁场强度,静压,气压高度,温度,动压 | 通用 |
6 | attitude_estimate | roll, pitch, yaw, rollspeed, pitchspeed | EKF融合后参数
滚转角,俯仰角,偏航角,滚转角速率,俯仰角速率,偏航角速率 | 通用 |
7 | global_position_estimate | lon, lat, alt, vel_n, vel_e, vel_d, eph, epv | EKF融合后参数
经度,纬度,海拔高度,北向速度,东向速度,垂向速度,eph,epv | 通用 |
8 | sensor_extIns | Gx_raw, Gy_raw, Gz_raw, Ax_raw, Ay_raw, Az_raw, temp_raw | INS_300E外置惯导参数
陀螺仪X轴原始数据,陀螺仪Y轴原始数据,陀螺仪Z轴原始数据,加速度计X轴原始数据,加速度计Y轴原始数据,加速度计Z轴原始数据,温度原始数据 | 通用 |
ㅤ | ㅤ | rawGps_posStatus, flags, rawGps_starUsed, posAccuracy_lat, year, month, day, hour, min, sec | INS_300E外置惯导参数
定位状态,标志,卫星数量,PDOP,年,月,日,时,分,秒 | 通用 |
ㅤ | ㅤ | roll, pitch, yaw, rollSpeed, pitchSpeed, yawSpeed, xAcc, yAcc, zAcc | INS_300E外置惯导参数
滚转角,俯仰角,偏航角,滚转角速度,俯仰角速度,偏航角速度,X轴加速度,Y轴加速度,Z轴加速度 | 通用 |
ㅤ | ㅤ | longitude, latitude, altitude, velocity_N, velocity_E, velocity_D | INS_300E外置惯导参数
经度,纬度,高度,北向速度,东向速度,垂直速度 | 通用 |
ㅤ | ㅤ | Nav_state_300E, data, time, type | INS_300E外置惯导参数
状态, 数据, 时间, 类型 | 通用 |
9 | airspeed_inside | indicated_airspeed_m_s, true_airspeed_m_s | 指示空速,真空速 | 通用 |
10 | vehicle_attitude | roll, pitch, yaw, rollspeed, pitchspeed, yawspeed | 滚转角,俯仰角,偏航角,滚转角速率,俯仰角速率,偏航角速率 | 通用 |
11 | vehicle_global_position | lon, lat, alt, vel_n, vel_e, vel_d, eph, epv, Vg_2d, Vg_3d | 经度,纬度,海拔高度,北向速度,动向速度,垂向速度,eph,epv,Vg_2d,Vg_3d | 通用 |
12 | airspeed | indicated_airspeed_m_s, true_airspeed_m_s | 指示空速,真空速 | 通用 |
13 | vehicle_sensor_manage | srcInuse_pos, srcInuse_att, srcInuse_heading, srcInuse_height, srcInuse_grdSpd, srcInuse_airSpd | 位置信号源,姿态信号源,航向信号源,高度信号源,地速信号源,空速信号源 | 通用 |
ㅤ | ㅤ | sensorValid_interINS_ATT, sensorValid_interINS_NAV, sensorValid_interBaro_ALT, sensorValid_interGNSS, .sensorValid_interBaro_Vi, sensorValid_interMag | 内置ATT有效性,内置NAV有效性,内置baro_alt有效性,内置GNSS有效性,内置Vi有效性,内置MAG有效性 | 通用 |
ㅤ | ㅤ | sensorValid_extINS_ATT, sensorValid_extINS_NAV, sensorValid_extBaro_ALT, sensorValid_extGNSS, sensorValid_extBaro_Vi, sensorValid_extMag, sensorValid_extHdt | 外置ATT有效性,外置NAV有效性,外置baro_alt有效性,外置GNSS有效性,外置Vi有效性,外置MAG有效性,外置HDT有效性 | 通用 |
14 | control_law_theta | theta_cmd, theta_cmd_phi, theta_var, theta_integra, theta, ele, Q, ctrl_type_theta | 控制律-theta
俯仰角指令,俯仰角指令_phi,俯仰角误差,俯仰角积分,俯仰角,升降舵,俯仰角速度,俯仰控制类型 | 固定翼或垂起类型 |
15 | control_law_phi | dyaw, psi2phicmd_integra, phi_cmd, phi_var, phi_integra, phi, ail, P, ctrl_type_phi | 控制律-phi
偏航角变化量,偏航角到滚转角指令积分,滚转角指令,滚转角误差,滚转角积分,滚转角,副翼,滚转角速度,滚转控制类型 | 固定翼或垂起类型 |
16 | control_law_psi | yaw_ctl_mode, psi_cmd, yaw_rate_cmd, yaw_rate_var, R, yaw_rate_integar, taxi_rud_u_integra_dY, taxi_rud_u_dY, taxi_rud_u_psi, Rud, psi, ctrl_type_yaw | 控制律-偏航
偏航控制模式, 偏航角指令, 偏航角速度指令, 偏航角速度误差, 偏航角速度, 偏航角速度积分, 滑行方向舵积分_dY, 滑行方向舵_dY, 滑行方向舵_psi, 方向舵, 偏航角, 偏航控制类型 | 固定翼或垂起类型 |
17 | control_law_thrust | vx_src, vx_cmd, vx_var, vx_integra, thrust_u, thrust_var, ctrl_type_thrust, vias, vground | 控制律-推力
速度源,速度指令,速度误差,速度积分,推力,推力误差,推力控制类型,指示空速,地速 | 固定翼或垂起类型 |
18 | control_law_wheel | wheel_front_u_integra_dY, wheel_front_u_dY, wheel_front_u_psi, wheel_front_u | 控制律-前轮
前轮积分_dY, 前轮_dY, 前轮_psi, 前轮_u | 固定翼或垂起类型 |
19 | control_law_break | break_cmd, break_u | 控制律-刹车
刹车指令,刹车 | 固定翼或垂起类型 |
20 | control_law_alt | h_cmd, h, hdot_cmd, hdot_cmd_h, hdot_var, hdot_integra, hdot, theta_trim. theta_cmd. ctrl_type_lon | 控制律-高度
高度指令,高度,高度变化率指令,高度变化率指令_高度,高度变化率误差,高度变化率积分,高度变化率,俯仰角修正,俯仰角指令,纵向控制类型
| 固定翼或垂起类型 |
21 | control_law_track | track_vy, track_dy, track_dl, track_psi, track_vycmd, phicmd_vy_integra, PhiCmd_Vy, PhiCmd_Psi | 控制律-航迹
航迹速度_y,航迹偏差_y,航迹偏差_l,航迹偏航角,航迹速度指令_y,滚转角指令_航迹速度积分,滚转角指令_航迹速度,滚转角指令_航迹偏航角 | 固定翼或垂起类型 |
22 | control_law_xyz_mc | Phi_Cmd, PhiRate_Cmd, PhiRate_Int, M_Roll, CtrlType_Lat | 滚转角指令_mc,滚转角速度指令_mc, 滚转角速度积分_mc, 滚转力矩_mc, 横向控制类型_mc | 旋翼或垂起类型 |
ㅤ | ㅤ | Theta_Cmd, ThetaRate_Cmd, ThetaRate_Int, M_Pitch, CtrlType_Lon | 俯仰角指令_mc,俯仰角速度指令_mc,俯仰角速度积分_mc, 俯仰力矩_mc,纵向控制类型_mc | 旋翼或垂起类型 |
ㅤ | ㅤ | Psi_Cmd, PsiRate_Cmd, PsiRate_Int, M_Yaw, CtrlType_Yaw | 偏航角指令_mc,偏航角速度指令_mc,偏航角速度积分_mc,偏航力矩_mc,偏航控制类型_mc | 旋翼或垂起类型 |
ㅤ | ㅤ | H_Cmd, Hdot_Cmd, Hdot_Int | 高度指令_mc, 高度变化率指令_mc, 高度变化率积分_mc, 推力_mc, 高度控制类型_mc | 旋翼或垂起类型 |
23 | control_law_track_mc | Vx_Cmd, Vx_var, VxInt, Vx, _X_Cmd, _X_var, _XInt, _dX | X轴速度指令_mc, X轴速度误差_mc, X轴速度积分_mc, X轴速度_mc, X轴位置指令_mc, X轴位置误差_mc, X轴位置积分_mc, X轴位置变化_mc | 旋翼或垂起类型 |
ㅤ | ㅤ | Vy_Cmd, Vy_var, VyInt, Vy, _Y_cmd, _Y_var, _YInt, _dY | Y轴速度指令_mc, Y轴速度误差_mc, Y轴速度积分_mc, Y轴速度_mc, Y轴位置指令_mc, Y轴位置误差_mc, Y轴位置积分_mc, Y轴位置变化_mc | 旋翼或垂起类型 |
24 | actuator_controls_0 | control[0], control[1], control[2], control[3], control[4], control[5], control[6], control[7], control[8], control[9], control[10], | 执行器控制_0
控制1,控制2,控制3,控制4,控制5,控制6,控制7,控制8,控制9,控制10 | 通用 |
25 | actuator_outputs | output[0], output[1], output[2], output[3], output[4], output[5], output[6], output[7], output[8], output[9], output[10], | 执行器输出
输出1,输出2,输出3,输出4,输出5,输出6,输出7,输出8,输出9,输出10 | 通用 |
26 | actuator_inputs | input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7], input[8], input[9], input[10], | 执行器输入
脉冲1,脉冲2,脉冲3,脉冲4,脉冲5,脉冲6,脉冲7,脉冲8,脉冲9,脉冲10 | 通用 |
Loading...