Перейти к содержанию
Материалы раздела

Внешняя SPI flash

Модуль spiflash подключает внешнюю последовательную NOR flash к SPI2 или SPI3. Накопитель можно использовать напрямую либо как LittleFS с именем spilfs.

local jedec = spiflash.init(
    spiflash.SPI2,
    pio.GPIO12,       -- MISO
    pio.GPIO13,       -- MOSI
    pio.GPIO14,       -- CLK
    pio.GPIO15,       -- CS
    spiflash.FREQ_10M
)

print(string.format("JEDEC ID: %06X", jedec))
local info = spiflash.info()
print(info.capacity, info.page_size, info.sector_size)

Поддерживаемые микросхемы и подключение

Драйвер рассчитан на SPI NOR с обычным JEDEC-набором команд: чтение 0x03, page program 0x02, стирание сектора 0x20, chip erase 0xC7, write enable 0x06, чтение status register 1 и JEDEC ID. Размер страницы должен быть 256 байт, а erase-сектора — 4096 байт.

Ограничения реализации:

  • режим SPI 0, только spiflash.SPI2 и spiflash.SPI3;
  • частота от 1 Гц до 20 МГц; предусмотрены константы FREQ_10M и FREQ_20M;
  • трёхбайтовая адресация и ёмкость не более 16 МиБ;
  • JEDEC capacity code от 0x10 до 0x18, то есть от 64 КиБ до 16 МиБ;
  • одна внешняя flash и один экземпляр spilfs в системе.

Подавайте на микросхему напряжение, разрешённое её документацией и уровнями GPIO ESP32, обычно 3,3 В. Соедините земли, установите развязывающий конденсатор рядом с корпусом flash и не оставляйте входы WP# и HOLD#/RESET# в воздухе: если они не используются, подтяните их к неактивному уровню согласно datasheet.

Шина может быть общей с другими устройствами. В таком случае MISO, MOSI и CLK должны совпадать с уже настроенными выводами шины, а CS flash должен быть отдельным. Драйвер удерживает общую блокировку SPI на протяжении всей команды, поэтому другой клиент не может вклиниться между opcode, адресом и данными.

Инициализация и информация

jedec_id = spiflash.init(spi, miso, mosi, clk, cs, clock)

Настраивает устройство, читает JEDEC ID и проверяет код ёмкости. Аргументы miso, mosi, clk и cs — номера GPIO. Константы spiflash.SPI2 и spiflash.SPI3 принадлежат этому модулю; не подменяйте их константами хоста из другого API.

Повторная инициализация без spiflash.deinit() вызывает исключение. Ошибка SPI, неподдерживаемый JEDEC ID, занятый CS или несовпадающие выводы общей шины также вызывают исключение и освобождают уже выделенные ресурсы.

info = spiflash.info()

Возвращает таблицу:

ПолеЗначение
jedec_id24-битный JEDEC ID
capacityобнаруженная ёмкость в байтах
page_sizeразмер программируемой страницы, 256 байт
sector_sizeразмер erase-сектора, 4096 байт
clockнастроенная частота SPI в герцах
spiномер шины Lua RTOS: 2 или 3

spiflash.deinit()

Отключает устройство и освобождает CS и, если на шине больше нет клиентов, ресурсы шины. Пока spilfs смонтирована, функция завершается исключением: сначала закройте файлы и каталоги и вызовите fs.umount().

LittleFS на внешней flash

Поддержка файловой системы включается параметрами CONFIG_LUA_RTOS_LUA_USE_SPIFLASH=y и CONFIG_LUA_RTOS_LUA_USE_SPIFLASH_LFS=y. Размер задаёт CONFIG_LUA_RTOS_SPIFLASH_BLOCK_COUNT: один блок равен 4096 байт, а произведение не должно превышать обнаруженную ёмкость flash. Допустимо от 64 до 4096 блоков; значение по умолчанию 2048 создаёт файловую систему размером 8 МиБ.

Автоматическое форматирование при ошибке mount по умолчанию выключено, потому что повреждение проводки или временная ошибка SPI не должны уничтожать данные. Новую микросхему один раз отформатируйте явно из интерактивной консоли:

spiflash.init(spiflash.SPI2, 12, 13, 14, 15, spiflash.FREQ_10M)
os.format("/spilfs", "spilfs")  -- запросит подтверждение и удалит все данные
fs.mount("/spilfs", "spilfs")

При обычной загрузке форматирование не требуется:

spiflash.init(spiflash.SPI2, 12, 13, 14, 15, spiflash.FREQ_10M)
fs.mount("/spilfs", "spilfs")

local file = assert(io.open("/spilfs/state.bin", "wb"))
assert(file:write("ready\0"))
assert(file:close())

Если spilfs уже смонтирована, os.format("/spilfs", "spilfs") сначала проверяет отсутствие открытых файлов и каталогов, затем отключает, форматирует и снова подключает её. При открытых дескрипторах операция завершается ошибкой EBUSY. Опция CONFIG_LUA_RTOS_SPIFLASH_LFS_FORMAT_IF_MOUNT_FAILED=y разрешает разрушительное автоформатирование, но для рабочих профилей её включать не следует.

Корректное завершение работы:

fs.umount("/spilfs")
spiflash.deinit()

Прямой доступ

Прямые операции нельзя выполнять параллельно с файловым доступом к тому же носителю: они обходят метаданные LittleFS и могут повредить файловую систему.

data = spiflash.read(address, length)

Читает бинарную Lua-строку. length должен быть от 1 до 4096 байт, а весь диапазон — находиться внутри обнаруженной ёмкости. Ошибки создают исключение.

status = spiflash.write(address, data [, length])

Записывает непустую бинарную строку. Драйвер сам делит операцию по границам 256-байтных страниц. Необязательный length сохранён для совместимости и должен точно совпадать с длиной строки; обрезание по NUL не выполняется.

NOR flash умеет программировать биты только из 1 в 0. Перед повторной записью данных, требующей перехода из 0 в 1, сотрите соответствующий сектор.

status = spiflash.erase([address])

С адресом стирает один 4096-байтный сектор; адрес должен быть выровнен по 4096. Без адреса запускает стирание всей микросхемы, которое может продолжаться несколько минут.

spiflash.dump(address)

Читает 256 байт и выводит hex/ASCII dump в консоль. Диапазон должен полностью помещаться в flash.

write() и erase() возвращают числовой статус:

КодЗначение
0операция выполнена
1ошибка передачи SPI
2включена аппаратная или status-register защита записи
3устройство не готово или не инициализировано
4неверный адрес, длина или выравнивание
5истёк тайм-аут операции
6недостаточно памяти
7шина, CS или устройство заняты
8JEDEC ID или ёмкость не поддерживаются

Для записи проверяйте status == 0. Драйвер подтверждает установку WEL, ожидает снятия BUSY с ограниченным тайм-аутом и проверяет, что после программирования или стирания WEL сброшен микросхемой.

Миграция со старого контура

  • SPI1, FREQ_40M и FREQ_80M больше не поддерживаются;
  • внешняя FAT (spifat) удалена; для внешней NOR используется только LittleFS;
  • размеры read/prog/page/sector больше не задаются отдельно: они фиксированы как 256/256/256/4096 байт;
  • автоформатирование при неудачном mount теперь выключено по умолчанию;
  • старый третий аргумент write(address, data, length) допустим только при точном совпадении length с бинарной длиной data.

Перед выпуском прошивки проверьте целевую микросхему на реальной плате: стабильность чтения на выбранной частоте, стирание и запись всех секторов, совместную работу остальных клиентов SPI и восстановление LittleFS после отключения питания во время записи.