Embedded HandbookПриложения
Все разделы › VI. Практикум
STM32F103, STM32F407 · уровень средний · 12 мин чтения
Открыть в интерактивном справочнике

41. Внешняя память: SPI Flash, NAND, SD

Встроенной Flash у микроконтроллера мало: 64 КБ у BluePill, 1 МБ у F407 — и вся она занята программой. Как только нужно писать логи, хранить картинки для дисплея или накапливать измерения, приходится ставить внешнюю память.

Как это называется в документации

Оригинал Перевод
page program запись страницы
sector / block erase стирание сектора / блока
write enable latch защёлка разрешения записи
busy flag флаг занятости
endurance ресурс, число циклов перезаписи
data retention срок хранения данных
wear levelling выравнивание износа
bad block management обработка сбойных блоков
error correction code (ECC) код коррекции ошибок
memory-mapped mode режим отображения в память

41.1 Что бывает

Тип Объём Чтение Запись Ресурс Чем хорош
EEPROM байты–килобайты побайтно побайтно 1 млн настройки, счётчики
FRAM килобайты побайтно побайтно, мгновенно 10¹² там, где пишут постоянно
NOR Flash 1–32 МБ побайтно страницами 100 тыс. логи, картинки, прошивки
NAND Flash 128 МБ–ГБ страницами страницами 1–100 тыс. много данных, дёшево
SD / eMMC гигабайты блоками блоками зависит съёмный носитель, готовая ФС

То, что в обиходе зовут «SPI‑флешкой» — микросхемы вроде W25Q или AT45 — это NOR, а не NAND. Разница принципиальная.


41.2 NOR против NAND

NOR NAND
чтение любого байта напрямую только страницами целиком
единица стирания сектор 4 КБ блок 64–256 КБ
сбойные блоки нет есть с завода, это норма
коррекция ошибок не нужна обязательна
цена за мегабайт выше ниже
типовой объём до 32 МБ от 128 МБ
сложность драйвера низкая высокая

Главное практическое различие. У NAND часть блоков нерабочая прямо с завода — производитель это документирует и помечает такие блоки специальным байтом. Драйвер обязан их находить, обходить и вести таблицу замен. Плюс биты в NAND самопроизвольно переворачиваются, поэтому нужен код коррекции ошибок: обычно от 1 до 8 бит на каждые 512 байт, на странице 2 КБ — до 32 бит. У NAND с интерфейсом SPI коррекция и учёт сбойных блоков часто встроены в микросхему — с неё проще начать, если NAND всё же нужна.

Отсюда вывод: начинать с NAND в своём проекте не стоит. Если нужны мегабайты — берите NOR по SPI, драйвер к ней пишется за вечер. Если нужны гигабайты — берите SD‑карту, где вся эта кухня уже спрятана внутри карты её собственным контроллером.

NAND оправдана, когда нужен именно большой объём в маленьком корпусе и есть готовый слой трансляции — например, файловые системы, специально рассчитанные на неё.


41.3 Чем подключать

Интерфейс Скорость Где есть Замечания
SPI до 2,25 МБ/с на F103 (18 МГц), до 5,25 МБ/с на F407 (SPI1, 42 МГц) везде четыре провода, проще некуда
Quad SPI до ~50 МБ/с F412, F446, F469 и старше отображение в память: содержимое видно как обычный массив
SDIO до ~24 МБ/с F103 высокой плотности, F4 четырёхбитная шина для SD
FSMC / FMC десятки МБ/с F103xC/xD/xE в корпусах на 100 и 144 вывода, F405/F407 (FSMC), F42x и F446 (FMC); у F401 и F411 нет параллельная шина: SRAM, NOR, NAND, SDRAM

⚠️ У BluePill нет ни SDIO, ни FSMC. STM32F103C8 — «средней плотности», эти контроллеры появляются только у старших кристаллов линейки (xC/xD/xE). Так что на BluePill остаётся SPI — и его, как правило, достаточно.

