Search
Write a publication
Pull to refresh

Пуск LittleFS (NVRAM с запретом до-записи flash)

Level of difficultyEasy
Reading time16 min
Views1.2K

Любая вещь лучше, когда внутри неё есть NVRAM.

Пролог

В программировании MCU порой приходится решать по-настоящему сложные задачи. При этом сложность как с алгоритмической точки зрения так и в объёме кода, который предстоит написать.

Вот вам яркий пример. Надо запустить NVRAM на микроконтроллере, где нет возможности дописывать интервалы памяти (это например FC7300x). Сказать по честному, не всякий программист МК способен разработать такой NVRAM. Чтобы сделать NVRAM при условии запрета до-записи нужна не хилая алгоритмическая подготовка (двух-связанные списки, деревья, дефрагментация памяти и прочее).

Существует два подхода к программированию

  • Писать всё самому. Это сложно. Зато вы контролируйте каждую строку кода в проекте и можете проводить очень гибкую товарную политику.

  • Портировать программные компоненты из Open-Source (third-party). Это быстро. Но вы теряете контроль над функционалом.

Однако, как ни крути, но порой приходится работать с third-party кодом. И вот настал такой случай.

Постановка задача

Организовать NVRAM на микроконтроллере с одноразовой flash память. Осуществить пуск файловой системы LittleFs на микроконтроллере. Понять, как конфигурировать LittleFs.

Требования к NVRAM
1--Позволять записывать данные произвольной длинны (а не только степени двойки).
2--Не позволять до записывать страницы Flash памяти.
3--NVRAM должен поддерживать Lazy Write. Не писать данные, если они там уже есть.
4--Если стирать flash, то секторами по 8k Byte
5--Если писать flash, то только страницами 128 байт.
6--Писать по выровненному адресу (128 byte)
7--Стирать по выровненному адресу (8k Byte)
8--Собирать код на Си
9--Рациональное использование Nor-Flash памяти (endurance optimization). Не писать всегда в одно место
10--Должна быть зашита данных от внезапного пропадания электропитания (power off tolerance).
11--Простота реализации чтобы нечему было ломаться.
12--Файл должен быстро находится по имени (<100ms)
13--Файл должен быстро записываться (<100ms)
14--Файл должен быстро стираться (<100ms)
15--Компактность кода, который реализует этот алгоритм NVRAM. Чем меньше кода, тем больше файлов получится загрузить.

Вот такие ограничения. Нормально так, правда?

Определения

Чтобы правильно понять сорцы LittleFs надо четко осознавать вот эти понятия.

выровненный адрес - адрес, который может принимать значения с некоторым периодом начиная с нуля. Периоды выровненных адресов как правило - это значения степеней двойки: 128 или 8192.

Сектор памяти - выровненный кусок памяти размером 8kByte

third-party - код со стороны. Программный компонент который вы скачали из интернета.

Блок - это по сути и есть сектор flash памяти. LittleFs стирает блоками. В моём микроконтроллере блок получается равен 8kByte

Страница - минимальный возможный размер для записи. В моем нынешнем микроконтроллере страница равна 128 байт. Страница памяти - выровненный кусок памяти размером 128байт.

NVRAM - энергонезависимая память (NV) с произвольным доступом (RAM). По сути Key Val-Map(ка). В ней могут храниться любые бинарные данные, ассоциированные со своим ID числом.

dword (double word) — это 4х байтная переменная (32 бит).

word — это двухбайтная переменная (16 бит)

byte — это 8ми битная переменная

данные - массив hex байтов с указанной длинной

File - это именованный бинарный массив байтов в памяти. В качества памяти может выступать RAM, ROM (Flash), FRAM, EEPROM, SD карты, компакт диски, дискеты, магнитная лента, стекло. Да всё что угодно. Что значит именованный? Это значит, что к этим данным можно обращаться по значению. Пусть это будет натуральное 32-битное число. Так проще. Так как это массив, то очевидно рядом с данными также надо хранить и длину этого массива.

