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

41в. Quad-SPI: внешняя флеш как память и код из неё

Раздел 41 назвал главное свойство Quad-SPI: контроллер показывает внешнюю флеш как обычную область адресов, и из неё можно даже исполнять код. Здесь — как это устроено: из каких фаз состоит команда, почему чтение на четырёх линиях в четыре раза быстрее не во всём, как включить отображение в память по регистрам, как стирать и писать ту же флеш и как положить в неё код.

Опоры: справочное руководство RM0385 (F74x/F75x, глава QUADSPI), руководство по ядру Cortex-M7 (DUI 0646B), пакет поддержки платы STM32746G-Discovery и драйвер флеш N25Q128A из пака Keil, описание пака (алгоритмы программирования). Код собран armclang для F746; такты и скорости — скриптом на ПК. QEMU контроллера не моделирует.

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

Оригинал Перевод
QUADSPI контроллер флеш с одной, двумя или четырьмя линиями данных
OCTOSPI его развитие на восемь линий у новых серий
indirect mode косвенный режим: команды через регистры
status polling mode автоматический опрос статуса флеш
memory-mapped mode отображение в память: флеш видна как область адресов
XIP (execute in place) исполнение кода прямо из внешней флеш
dummy cycles пустые такты между адресом и данными
FSIZE размер флеш: байт = 2^(FSIZE+1)
nCS выбор микросхемы флеш

41в.1 Три режима и где они есть

QUADSPI у F7 работает в трёх режимах (RM0385, 14.1):

Quad-SPI есть у F412, F446, F469 и старше (раздел 41), у F7 и H7. У новых кристаллов — STM32L4R9, STM32H7B3, STM32U585 — блок называется OCTOSPI: до восьми линий данных, а устроен так же. Здесь разобран QUADSPI F746.

Команда чтения 0xEB на четырёх линиях: 8 тактов инструкции по одной линии, 6 тактов адреса по четырём, 10 пустых тактов, дальше по байту за 2 такта; первые 4 байта — 32 такта, 296 нс при 108 МГц; у быстрого чтения 0x0B по одной линии — 72 такта


41в.2 Команда: фазы и линии

Каждое обращение к флеш — команда из фаз (RM0385, 14.3.3): инструкция (8 бит), адрес (8–32 бита), дополнительные байты, пустые такты, данные. Для каждой фазы отдельно задаётся число линий: IMODE для инструкции, ADMODE для адреса, DMODE для данных — 00 фазы нет, 01 одна линия, 10 две, 11 четыре. Пустые такты — поле DCYC, 0–31. Всё это лежит в регистре QUADSPI_CCR вместе с кодом инструкции и режимом FMODE.

Запись вида «1-4-4» и значит: инструкция по одной линии, адрес и данные — по четырём. Команда 0xEB флеш N25Q128A — именно такая, с 10 пустыми тактами (драйвер BSP, N25Q128A_DUMMY_CYCLES_READ_QUAD). Тактов на чтение, адрес 24 бита, QUADSPI на 108 МГц (216 МГц / 2, как в BSP):

Команда Линии Пустых 4 байта 256 байт Поток
0x0B быстрое чтение 1-1-1 8 72 такта, 666,7 нс 2088 тактов, 19,33 мкс 13,2 МБ/с
0xEB 1-4-4 10 32 такта, 296,3 нс 536 тактов, 4,96 мкс 51,6 МБ/с

На длинном чтении четыре линии дают почти вчетверо больше (предел — 54 МБ/с). На одном слове выигрыш вдвое: инструкция по-прежнему идёт по одной линии 8 тактов, и пустые такты никуда не деваются. Поэтому код, прыгающий по флеш, выигрывает от Quad-SPI меньше, чем чтение подряд, — и поэтому важны кэш и упреждающее чтение (41в.4).


41в.3 Настройка по регистрам

У STM32746G-Discovery флеш N25Q128A на 16 МБ: nCS — PB6, CLK — PB2, IO0–IO3 — PD11, PD12, PE2, PD13 (AF9 и AF10). Числа BSP в регистрах:

void qspi_init(void)                    /* выводы PB2, PB6, PD11–13, PE2 — AF9/AF10, их настраивает CubeMX */
{
    RCC->AHB3ENR |= RCC_AHB3ENR_QSPIEN;
    QUADSPI->CR  = (1u << QUADSPI_CR_PRESCALER_Pos)     /* 216 / (1 + 1) = 108 МГц */
                 | (3u << QUADSPI_CR_FTHRES_Pos)        /* порог FIFO — 4 байта */
                 | QUADSPI_CR_SSHIFT;                   /* выборка на полтакта позже */
    QUADSPI->DCR = (23u << QUADSPI_DCR_FSIZE_Pos)       /* 2^(23 + 1) = 16 МБ */
                 | (5u << QUADSPI_DCR_CSHT_Pos);        /* nCS между командами — 6 тактов */
    QUADSPI->CR |= QUADSPI_CR_EN;
}

После записи CCR с FMODE = 11 флеш — это массив с 0x9000 0000. Функция qspi_read_word(offset) из файла примера у armclang — одна команда загрузки:

qspi_read_word:  MOV  r1,#0x90000000
                 LDR  r0,[r0,r1]

Никакого кода обмена: команду 0xEB контроллер шлёт сам, как только процессор или DMA читают окно.


41в.4 Отображение в память: что можно и чего нельзя

RM0385 (14.3.7 и 14.3.11) ставит правила:

Цена упреждения: контроллер по умолчанию не останавливает его и держит nCS внизу, даже если обращений давно нет, а флеш с опущенным nCS потребляет больше. Бит TCEN в CR и таймаут в LPTR отпускают nCS после паузы. И пока идёт упреждение, флаг BUSY не падает — а поля CCR можно менять только при BUSY = 0. Выйти из отображения можно битом ABORT: он сбрасывается сам, когда отмена закончена (функция qspi_leave_mapped).


41в.5 Стирание и запись

Флеш N25Q128A: страница 256 байт, подсектор 4 КБ, сектор 64 КБ — 65 536 страниц, 4096 подсекторов, 256 секторов. Образ прошивки на 1 МБ — это 4096 страниц и 256 подсекторов (или 16 секторов). Порядок для каждого куска:

  1. Выйти из отображения (ABORT).
  2. Команда 0x06 — разрешить запись (qspi_write_enable: только инструкция, без адреса и данных).
  3. Стирание подсектора 0x20 с адресом.
  4. Ждать, пока флеш закончит: автоматический опрос статуса 0x05 с маской на бит WIP (qspi_wait_ready). Процессор не читает статус в цикле — контроллер сам шлёт 0x05 и поднимет флаг SMF при WIP = 0.
  5. Снова 0x06 и запись страницы (256 байт за команду).
  6. Снова ждать WIP.
  7. Вернуть отображение.

Сколько ждать: драйвер BSP даёт на стирание подсектора до 800 мс, сектора — до 3 с, всей микросхемы — до 250 с.

Функции, которые это делают, не могут лежать в той же флеш: пока идёт косвенная команда, окна 0x9000 0000 нет. Их держат во встроенной Flash или в ОЗУ. И на F7 после записи надо сбросить D-кэш на изменённый участок (SCB_InvalidateDCache_by_Addr, раздел 11б): иначе ядро прочитает старые данные из кэша.


41в.6 Код во внешней флеш

По карте памяти Cortex-M7 (DUI 0646B, таблицы 2-11 и 2-12) область 0x8000 0000–0x9FFF FFFF — External RAM: тип Normal, исполнять можно, кэш — сквозная запись. Значит, окно QUADSPI с 0x9000 0000 годится для кода без настройки MPU, и кэш команд прячет задержку первого обращения.

Что нужно сделать:

Удобное разделение: код, от которого зависит запуск (стартовый код, настройка тактирования и QUADSPI, обновление прошивки), — во встроенной Flash; большие данные и редко вызываемый код — снаружи.


41в.7 Грабли


41в.8 Итоги

Полные исходники

qspi41c.c — Quad-SPI на STM32F746 по регистрам с флеш N25Q128A.

/* 41в: Quad-SPI по регистрам на STM32F746 — флеш N25Q128A (16 МБ), как у STM32746G-Discovery.
   Собирается armclang: -DSTM32F746xx. */
#include <stdint.h>