Про memory‑mapped режим Quad SPI стоит знать отдельно: контроллер показывает содержимое внешней флешки как обычную область адресов. Читать оттуда можно как из массива, и даже исполнять код — приём, которым расширяют программную память, когда встроенной не хватает.


41.4 Как устроена SPI NOR Flash

Три уровня организации, и их легко перепутать:

Единица Размер Что с ней можно
страница 256 байт записать за одну команду
сектор 4 КБ минимум, который можно стереть
блок 32 или 64 КБ стереть быстрее, чем посекторно

Ключевое правило флеш‑памяти: запись умеет только превращать единицы в нули. Обратно — лишь стиранием, и только целым сектором. Поэтому «переписать один байт» физически означает: считать сектор в ОЗУ, стереть сектор, изменить байт, записать сектор обратно.

Переписать один байт в W25Q128FV: сектор 4 КБ читается командой 0x03 в буфер в ОЗУ — 4 КБ из 20 у F103C8, сектор стирается командой 0x20 за время до 400 мс, байт правится в буфере, буфер возвращается шестнадцатью Page Program 0x02 по 256 байт, перед каждой — Write Enable 0x06

Отсюда и подход к логам: писать последовательно, никогда не возвращаясь назад, а стирать секторами по мере надобности.

Порядок любой операции записи:

  1. послать Write Enable — иначе микросхема молча проигнорирует команду;
  2. послать саму команду с адресом и данными;
  3. дождаться сброса флага занятости в регистре статуса.

Разрешение записи сбрасывается железом автоматически после каждой операции, поэтому его взводят перед каждой, а не один раз при старте.


41.5 🔧 W25Q128FV: типовая микросхема

16 МБ, страница 256 байт, сектор 4 КБ, питание 2,7–3,6 В — подключается к STM32 напрямую, без согласования уровней.

Первым делом — проверка связи. Микросхема должна назвать себя:

#define W25_CMD_JEDEC_ID  0x9Fu

uint32_t w25_read_id(void)
{
    uint32_t id;

    CS_LOW();
    spi_xfer(W25_CMD_JEDEC_ID);
    id  = (uint32_t)spi_xfer(0xFF) << 16;   /* производитель: 0xEF — Winbond */
    id |= (uint32_t)spi_xfer(0xFF) << 8;    /* тип памяти */
    id |= (uint32_t)spi_xfer(0xFF);         /* ёмкость: 0x18 = 128 Мбит */
    CS_HIGH();

    return id;
}

Для W25Q128FV ответ — 0xEF4018. Получили нули или 0xFFFFFF — проблема в обвязке, а не в коде: проверяйте питание, CS, режим SPI и подтяжки WP#/HOLD#.

Чтение — самая простая операция, стирания не требует:

void w25_read(uint32_t addr, uint8_t *buf, uint32_t len)
{
    CS_LOW();
    spi_xfer(0x03);                      /* Read Data */
    spi_xfer((uint8_t)(addr >> 16));
    spi_xfer((uint8_t)(addr >> 8));
    spi_xfer((uint8_t)addr);
    for (uint32_t i = 0; i < len; i++) {
        buf[i] = spi_xfer(0xFF);
    }
    CS_HIGH();
}

Запись и стирание обходятся двумя служебными командами. Write Enable (0x06) разрешает одну следующую запись или стирание, а бит 0 BUSY регистра статуса 1 (чтение — командой 0x05) стоит в единице, пока операция идёт:

static void w25_write_enable(void)
{
    CS_LOW();
    spi_xfer(0x06);                      /* Write Enable */
    CS_HIGH();
}

static void w25_wait_ready(void)
{
    CS_LOW();
    spi_xfer(0x05);                      /* Read Status Register-1 */
    while (spi_xfer(0xFF) & 0x01u) { }   /* бит 0 — BUSY */
    CS_HIGH();
}

