Обычно для удобства работы с различными носителями информации используются различные файловые системы. Основное удобство заключается в абстрагировании от особенностей физического исполнения носителя ( как размер страницы, блока и т.д) и сосредоточиться на работе с полезной нам информацией. В данной статье пойдет речь о параллельной NAND Flash память и файловой системе LittleFS.
До недавнего времени для встроенных систем на основе микроконтроллеров и предназначенных для работы с NAND чипами мне была известна, только Yaffs. Про эту файловую систему есть отличная серия статей. Основной недостаток Yaffs, на мой взгляд, это высокие требования к объему ОЗУ. По этой причине мне даже не удавалось ее протестировать, т.к. у меня нет плат у которых на борту были бы SDRAM и NAND память. LittleFS лишена, по крайней мере, этого недостатка и мне удалось ее запустить ее и немного протестировать.
Заявленные характеристики LittleFS:
- специально разработана для работы с блочными устройствами памяти
- устойчивость к пропаданию питания
- динамический контроль износа
- работа с ограниченным объемом ОЗУ/ПЗУ.
Подготовка «железа».
Для тестов буду использовать проект под плату Waveshare и модуль с установленной памятью K9F1G08UOE объемом 128 Мб. Для вывода отладочной информации используется UART3. CubeMX сгенерировал основу проекта, для доступа к флешь памяти использую интерфейс FSMC со следующими параметрами:
Здесь нечего описывать, но пару слов все же сказать стоит. С пересчетом таймингов указанных в настройках FSMC в реальные единицы времени я не стал заморачиваться, а подобрал опытным путем. Эти тайминги актуальны для тактовой частоты шины AHB 168 МГц. Также в проекте необходимо настроить пин управления чип селектом микросхемы памяти (CS) в моем случаи это PD13. Вся его настройка — это выход с постоянным уровнем логического нуля.
Есть еще одна особенность характерная именно для моей комбинации устройств это выходы ALE/CLE. Дело в том, что они перепутаны местами, эту проблему можно легко обойти софтовым способом. Нужно отредактировать дефайны в файле stm32f4xx_hal_nand.h таким образом:
#define CMD_AREA ((uint32_t)(1U<<17U)) /* A16 = CLE high */ #define ADDR_AREA ((uint32_t)(1U<<16U)) /* A17 = ALE high */
Возможно, есть менее варварский способ, но мне для тестов он вполне сгодился.
Для проверки, функционирования памяти, можно запустить функцию test_nand_flash(). Там происходит стирание блока, чтение по нулевому адресу, проверка, что все данные равны 0xFF. Следующий шаг — проверка запись/чтение четырех страниц. Если тест проходит без ошибок, то можно считать, что флешь память работает корректно.
Имплементация LittleFS и простой тест.
Когда функции чтения/записи и стирания флешь памяти отлажены, можно приступить к имплементации LittleFS. Делается это очень просто, мы просто должны заполнить структуру lfs_config. Подробное описание ее полей есть в файле lsf.h. Названия полей говорят сами за себя, поэтому опишу самое основное.
В моем тестовом проекте заполнение структуры lfs_config происходит в функции io_fs_init() в файле io_fs.c.
int io_fs_init(void) { uint32_t page_size = io_nand_get_page_size(); _lfs_config.read_size = page_size; _lfs_config.prog_size = page_size; _lfs_config.block_size = io_nand_get_block_size() * page_size; _lfs_config.block_count = io_nand_get_block_number(); _lfs_config.lookahead_size = page_size; _lfs_config.cache_size = page_size; _lfs_config.read_buffer = _rd; _lfs_config.prog_buffer = _wr; _lfs_config.read = _fs_flash_read; _lfs_config.prog = _fs_flash_prog; _lfs_config.erase = _fs_flash_erase; _lfs_config.sync = _fs_flash_sync; return 0; }
read/prog size — минимальное кол-во байт на чтение/запись, должно быть больше или равно размеру странице.
block_size — размер блока в байтах. Блок это минимальный объем флешь памяти, который возможно стереть.
block_count — общее кол-во блоков флешь памяти т.е. вообще всего с учетом кол-ва плейнов (Plane).
lookahed_size — размер буфера, необходимого для хранения данных о блоках, в которые можно разместить данные. Должен быть кратным числу 4.
cache_size — размер буфер под кэш. Увеличение этого буфер приводит к увеличению производительности LittleFS, за счет уменьшения кол-ва обращений к носителю. Должен быть кратен read/prog size.
read/prog_buffer — опциональные буферы для чтения/записи. Удобно их создавать для отладки т.к. видно содержимое страницы. Должны быть равны cache_size.
Теперь подходим к самому интересному указателям на функции, которые взаимодействуют с флешь памятью. Названия функций опять же очень говорящие, поэтому опишу реализацию одну из них.
int _fs_flash_prog( const struct lfs_config *cfg, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { assert(off % cfg->prog_size == 0); assert(size % cfg->prog_size == 0); assert(block < cfg->block_count); uint32_t addr = block * io_nand_get_block_size() + off / io_nand_get_page_size(); io_nand_write_8b(addr, (uint8_t*) buffer, size); return 0; }
Особенностью этой функции можно считать лишь пересчет адреса блока и смещения в адрес совместимый с функцией io_nand_write_8b(uint32_t addr, uint8_t *buffer, uint32_t size, uint32_t offst). Данная функция использует линейную адресацию станиц флешь памяти. Например, addr == 168 будет равен Plane == 0 | Block ==2 | Page == 40 . Если еще необходимо смещение в байтах от начала страницы, то нужно отправить аргумент offst.
В функцию _fs_flash_prog() LittleFS будет отправлять номер блока и смещение в байтах, причем off будет всегда кратно размеру страницы, т.е. это по сути её адрес. Поэтому нам просто нужно block и off привести к линейной страничной адресации.
Теперь когда мы заполнили структуру lfs_config можно попробовать запустить пример.
io_fs_file file; // инциализация файловой системы volatile int32_t err = io_fs_init(); err = io_fs_mount(); if (err < 0) // если есть ошибка монтирования { // то произвести форматирование printf("Format...\r\n"); err = io_fs_format(); err = io_fs_mount(); } // прочитать текущее значение кол-ва загрузок uint32_t boot_count = 0; err = io_fs_file_open(&file, "boot_count", IO_FS_O_RDWR | IO_FS_O_CREAT); err = io_fs_file_read(&file, &boot_count, sizeof(boot_count)); // обновить счетчик загрузок boot_count += 1; // перейти в начало файла err = io_fs_file_rewind(&file); // произвести запись err = io_fs_file_write(&file, &boot_count, sizeof(boot_count)); // закрыть файл, окончательно производится дозапись на носитель err = io_fs_file_close(&file); // размонтировать файловую систему err = io_fs_unmount(); // печать значения счетчика включений printf("boot_count: %d\n", boot_count);
Подключившись терминалом к UART3 можно увидеть значение счетчика включений. Для его инкремента достаточно перезагрузить плату. Если счетчик адекватно инкрементируется и в терминал не выводится никаких других сообщений, то можно себя поздравить LittleFS успешно запущена.
Тест скорости.
Пора произвести измерения скорости чтения/записи, а также, как дополнительный тест проверить работу файловой системы с относительно большим файлом. Сам тест находится в main.c, а дефайны определяющие его поведение в самом начале файла. В данном тесте проводится время записи, после форматирования, повторная запись с самого начала файла, а также измерение времени записи. Временные интервалы я измерял с помощью осциллографа, размер исследуемого файла 64 кБ.
Результаты:
- первая запись 101,4 мс
- повторная запись 101 мс
- чтение 16,15 мс
Как видно первая и повторная запись занимает одинаковое время.
Ниже приведены соответствующие осциллограммы.
Полезные ссылки: