В этом тексте я произвел обзор микросхемы SPI-NOR FLASH памяти MX25L6433F.

микросхема памяти MX25L6433F M2I-08Q 5B456800 в натуре
микросхема памяти MX25L6433F M2I-08Q 5B456800 в натуре

Из микросхемы торчит 8 пинов.

Вот более подробная справка по распиновке.

Корпус SOP-8 весьма удобен для отладки SPI драйвера при помощи прищепок SDK08. Для отладки чтения и записи в SPI Flash вам потребуется логический анализатор.

С точки зрения программиста микросхема MX25L6433F выглядит так.

Сейчас такую микросхему можно купить за 270 RUR. Получается 34 RUR за мегабайт.

Интерфейс MCU <---> ASIC

Связь с микросхемой происходит по интерфейсу SPI. Биты выхватываются по положительному перепаду на проводе тактирования. Полярность тактирования не имеет значения. Биты передаются старшим битом вперед.

Программная часть

Помимо полей с памятью микросхема MX25L6433F обладает четырьмя внутренними регистрами для конфигурирования и чтения статуса ASICa. Вот они перед вами.

Микросхема обладает широкой системой бинарных команд. Всего 26 команд. Однако для работы достаточно всего пяти команд: Write Enable, Read Status Register, Read Data Bytes, Sector Erase, Page Program.

Назначение микросхемы - читать и писать массивы. Разберемся с каждой операцией в отдельности.

Запись массива в микросхему

Перед записью надо разрешить модификацию памяти в статусном регистре. Под записью во flash понимается обнуление конкретных битов. Адрес куда писать указывается в формате big-endian. За один раз можно прописать только 256 байт.

Запись страницы выглядит так

/* 10-22. Page Program (PP) (page 28 )Figure 12. Program/Erase flow with read array data  */
bool mx25l6433f_page_program(uint8_t num, 
                             uint32_t address, 
                             const uint8_t* const data, 
                             uint32_t size) {
    bool res = false;
    LOG_INFO(MX25L6433F, "PageProg,Addr:0x%08x,Size:%u byte", address, size);
    Mx25l6433fHandle_t* Node = Mx25l6433fGetNode(num);
    if (Node) {
        res = mx25l6433f_write_activate(num, MX25_WR_TIMEOUT_MS);
        if (res) {
            gpio_logic_level_set(Node->chip_select, 0);
            res = mx25l6433f_write_cmd_address(num, MX25_CMD_PAGE_PROGRAM, address);
            if (res) {
                res = spi_mcal_write(Node->spi_num, data, size);
            }
            gpio_logic_level_set(Node->chip_select, 1);
            res = mx25l6433f_wait_write_done(num, MX25_WR_TIMEOUT_MS);
        }
    }
    return res;
}

Чтение массива

Читать можно по любому адресу. Адрес указывать в формате big-endian.

Чтение страницы

/* Read Data Bytes (READ)*/
bool mx25l6433f_read(uint8_t num, uint32_t address, uint8_t* data, uint32_t size) {
    bool res = false;
    Mx25l6433fHandle_t *Node = Mx25l6433fGetNode(num);
    if (data) {
        if (size) {
            gpio_logic_level_set(Node->chip_select, 0);
            res = mx25l6433f_write_cmd_address(num, MX25_CMD_NORMAL_READ, address);
            if (res) {
                res = spi_mcal_read(Node->spi_num, data, size);
            }
            gpio_logic_level_set(Node->chip_select, 1);
        }
    }
    return res;
}

Стереть 4k сектор

Перед стиранием надо разрешить стирание в статусном регистре. Стирание сектора подразумевает установку 0xFF в ячейки памяти. Команда 20h стирает весь сектор за раз. Это 4k Byte. Адрес должен быть выровненным.

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

стереть сектор можно функцией mx25l6433f_erase_sector

bool pack_24bit_big_endian(uint32_t address, uint8_t* const buff) {
    bool res = false;
    if (buff) {
        buff[0] = (address & 0xFF0000) >> 16;
        buff[1] = (address & 0xFF00) >> 8;
        buff[2] = (address & 0xFF);
        res = true;
    }
    return res;
}

static bool mx25l6433f_write_cmd_address(uint8_t num, Mx25l6433fCommands_t cmd, uint32_t address) {
    bool res = false;
    Mx25l6433fHandle_t *Node = Mx25l6433fGetNode(num);
    if(Node){  
        uint8_t buff[4] = { cmd, 0xFF, 0xFF, 0xFF };
        res = pack_24bit_big_endian(address, &buff[1]);
        if (res) {
            res = spi_mcal_write(Node->spi_num, buff, 4);
        }
    }
    return res;
}

