
Пролог
В этом тексте я попробовал научиться пользоваться микросхемой w25q16j. Это энергонезависимая 133MHz SPI Nor flash память объемом 2 MByte. Поддерживает QSPI и исполнение прошивок из SPI (XIP).
Далее будет просто перевод даташита с английского на русский. Плюс некоторые комментарии со стороны разработчика системного ПО.

Схемотехника
В широкую клетку микросхему w25q16j можно представить вот в такой мнемосхемой. Память дорогая. Получается 45 RUR за мегабайт.

Из чипа выходит ровно 8 пинов

Что можно сказать про организацию микросхемы? Логически память можно разбить либо на страницы, либо на секторы или блоки. Микросхема W25Q16JV выдерживает минимум 100 000 (100K) циклов программирования/стирания на сектор. Срок хранения данных составляет более 20 лет.
бит для адресации |
размер части, байт |
Емкость чипа |
единица измерения |
21 |
1 |
2097152 |
байт |
13 |
256 |
8192 |
страниц |
9 |
4096 |
512 |
секторов |
5 |
65536 |
32 |
блоков |
То есть для адресации 2MByte памяти достаточно всего 21 битного адреса. При этом система команд оперирует с 24 битными адресами. Это значит, что данный SPI протокол в принципе не позволяет работать с Flash памятью размером более 16 MByte, ибо 2 в степени 24 равно 16 777 216 байт.

Отладочная плата для экспериментов
Где же можно встретить микросмеху w25q16j в природе? По факту ее лепят повсюду. Ее можно обнаружить на плате STM32F4XX M. Для изучения микросхемы можно купить учебную электронную плату JL_32F4XX. На ней как раз припаяна восьми пиновая микросхема памяти w25q16j в верхнем слое.

Микросхема памяти подключена к SPI3

Отлаживать драйвер я буду на отладочной плате STM32F4XX. Там чип W25Q16JV подключен согласно вот этому правилу.
W25Q16JV |
GPIO |
JTAG |
DIR MCU |
Function |
PINmux |
Connect |
CS |
PB14 |
-- |
out |
GPIO |
0 |
H2.54 |
DI |
PB5 |
-- |
out |
SPI3_MOSI |
6 |
H2.5 |
DO |
PB4 |
-- |
in |
SPI3_MISO |
6 |
H2.6 |
CLK |
PB3 |
JTDO |
out |
SPI3_SCK |
6 |
H2.7 |
HOKD |
-- |
-- |
-- |
-- |
-- |
|
WP |
-- |
-- |
-- |
-- |
-- |
Можно сразу подключиться к этим 4м пинам логическим анализатором для отладки SPI3 траффика.
Прошивку для платы JL_32F4XX надо отлаживать по интерфейсу SWD. Дело в том, что SPI3 занимает JTAG пин JTDO(PB3). PB3 занят SPI3_SCK. При этом PB3 изначально используется JTDO.
Программная часть
Интерфейс
Связь с микросхемой происходит по интерфейсу SPI. Либо QSPI. Биты захватываются по положительному перепаду на проводе тактирования. Полярность тактирования не имеет значения. Биты передаются старшим вперед (MSB First). Частоту тактирования можно разогнать до 133 MHz.
На STM32 самый минимальный пред делитель для тактирования SPI3 это 2.
/** @defgroup SPI_BaudRate_Prescaler SPI BaudRate Prescaler * @{ */ #define SPI_BAUDRATEPRESCALER_2 (0x00000000U) #define SPI_BAUDRATEPRESCALER_4 (SPI_CR1_BR_0) #define SPI_BAUDRATEPRESCALER_8 (SPI_CR1_BR_1) #define SPI_BAUDRATEPRESCALER_16 (SPI_CR1_BR_1 | SPI_CR1_BR_0) #define SPI_BAUDRATEPRESCALER_32 (SPI_CR1_BR_2) #define SPI_BAUDRATEPRESCALER_64 (SPI_CR1_BR_2 | SPI_CR1_BR_0) #define SPI_BAUDRATEPRESCALER_128 (SPI_CR1_BR_2 | SPI_CR1_BR_1) #define SPI_BAUDRATEPRESCALER_256 (SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_BR_0)
При этом SPI3 тактируется от шины APB1 у которой может быть в пределе только 42MHz.