Lazy write - это алгоритм записи, который записывает данные только в том случае, если они реально отсутствуют в памяти. Если такие данные уже там лежат, то процедура write просто ничего не делает, и возвращает OK. Это как если вы хотите записать новый номер в телефон, а он уже там есть, то вы не будете записывать его снова.

Исследование вопроса
FatFs не подходит так как она не обеспечивает устойчивости к отключению питания. B FatFs отсутствует механизм выравнивая износа массивов Flash памяти. Обычно FatFs запускают на SD картах которые и реализуют механизм износа Flash памяти.

Реализация

Десять лет назад я был свидетелем как у нас выкручиваются от отсутствия полноценного NVRAM. Выглядит это так. Схемотехники в плату закладывают 3 или 6 GPIO пинов и джамперами выставлять бинарный код на GPIO, чтобы при старте дать прошивке какую-то команду. Получается 1х3х6=18 кубических миллиметров на 1 бит. Нормально так, да? NVRAM на джамперах - это ярчайший пример лютого технического отчаяния. В этом же тексте я написал, как запрограммировать полноценную NVRAM c минимальными усилиями.

У меня уже были тексты про различные алгоритмические реализации NVRAM. Вот они

Название

URL

Тип

NVRAM для микроконтроллеров

https://habr.com/ru/articles/706972/

on-chip

NVRAM Поверх off-chip SPI-NOR Flash

https://habr.com/ru/articles/732442/

off-chip

NVRAM из EEPROM

https://habr.com/ru/articles/815639/

on-chip

Все эти алгоритмы реализации NVRAM работают только при условии, что в данном микроконтроллере можно взять и до-записывать адреса Flash памяти. Однако надо отметить, что такого люкса на многих семействах микроконтроллеров просто нет в помине! Да... Во накануне мне попался MCU с одноразовой Flash памятью. Если бы не ограничения Flash я бы выбрал имеющийся отлаженный алгоритм и дело с концом. Однако ограничения сподвигли искать различные альтернативные механизмы построения файловых систем на микроконтроллере.

После широчайшего и интенсивного исследования вопроса я обнаружил, что существует такой open-source проект, как LittleFs. Решил попробовать его освоить.

Ликбез по LittleFs

Little Fs - это небольшая отказоустойчивая файловая система, разработанная специально для микроконтроллеров и написана на чистом Си (С99). Обладает тремя ключевыми характеристиками:

Устойчивость к сбоям питания — littlefs разработана для обработки случайных сбоев питания. Все файловые операции имеют надежную гарантию копирования при записи, и в случае отключения питания файловая система возвращается к последнему работоспособному состоянию. Динамическое выравнивание износа — littlefs разработана с учётом флэш-памяти и обеспечивает выравнивание износа динамических блоков. Кроме того, littlefs может обнаруживать сбойные блоки и обходить их. Ограниченный объём ОЗУ/ПЗУ — littlefs разработана для работы с небольшим объёмом памяти. Использование ОЗУ строго ограничено, что означает, что потребление ОЗУ не меняется по мере роста файловой системы. Файловая система не содержит неограниченной рекурсии, а динамическая память ограничена настраиваемыми буферами, которые могут быть предоставлены статически.

Сначала я попробовал погонять LittleFs в виде консольного Win приложения на своем LapTop-е. По началу не было похоже, чтобы Little FS вообще работал. После block_cycles раз записи и чтения файла, файловая система падает и перестает, чтобы бы то ни было делать. Оказалось я забыл добавлять к вычислению физического адреса слагаемое offset. Затем я обнаружил, что LittleFs успешно пишет читает до первого переполнения сектора. Оказывается, что при чтении и записи надо было домножить блок не на 128 байта, а на 8192 байта. В результате кристаллизировался корректный код конфига для МК FC7300. Вот он.

#include "little_fs_config.h"

#include "data_utils.h"
#include "sw_nor_flash.h"
#include "log.h"

