Embedded HandbookApps
All sections › VI. Practice
STM32F407, STM32F412, STM32F429, STM32F746 · level intermediate · 13 min read
Open in the interactive handbook

41б. FSMC and FMC: 8080 display, external SRAM and SDRAM

Section 41 gave FSMC one row of a table: a parallel bus for SRAM, NOR, NAND and SDRAM. Section 22а connects a display with its own controller to it: a command and a pixel are each a write to one of two addresses. Here is how it works: where these addresses come from, how to count timings in bus clocks, how external SRAM differs from SDRAM, and why SDRAM has to be started step by step and refreshed.

Sources: the reference manual RM0090 (F405/F407 and F427/F429, the FSMC and FMC chapters), the Cortex-M7 core manual (DUI 0646B) and RM0385 for the F7. The board numbers come from the board support packages (BSP) in the Keil packs: STM32412G-Discovery (display), STM324xG-EVAL (SRAM), STM32F429I-Discovery and STM32746G-Discovery (SDRAM). The code is built with armclang for the F407 and F429; the calculations are done by a script on a PC. QEMU does not model external buses — this was checked against the documents, not by running it.

Terms used in the documentation

Term Meaning
FSMC (flexible static memory controller) a static memory controller: SRAM, NOR, NAND, PC Card
FMC (flexible memory controller) the same plus an SDRAM controller
NE1…NE4 chip selects in bank 1: four regions of 64 MB
HADDR the internal AHB bus address
ADDSET, DATAST the address phase and the data phase, in HCLK cycles
BUSTURN the pause between accesses, in HCLK cycles
extended mode, mode A separate read and write timings
SDRAM refresh recharging the rows of dynamic memory
CAS latency the delay before data comes out after a column read command

41б.1 Where FSMC and FMC exist

FSMC is on the high-density F103 (section 41) and on the F405/F407; the BluePill does not have it. FMC is on the F427/F429, F7 and H7: an SDRAM controller is added to the same static memory banks. To the program both look the same: external memory is a range of addresses, and a read or write there becomes a cycle on the pins. The controller itself drives the address, the chip select and the read and write strobes, waits the configured clocks and picks up the data. Not a single line of code per bus cycle — only the timing setup.

The banks in RM0090 (table 218 and its neighbours):

Addresses Bank What is connected
0x6000 0000–0x6FFF FFFF bank 1: NE1…NE4, 64 MB each SRAM, PSRAM, NOR, 8080 display
0x7000 0000, 0x8000 0000 banks 2 and 3 NAND
0x9000 0000 bank 4 PC Card
0xC000 0000, 0xD000 0000 SDRAM banks 1 and 2 (FMC only) SDRAM

An 8080 display on FSMC: sixteen data lines, the NOE read strobe, the NWE write strobe, the NE1 select and a single address line A0 on the RS input; a write to 0x6000 0000 goes out as a command, a write to 0x6000 0002 as data, because on a 16-bit bus line A0 is the second address bit; on the right, the bank map from 0x6000 0000 to 0xD000 0000


41б.2 Addresses: bank, NE and the shift on a 16-bit bus

Address bits HADDR[27:26] select one of the four chips of bank 1 (RM0090, table 216): NE1 — 0x6000 0000, NE2 — 0x6400 0000, NE3 — 0x6800 0000, NE4 — 0x6C00 0000. Each gets 64 MB of addresses.

The subtlety is in the bus width (table 217). HADDR addresses bytes, while a 16-bit memory addresses words. So on a 16-bit bus the controller puts HADDR[25:1] on the FSMC_A[24:0] lines: line A0 is address bit 1, A16 is bit 17. This gives the addresses of a display whose RS input ("command or data") is tied to a single address line:

Select RS on line Command Data
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

For A16 the naive calculation 1 << 16 gives 0x6001 0000 — that is still a command: line A16 is zero. The shift by one is the thing people forget most often.

Examples for cheap boards use the base 0x6C00 007E. It is the same calculation: a structure of two 16-bit fields {REG; RAM} placed at 0x6C00 007E puts REG at 0x6C00 007E (A6 = 0) and RAM at 0x6C00 0080 (A6 = 1) — RS on line A6.


41б.3 An 8080 display through FSMC

On the STM32412G-Discovery the display sits on NE1, with RS on line A0 (pin PF0). The BSP describes it with a two-field structure — and then a command and data are written with an ordinary assignment:

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 for the F407 turns each function into a single 16-bit write:

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

volatile is mandatory here (section 124): without it the compiler may merge two consecutive writes to the RAM address into one, and for a display those are two pixels. The register-level bank setup is in the fsmc_lcd_init function of the example file: the timings in BTR1 and BWTR1 (41б.4) and BCR1 with a 16-bit width, write enable and separate timings (EXTMOD). The pins use alternate function AF12; it is easier to leave them to CubeMX.

A write over the bus does not keep the CPU waiting longer than the cycle lasts — and it can be handed to DMA in "memory to memory" mode (section 9): the source is a line buffer in RAM, the destination is the RAM address without increment.


41б.4 Timings: mode A in HCLK cycles

An asynchronous cycle consists of phases, each an integer number of HCLK cycles. In mode A (separate read and write timings, EXTMOD = 1) RM0090 gives (tables 229–230):