/* 10-15. Sector Erase (SE) */
bool mx25l6433f_erase_sector(uint8_t num, uint32_t address) {
    bool res = false;
    LOG_WARNING(MX25L6433F, "MX25L6433F_%u,EraseError:Addr:0x%08x", num, address);
    Mx25l6433fHandle_t *Node = Mx25l6433fGetNode(num);
    if (Node) {
        res = mx25l6433f_write_activate(num, 1000);
        if (res) {
            gpio_logic_level_set(Node->chip_select, 0);
            wait_us(2);
            res = mx25l6433f_write_cmd_address(num, MX25_CMD_SECTOR_ERASE, address);
            gpio_logic_level_set(Node->chip_select, 1);
            wait_ms(25);
            if (res) {
                res = mx25l6433f_wait_write_done(num, MX25_SECTOR_ERASE_TIMEOUT_MS);
            }
        }
    }

    return res;
}

В принципе этого набора команд вполне достаточно для дальнейшей работы с микросхемой.

Пуск файловой системы Little-FS

Сам по себе голый драйвер микросхемы MX25L6433F не представляет существенной ценности. Микросхема становится полезной только после запуска на ней какой-нибудь из многочисленных файловых систем. Вот например LittleFS, которая появилась в 2018 году. С файловой системой LittleFS можно создавать, писать и читать привычные файлы не задумываясь про равномерный износ полей NOR Flash. Как будто вы программируете на PC. Алгоритмы dynamic wear leveling заложены прямо в реализацию LittleFS.

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

Исходники файловой системы LittleFS лежат в открытом доступе на GitHub. Задача сводится лишь к тому, чтобы написать конфиг-структуру для подключение API драйвера микросхемы MX25L6433F к коду LittleFS. Вот этот конфиг.

#include "little_fs_config.h"

#include "data_utils.h"
#include "flash_mcal.h"
#include "log.h"
#include "mx25l6433f_mcal.h"
#include "little_fs.h"
#include "std_includes.h"
#include "little_fs_types.h"


#define LITTLE_FS_FLASH_SIZE MBYTE_BYTE(1)
#define LITTLE_FS_NOR_FLASH_NUM 1
#define LITTLE_FS_BLOCK_SIZE  (4096)
#define LITTLE_FS_PAGE_SIZE  256

#define LITTLE_FS_CACHE_SIZE (LITTLE_FS_PAGE_SIZE)

extern const LittleFsConfig_t LittleFsConfig[];
extern LittleFsHandle_t LittleFsInstance[];

uint32_t little_fs_get_cnt(void);