#define LITTLE_FS_NOR_FLASH_NUM 1
#define LITTLE_FS_BLOCK_SIZE  8192
#define LITTLE_FS_PAGE_SIZE  128
#define LITTLE_FS_CACHE_SIZE (LITTLE_FS_PAGE_SIZE)


#ifdef LFS_NO_MALLOC
// Optional statically allocated read buffer.
// Must be cache_size.
// By default lfs_malloc is used to allocate this buffer.
static uint8_t readBuffer[LITTLE_FS_CACHE_SIZE] = {0};
static uint8_t progBuffer[LITTLE_FS_CACHE_SIZE] = {0};
static uint8_t lookaheadBuffer[LITTLE_FS_PAGE_SIZE] = {0};
#endif

static int FlashToLittleFsRet(bool res) {
    int ret = LFS_ERR_OK;
    if (false == res) {
        ret = LFS_ERR_CORRUPT;
    }
    return ret;
}

static int user_provided_block_device_prog(const struct lfs_config* c,
                                           lfs_block_t block,
                                           lfs_off_t off,
                                           const void* buffer,
                                           lfs_size_t size) {
    int ret = LFS_ERR_CORRUPT;
    bool res = false;
    LOG_NOTICE(LITTLE_FS, "Write,Block:%u,OffSet:%u,Size:%u", 
               block, off, size);
    uint32_t phy_address = 0;
    phy_address = block * LITTLE_FS_BLOCK_SIZE + off;
    res = sw_nor_flash_write(LITTLE_FS_NOR_FLASH_NUM,
                             phy_address,
                             (uint8_t*) buffer,
                             size);
    ret = FlashToLittleFsRet(res);
    return ret;
}

static int user_provided_block_device_read(const struct lfs_config* c,
                                           lfs_block_t block,
                                           lfs_off_t off,
                                           void* buffer,
                                           lfs_size_t size) {
    int ret = 0;
    LOG_DEBUG(LITTLE_FS, "Read,Block:%u,OffSet:%u,Size:%u", 
              block, off, size);
    uint32_t phy_address = 0;
    phy_address = block * LITTLE_FS_BLOCK_SIZE + off;
    bool res = sw_nor_flash_read(LITTLE_FS_NOR_FLASH_NUM,
                                 phy_address,
                                 (uint8_t*) buffer,
                                 (uint32_t) size);
    ret = FlashToLittleFsRet(res);
    return ret;
}

static int user_provided_block_device_erase(const struct lfs_config* c,
                                            lfs_block_t block) {
    int ret = LFS_ERR_CORRUPT;
    LOG_WARNING(LITTLE_FS, "EraseBlock:%u", block);
    uint32_t phy_address = block * LITTLE_FS_BLOCK_SIZE;
    bool res = sw_nor_flash_erase_mem(LITTLE_FS_NOR_FLASH_NUM,
                                      phy_address,
                                      LITTLE_FS_BLOCK_SIZE);
    ret = FlashToLittleFsRet(res);
    return ret;
}

static int user_provided_block_device_sync(const struct lfs_config* c) {
    int ret = LFS_ERR_OK;
    LOG_INFO(LITTLE_FS, "Sync");
    return ret;
}

const LittleFsConfig_t LittleFsConfig[] = {
        { .num = 1, .valid = true, .name = "LittleFs1",
          .cfg = {
                 // block device operations
              .read = user_provided_block_device_read,
              .prog = user_provided_block_device_prog,
              .erase =                user_provided_block_device_erase,
              .sync = user_provided_block_device_sync,
        #ifdef LFS_NO_MALLOC
              .read_buffer = readBuffer,
              .prog_buffer = progBuffer,
              .lookahead_buffer = lookaheadBuffer,
        #endif
              // block device configuration
              .read_size = LITTLE_FS_PAGE_SIZE,
              .prog_size = LITTLE_FS_PAGE_SIZE,
              .cache_size = LITTLE_FS_CACHE_SIZE,
              .lookahead_size = LITTLE_FS_PAGE_SIZE,
              .block_cycles = 4,
              .block_size = LITTLE_FS_BLOCK_SIZE,
              .block_count = 4,
        },
    },
};