The read timings are in BTR1, the write timings in BWTR1. A display usually reads much more slowly than it writes, and that is exactly why mode A is used: the same numbers for both directions would either slow down writes or break reads.

The STM32412G-Discovery BSP sets (a 10 ns cycle at an HCLK of 100 MHz — the maximum frequency of the F412):

ADDSET DATAST Cycles Cycle time
read 9 36 45 450 ns
write 1 7 9 90 ns

How to get the numbers for your own display: from its documentation take the minimum write cycle time and the minimum low time on WR, and divide by the HCLK period, rounding up: DATAST + 1 must be no less than the WR low time, ADDSET + DATAST + 1 no less than the cycle. For reads, the same with the read cycle. A margin of a cycle is fine; a shortfall gives errors that do not show up right away but only on some of the pixels.

The limit on image speed is the number of writes per bus cycle. At 90 ns per write (16 bits per pixel, one write per pixel):

Screen Writes Frame time No more than frames/s
240×240 57600 5.184 ms 192
320×240 76800 6.912 ms 144
480×320 153600 13.824 ms 72

This is the ceiling of the bus; the program loop or DMA adds its own overhead.


41б.5 External SRAM

Static memory is connected the same way as a display, only with many address lines. The STM324xG-EVAL has a 2 MB IS61WV102416BLL SRAM (16 bits) on NE2 — address 0x6400 0000. The BSP sets ADDSET 2, DATAST 2, BUSTURN 1 in mode A; at an HCLK of 168 MHz that is 4 cycles (23.8 ns) for a read and 5 cycles (29.8 ns) for a write.

Such memory looks like an ordinary array: you can put the heap, a frame buffer or large tables there — through a linker section (section 11) or simply with a pointer. It is slower than the internal SRAM: the controller splits each 32-bit access to a 16-bit memory into two bus cycles (RM0090, the transaction rules in 36.3.1).


41б.6 SDRAM on FMC: startup and refresh

SDRAM is cheaper than SRAM of the same size, but it is dynamic: the cells discharge, and every row has to be refreshed at least once per refresh period (usually 64 ms for all rows). Its address is multiplexed: row, then column. So FMC has a separate SDRAM controller with its own registers SDCR, SDTR, SDCMR and SDRTR.

On the STM32F429I-Discovery the IS42S16400J SDRAM is on bank 2 — address 0xD000 0000: 12 row bits, 8 column bits, 4 banks, a 16-bit bus. The size is 4096 × 256 × 4 × 2 = 8388608 bytes = 0x80 0000, as in SDRAM_DEVICE_SIZE in the BSP. SDCLK is HCLK / 2.

Startup according to RM0090 takes eight steps:

  1. SDCR: SDCLK frequency, burst reads RBURST, the RPIPE delay, rows, columns, width, banks, CAS.
  2. SDTR: the memory timings.
  3. The "Clock Configuration Enable" command — SDCLK starts going to the memory.
  4. A pause: usually about 100 µs (per the memory documentation).
  5. The "Precharge All" command — close all rows.
  6. The "Auto-refresh" command — usually 8 times in a row (the NRFS field).
  7. The "Load Mode Register" command: burst length 1 and CAS as in SDCR.
  8. The refresh counter in SDRTR.

At register level this is the sdram_init function of the example file. Two things in it are easy to miss. The SDCLK frequency, RBURST and RPIPE are written only into SDCR1, and TRC and TRP only into SDTR1, even when the memory is on bank 2: in SDCR2 and SDTR2 these fields have no effect. And the timing fields take "cycles minus one": a TRC of 7 cycles is 6 in the field (HAL does the same).

The F429I-Discovery BSP timings at an SDCLK of 90 MHz (11.1 ns):

Parameter Cycles ns
TMRD (after Load Mode) 2 22.2
TXSR (exit from self-refresh) 7 77.8
TRAS (row open) 4 44.4
TRC (row to row) 7 77.8
TWR (after a write) 2 22.2
TRP (row close) 2 22.2
TRCD (row → column) 2 22.2

The refresh counter (the FMC_SDRTR register):

COUNT = (refresh period / number of rows) × SDCLK − 20

The twenty cycles are a margin in case a refresh request arrives during an accepted read. COUNT must be at least 41; the sum TWR + TRP + TRC + TRCD + 4 (17 here) is forbidden.

Board SDCLK Rows Interval Calculated COUNT In the BSP
F429I-Discovery 90 MHz 4096 15.625 µs 1386 1386
746G-Discovery 100 MHz 4096 15.625 µs 1542 1539
RM0090 example 60 MHz 8192 7.812 µs 448 448

On the 746G-Discovery the BSP value is three cycles below the calculation: the refresh is slightly more frequent than needed — that is the safe side. The RM0090 example prints the number of rows as 8196 — a typo; it does not affect the result of 448.


41б.7 Pitfalls


41б.8 Summary

Full sources

fmc41b.c — register-level FSMC/FMC: an 8080 display on the F407 and SDRAM on the 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

Next: section 41в "Quad-SPI: external flash as memory and code from it" — a serial external flash that is also visible as a range of addresses.

Open in the interactive handbook