Получается что на данной PCB можно тактировать W25 только на частоте 21 MHz. Разработчикам схемотехники JL_32F4XX стоило бы всё же выбрать для W25 интерфейс SPI1, который на STM32 может достигать 42MHz.

Система команд
У микросхемы есть свой бинарный протокол. По существу это 42 различных пакета. Каждый пакет это отдельная команда со своей спецификацией структуры пакета.

Регистры
Как и у каждого навороченного ASIC-а у микросхемы w25q16j есть свои внутренние регистры. Их всего семь штук.
Название |
Размер |
RD Cmd |
WR Cmd |
Status1 |
1 |
0x05 |
0x01 |
Status2 |
1 |
0x35 |
0x31 |
Status3 |
1 |
0x15 |
0x11 |
JEDEC ID |
3 |
0x9f |
-- |
Mad ID |
4 |
0x92 |
|
sfdp |
256 |
0x5A |
-- |
unique_id |
8 |
0x4B |
-- |
Микросхему w25q16j можно идентифицировать на шине SPI по её ID.
Константа |
Значение |
SPI Команда |
структура |
manufacturer_id |
0xef |
0x9F |
JEDEC ID |
memory type |
0x40 |
0x9F |
JEDEC ID |
capacity id |
0x15 |
0x9F |
JEDEC ID |
Device Id |
0x14 |
0x90 |
Manufacturer / Device ID |
unique_id |
0x2a7664cb278863e6 |
0x4B |
Unique ID Number |
Как уже было отмечено, у микросхемы w25q16j внушительное количество команд. Всего 42 команды! Однако для 99 % случаев нужно всего крохотное подмножество SPI команд, которые позволят выполнить три фундаментальные операции: чтение страницы, стирание сектора, запись страницы. Остальное - просто опции.
Операция чтения страници
1) Отправить команду чтения (0x03) с указанием начального 24-битного адреса.
Получить по SPI массив данных.
Операция стирание сектора
1) Активировать режим записи. Отправить команду WRITE_ENABLE, опрашивать бит WEL (WRITE ENABLE LATCH) статусного регистра 1. В случае WEL=1 можно двигаться дальше.
2) Отправить команду стереть сектор (SECTOR_ERASE_4KB) с указанием выровненного адреса начала сектора.
3) Ожидать окончания стирания. Для этого надо опрашивать статусный регистр 1 и ожидать чтобы в бите BUSY появилось значение 0.
4) Проверить факт стирания путем чтения и сверки результата с FF.
Операция записи
1) Подготовить выровненный по периоду 256 адрес для записи.
Выровненный адрес — адрес, который может принимать значения с некоторым периодом начиная с нуля. Периоды выровненных адресов, как правило, это значения степеней двойки — например, 256.
2) Активировать разрешение на запись в статусном регистре. Отправить команду WRITE_ENABLE, опрашивать бит WEL (WRITE ENABLE LATCH) статусного регистра 1. В случае WEL=1 можно двигаться дальше.
3) Выполнить команду Page program.
4) Ожидать окончания записи опрашивая статусный регистр. Для этого надо опрашивать статусный регистр 1 и ожидать чтобы в бите BUSY появилось значение 0.
5) Проверить, что записанный массив совпал с прочитанным при помощи алгоритма чтения страницы.
Достоинства и недостатки w25q16
№ |
Достоинства |
недостатки |
1 |
Есть отдельная команда для стирания всего чипа |
Сравнительно мало памяти. В этой серии максимум это 16 MByte |
2 |
нет выбитых ячеек как в случае с NAND |
надо делать равномерный износ программно |
3 |
Работает дозапись ячеек флеш‑памяти. То есть можно прописать в байт значение 0xFE, затем прописать 0xFC, и в финале прочитается тоже 0xFC. Это позволяет использовать достаточно простые реализации NVRAM на такой памяти. |
Однако работать со страницами и секторами голой SPI flash крайне неудобно. Особенно когда вам надо прописать массив прямо по границе страниц или, еще хуже, стереть word по границе секторов. В этом случае алгоритмы работы с SPI flash могут оказаться настолько сложным, что можно запутаться в их реализации и последующей отладке. Классическое решение этой проблемы - запуск какой-нибудь файловой системы.
Портирование Little-FS
Одной из таких популярных файловых систем является third‑party open source Little FS. Third‑party — код со стороны. Программный компонент, который вы скачали из интернета.
Сам по себе голый драйвер микросхемы w25q16 не представляет существенной ценности. Микросхема становится полезной только после запуска на ней какой-нибудь из многочисленных файловых систем. Вот например LittleFS, которая появилась в 2018 году.
File - это именованный бинарный массив байтов в памяти. В качества памяти может выступать RAM, ROM (Flash), FRAM, EEPROM, SD карты. Что значит именованный? Это значит, что к этим данным можно обращаться по значению. Пусть это будет натуральное 16-битное число. Так как это массив, то рядом с данными также надо хранить и длину этого массива. Длину файла запоминает файловая система.
Исходники файловой системы LittleFS лежат в открытом доступе на GitHub. Задача сводится лишь к тому, чтобы написать конфиг-структуру для подключение API драйвера микросхемы w25q16 к коду LittleFS. Перед вами конфиг для подключения драйвера W25q16 к файловой системе little fs.
#include "little_fs_config.h" #include "data_utils.h" #include "flash_mcal.h" #include "log.h" #ifdef HAS_W25Q16J #include "w25q16j_mcal.h" #endif #include "little_fs.h" #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) { #ifdef HAS_W25Q16J uint32_t phy_address = 0; phy_address = Node->base_address + block * LITTLE_FS_BLOCK_SIZE + off; bool res = false; res = w25q16j_page_program(1, phy_address, (uint8_t*) buffer, size); ret = FlashToLittleFsRet(res); #endif } 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) { #ifdef HAS_W25Q16J 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 = w25q16j_read(1, phy_address, (uint8_t*) buffer, size); ret = FlashToLittleFsRet(res); #endif } 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) { #ifdef HAS_W25Q16J uint32_t phy_address = Node->base_address + block * LITTLE_FS_BLOCK_SIZE; bool res = w25q16j_erase_sector(1, phy_address); ret = FlashToLittleFsRet(res); #endif } 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 // identify the attribute .type = 0, // Pointer to buffer containing the attribute .buffer = NULL, // Size of attribute in bytes, limited to LFS_ATTR_MAX .size = 0, }; 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 = 100000, .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 можно создавать, писать и читать привычные файлы не задумываясь про равномерный износ полей NOR Flash. Как будто вы программируете на PC. Алгоритмы dynamic wear leveling заложены прямо в реализацию LittleFS. Теперь вы можете просто использовать привычные функции для работы с файлом.
int lfs_format(lfs_t lfs, const struct lfs_config config); int lfs_mount(lfs_t lfs, const struct lfs_config config); int lfs_unmount(lfs_t *lfs); int lfs_remove(lfs_t lfs, const char path); int lfs_rename(lfs_t lfs, const char oldpath, const char *newpath); int lfs_stat(lfs_t lfs, const char path, struct lfs_info *info); lfs_ssize_t lfs_getattr(lfs_t lfs, const char path, uint8_t type, void *buffer, lfs_size_t size); int lfs_setattr(lfs_t lfs, const char path, uint8_t type, const void *buffer, lfs_size_t size); int lfs_removeattr(lfs_t lfs, const char path, uint8_t type); int lfs_file_open(lfs_t lfs, lfs_file_t file, const char *path, int flags); int lfs_file_opencfg(lfs_t lfs, lfs_file_t file, const char *path, int flags, const struct lfs_file_config *config); int lfs_file_close(lfs_t lfs, lfs_file_t file); int lfs_file_sync(lfs_t lfs, lfs_file_t file); lfs_ssize_t lfs_file_read(lfs_t lfs, lfs_file_t file, void *buffer, lfs_size_t size); lfs_ssize_t lfs_file_write(lfs_t lfs, lfs_file_t file, const void *buffer, lfs_size_t size); lfs_soff_t lfs_file_seek(lfs_t lfs, lfs_file_t file, lfs_soff_t off, int whence); int lfs_file_truncate(lfs_t lfs, lfs_file_t file, lfs_off_t size); lfs_soff_t lfs_file_tell(lfs_t lfs, lfs_file_t file); int lfs_file_rewind(lfs_t lfs, lfs_file_t file); lfs_soff_t lfs_file_size(lfs_t lfs, lfs_file_t file); int lfs_mkdir(lfs_t lfs, const char path); int lfs_dir_open(lfs_t lfs, lfs_dir_t dir, const char *path); int lfs_dir_close(lfs_t lfs, lfs_dir_t dir); int lfs_dir_read(lfs_t lfs, lfs_dir_t dir, struct lfs_info *info); int lfs_dir_seek(lfs_t lfs, lfs_dir_t dir, lfs_off_t off); lfs_soff_t lfs_dir_tell(lfs_t lfs, lfs_dir_t dir); int lfs_dir_rewind(lfs_t lfs, lfs_dir_t dir); int lfs_fs_stat(lfs_t lfs, struct lfs_fsinfo fsinfo); lfs_ssize_t lfs_fs_size(lfs_t *lfs); int lfs_fs_traverse(lfs_t lfs, int (cb)(void*, lfs_block_t), void *data); int lfs_fs_mkconsistent(lfs_t *lfs); int lfs_fs_gc(lfs_t *lfs); int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count); int lfs_migrate(lfs_t lfs, const struct lfs_config cfg);
Чтобы предотвратить зависания функции printf надо отключить логирование внутри кода LittleFS. Чтобы отключить в LittleFS логирование надо собрать программный компонент с ключами компилятора -DLFS_NO_DEBUG, -DLFS_NO_WARN, -DLFS_NO_ASSERT, -DLFS_NO_ERROR.В Embedded программировании не следует вызывать функцию printf из LibC.
Приложения LitteFS
1--Можно в LittleFS файле хранить целую прошивку и обновлять ее в bootloader-ах.
2--В файловой системе вы можете складировать текстовые логи работы каких-либо программных компонентов или даже всей прошивки. Можете сделать своеобразный черный ящик. Благо памяти очень много: 8 мегабайт.
3--Можно хранить звуковые файлы, картинки для экранов, записи с ADC и прочее.
4--Можно организовать NVRAM и хранить там настройки тактирования, коэффициенты PLL, счетчик запусков устройства, серийный номер, ключи шифрования, периоды сторожевого таймера, наработки на отказ и пр. Изменять тактирование во время исполнения прошивки без необходимости пересборки всего проекта. Благодаря NVRAM вам не придется подготавливать множество прошивок с разными конфигурациями. Вы просто сделаете одну прошивку с NVRAM и будете подгружать туда специфические параметры для каждого отдельного клиента. Наличие NVRAM это вообще требование автомобильного протокола UDS.
Пуск NVRAM
Запуск файловой системы это еще не конец работы. Любые приложения работают не сколько с файлами, сколько с параметрами. Параметр - это не просто файл. Это файл с метаданными: единицы измерения, размерные множители, значения по умолчанию, уровень доступа, парсеры типов данных и прочее. А хранение метаданных это как раз задача NVRAM.
NVRAM - энергонезависимая память (NV) с произвольным доступом (RAM). По сути Key Val-Map(ка). В ней могут храниться любые бинарные данные, ассоциированные со своим ID числом.

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