Скорее всего Вы захотите просматривать содержимое папок. Это можно сделать так.

bool little_fs_list(uint8_t num, const char* const path) {
    bool res = false;
    LOG_INFO(LITTLE_FS, "%u,path:[%s]", num, path);

    const table_col_t cols[] = { { 5, "N" }, { 17, "name" },
    { 9, "size" }, { 5, "type" }, { 5, "type" },

    };
    LittleFsHandle_t *Node = LittleFsGetNode(num);
    if(Node) {
        uint32_t cnt = 0;
        lfs_dir_t dir = { 0 };
        int err = lfs_dir_open(&Node->lfs, &dir, path);
        res = LittleFsRetToRes(err);
        if(err) {
            table_header(&(curWriterPtr->stream), cols, ARRAY_SIZE(cols));
            struct lfs_info FileInfo = { 0 };
            bool loop = true;
            while(loop) {
                int ret = lfs_dir_read(&Node->lfs, &dir, &FileInfo);
                if(ret < 0) {
                    LOG_ERROR(LITTLE_FS, "ReadDirErr");
                    loop = false;
                    break;
                } else {
                    LOG_DEBUG(LITTLE_FS, "ReadOk %s", 
                              LittleFsFileInfoToStr(&FileInfo));
                    cnt++;
                }

                if(0 == ret) {
                    loop = false;
                    break;
                }

                char temp[150] = { 0 };
                strcpy(temp, TSEP);
                snprintf(temp, sizeof(temp), "%s %3u " TSEP, temp, cnt);
                snprintf(temp, sizeof(temp), "%s %15s " TSEP, temp, FileInfo.name);
                snprintf(temp, sizeof(temp), "%s %7u " TSEP, temp, FileInfo.size);
                snprintf(temp, sizeof(temp), "%s %3u " TSEP, temp, FileInfo.type);
                snprintf(temp, sizeof(temp), "%s %3s " TSEP, temp, LittleFsTypeToStr(FileInfo.type));
                cli_printf("%s"CRLF, temp);
            }

            table_row_bottom(&(curWriterPtr->stream), cols, ARRAY_SIZE(cols));
            err = lfs_dir_close(&Node->lfs, &dir);
            if(err) {
                res = false;
            }
        } 
    } 

    return res;
}

Можно прямо в консоль печатать содержимое корневой директории

Пуск NVRAM

Файловая система запущена. Мы можем писать и читать именованные массивы в энерго-независимую Flash память микроконтроллера. Красота! Однако это далеко не всё. Теперь надо как-то интерпретировать сохраненные данные. Надо добавить NVRAM. Зачем нужен NVRAM? Задача NVRAM в следующем:
1—прописать значения по умолчанию, если их не оказалось в памяти файловой системы при пуске устройства.
2—указать как интерпретировать бинарный массив данных лежащий в файловой системе в реальную физическую переменную.

Основная идея в том, чтобы для каждой записи NVRAM создавать бинарный файл с расширением *.nvram (или *.nv). Имя файла NVRAM имеет однообразную структуру. Вот например файл ID_123.nvram - это NVRAM запись с индексом 123. Именно по номеру ID отдельный программный компонент NVRAM производит синтаксический разбор того, что записано внутри бинарного файла. Вот небольшая подсказка. Это по сути LookUp таблица по которой вы поймете как именно распознавать то, что вы прочитаете из LittleFs-файла.

ID

тип данных

размер

Назначение

Units

Начальное значение по умолчанию

60

UINT32

4

MaxUpTime

ms

0

1

UINT32

4

reboot counter

enum

0

12

UINT8

1

Booloader command

N

launch

25

UINT32

4

Serial number

N

1

11

UINT32

4

boot address

phy address

0x0800_0000

3

UINT8

1

Time Zone

N

+10

2

UINT32

4

core frequency

Hz

250MHz

4

Array

6

MAC address

array

00:00:00:00:00:00

5

UINT32

4

IP address

array

