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

41б. FSMC и FMC: дисплей 8080, внешняя SRAM и SDRAM

Раздел 41 назвал FSMC одной строкой таблицы: параллельная шина для SRAM, NOR, NAND и SDRAM. Раздел 22а подключает к ней дисплей со своим контроллером: команда и пиксель — запись по одному из двух адресов. Здесь — как это устроено: откуда берутся эти адреса, как считать тайминги в тактах шины, чем внешняя SRAM отличается от SDRAM и почему SDRAM надо запускать по шагам и регенерировать.

Опоры: справочное руководство RM0090 (F405/F407 и F427/F429, главы FSMC и FMC), руководство по ядру Cortex-M7 (DUI 0646B) и RM0385 для F7. Числа плат — из пакета поддержки плат (BSP) в паках Keil: STM32412G-Discovery (дисплей), STM324xG-EVAL (SRAM), STM32F429I-Discovery и STM32746G-Discovery (SDRAM). Код собран armclang для F407 и F429; расчёты — скриптом на ПК. Внешних шин QEMU не моделирует — это проверено сверкой с документами, а не прогоном.

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

Оригинал Перевод
FSMC (flexible static memory controller) контроллер статической памяти: SRAM, NOR, NAND, PC Card
FMC (flexible memory controller) то же плюс контроллер SDRAM
NE1…NE4 выбор микросхемы в банке 1: четыре области по 64 МБ
HADDR внутренний адрес шины AHB
ADDSET, DATAST фаза адреса и фаза данных, в тактах HCLK
BUSTURN пауза между обращениями, в тактах HCLK
extended mode, mode A раздельные тайминги чтения и записи
SDRAM refresh регенерация: подзарядка строк динамической памяти
CAS latency задержка выдачи данных после команды чтения столбца

41б.1 Где есть FSMC и FMC

FSMC — у F103 высокой плотности (раздел 41) и у F405/F407; у BluePill его нет. FMC — у F427/F429, F7 и H7: к тем же банкам статической памяти добавлен контроллер SDRAM. Для программы оба выглядят одинаково: внешняя память — это область адресов, и чтение или запись по ней становится циклом на выводах. Контроллер сам выставляет адрес, выбор микросхемы и стробы чтения и записи, выжидает заданные такты и забирает данные. Ни одной строки кода на цикл шины — только настройка таймингов.

Банки у RM0090 (таблица 218 и соседние):

Адреса Банк Что подключают
0x6000 0000–0x6FFF FFFF банк 1: NE1…NE4 по 64 МБ SRAM, PSRAM, NOR, дисплей 8080
0x7000 0000, 0x8000 0000 банки 2 и 3 NAND
0x9000 0000 банк 4 PC Card
0xC000 0000, 0xD000 0000 банки SDRAM 1 и 2 (только FMC) SDRAM

Дисплей 8080 на FSMC: шестнадцать линий данных, строб чтения NOE, строб записи NWE, выбор NE1 и одна адресная линия A0 на входе RS; запись по 0x6000 0000 уходит как команда, по 0x6000 0002 — как данные, потому что на 16-битной шине линия A0 — это второй бит адреса; справа карта банков от 0x6000 0000 до 0xD000 0000


41б.2 Адреса: банк, NE и сдвиг на 16-битной шине

Биты адреса HADDR[27:26] выбирают одну из четырёх микросхем банка 1 (RM0090, таблица 216): NE1 — 0x6000 0000, NE2 — 0x6400 0000, NE3 — 0x6800 0000, NE4 — 0x6C00 0000. Каждой достаётся 64 МБ адресов.

Тонкость — в ширине шины (таблица 217). HADDR адресует байты, а 16-битная память — слова. Поэтому на 16-битной шине контроллер выдаёт на линии FSMC_A[24:0] биты HADDR[25:1]: линия A0 — это бит 1 адреса, A16 — бит 17. Отсюда адреса дисплея, у которого вход RS («команда или данные») посажен на одну адресную линию:

Выбор RS на линии Команда Данные
NE1 A0 0x6000 0000 0x6000 0002
NE1 A16 0x6000 0000 0x6002 0000
NE1 A18 0x6000 0000 0x6008 0000
NE4 A6 0x6C00 0000 0x6C00 0080
NE4 A10 0x6C00 0000 0x6C00 0800

Для A16 наивный расчёт 1 << 16 даёт 0x6001 0000 — это всё ещё команда: на линии A16 ноль. Сдвиг на единицу забывают чаще всего.

