Программирование ARM nRF5 SDK Flash Data Storage Thu, March 28 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

nRF5 SDK Flash Data Storage Печать
Добавил(а) microsin   

Модуль Flash Data Storage (FDS) это облегченная файловая система для хранилища на основе памяти FLASH чипа MCU, разработанная с минимизацией риска повреждения данных и для упрощения работы с энергонезависимым хранением данных. Данные сохраняются в файлах, состоящих из одной или большего количества записей. Записи содержат в себе актуальные данные, и могут быть записаны, удалены, обновлены или извлечены.

Концепция обработки данных как файлов обеспечивает высокий уровень абстракции. Вы можете использовать модуль FDS без подробного изучения реального формата данных, который используется внутри модуля. Вместо этого просто работайте с файлами и записями, используя модуль FDS как черный ящик.

Модуль FDS разработан для обеспечения следующих преимуществ:

• Минимизация риска доступа к поврежденным данным путем постоянной проверки: в случае пропадания питания данные потенциально могли бы быть записаны не полностью. Такая проверка гарантирует, что FDS обнаружит недостоверные данные, и никогда не передаст поврежденные данные пользователю.
• Предоставление (опционально) проверки CRC при открытии записи, чтобы гарантировать, что данные не изменились с момента их записи.
• Минимизация операция с памятью FLASH (обновление и удаление): вместо удаления полных страниц FDS сохраняет копии новых данных и помечает как недостоверные те данные, которые стали не актуальными, путем записи одного слова.
• Базовое выравнивание износа памяти FLASH (wear leveling): gоследовательные записи и сбор мусора обеспечивают равномерный уровень использования страниц FLASH.
• Упрощение доступа к данным без их копирования, что делает доступ к данным независимым от размера данных.
• Минимизация использования памяти благодаря гибкому размеру данных.
• Предлагается полноценная гибкость работы с сохраняемыми данными: порция данных (запись) может состоять из нескольких кусков, которые не находятся в памяти друг за другом, но будут сохранены во FLASH вместе. Также нет ограничений на содержимое данных (это значит, что данные могут содержать в себе в том числе и специальные символы).

FDS использует модуль Flash Storage [2] как драйвер (backend) записи в память FLASH. В свою очередь Flash Storage полагается на библиотеку двоичного кода SoftDevice для выполнения реальной записи. FDS поддерживает синхронные операции чтения и асинхронные операции записи.

[Функционал FDS]

Flash Data Storage API [3] предоставляет функции для манипуляции файлами и записями файлов. Файлы состоят из одной или большего количества записей (record), которые содержат реальные данные.

Каждая запись идентифицируется ключом (key), и назначается файлу через идентификатор файла (file ID). Файлы в основном представляют собой группы записей. Ни ключи записей (record keys), ни идентификаторы файла (file IDs) не должны быть уникальными, и файлы могут содержать в себе несколько записей с одинаковым ключом. К записям можно получить доступ через любую комбинацию file ID и record key.

Например, приложение может использовать следующие два файла:

Файл 1 с двумя записями: 0x1111="Phone1", 0x2222="data: 12345"
Файл 2 с тремя записями: 0x1111="Tablet1", 0x2222="data: abcdef", 0x2222="data: 67890"

Затем можно выполнить итерацию, например, через все записи в файле 1, через все записи с ключом 0x1111, или через все записи с ключом 0x2222 в файле 2.

Создание записей. Когда вписывается новая запись во FLASH, необходимо предоставить record key, file ID, и данные для сохранения в записи. Вместо немедленной модификации FLASH можно также зарезервировать память, и использовать полученный токен резервирования либо чтобы выполнить запись позже, либо чтобы снова отменить резервирование.

Функция модификации хранилища вернет дескриптор записи (record descriptor), который можно использовать для последующего доступа к этой записи. Перед использованием этого дескриптора подождите возникновения события, которое сигнализирует о том, что операция модификации хранилища (write operation) была выполнена успешно.

Манипулирование записями. для чтения, обновления или удаления содержимого записи Вы должны осуществить доступ к записи через её дескриптор. Этот дескриптор создается и возвращается, когда Вы делаете первую операцию write в память FLASH для записи (record). После того, как record была создана, Вы можете получить её дескриптор путем вызова одной из функций поиска записи (fds_record_find, fds_record_find_by_key или fds_record_find_in_file). Эти функции позволят Вам найти нужную запись на основе её ключа (record key) и идентификатора файла (file ID).