/* поля QUADSPI_CCR — RM0385, 14.5.6 */
#define CCR(fmode, dmode, dcyc, adsize, admode, imode, inst) \
    (((uint32_t)(fmode) << QUADSPI_CCR_FMODE_Pos) | ((uint32_t)(dmode) << QUADSPI_CCR_DMODE_Pos) | \
     ((uint32_t)(dcyc) << QUADSPI_CCR_DCYC_Pos) | ((uint32_t)(adsize) << QUADSPI_CCR_ADSIZE_Pos) | \
     ((uint32_t)(admode) << QUADSPI_CCR_ADMODE_Pos) | ((uint32_t)(imode) << QUADSPI_CCR_IMODE_Pos) | (inst))

enum { F_WRITE = 0, F_READ = 1, F_POLL = 2, F_MAPPED = 3 };        /* FMODE */
enum { L_NONE = 0, L_1 = 1, L_2 = 2, L_4 = 3 };                     /* IMODE, ADMODE, DMODE */
enum { A_24 = 2 };                                                  /* ADSIZE */

#define CCR_MAPPED  CCR(F_MAPPED, L_4, 10, A_24, L_4, L_1, 0xEB)   /* 0xEB: 1-4-4, 10 пустых */
_Static_assert(CCR_MAPPED == 0x0F282DEBu, "CCR режима отображения");
_Static_assert(QSPI_BASE == 0x90000000u && QSPI_R_BASE == 0xA0001000u, "окно флеш и регистры");

static void qspi_wait_idle(void)
{
    while (QUADSPI->SR & QUADSPI_SR_BUSY) {
    }
}

void qspi_init(void)                    /* выводы PB2, PB6, PD11–13, PE2 — AF9/AF10, их настраивает CubeMX */
{
    RCC->AHB3ENR |= RCC_AHB3ENR_QSPIEN;
    QUADSPI->CR  = (1u << QUADSPI_CR_PRESCALER_Pos)     /* 216 / (1 + 1) = 108 МГц */
                 | (3u << QUADSPI_CR_FTHRES_Pos)        /* порог FIFO — 4 байта */
                 | QUADSPI_CR_SSHIFT;                   /* выборка на полтакта позже */
    QUADSPI->DCR = (23u << QUADSPI_DCR_FSIZE_Pos)       /* 2^(23 + 1) = 16 МБ */
                 | (5u << QUADSPI_DCR_CSHT_Pos);        /* nCS между командами — 6 тактов */
    QUADSPI->CR |= QUADSPI_CR_EN;
}

/* команда без адреса и данных: 0x06 — разрешить запись */
void qspi_write_enable(void)
{
    qspi_wait_idle();
    QUADSPI->CCR = CCR(F_WRITE, L_NONE, 0, 0, L_NONE, L_1, 0x06);
    qspi_wait_idle();
}

/* ждать конца стирания или записи: контроллер сам читает статус 0x05, пока бит WIP не станет 0 */
void qspi_wait_ready(void)
{
    qspi_wait_idle();
    QUADSPI->DLR   = 0;                  /* один байт статуса */
    QUADSPI->PSMKR = 0x01;               /* маска: WIP */
    QUADSPI->PSMAR = 0x00;               /* ждём WIP = 0 */
    QUADSPI->PIR   = 16;                 /* пауза между опросами, тактов */
    QUADSPI->CR   |= QUADSPI_CR_APMS;    /* остановиться при совпадении */
    QUADSPI->CCR   = CCR(F_POLL, L_1, 0, 0, L_NONE, L_1, 0x05);
    while (!(QUADSPI->SR & QUADSPI_SR_SMF)) {
    }
    QUADSPI->FCR = QUADSPI_FCR_CSMF;
}

/* включить отображение: флеш видна с 0x90000000, только чтение */
void qspi_memory_mapped(void)
{
    qspi_wait_idle();
    QUADSPI->CCR = CCR_MAPPED;
}

/* выйти из отображения перед стиранием и записью: BUSY держится из-за упреждающего чтения */
void qspi_leave_mapped(void)
{
    QUADSPI->CR |= QUADSPI_CR_ABORT;
    while (QUADSPI->CR & QUADSPI_CR_ABORT) {
    }
}

uint32_t qspi_read_word(uint32_t offset)
{
    return *(volatile const uint32_t *)(QSPI_BASE + offset);
}

Дальше: раздел 42 «Файловая система FatFS» — как поверх внешней памяти хранить файлы.

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