#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 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 FlashToLittleFsRet(bool res) {
    int ret = LFS_ERR_OK;
    if (false == res) {
        ret = LFS_ERR_CORRUPT;
    }
    return ret;
}

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;
    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;
        bool res = false;
        res = mx25l6433f_page_program(1, 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 = false;
        res = mx25l6433f_read(1, phy_address, (uint8_t*) buffer, 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 = mx25l6433f_erase_sector(1,  phy_address);
        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={
        // 8-bit type of attribute, provided by user and used to
        .type = 0,    // identify the attribute
        .buffer = NULL, // Pointer to buffer containing the attribute
        .size = 0,  // Size of attribute in bytes, limited to LFS_ATTR_MAX
};

const LittleFsConfig_t SECTION_CFG_DATA LittleFsConfig[] = {
        {
          .num = 1,
          .base_address = 0,
          .valid = true,
          .name = "LittleFsForNVRAM",
          // Optional configuration provided during lfs_file_opencfg
          .file_config = {
                // Optional statically allocated file buffer. Must be cache_size.
                // By default lfs_malloc is used to allocate this buffer.
               .buffer= StaticFileBuffer,

                // Optional list of custom attributes related to the file. If the file
                // is opened with read access, these attributes will be read from disk
                // during the open call. If the file is opened with write access, the
                // attributes will be written to disk every file sync or close. This
                // write occurs atomically with update to the file's contents.
                //
                // Custom attributes are uniquely identified by an 8-bit type and limited
                // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller
                // than the buffer, it will be padded with zeros. If the stored attribute
                // is larger, then it will be silently truncated. If the attribute is not
                // found, it will be created implicitly.

                // Custom attribute structure, used to describe custom attributes
                // committed atomically during file writes.
               .attrs = &attrs,

                // Number of custom attributes in the list
               .attr_count=0,
          },
          .cfg = {
                 // block device operations
              .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
              // 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 = LITTLE_FS_FLASH_SIZE/LITTLE_FS_BLOCK_SIZE,
        },
    },
};

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

COMPONENT_GET_CNT(LittleFs, little_fs)


Список littleFs файлов можно показать из-под shell

Список littleFs файлов можно показать из-под shell
Список littleFs файлов можно показать из-под shell

Приложения LitteFS
1--Можно хранить прошивки для микроконтроллеров. Можно в LittleFS файле хранить целую прошивку и обновлять ее в bootloader-ах.
2--В файловой системе вы можете складировать текстовые логи работы каких-либо программных компонентов или даже всей прошивки. Можете сделать своеобразный черный ящик. Благо памяти очень много: 8 мегабайт.
3--Можно хранить звуковые файлы, картинки для экранов, записи с ADC и прочее.
3--Можно организовать NVRAM и хранить там настройки тактирования, коэффициенты PLL, счетчик запусков устройства, серийный номер, ключи шифрования, периоды сторожевого таймера, наработки на отказ и пр. Изменять тактирование во время исполнения прошивки без необходимости пересборки всего проекта. Благодаря NVRAM вам не придется подготавливать множество прошивок с разными конфигурациями. Вы просто сделаете одну прошивку с NVRAM и будете подгружать туда специфические параметры для каждого отдельного клиента. Наличие NVRAM это вообще требование автомобильного протокола UDS.

Пуск NVRAM

Запуск файловой системы это еще не финал. Любые приложения работают не сколько с файлами, сколько с параметрами. Параметр - это не просто файл. Это файл с метаданными: единицы измерения, размерные множители, значения по умолчанию, уровень доступа, парсеры типов данных и прочее. А хранение метаданных это как раз задача NVRAM.

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

Обзор параметров NVRAM через CLI
Обзор параметров NVRAM через CLI

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

Итог

Удалось запустить NVRAM и файловую систему LittleFS на основе микросхемы SPI Nor Flash памяти MX25L6433F. Это открывает дорогу для создания и редактирования удобных текстовых и бинарных файлов на основе чипа MX25L6433F. Можно писать логи, вести журнал, сделать черный ящик.

Ссылки

Название

URL

SPI-NOR Flash на примере MX25R6435F

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

Си код для драйвера MX25L6433F

https://github.com/aabzel/trunk/tree/main/source/asics/mx25l6433f

Исходные коды программного компонента Little-FS

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

Пуск SPI трансивера на STM32

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

Открытый проект файловой системы для внутренней памяти STM32H

https://habr.com/ru/post/584156/

ESP32 и файловая система SPIFFS

https://habr.com/ru/post/483280/

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

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

Очередной драйвер SPI флэшек… Но уже с кэшем и «нормальным» api

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

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

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

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

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

Реестр сравнения разных типов памяти: NAND, NOR, FRAM, SRAM и т. п.

https://docs.google.com/spreadsheets/d/1m3TZK1-Uz6JjKpv7rpEAgvpW_WwF1x2sX78ZtoO9huI/edit?gid=634440697#gid=634440697

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

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

Устройство NVRAM в UEFI-совместимых прошивках, часть первая

https://habr.com/ru/post/281242/

Как избежать износа EEPROM

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

Аналитика по микросхеме

https://docs.google.com/spreadsheets/d/1NpEczFbE3gE9Acx4THqYS8aoAVmYZJG-4YJT4lCXITo/edit?gid=0#gid=0

Словарь

Аббревиатура

Расшифровка

SPI

Serial Peripheral Interface

MSB

most significant bit

VCC

Voltage on Collector-Collector

OTP

One-Time Program

WSON

Very Very thin Small Outline No-Lead

JEDEC

Joint Electron Device Engineering Council

CMOS

Complementary Metal-Oxide-Semiconductor

ASIC

Application-specific integrated circuit

Вопросы

--Пин hold со стороны микросхемы это вход или выход?

--Как можно организовать равномерный износ SPI-NOR Flash памяти? Как обрабатывать виртуальные адреса? Запустить не ней LittleFS

--Сколько надо бит, чтобы адресовать 8МByte памяти? 23 бита. 2^23=8388608

Only registered users can participate in poll. Log in, please.
Вы программировали микросхему MX25L6433F?
31.58%да6
68.42%нет13
19 users voted. 1 user abstained.