Не существует требования к уникальности ключа или идентификатора файла. Таким образом, запросу может соответствовать больше чем одна запись. Функции поиска записи возвратят одно совпадение на каждый вызов, и одновременно отслеживают ход выполнения операции поиска записей. Они возвратят токен статус, который кодирует последнее совпадение; этот токен может использоваться для последующих вызовов, чтобы продолжить итерацию поиска с текущей позиции. Таким образом можно выполнить итерацию по всем совпадениям, если вызывать функцию поиска с указанием этого токена до тех пор, пока не будут просмотрены все совпадения с запросом поиска. См. далее секцию "Получение данных" в разделе "Использование FDS", где показано, как провести энумерацию всех записей по указанным key и file ID.

Чтение записей. Вы можете прочитать содержимое записи (как сохраненных данных, так и метаданных, если необходимо) напрямую из хранилища FLASH. Это означает, что приложение само решает, должны ли эти данные быть скопированы, сохранены в RAM, либо должны использоваться по месту из хранения.

Чтобы получить доступ к содержимому записи, откройте запись для получения указателей на данные записи (record data) или метаданные (metadata), сохраненные в память FLASH. Функция fds_record_open гарантирует, что эта запись не будет модифицирована или перемещена в другое место FLASH, когда к ней осуществляется доступ. Помните о необходимости закрыть запись (close record), чтобы освободить эту блокировку после чтения записи.

Обновление записей. Когда запись обновляется, FDS в действительности просто создает новую запись (create new record), и помечает старую запись как недостоверную (invalidate old record). Функция обновления вернет дескриптор новой записи для обновляемой записи.

Имейте в виду, что из-за используемого в коде FDS механизма обработки обновления записей частые изменения данных записи (record data), ключа (key) или идентификатора файла (file ID) может переполнить хранилище FLASH, и тогда может потребоваться освободить место (см. далее "Сборка мусора").

Удаление записей. Удаление record на самом деле не удаляет её данные и не очищает используемую записью область FLASH, вместо этого запись помечается как "недостоверная" (invalidate record). После того, как запись была удалена, она больше не может быть открыта, прочитана или найдена.

Однако пространство, где находилась удаленная запись, физически не освобождается и не очищается. Чтобы освободить пространство удаленных записей необходимо запустить сборку мусора (garbage collection).

Сборка мусора. Под мусором здесь подразумевается области памяти FLASH, которые были заняты ранее удаленными или обновленными записями. Вместо реального удаления записей код FDS полагается на процедуру сборки мусора (garbage collection), чтобы забрать обратно области FLASH, которые использовались записями, помеченными как invalidated. Сборка мусора обычно требует больше операций с памятью FLASH, что может стоить дорого в контексте потребления энергии.

Таким образом, сборка мусора не запускается кодом FDS автоматически, так что эта процедура должна быть запущена по инициативе приложения. Идеальный случай - запуск сборки мусора тогда, когда низка активность BLE. Однако сборку мусора следует выполнять только при необходимости, когда FLASH почти заполнена. Когда свободное пространство FLASH исчерпано, запросы записи вернут ошибку FDS_ERR_NO_SPACE_IN_FLASH, и тогда необходимо запустить сборку мусора и подождать её завершения перед попыткой снова вызвать операцию write.

Конфигурация. Существует несколько опций настройки модуля FDS, которые можно определить во время компиляции. Для этого в заголовке fds_config.h можно поменять следующие макросы, чтобы модуль FDS подошел определенным требованиям для использования:

FDS_OP_QUEUE_SIZE: размер внутренней очереди операций FDS, это некий кеш для операций FDS. Увеличьте размер, если много пользователей, или если приложение будет запрашивать много операций за раз, чтобы не ждать завершения предыдущих операций. Обычно нужно увеличить размер очереди, если часто получаете при вызовах функций сообщение об ошибке FDS_ERR_NO_SPACE_IN_QUEUES.

FDS_CHUNK_QUEUE_SIZE: количество кусков записи (порций данных записи) которое можно буферизировать. Увеличьте этот параметр, если часто получаете код ошибки FDS_ERR_NO_SPACE_IN_QUEUES, и Ваши данные обычно состоят из множества кусков.

FDS_VIRTUAL_PAGE_SIZE: размер виртуальной страницы. По умолчанию у виртуальной страницы тот же размер, что и у физической страницы FLASH, но можно увеличить размер виртуальной страницы, чтобы можно было сохранять данные по размеру больше, чем размер физической страницы (см. ограничение на максимальную длину в разделе "Формат хранилища FDS").

FDS_VIRTUAL_PAGES: количество виртуальных страниц для использования. Общее количество используемой FLASH зависит от размера и количества виртуальных страниц.

FDS_MAX_USERS: максимальное количество callback-функций, которое можно зарегистрировать. Это определяет, как много модулей может использовать FDS одновременно. Увеличьте это значение, если получаете ошибку FDS_ERR_USER_LIMIT_REACHED, чтобы можно было увеличить количество зарегистрированных пользователей FDS.