Бит 1 того же регистра — WEL: после Write Enable он становится единицей, а по окончании записи или стирания микросхема сбрасывает его сама. По нему проверяют, что разрешение дошло. В изделии ожидание ограничивают тайм-аутом.

Запись страницы — с обязательным разрешением и ожиданием:

void w25_page_program(uint32_t addr, const uint8_t *buf, uint32_t len)
{
    /* Запись, вышедшая за границу страницы, заворачивается на её начало
       и затирает то, что там уже лежало. Обрезаем по границе. */
    uint32_t space = 256u - (addr % 256u);
    if (len > space) {
        len = space;
    }

    w25_write_enable();

    CS_LOW();
    spi_xfer(0x02);                      /* Page Program */
    spi_xfer((uint8_t)(addr >> 16));
    spi_xfer((uint8_t)(addr >> 8));
    spi_xfer((uint8_t)addr);
    for (uint32_t i = 0; i < len; i++) {
        spi_xfer(buf[i]);
    }
    CS_HIGH();

    w25_wait_ready();
}

⚠️ Заворачивание внутри страницы — ловушка, которую замечают не сразу. Если начать запись с адреса 0x0000 00F0 и передать 32 байта, то первые 16 лягут по адресам F0…FF, а следующие 16 — не в начало следующей страницы, а обратно в 00…0F той же страницы, поверх уже записанного. Ошибки не будет, данные просто окажутся не там. Отсюда обрезка по границе в примере выше.

А поскольку запись умеет только превращать единицы в нули, завернувшиеся байты не заменят старые, а сольются с ними по И — испорчено будет и то, и другое.

Запись 32 байт с адреса 0xF0: первая половина легла в конец страницы, вторая слилась с началом

Стирание — только целым сектором:

void w25_sector_erase(uint32_t addr)      /* 4 КБ, адрес любой внутри сектора */
{
    w25_write_enable();

    CS_LOW();
    spi_xfer(0x20);                      /* Sector Erase */
    spi_xfer((uint8_t)(addr >> 16));
    spi_xfer((uint8_t)(addr >> 8));
    spi_xfer((uint8_t)addr);
    CS_HIGH();

    w25_wait_ready();                    /* до 400 мс — это долго */
}

⚠️ Стирание занимает сотни миллисекунд, стирание всей микросхемы — десятки секунд. Ожидание в цикле опроса на это время блокирует программу. В реальном изделии флаг занятости опрашивают из основного цикла или по таймеру, а не крутят в ожидании.

Почему трёхбайтный адрес — это ровно 16 МБ. Три байта дают 2²⁴ = 16 777 216 адресов. W25Q128 использует их полностью, и это предел: микросхемы большего объёма требуют перехода в режим четырёхбайтной адресации, где у всех команд появляется лишний байт.

Адрес W25Q128FV из трёх байт: 8 бит номера блока 64 КБ, 4 бита сектора 4 КБ в блоке, 4 бита страницы 256 байт в секторе и 8 бит байта в странице — 256 блоков, 4096 секторов, ровно 16 МБ; последний адрес 0xFFFFFF, дальше нужен четырёхбайтный режим


41.6 🔧 AT45DB161E: когда микросхема нестандартная

Полезный контрпример. Тоже SPI Flash, тоже 3,3 В, но устроена иначе.

W25Q128FV AT45DB161E
объём 16 МБ 2 МБ
страница 256 байт 528 байт
буферы на кристалле нет два SRAM по 528 байт
наименьшее стирание сектор 4 КБ страница 528 байт
правка байта на месте через ОЗУ: считать сектор, стереть, записать внутри микросхемы: чтение, изменение, стирание и запись через буфер

Откуда 528. Это 512 байт данных плюс 16 служебных — исторически под контрольные суммы и метки. Микросхема умеет работать и с 512‑байтными страницами. У серии E переключение обратимо: команда 3D 2A 80 A6 ставит 512 байт, 3D 2A 80 A7 — снова 528, а размер хранится в энергонезависимом регистре, рассчитанном примерно на 10 000 переключений. Однократным оно было у старой серии D — ревизию проверяют по даташиту.