192.168.1.1

6

string

N

SSID for WiFi

text

pentagon

7

string

N

WiFi password

text

1234

71

UINT32

4

WatchDog period

ms

3000

70

UINT8

1

WatchDog ctrl

on/off

on

8

UINT32

4

App CRC32

N

0x55555555

9

UINT32

4

App LEN

bytes

1

Эти переменные можно пополнять по мере развития проекта и заносить в константный массив структур. Например при работе с радио трансиверами Вы можете хранить в NVRAM настройки модуляции и мощности. Или ключи шифрования. Тут важно лишь то, чтобы все ID были разные. Поэтому их надо определить в перечислении Си (enum).

В особо ответственных системах можно запрограммировать три или пять экземпляров NVRAM. Писать одно и тоже, а выбирать голосованием тот результат чтения, который совпал в большем числе раз. Это классическое мажорирование.

Перенос кода LittleFS на микроконтроллер

Вот я полностью отладил NVRAM на LapTop-е. Теперь я просто переписываю конфиг и этот же код начинает работать на MCU.

Как можно заметить, внутри FC7300F8MDT 8 мегабайт on-chip памяти. Про это указывает токен 8M в самом названии микросхемы. По меркам разработок на микроконтроллерах 8 MByte on-chip памяти - это очень много. Можно свободно писать циклопические прошивки. Понятное дело, что помимо самой программы (прошивки) в чипе можно хранить и полезные данные (настройки, конфигурационные данные, установки, калибровочные данные, наработки на отказ и прочее, журнал, черный ящик). Поэтому попробуем запустить on-chip NVRAM внутри FC7300F8MDT.

Типичные операции для работы с Flash памятью.

Процедура

Изменяет память

минимальный размер данных операции

program

да

128

erase

да

8192

read

нет

1

Чтение из on-chip Flash

Процедура чтения не имеет никаких сложностей. На любых ARM Cortex микроконтроллерах для того, чтобы прочитать Flash память достаточно всего лишь обратиться к памяти по указателю. Это происходит потому, что Flash память доступна по физическим адресам памяти микроконтроллера. Как говорят: "Flash память map-ится на карту памяти."

Запись в on-chip Flash

Микроконтроллер FC7300F8MDT накладывает следующие ограничения:
1--за один раз можно записать массив длинной 128 байт
2--запись должна начинаться по выровненному адресу
3--Нельзя до-записать страницу. Если в странице хоть один бит из 1024 бит в нуле, то страница полностью использована. Повторная запись страницы приведет к непредсказуемому результату. Проше говоря писать можно только в те страницы, которые полностью покрыты 0xFF.

В SDK от производителя API устроено так, что можно записывать только по началу сектора. То есть первые 7 бит адреса должны быть равны нулю.

page

bin

hex

dec

1

0b0000_0000_0000

0

0

2

0b0000_1000_0000

0x80

128

3

0b0001_0000_0000

0x100

256

4

0b0001_1000_0000

0x180

384

Эксперименты в консоли показывают, что если записывать менее 128 байт, то данные-то запишутся, но всё, что будет дальше до конца страницы (128 байт) окажется в случайных символах. Если Вы попробуйте записать больше, чем 128байт (например 129 байт), то запишутся только первые 128 байт, а остальные будут проигнорированы. По факту, корректно происходит запись только страницы (128 байт). Что бы Вы ни писали, массив должен быть 128 байт. Да. Иначе в память запишутся случайные символы из стека.

Стирать on-chip Flash

Со стиранием Flash на FC7300F8MDT всё совсем весело. При помощи API из SDK cтирать DFLASH можно только по выровненному адресу по 8kByte (0x2000) за раз. Когда Flash память стерта, то она заполнена значениями 0xFF, то есть в каждой ячейке одни единицы. 

Учитывая всё это для микроконтроллера получился вот такой LittleFs конфиг.

#include "little_fs_config.h"

#include "data_utils.h"
#include "flash_mcal.h"
#include "little_fs.h"