Отладка
Отлаживать SPI команды можно при помощи логического анализатора DS-Logic.

Чтение уникального 64-битного ID

При инициализации ASICа можно прочитать его ID и отобразить в главную консоль управления

Запустить все модульные тесты для w25q можно тут же в UART консоли командой tr w25q+.

Лог теста
tr w25* cmd_unit_test_run() argc 1 255.622,+5703,273,I,[TEST],key [w25*] 255.623,+1,274,I,[TEST],test_w25q16j_types() ********* test,OkTest,58/117,[Name:[w25q16j_types],Fun:0x803d7c1],FailCnt:0 255.925,+302,275,I,[TEST],test_w25q16j_read_jedec() 255.925,+0,276,I,[w25q16j],manufacturer_id:0xef 255.926,+1,277,I,[w25q16j],memory_type:0x40 255.926,+0,278,I,[w25q16j],capacity_id:0x15 ********* test,OkTest,59/117,[Name:[w25q16j_read_jedec],Fun:0x803e1c5],FailCnt:0 256.228,+302,279,I,[TEST],test_w25q16j_read_unique_id() 256.228,+0,280,I,[TEST],unique_id1:0x2a7664cb278863e6 256.229,+1,281,I,[TEST],unique_id2:0x2a7664cb278863e6 ********* test,OkTest,60/117,[Name:[w25q16j_read_unique_id],Fun:0x803e289],FailCnt:0 256.531,+302,282,I,[TEST],test_w25q16j_read_sfdp() 53464450050100FF00050110800000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE520F9FFFFFFFF0044EB086B083B42BBFEFFFFFFFFFF0000FFFF40EB0C200F5210D800003602A60082EA14B3E96376337A757A75F7A2D55C19F74DFFE930F880FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ********* test,OkTest,61/117,[Name:[w25q16j_read_sfdp],Fun:0x803e135],FailCnt:0 256.844,+313,283,I,[TEST],test_w25q16j_chip_select() 256.844,+0,284,W,[TEST], Gpio PB14=W25Q16J_CS... 256.845,+1,285,I,[TEST],test_gpio_pin_lev() 256.946,+101,286,I,[TEST],Set Pad:PB14 W25Q16J_CS to 0 OK! 256.946,+0,287,I,[TEST],test_gpio_pin_lev() 257.048,+102,288,I,[TEST],Set Pad:PB14 W25Q16J_CS to 1 OK! 257.048,+0,289,I,[TEST],Wire,Pad:PB14,W25Q16J_CS,Ok! ********* test,OkTest,62/117,[Name:[w25q16j_chip_select],Fun:0x803e0b5],FailCnt:0 257.351,+303,290,I,[TEST],test_w25q16j_write_enable() ********* test,OkTest,63/117,[Name:[w25q16j_write_enable],Fun:0x803e009],FailCnt:0 257.653,+302,291,I,[TEST],test_w25q16j_is_connected() 257.653,+0,292,W,[w25q16j],WORD:0x14ef 257.654,+1,293,I,[w25q16j],ManufId:0xef 257.654,+0,294,I,[w25q16j],DevId:0x14 257.655,+1,295,I,[w25q16j],RegID Ok ********* test,OkTest,64/117,[Name:[w25q16j_is_connected],Fun:0x803e061],FailCnt:0 257.957,+302,296,I,[TEST],test_w25q16j_form_addr() ********* test,OkTest,65/117,[Name:[w25q16j_form_addr],Fun:0x803d9ed],FailCnt:0 258.259,+302,297,I,[TEST],test_w25q16j_read() 5555555555555555555555555555555555555555555555555555555555555555********* test,OkTest,66/117,[Name:[w25q16j_read],Fun:0x803db75],FailCnt:0 258.562,+303,298,I,[TEST],test_w25q16j_write_activate() ********* test,OkTest,67/117,[Name:[w25q16j_write_activate],Fun:0x803dfb1],FailCnt:0 258.864,+302,299,I,[TEST],test_w25q16j_erase_sector() ********* test,OkTest,68/117,[Name:[w25q16j_erase_sector],Fun:0x803df25],FailCnt:0 260.105,+1241,300,I,[TEST],test_w25q16j_write() 260.105,+0,301,I,[TEST],test_w25q16j_write_one() Addr: 0x1fb000 Pattern:0xaa 261.104,+999,302,I,[TEST],WriteOk! 261.106,+2,303,I,[TEST],DataMatch! 261.106,+0,304,I,[TEST],test_w25q16j_write_one() Addr: 0x1fb000 Pattern:0x55 262.105,+999,305,I,[TEST],WriteOk! 262.107,+2,306,I,[TEST],DataMatch! 262.107,+0,307,I,[TEST],test_w25q16j_write_one() Addr: 0x1fc000 Pattern:0xaa 263.105,+998,308,I,[TEST],WriteOk! 263.107,+2,309,I,[TEST],DataMatch! 263.108,+1,310,I,[TEST],test_w25q16j_write_one() Addr: 0x1fc000 Pattern:0x55 264.105,+997,311,I,[TEST],WriteOk! 264.107,+2,312,I,[TEST],DataMatch! ********* test,OkTest,69/117,[Name:[w25q16j_write],Fun:0x803de31],FailCnt:0 264.410,+303,313,I,[TEST],TestDuration:8788 ms=8.7880001 s=0.14646667 min 264.410,+0,314,I,[TEST],Report 264.411,+1,315,I,[TEST],ok:12/117 264.411,+0,316,I,[TEST],failed:0 264.412,+1,317,I,[TEST],All 12 tests passed!
Есть отдельный набор модульных тестов для LittleFS