Два буфера — её сильная сторона. Можно записать данные в буфер, а потом одной командой перенести в основной массив, причём с автоматическим стиранием страницы. Пока идёт перенос, во второй буфер уже можно готовить следующую порцию. Для микроконтроллера с 20 КБ ОЗУ это ощутимая экономия: не нужно держать копию сектора у себя.

Когда какую брать:


41.7 SD‑карта по SPI

Способ получить гигабайты и съёмный носитель, не разбираясь с NAND: внутри карты свой контроллер, который уже занимается износом, сбойными блоками и коррекцией ошибок.

Порядок инициализации:

  1. подать не менее 74 тактов при поднятом CS — карта «просыпается»;
  2. CMD0 — перевести в режим ожидания;
  3. CMD8 — выяснить версию (SDv1 или SDv2);
  4. ACMD41 в цикле — дождаться готовности;
  5. CMD58 — узнать, адресуется карта байтами или блоками.

⚠️ Инициализация идёт на низкой скорости — не быстрее 400 кГц. Только после её завершения делитель SPI переключают на рабочую частоту. Попытка начать сразу на 20 МГц даёт карту, которая «не определяется», причём у разных карт по‑разному, что запутывает окончательно.

CMD0 приходит, пока карта ещё в родном режиме SD, где сумма проверяется всегда, а CMD8 карта проверяет по контрольной сумме даже в режиме SPI, поэтому их кадры берут готовыми: 40 00 00 00 00 95 и 48 00 00 01 AA 87 — последний байт и есть CRC7 со стоповой единицей. У остальных команд сумму в SPI по умолчанию не проверяют, и на её месте шлют 0x01 — одну стоповую единицу.

Обмен блоком:

После снятия CS нужен ещё один байт 0xFF: карта отпускает MISO только по тактам, и без него соседняя микросхема на той же шине (например, W25Q128 из 41.5) прочитает мусор.

Блок у SD всегда 512 байт — ровно сектор FAT, поэтому слой диска для файловой системы пишется почти без переходников.

Питание — отдельная тема: карта в пике потребляет до 100 мА и роняет напряжение на тонких проводниках. Конденсатор рядом с держателем обязателен.


41.8 Грабли

Забытый Write Enable. Команда записи выполняется «вхолостую», ошибки нет, данные не меняются. Проверять — чтением бита WEL (бит 1 регистра статуса 1).

Запись без стирания. Флеш меняет единицы на нули, но не обратно. Записали в непустой сектор — получите побитовое «И» старого и нового. Данные будут похожи на правду, но испорчены.

Ожидание занятости. Стирание сектора длится сотни миллисекунд. Программа на это время замирает, сторожевой таймер может успеть сработать.

Неверный режим SPI. Большинство таких микросхем работают в режимах 0 и 3. Перепутанные CPOL/CPHA дают чтение мусора, часто устойчивого — похоже на «микросхема битая».

Незакреплённые WP# и HOLD#. Оставленные висеть в воздухе, они ловят наводки: запись то проходит, то нет. Подтянуть к питанию.

Длинные провода. На макетке при 20 МГц SPI работает через раз. Для проверки снизьте частоту до пары мегагерц: заработало — дело в разводке, а не в коде.

Буфер DMA в CCM. Если передача идёт через DMA, буфер не должен лежать в памяти, подключённой напрямую к ядру (раздел 9).

Ресурс. 100 000 циклов на сектор — много, но не бесконечно. Журнал, пишущий в один и тот же сектор каждую секунду, изнашивает его за сутки. Пишите последовательно по кругу.


41.9 Итоги

Дальше: раздел 41а «SD-карта по SDIO: четыре линии данных, команды, DMA и FatFS» — та же карта, но по родному протоколу и вчетверо быстрее.

Открыть в интерактивном справочнике