Дополнительно можно установить следующие флаги компиляции:

FDS_CRC_ENABLED: если установлен, то FDS будет проверять запись перед открытием её для чтения. Дополнительно можно включить проверку, чтобы гарантировать, что данные не были изменены запросом операции write для записи и реальной операцией write (см. fds_verify_crc_on_writes). Разрешение флага FDS_CRC_ENABLED незначительно увеличит размер кода.

FDS_THREADS: если установлен, то модуль FDS можно использовать несколькими модулями одновременно (например, кодом Peer Manager и кодом приложения). Разрешение FDS_THREADS увеличит размер кода. Имейте в виду, что эта функция экспериментальная, и не должна использоваться для выпуска конечной продукции.

Ограничения для ключей и ID. Записи ключей (record key) должны быть в диапазоне 0x0001 - 0xBFFF. Значение 0x0000 зарезервировано для использования системой. Значения 0xC000 .. 0xFFFF зарезервированы модулем Peer Manager, и могут использоваться только приложениях, которые не применяют Peer Manager.

Идентификаторы файлов (file ID) должны быть в диапазоне 0x0000 - 0xBFFF. Значение 0xFFFF используется системой. Значения 0xC000 .. 0xFFFE для использования модулем Peer Manager, и могут использоваться только в тех приложениях, которые не применяют Peer Manager.

[Формат хранилища FDS]

Flash Data Storage сохраняет данные в записи (records), которые группируются в файлы. В большинстве случаев не требуется вникать в детали того, как FDS сохраняет данные в память FLASH. Далее во врезке приведена некоторая информация о внутреннем формате данных, используемых FDS.

Записи (record) состоят из заголовка (header, или record metadata) и реального содержимого. Эти данные сохраняются друг за другом последовательно во FLASH, в порядке следования операций write.

Рис. 1. Структура данных записи (record).

Record header. Заголовок записи (header) состоит и трех слов (12 байт), используемых следующим образом:

Поле Размер Описание
Record key 16 бит Ключ, который может использоваться для поиска записи. Значение FDS_RECORD_KEY_DIRTY (0x0000) зарезервировано системой для пометки тех записей, которые становятся не актуальными (invalidated records) - после обновления или удаления записи. См. выше секцию "Ограничения для ключей и ID" для дополнительных ограничений.
Data length 16 бит Длина данных, которые сохранены в записи (в 4-байтных словах).
File ID 16 бит Идентификатор (ID) файла, с которым связана запись. Значение FDS_FILE_ID_INVALID (0xFFFF) используется системой для идентификации записей, которые не были корректно записаны. См. выше секцию "Ограничения для ключей и ID" для дополнительных ограничений.
CRC 16 бит Значение CRC всей записи целиком (проверки могут быть разрешены установкой флага компиляции FDS_CRC_ENABLED, см. выше секцию "Конфигурация".
Record ID 32 бита Уникальный идентификатор (ID) записи.

Когда в память FLASH записывается record header, FDS вписывает сначала ключ записи (record key) и длину данных (data length), а затем идентификатор записи (record ID). Значение file ID и CRC записываются последними, и финализируют успешную операцию write. Когда происходит сканирование по записям, модуль FDS игнорирует все записи, у которых второе слове заголовка не было вписано, потому что отсутствие ключа показывает, что record не была сохранена полностью.

Максимальная длина. Максимальная длина записи зависит от размера виртуальной страницы FLASH (определяется в заголовке fds_config.h, см. макроопределение FDS_VIRTUAL_PAGE_SIZE), размера тэга страницы (page tag, 2 слова) и размера record header (3 слова). По умолчанию размер виртуальной страницы устанавливается равным размеру физической страницы FLASH (1024 слова), что дает максимальную длину полезных данных 1019 слова.

Чтобы сохранить большее количество данных, увеличьте размер виртуальной страницы, или используйте модуль Flash Storage [2] вместо FDS.

Тэг страницы. Каждая виртуальная страница, используемая FDS, промаркирована тэгом (page tag), который используется системой для хранения информации об этой странице. Page tag, занимающий 2 слова (64 бита, 8 байт) содержит информацию о том, для чего используется страница (хранилище информации или сбор мусора), и какая версия файловой системы установлена на странице.

Используются следующие page tag:

Word 0 Word 1 Описание
0xDEADC0DE 0xF11E01FF Страница, используемая для swap во время сбора мусора.
0xDEADC0DE 0xF11E01FE Страница, используемая для хранения данных.

Page tag записываются, когда FDS инициализируется первый раз, и обновляются только во время процесса сбора мусора.

[Использование FDS]

Следующие примеры кода показывают типовое использование Flash Data Storage в приложении.

Инициализация модуля. Инициализация выполняется асинхронно, в отличие от всех других операций FDS, которые выполняют операции записи (write) или стирания (erase) памяти FLASH. О завершении операции инициализации сообщается приложению путем вызова callback-функции. Таким образом, перед инициализацией FDS сначала нужно зарегистрировать callback-функцию для обработки событий (FDS events).

Следующий пример кода показывает, как регистрировать простой обработчик события (event handler) для модуля FDS и инициализировать модуль FDS:

// Простой обработчик событий для обработки событий ошибки во время инициализации.
static void fds_evt_handler(fds_evt_t const * const p_fds_evt)
{
   switch (p_fds_evt->id)
   {
   case FDS_EVT_INIT:
      if (p_fds_evt->result != FDS_SUCCESS)
      {
         // Ошибка инициализации.
      }
      break;
   default:
      break;
   }
}
 
ret_code_t ret = fds_register(fds_evt_handler);
if (ret != FDS_SUCCESS)
{
   // Регистрация обработчика событий FDS потерпела неудачу.
}
 
ret_code_t ret = fds_init();
if (ret != FDS_SUCCESS)
{
   // Обработка ошибки.
}

Операция инициализации вызовет событие оповещения об успехе или ошибке. В модуле Peer Manager [3] вызов fds_init работает как часть его функции инициализации pm_init.

Write record. Следующий пример показывает, как реализована операция записи:

#define FILE_ID     0x1111
#define REC_KEY     0x2222
 
static uint32_t const m_deadbeef = 0xDEADBEEF;
 
fds_record_t        record;
fds_record_desc_t   record_desc;
fds_record_chunk_t  record_chunk;
 
// Подготовка данных:
record_chunk.p_data         = &m_deadbeef;
record_chunk.length_words   = 1;
// Подготовка record:
record.file_id                  = FILE_ID;
record.record_key               = REC_KEY;
record.record_data.p_chunks     = &record_chunk;
record.record_data.num_chunks   = 1;
 
ret_code_t ret = fds_record_write(&record_desc, &record);if (ret != FDS_SUCCESS)
{
   // Обработка ошибки.
}

Команда ставится в очередь, и об успехе или ошибке операции команды будет сообщено путем вызова обработчика события (event callback). При успехе функция fds_record_write вернет дескриптор для record, который может использоваться впоследствии для манипуляций с record.

См. выше секцию "Ограничения для ключей и ID" для получения информации по допустимым record key и file ID.

Получение данных. Следующий пример кода показывает, как использовать функционал поиска записи (find record), чтобы получить дескрипторы всех record, которые соответствуют указанным key и file ID, и прочитать содержимое этих record:

#define FILE_ID     0x1111
#define REC_KEY     0x2222
 
fds_flash_record_t  flash_record;
fds_record_desc_t   record_desc;
fds_find_token_t    ftok;
 
memset(&ftok, 0x00, sizeof(fds_find_token_t));
 
// Цикл по всем записям, у которых найден указанный key и file ID:
while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS)
{
   if (fds_record_open(&record_desc, &flash_record) != FDS_SUCCESS)
   {
      // Обработка ошибки.
   }
 
   // Доступ к записи через структуру flash_record:
   ...
 
   // По завершении закроем запись:
   if (fds_record_close(&record_desc) != FDS_SUCCESS)
   {
      // Обработка ошибки.
   }
}

Закрытие record вызовом функции fds_record_close оставляет актуальными дескриптор записи (record descriptor) и её структуру fds_flash_record_t. Дескриптор записи все еще можно использовать для манипуляции над записью (manipulate record), например можно открыть эту запись снова или удалить её. Однако данные, на которые указывает структура fds_flash_record_t, могут поменяться в любой момент после закрытия записи. Таким образом, если необходимо осуществить доступ к данным record после её закрытия, необходимо заново открыть record.

Удаление record. Следующий пример показывает, как удалить запись (delete record):

/* Предполагается, что дескриптор record_desc был возвращен вызовом fds_record_write()
   или fds_record_find(), как было показано в предыдущем примере. */
ret_code_t ret = fds_record_delete(&record_desc);
if (ret != FDS_SUCCESS)
{
   // Ошибка.
}

Операция удаления ставится в очередь, и об её успехе или неудаче будет сообщено вызовом обработчика события (event callback).

Вызов fds_record_delete не освобождает занимаемую область памяти FLASH (где находилась удаляемая запись). Чтобы получить обратно эту область FLASH, запустите обработку мусора (fds_gc).

[Ссылки]

1. nRF5 SDK v13 Flash Data Storage site:nordicsemi.com.
2. nRF5 SDK Flash Storage.
3nRF5 SDK Peer Manager.

 

Добавить комментарий


Защитный код
Обновить

Top of Page