Тесты little_fs можно запустить из консоли
Лог прогона модульных тестов LittleFS
8:5-->tr litt+ cmd_unit_test_run() argc 1 488.675,+57355,325,I,[TEST],key [litt+] 488.675,+0,326,W,[TEST],unit_test_run_key() key litt 488.676,+1,327,I,[TEST],test_little_fs_delete(): 488.676,+0,328,I,[TEST],test_little_fs_write_one():File:[FileToDel.txt] 488.871,+195,329,W,[LittleFs],1,ReMoveFile:[FileToDel.txt] 488.982,+111,330,E,[LittleFs],Ret:-2=NoDirEntry 488.982,+0,331,E,[LittleFs],LITTLE_FS_1,OpenError,File:[FileToDel.txt],Flag:1 ********* test,OkTest, 1/117,[Name:[little_fs_delete],Fun:0x8038b59],FailCnt:0 489.284,+302,332,I,[TEST],test_little_fs_mount(): ********* test,OkTest, 2/117,[Name:[little_fs_mount],Fun:0x8038689],FailCnt:0 489.665,+381,333,I,[TEST],test_little_fs_open(): ********* test,OkTest, 3/117,[Name:[little_fs_open],Fun:0x80388fd],FailCnt:0 490.012,+347,334,I,[TEST],test_little_fs_max_size(): ********* test,OkTest, 4/117,[Name:[little_fs_max_size],Fun:0x803898d],FailCnt:0 490.314,+302,335,I,[TEST],test_little_fs_write() 490.314,+0,336,I,[TEST],test_little_fs_write_one():File:[TestWrFile1.txt] 490.469,+155,337,I,[TEST],test_little_fs_write_one():File:[TestWrFile1.txt] 490.594,+125,338,I,[TEST],test_little_fs_write_one():File:[TstWrF3.txt] ********* test,OkTest, 5/117,[Name:[little_fs_write],Fun:0x80389a9],FailCnt:0 491.068,+474,339,I,[TEST],test_little_fs_write_lazy(): 491.671,+603,340,W,[LittleFs],EraseBlock:176 493.079,+1408,341,W,[LittleFs],EraseBlock:177 494.491,+1412,342,W,[LittleFs],EraseBlock:178 ********* test,OkTest, 6/117,[Name:[little_fs_write_lazy],Fun:0x8038a71],FailCnt:0 496.165,+1674,343,I,[TEST],test_little_fs_write_line(): 497.516,+1351,344,W,[LittleFs],EraseBlock:175 ********* test,OkTest, 7/117,[Name:[little_fs_write_line],Fun:0x803876d],FailCnt:0 500.453,+2937,345,I,[TEST],test_little_fs_cat(): 500.473,+20,346,I,[LittleFs],File:test_log.txt,Size:90 Byte ********* test,OkTest, 8/117,[Name:[little_fs_cat],Fun:0x8038895],FailCnt:0 500.778,+305,347,I,[TEST],test_little_fs_write_4k(): 500.811,+33,348,W,[LittleFs],EraseBlock:180 502.708,+1897,349,W,[LittleFs],EraseBlock:182 ********* test,OkTest, 9/117,[Name:[little_fs_write_4k],Fun:0x8038c45],FailCnt:0 504.070,+1362,350,I,[TEST],test_little_fs_write_bandwidth(): 537.909,+2518,372,I,[TEST],write_bandwidth:1210.739 bytes/s=1.1823623 kBytes/s=0.0011546507 MBytes/s ********* test,OkTest,10/117,[Name:[little_fs_write_bandwidth],Fun:0x8038d01],FailCnt:0 538.213,+304,373,I,[TEST],TestDuration:49538 ms=49.537998 s=0.82563335 min 538.214,+1,374,I,[TEST],Report 538.215,+1,375,I,[TEST],ok:10/117 538.215,+0,376,I,[TEST],failed:0 538.216,+1,377,I,[TEST],All 10 tests passed! 8:58-->
Проверить NVRAM (store_fs) можно отдельным модульным тестом, который пишет и читает однобайтный параметр с адресом 98. Как видно, модульный тест store_fs_set_get работает.

Итоги
Удалось научитьcя работать с микросхемой памяти w25q16j. Удалось организовать на основе w25q16j файловую систему LittleFS. Удалось организовать NVRAM на w25q16.
Словарь
Аббревиатура |
Расшифровка |
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 |
Ссылки
Название |
URL |
STM32F4XX |
https://stm32-base.org/boards/STM32F407ZGT6-STM32F4XX.html#W25Q16JV |
Обзор SPI Flash памяти MX25L6433F |
|
Пуск LittleFS (NVRAM с запретом до-записи flash) |
|
Запуск файловой системы LittleFS на SPI‑NAND Flash от GigaDevice |