#define LITTLE_FS_NOR_FLASH_NUM 1
#define LITTLE_FS_BLOCK_SIZE  8192
#define LITTLE_FS_PAGE_SIZE  128

#ifdef LFS_NO_MALLOC
static uint8_t StaticFileBuffer[LITTLE_FS_CACHE_SIZE] = {0};
static uint8_t readBuffer[LITTLE_FS_CACHE_SIZE] = {0};
static uint8_t progBuffer[LITTLE_FS_CACHE_SIZE] = {0};
static uint8_t lookaheadBuffer[LITTLE_FS_PAGE_SIZE] = {0};
#endif


static int flash_device_prog(const struct lfs_config* c,
                                           lfs_block_t block,
                                           lfs_off_t off,
                                           const void* buffer,
                                           lfs_size_t size) {
    int ret = LFS_ERR_CORRUPT;
    bool res = false;
    LOG_NOTICE(LITTLE_FS, "Write,Block:%u,OffSet:%u,Size:%u", block, off, size);
    LittleFsHandle_t* Node = LittleFsGetNode(1);
    if(Node) {
        uint32_t phy_address = 0;
        phy_address = Node->base_address  + block * LITTLE_FS_BLOCK_SIZE + off;
        res = flash_mcal_write(phy_address, (uint8_t*) buffer, size);
        ret = FlashToLittleFsRet(res);
    }
    return ret;
}

static int flash_block_device_read(const struct lfs_config* c,
                                   lfs_block_t block,
                                   lfs_off_t off,
                                   void* buffer,
                                   lfs_size_t size) {
    int ret = 0;
    LittleFsHandle_t* Node = LittleFsGetNode(1);
    if(Node) {
        LOG_PARN(LITTLE_FS, "Read,Block:%u,OffSet:%u,Size:%u", block, off, size);
        uint32_t phy_address = 0;
        phy_address =  Node->base_address   +block * LITTLE_FS_BLOCK_SIZE + off;
        bool res = flash_mcal_read( phy_address,
                                    (uint8_t*) buffer,
                                    (uint32_t) size);
        ret = FlashToLittleFsRet(res);
    }
    return ret;
}

static int flash_block_device_erase(const struct lfs_config* c,
                                    lfs_block_t block) {
    int ret = LFS_ERR_CORRUPT;
    LOG_WARNING(LITTLE_FS, "EraseBlock:%u", block);
    LittleFsHandle_t* Node = LittleFsGetNode(1);
    if(Node) {
        uint32_t phy_address = Node->base_address + block * LITTLE_FS_BLOCK_SIZE;
        bool res = flash_mcal_erase( phy_address,
                                     LITTLE_FS_BLOCK_SIZE);
        ret = FlashToLittleFsRet(res);
    }
    return ret;
}

static int flash_block_device_sync(const struct lfs_config* c) {
    int ret = LFS_ERR_OK;
    LOG_DEBUG(LITTLE_FS, "Sync");
    return ret;
}

static struct lfs_attr attrs={
        .type = 0,
        .buffer = NULL,
        .size = 0,
};

const LittleFsConfig_t LittleFsConfig[] = {
        {
          .num = 1,
          .base_address = DFLASH_START,
          .valid = true,
          .name = "LittleFsForNVRAM",
          .file_config = {
               .buffer= StaticFileBuffer,
               .attrs = &attrs,
               .attr_count=0,
          },
          .cfg = {
              .read = flash_block_device_read,
              .prog = flash_device_prog,
              .erase = flash_block_device_erase,
              .sync = flash_block_device_sync,
        #ifdef LFS_NO_MALLOC
              .read_buffer = readBuffer,
              .prog_buffer = progBuffer,
              .lookahead_buffer = lookaheadBuffer,
        #endif
              .read_size = LITTLE_FS_PAGE_SIZE,
              .prog_size = LITTLE_FS_PAGE_SIZE,
              .cache_size = LITTLE_FS_CACHE_SIZE,
              .lookahead_size = LITTLE_FS_PAGE_SIZE,
              .block_cycles = 4,
              .block_size = LITTLE_FS_BLOCK_SIZE,
              .block_count = DFLASH_SIZE/LITTLE_FS_BLOCK_SIZE,
        },
    },
};

