无人机|APM 学习 9 --- 存储和 EEPROM 管理

英文原文地址:https://ardupilot.org/dev/docs/learning-ardupilot-storage-and-eeprom-management.html
存储和 EEPROM 管理 ArduPilot 支持的每个板都有某种形式的持久性存储。它用于保存用户参数,航路点,集会点,地形数据和许多其他有用的信息。为了提供对此存储的访问,ArduPilot 具有4种基本机制 :

  • the AP_HAL::Storage object, accessed as hal.storage (存储对象,作为 hal.storage 访问)
  • the StorageManager library to give a higher level abstraction layer on hal.storage(StorageManager 库以在 hal.storage 上提供更高级别的抽象层)
  • DataFlash for storing to an on-board logging area(DataFlash,用于存储到板载日志记录区域)
  • Posix IO functions to traditional filesystems (for example VFAT on a microSD card), on boards that support it(Posix IO 针对传统文件系统的功能(例如microSD卡上的VFAT),适用于支持它的硬件。)
需要持久存储的其他库和功能都建立在这些基本系统上,例如,AP_Param 库(处理用户可设置的参数)建立在 StorageManager 的顶部,而 StorageManager 又建立在 AP_HAL :: Storage 的顶部。 AP_Terrain 库(处理地形数据)建立在 Posix IO 功能的基础上,用于保存地形数据库。
1,AP_HAL :: Storage 库 AP_HAL :: Storage 对象适用于所有平台。 ArduPilot 支持的板上通过此接口可用的最小存储大小为 4096 字节。一些板卡提供了更多的空间-例如PX4v1具有8k的EEPROM,而Pixhawk具有16k的FRAM。所有这些都隐藏在 AP_HAL :: Storage API 之中。
hal.storage API 非常简单,它只有3个功能:
  • init() to start up the storage subsystem (init()启动存储子系统)
  • read_block() to read a block of bytes (read_block()读取一个字节块)
  • write_block() to write a block of bytes (write_block()写入一个字节块)
使用这种非常简单的API的原因是,鼓励开发人员使用 StorageManager API 而不是hal.storage,仅在启动新板或进行调试时才研究hal.storage。
可用存储空间的大小在宏HAL_STORAGE_SIZE的AP_HAL / AP_HAL_Boards.h中设置。这意味着我们尚不支持动态确定此接口可用的存储空间。如果要动态调整大小的存储,则需要使用Posix IO。
【无人机|APM 学习 9 --- 存储和 EEPROM 管理】目前系统没有用于AP_HAL :: Storage API的示例,因此您可以编写一个。如果您已经深入到ArduPilot教程中,那么您应该已经看到了足够的示例,以了解如何从头开始编写示例。因此,编写一个library / AP_HAL / examples / Storage示例,该示例计算 hal.storage 数据的全部内容的8位 XOR,并将其打印到控制台。然后将示例作为补丁提交至 ArduPilot github,请务必遵循补丁提交指南。
我会很感兴趣地看到将这个练习添加到教程中多长时间后才能提交…。
2,StorageManager 库 简单的 hal.storage API 可轻松轻松地将 ArduPilot 移植到新板上,但实际使用起来并不方便。为此,我们设计 StorageManager。 StorageManager 库提供了一个API,该API将存储抽象为具有指定用途的伪连续数据块。我们将可用存储划分为以下区域:
  • parameters (参数)
  • fence points (栅栏点)
  • waypoints (航点)
  • rally points (集结点)
  • signing key (签名密钥)
  • RC bind info (RC绑定信息)
阅读库 /StorageManager/StorageManager.cpp,特别要看一下顶部的表格。注意,如何为具有更大存储量的系统定义每种类型的多个区域。将多个不连续的存储区域合并为一个逻辑存储区域的能力是添加 StorageManager 的主要动机。它使我们从在所有板上使用 4k 的存储空间发展到使用每个板上可用的完整存储空间,而无需要求用户重新加载所有参数或重新加载航路点。
避免让用户重新配置其自动驾驶仪板这一问题在 ArduPilot 使用中非常现象,当用户可以更新到最新固件,他们以前设置的所有内容仍然可以使用,同时获得新功能时,这一特征非常重要。有时,这意味着我们必须做更多的工作,开发人员才能实现这一目标-例如这是 StorageManager 中设计的初衷。
StorageManager API 还提供了用于读取和写入变量(如整数)的便捷功能。这是API,类似AP_Mission)保存和还原航点。
代码位于库 /StorageManager/examples/StorageTest.cpp, 这是对StoageManager层的压力测试,同时也是对AP_HAL :: Storage对象的压力测试。它以随机长度的随机偏移量执行随机 IO,这意味着 IO 会跨越边界,在 FRAM 或 EEPROM 中单个参数值可能不连续。该测试示例旨在对 StorageManager API 进行压力测试,即使将 ArduPilot 移植到新板上时,它也非常有用,因为它很好地强调了EEPROM访问功能。
需要注意的是,在主板上尝试使用 StorageTest是破坏性的测试。它不会破坏您的电路板,但会擦除您的所有参数和航路点。因此,如果您要在自己喜欢的飞机上的板上进行测试,请备份您的配置。
作为练习,可以向 StorageTest 添加一些配置文件,以便以 kb / sec 的形式显示总 IO 速率以及读写的 IO 速率,是否注意到读写速率之间的差异?您是否注意到有关写入已在该地址中设置的值的速度的任何信息?查看是否可以在StorageManager中找到解释您的观察结果的代码。然后提交一个补丁,将分析输出添加到ArduPilot github。
接下来,搜索ArduPilot库以优化所有使用StorageManager的位置。您正在寻找的是StorageAccess句柄,如下所示:
StorageAccess AP_Param::_storage(StorageManager::StorageParam);

声明了一个名为AP_Param :: __ storage的变量,该变量可访问此板上存储的StorageParam区域。
3,DataFlash库 自动驾驶仪需要的另一种存储方式是机载日志,ArduPilot 提供DataFlash库。从许多方面来说,这是一个很奇怪的库,首先,这个名称很奇怪,因为它最初是为 APM1 的 DataFlash 芯片设计的。它是一个硬件设备驱动程序库,随着时间的流逝逐渐演变成一个通用的日志记录系统。在内部,库的结构以几种方式显示此历史记录(虽然这并不是一个好方法)。
如今,DataFlash API 是围绕日志记录基础结构模型设计的。它允许为单个日志消息定义自描述数据结构,例如用于记录来自 GPS 传感器的数据的“ GPS”消息。它以高效的方式处理将数据记录到持久性存储中的问题,还为其他库提供 API,供用户在飞行后希望下载其日志文件时将其读取。
ArduPilot 用于存储日志消息的格式为 “ * .bin” 。 格式内容非常灵活,地面站可以制定日志文件中消息的格式,而无需某种通用方案。每个日志文件的最前面是一组 FMT 消息,这些消息具有众所周知的格式,并描述了随后的消息的格式。
参考库/ DataFlash /示例 /DataFlash_test/DataFlash_test.cpp,会在顶部看到一个小表,该表定义了我们将要写入的日志消息,在这种情况下为“ TEST”消息,其中包含4个无符号的16位整数和两个有符号的32位整数(这就是“ HHHHii”的含义) 。它还提供了这6个变量的名称(分别标记为V1至V4以及L1和L2)。
在loop()函数中,您将看到一个调用,如下所示:
DataFlash.get_log_boundaries(log_num, start, end);

这是公用API,用于DataFlash库隐藏开发板实际存储日志文件的方式。在具有Posix IO的系统上(例如Pixhawk或Linux),日志文件作为单独的文件存储在microSD卡上的“ LOGS”目录中,用户可以通过拔出microSD卡并将其放入PC来直接复制这些文件。
在APM2之类的板上,事情并不是那么简单, 因为 APM2 在 DataFlash 芯片上具有4 MB的存储空间,可通过 SPI 接口进行访问。接口本身是面向页面的,因此需要填充一个512字节(或可能是528字节!)的页面,然后告诉芯片在填充下一页的同时将该页面复制到持久性存储中。 DataFlash 执行随机IO不是特别适用,因为它是专为需要连续写入的代码而设计,例如记录 log 文档。对于自动驾驶仪记录数量而言,4兆字节的大小确实不是很大,因此我们也需要在自动驾驶仪填满时对其进行处理。
所有这些复杂性都隐藏在一个API中,该API提出了“日志号”的概念,该日志号只是在自动驾驶仪的一次飞行中写入的一堆字节。 APM1和APM2上的DataFlash实现在每个页面的前部使用很少的标记字节来说明正在写入哪个日志号,这些日志号对应于用户要求检索其日志时下载的日志号。
4,Posix IO ArduPilot 支持的某些自动驾驶板基于具有Posix类API的操作系统。例如,Linux端口具有非常好的Posix子系统,而用于PX4的NuttX操作系统(例如在Pixhawk上)具有相当合理的Posix层。您可以在ArduPilot的库中利用此功能,只要您不依赖它来满足所有电路板的要求。
AP_Terrain库就是一个很好的例子,该库保存了地形数据。地形数据太大,无法容纳在EEPROM中,并且是随机访问的,因此不适合 DataFlash。对于自动驾驶仪的基本功能而言,它也不是必需的,因此,它是使用Posix IO实现的理想选择。
我们使用Posix IO的方法是,首先通过检查AP_HAL_Boards.h中的HAVE_OS_POSIX_IO宏来检查开发板是否支持Posix IO。然后,要知道应在文件系统上的什么位置存储数据,请在AP_HAL_Boards.h中添加一个特定于数据的宏,该宏给出应放置此类数据的目录路径。例如,宏HAL_BOARD_TERRAIN_DIRECTORY用于定义地形数据应存放的目录。
一旦有了满足这两个需求,尽管有一些注意事项,您应该只使用普通的Posix IO功能(即打开,关闭,读取,写入等):
  • 只能从IO计时器或自己的低优先级线程调用IO功能。
  • 切勿从可用库的API中调用任何IO函数。甚至像stat()这样的简单对象。
  • 尝试减慢存储卡的速度,以合理大小的块进行IO,并尽可能避免寻找。
这些规则确实很重要, Pixhawk 上的 microSD 卡上的简单IO可能需要一秒钟的时间,这段时间足够的时间让您珍贵的四轴飞行器坠毁。 Pixhawk microSD 卡上 IO 的平均时间非常短(几毫秒),但是当 microSD 卡决定需要花费一些时间重新读取SD卡规定格并进行计算时,偶尔会收到缓慢的信号,不要因为大多数时候看起来很快而尝试进行一些小小的操作。唯一的例外是初始化功能,需要知道这些初始化功能只能在车辆启动或撤防时调用,此时稍微的延迟也可以被接受。
参考library / AP_Terrain / TerrainIO.cpp,看看它如何使用 Posix IO。请注意,它用于处理所有IO的小状态机都是通过AP_Terrain :: io_timer函数调用的。看看是否可以发现任何错误,并报告可能的错误!

    推荐阅读