В примерах к недорогим платам встречается база 0x6C00 007E. Это тот же расчёт: структура из двух 16-битных полей {REG; RAM}, положенная с 0x6C00 007E, даёт REG по 0x6C00 007E (A6 = 0) и RAM по 0x6C00 0080 (A6 = 1) — RS на линии A6.


41б.3 Дисплей 8080 через FSMC

У STM32412G-Discovery дисплей висит на NE1, RS — на линии A0 (вывод PF0). BSP описывает его структурой из двух полей — и тогда команда и данные пишутся обычным присваиванием:

typedef struct {
    volatile uint16_t REG;              /* 0x60000000: A0 = 0 — команда */
    volatile uint16_t RAM;              /* 0x60000002: A0 = 1 — данные  */
} lcd_t;
#define LCD ((lcd_t *)0x60000000u)

_Static_assert(offsetof(lcd_t, RAM) == 2, "на 16-битной шине A0 = HADDR[1]: данные на 2 байта выше");

void lcd_cmd(uint8_t c)   { LCD->REG = c; }
void lcd_data(uint16_t d) { LCD->RAM = d; }

armclang для F407 превращает каждую функцию в одну 16-битную запись:

lcd_cmd:   MOV   r1,#0x60000000
           STRH  r0,[r1,#0]
lcd_data:  MOVS  r1,#2
           MOVT  r1,#0x6000
           STRH  r0,[r1,#0]

volatile здесь обязателен (раздел 124): без него две записи подряд по адресу RAM компилятор вправе слить в одну, а для дисплея это два пикселя. Настройка банка по регистрам — в функции fsmc_lcd_init файла примера: тайминги в BTR1 и BWTR1 (41б.4) и BCR1 с шириной 16 бит, разрешением записи и раздельными таймингами (EXTMOD). Выводы — альтернативная функция AF12; их проще отдать CubeMX.

Запись по шине не ждёт процессор дольше, чем идёт цикл, — и её можно отдать DMA в режиме «память → память» (раздел 9): источник — буфер строки в ОЗУ, приёмник — адрес RAM без приращения.


41б.4 Тайминги: режим A в тактах HCLK

Асинхронный цикл состоит из фаз, каждая — целое число тактов HCLK. В режиме A (раздельные тайминги чтения и записи, EXTMOD = 1) RM0090 даёт (таблицы 229–230):

Тайминги чтения лежат в BTR1, записи — в BWTR1. У дисплея чтение обычно намного медленнее записи, и именно поэтому режим A: одни и те же числа на оба направления либо замедлят запись, либо сорвут чтение.

BSP STM32412G-Discovery ставит (такт 10 нс при HCLK 100 МГц — предельной частоте F412):

ADDSET DATAST Тактов Время цикла
чтение 9 36 45 450 нс
запись 1 7 9 90 нс

Как из этого получить числа под свой дисплей: из его документации берут минимальную длительность цикла записи и минимальную длительность низкого уровня на WR, делят на такт HCLK с округлением вверх: DATAST + 1 — не меньше низкого уровня WR, ADDSET + DATAST + 1 — не меньше цикла. Для чтения — так же по циклу чтения. Запас по такту — нормально; нехватка даёт ошибки, которые видны не сразу, а на части точек.

Предел скорости картинки — число записей на такт шины. При 90 нс на запись (16 бит на точку, одна запись на точку):

Экран Записей Время кадра Не больше кадров/с
240×240 57 600 5,184 мс 192
320×240 76 800 6,912 мс 144
480×320 153 600 13,824 мс 72

Это потолок шины; цикл программы или DMA добавляют своё.


41б.5 Внешняя SRAM

Статическая память подключается так же, как дисплей, только адресных линий много. У STM324xG-EVAL SRAM IS61WV102416BLL на 2 МБ (16 бит) стоит на NE2 — адрес 0x6400 0000. BSP ставит ADDSET 2, DATAST 2, BUSTURN 1 в режиме A; при HCLK 168 МГц это 4 такта (23,8 нс) на чтение и 5 тактов (29,8 нс) на запись.

Такая память видна как обычный массив: в неё можно положить кучу, буфер кадра или большие таблицы — через секцию компоновщика (раздел 11) или просто указателем. Она медленнее встроенной SRAM: каждое 32-битное обращение к 16-битной памяти контроллер делит на два цикла шины (RM0090, правила транзакций в 36.3.1).


41б.6 SDRAM на FMC: запуск и регенерация

SDRAM дешевле SRAM того же объёма, но она динамическая: ячейки разряжаются, и каждую строку надо регенерировать не реже, чем за период регенерации (обычно 64 мс на все строки). Её адрес мультиплексирован: строка, потом столбец. Поэтому FMC держит отдельный контроллер SDRAM со своими регистрами SDCR, SDTR, SDCMR и SDRTR.

У STM32F429I-Discovery SDRAM IS42S16400J на банке 2 — адрес 0xD000 0000: 12 бит строки, 8 бит столбца, 4 банка, шина 16 бит. Объём — 4096 × 256 × 4 × 2 = 8 388 608 байт = 0x80 0000, как SDRAM_DEVICE_SIZE в BSP. SDCLK — HCLK / 2.

Запуск по RM0090 — восемь шагов:

  1. SDCR: частота SDCLK, чтение пачками RBURST, задержка RPIPE, строки, столбцы, ширина, банки, CAS.
  2. SDTR: тайминги памяти.
  3. Команда «Clock Configuration Enable» — на память пошёл SDCLK.
  4. Пауза: обычно около 100 мкс (по документации памяти).
  5. Команда «Precharge All» — закрыть все строки.
  6. Команда «Auto-refresh» — обычно 8 раз подряд (поле NRFS).
  7. Команда «Load Mode Register»: длина пачки 1 и CAS, как в SDCR.
  8. Счётчик регенерации в SDRTR.

По регистрам это функция sdram_init файла примера. Две вещи в ней легко пропустить. Частоту SDCLK, RBURST и RPIPE пишут только в SDCR1, а TRC и TRP — только в SDTR1, даже когда память стоит на банке 2: у SDCR2 и SDTR2 эти поля не действуют. И в поля таймингов пишут «тактов минус один»: TRC 7 тактов — это 6 в поле (так же делает HAL).

Тайминги BSP F429I-Discovery при SDCLK 90 МГц (11,1 нс):

Параметр Тактов нс
TMRD (после Load Mode) 2 22,2
TXSR (выход из саморегенерации) 7 77,8
TRAS (строка открыта) 4 44,4
TRC (от строки до строки) 7 77,8
TWR (после записи) 2 22,2
TRP (закрытие строки) 2 22,2
TRCD (строка → столбец) 2 22,2

Счётчик регенерации (регистр FMC_SDRTR):

COUNT = (период регенерации / число строк) × SDCLK − 20

Двадцать тактов — запас на случай, если запрос регенерации придёт во время принятого чтения. COUNT не меньше 41; сумма TWR + TRP + TRC + TRCD + 4 (здесь 17) запрещена.

Плата SDCLK Строк Интервал Расчёт COUNT В BSP
F429I-Discovery 90 МГц 4096 15,625 мкс 1386 1386
746G-Discovery 100 МГц 4096 15,625 мкс 1542 1539
пример RM0090 60 МГц 8192 7,812 мкс 448 448

У 746G-Discovery в BSP на три такта меньше расчёта: регенерация чуть чаще нужного — это безопасная сторона. В примере RM0090 строк напечатано 8196 — опечатка, на результат 448 она не влияет.


41б.7 Грабли


41б.8 Итоги

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

fmc41b.c — FSMC/FMC по регистрам: дисплей 8080 на F407 и SDRAM на F429.

/* 41б: FSMC/FMC по регистрам — дисплей 8080 на F407 и SDRAM на F429.
   Собирается armclang: -DSTM32F407xx (дисплей) и -DSTM32F429xx (SDRAM). */
#include <stddef.h>
#include <stdint.h>

/* ── дисплей 8080 на NE1, линия RS на A0 (как у STM32412G-Discovery) ── */
typedef struct {
    volatile uint16_t REG;              /* 0x60000000: A0 = 0 — команда */
    volatile uint16_t RAM;              /* 0x60000002: A0 = 1 — данные  */
} lcd_t;
#define LCD ((lcd_t *)0x60000000u)

_Static_assert(offsetof(lcd_t, RAM) == 2, "на 16-битной шине A0 = HADDR[1]: данные на 2 байта выше");

void lcd_cmd(uint8_t c)   { LCD->REG = c; }
void lcd_data(uint16_t d) { LCD->RAM = d; }

void lcd_fill(uint16_t color, uint32_t n)
{
    while (n--)
        LCD->RAM = color;               /* одна запись по шине на точку */
}

#if defined(STM32F407xx)
/* поля BTR/BWTR — RM0090, таблицы 229–230; ACCMOD = 0 — режим A */
#define BTR(addset, datast, busturn) \
    (((uint32_t)(addset) << FSMC_BTR1_ADDSET_Pos) | ((uint32_t)(datast) << FSMC_BTR1_DATAST_Pos) | \
     ((uint32_t)(busturn) << FSMC_BTR1_BUSTURN_Pos))

_Static_assert(FSMC_Bank1_R_BASE == 0xA0000000u && FSMC_Bank1E_R_BASE == 0xA0000104u, "регистры FSMC");
_Static_assert(BTR(9, 36, 1) == 0x00012409u && BTR(1, 7, 0) == 0x00000701u, "BTR1 и BWTR1");

void fsmc_lcd_init(void)                /* выводы PD, PE, PF — AF12, их настраивает CubeMX */
{
    RCC->AHB3ENR |= RCC_AHB3ENR_FSMCEN;
    FSMC_Bank1->BTCR[1]  = BTR(9, 36, 1);       /* BTR1, чтение: 9 + 36 = 45 HCLK */
    FSMC_Bank1E->BWTR[0] = BTR(1, 7, 0);        /* BWTR1, запись: 1 + 7 + 1 = 9 HCLK */
    /* после сброса BCR1 = 0x30DB: NOR, адрес и данные на одних линиях (MUXEN), WAITEN */
    FSMC_Bank1->BTCR[0] = (FSMC_Bank1->BTCR[0]
                           & ~(FSMC_BCR1_MUXEN | FSMC_BCR1_MTYP | FSMC_BCR1_MWID | FSMC_BCR1_WAITEN))
                        | FSMC_BCR1_MWID_0 | FSMC_BCR1_WREN | FSMC_BCR1_EXTMOD | FSMC_BCR1_MBKEN;
}

#elif defined(STM32F429xx)
/* ── SDRAM IS42S16400J на банке 2 (0xD0000000), как у STM32F429I-Discovery ── */
_Static_assert(FMC_Bank5_6_R_BASE == 0xA0000140u, "регистры SDRAM");

enum { CMD_CLK = 1, CMD_PALL = 2, CMD_AUTOREFRESH = 3, CMD_LOAD_MODE = 4 };

static void sdram_cmd(uint32_t mode, uint32_t nrfs, uint32_t mrd)
{
    FMC_Bank5_6->SDCMR = (mode << FMC_SDCMR_MODE_Pos) | FMC_SDCMR_CTB2 |
                         ((nrfs - 1u) << FMC_SDCMR_NRFS_Pos) | (mrd << FMC_SDCMR_MRD_Pos);
    while (FMC_Bank5_6->SDSR & FMC_SDSR_BUSY) {
    }
}

extern void delay_us(uint32_t us);

void sdram_init(uint32_t count)         /* 1386 при SDCLK 90 МГц, 1292 при 84 МГц */
{
    RCC->AHB3ENR |= RCC_AHB3ENR_FMCEN;
    /* SDCLK, RBURST и RPIPE — только в SDCR1, даже когда память на банке 2 */
    FMC_Bank5_6->SDCR[0] = (2u << FMC_SDCR1_SDCLK_Pos) | (1u << FMC_SDCR1_RPIPE_Pos);
    FMC_Bank5_6->SDCR[1] = (0u << FMC_SDCR1_NC_Pos) | (1u << FMC_SDCR1_NR_Pos) | (1u << FMC_SDCR1_MWID_Pos)
                         | (1u << FMC_SDCR1_NB_Pos) | (3u << FMC_SDCR1_CAS_Pos);
    /* TRC и TRP — только в SDTR1; в поля пишут «тактов минус один» */
    FMC_Bank5_6->SDTR[0] = ((7u - 1u) << FMC_SDTR1_TRC_Pos) | ((2u - 1u) << FMC_SDTR1_TRP_Pos);
    FMC_Bank5_6->SDTR[1] = ((2u - 1u) << FMC_SDTR1_TMRD_Pos) | ((7u - 1u) << FMC_SDTR1_TXSR_Pos)
                         | ((4u - 1u) << FMC_SDTR1_TRAS_Pos) | ((2u - 1u) << FMC_SDTR1_TWR_Pos)
                         | ((2u - 1u) << FMC_SDTR1_TRCD_Pos);
    sdram_cmd(CMD_CLK, 1, 0);           /* 3: подать SDCLK */
    delay_us(100);                      /* 4: около 100 мкс */
    sdram_cmd(CMD_PALL, 1, 0);          /* 5: закрыть все строки */
    sdram_cmd(CMD_AUTOREFRESH, 8, 0);   /* 6: восемь регенераций подряд */
    sdram_cmd(CMD_LOAD_MODE, 1, 0x0230);        /* 7: BL 1, CAS 3, запись одиночная */
    FMC_Bank5_6->SDRTR = count << FMC_SDRTR_COUNT_Pos;         /* 8: счётчик регенерации */
}
#endif

Дальше: раздел 41в «Quad-SPI: внешняя флеш как память и код из неё» — последовательная внешняя флеш, которую тоже видно как область адресов.

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