LittleFsHandle_t LittleFsInstance[] = { { .num = 1, .valid = true, }, };

uint32_t little_fs_get_cnt(void) {
    uint8_t cnt1 = 0;
    uint8_t cnt2 = 0;
    cnt1 = ARRAY_SIZE(LittleFsConfig);
    cnt2 = ARRAY_SIZE(LittleFsInstance);
    if (cnt2 == cnt1) {
    }
    return cnt1;
}

Отладка

Инициализация прошла успешно.

Это диагностика переменных в памяти.

Достоинства NVRAM

Благодаря наличию в прошивке NVRAM перед вами открываются следующие возможности:

1++Вы можете до-программировать устройство уже в Run-time просто поменять конфигурацию в NVRAM.
2++Вы можете обновлять прошивку просто взяв ее из NVRAM
3++Вы можете передавать команды от приложения в загрузчик и наоборот. Например команду остаться в загрузчике или запуститься по другому смещению.
4++Ваше устройство может писать в NVRAM наработки на отказ
5++Вы можете сохранять в NVRAM логи, например, одометр автомобиля.
6++Простой и универсальный API. Можно сохранять данные любого размера по любому адресу (ключу).
7++При работе с NVRAM вам не надо беспокоиться о вероятном нахлёсте адресов и данных друг на друга. Вы можете для ID назначить любое число, а для длинны любое натуральное число.

Достоинства LittleFs

1++Написана на Си. Легко портировать в любой Си проект.
2++Dynamic wear leveling.
3++Power-loss resilience.

Недостатки LittleFs

1--Не собирается c GCC ключом -Werror=shadow.. Это детская ошибка написания Cи- кода почему-то просочилась с сорцы LittleFs.

2--LittleFs использует непереносимые типы данных. Тот же int может быть и 2 байта и 4 байта.

#ifndef LFS_NO_MALLOC
int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags) {
    int err = LFS_LOCK(lfs->cfg);
    if (err) {
        return err;
    }
    LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)",
            (void*)lfs, (void*)file, path, (unsigned)flags);
    LFS_ASSERT(!lfs_mlist_isopen(lfs->mlist, (struct lfs_mlist*)file));

    err = lfs_file_open_(lfs, file, path, flags);

    LFS_TRACE("lfs_file_open -> %d", err);
    LFS_UNLOCK(lfs->cfg);
    return err;
}
#endif

3--LittleFs - это open-source код. Поэтому 101% исходники LittleFs не будут соответствовать вашему внутреннему самобытному code-style.

4--У LittleFS нет Lazy Write. В переводе на кухонный, если вы будете последовательно записывать одно и то же (бит в бит), то Little FS будет буквально записывать это, вместо того чтобы просто вернуть OK и указать, что эти данные нет смысла записывать, так как они уже присутствуют в памяти. Это приводит к необоснованным жертвам ресурса полей Flash памяти.

Как видно, одни и те же данные прописываются дважды (((
Как видно, одни и те же данные прописываются дважды (((

5--LittleFs падает при попытке открыть на чтение удаленный файл. Чтобы не происходило заклинивания программы в assert-е, надо собирать код с ключом -DLFS_NO_ASSERT.

6--Чтобы просто добавить в сборку исходники LittleFS вам понадобится 30kByte Flash памяти. Это может оказаться очень много для микроконтроллеров с малыми ресурсами Flash.

7--LittleFS не подойдёт для stm32 так как у STM32 блоки разного размера. Разве что для секторов по 16kByte.

8--Невозможно прочитать LittleFS файл до вызова секвенции монтирования. Это проблема, когда Вы храните настройки тактирования в файле. Поэтому Вам надо инициализировать файловую систему до инициализации кварцевого резонатора. Вот такие пирожки с капустой.

Приложения LitteFS

1--SIM карты. Можно хранить телефонные номера на SIM карте.
2--Прошивки для микроконтроллеров. Можно в LittleFS файлике хранить целую прошивку и обновлять ее в bootloader-ах.
3--In-memory файловые системы. Можно собирать образ файловой системы и прописывать готовую файловую систему вместе с прошивкой на какой-н микроконтроллер или SPI-Flash ASIC.
4--Можно в NVRAM хранить настройки тактирования. И изменять тактирование во время исполнения прошивки без необходимости пере сборки всего проекта.

Итоги

Удалось научиться пользоваться программным компонентом LittleFS. Удалось организовать NVRAM поверх LittleFS.

Как видите, достоинство программирования на Си в том, что Вы можете одну и ту же программу разрабатывать отлаживать как на PC, так и на микроконтроллере.

Словарь

Акроним

Расшифровка

FS

File System

RAM (ОЗУ)

Random-Access Memory

NVRAM

Non-Volatile Random-Access Memory

ASIC

Application-Specific Integrated Circuit

BSD

Berkeley Software Distribution licenses

API

Application Programming Interface

COW

copy-on-write

Ссылки

Название

URL

littlefs technical specification

https://github.com/littlefs-project/littlefs/blob/master/SPEC.md

The design of littlefs

https://github.com/littlefs-project/littlefs/blob/master/DESIGN.md

FatFs - Generic FAT Filesystem Module

https://elm-chan.org/fsw/ff/00index_e.html

Сорцы LittleFs

https://github.com/littlefs-project/littlefs

Консольная утилита, чтобы погонять LittleFs как процесс в Windows

https://github.com/aabzel/Artifacts/tree/main/little_fs_on_pc

LittleFS File System with MCU Internal FLASH Memory

LittleFS File System with MCU Internal FLASH Memory | MCU on Eclipse

Мажоритарный элемент

https://ru.wikipedia.org/wiki/Мажоритарный_элемент

Дайте мне 15 минут, и я изменю ваш взгляд на GDB @Djivs

https://habr.com/ru/articles/824638/

LittleFS – компактная и экономичная файловая система для ARM микроконтроллеров в составе mbed os. Быстрый старт @Sub_Dia

https://habr.com/ru/articles/347348/

STM32 - LittleFS Flash File System Interfacing

https://merkles.com/wiki/index.php/STM32_-_LittleFS_Flash_File_System_Interfacing

Настройка ToolChain-a для программирования MCU FlagChip FC7300F8MDT

https://habr.com/ru/articles/742274/

Выпуск отказоустойчивой файловой системы LittleFS 2.10

https://www.opennet.ru/opennews/art.shtml?num=62398

Сравнение файловых систем

https://docs.google.com/spreadsheets/d/1kPGSk1cYiuqVTVtPJ4ygkyZVwmDAK6NuMpNz0YGr1M4/edit?gid=0#gid=0

Вопросы:

  1. Как отладить большой кусок кода, если нет возможности пройтись по коду пошаговым отладчиком? Надо покрыть код модульными тестами.

  2. Как организовать NVRAM при условии одноразовой записи страниц Flash? Когда микроконтроллер не позволяет до-записать уже прописанную страницу flash памяти. Использую LittleFS

  3. Увидит ли ПК файлы, если запрограммировать LittleFs на SD карте?

  4. Есть ли в LittleFs Lazy Write? Нет

  5. Как на Windows из micro-SD карты прочитать сырой дамп содержания? Это может быть полезно, если внутри какая-нибудь экзотическая файловая система, скажем та же LittleFS.

  6. Можно ли прочитать LittleFS файл до вызова процедуры монтирования? Нет

Only registered users can participate in poll. Log in, please.
Вы работали с LittleFS?
30% да3
70% нет7
10 users voted. 3 users abstained.
Only registered users can participate in poll. Log in, please.
Вы добавляете в прошивки NVRAM?
37.5% да3
62.5% нет5
8 users voted. 2 users abstained.
Tags:
Hubs:
+6
Comments15

Articles