Vaja 8: FMC & SDRAM

Namen tokratne vaje je, da se spoznamo z napravo FMC, s pomočjo katere bomo brali in pisali iz/v SDRAM.

Naprava FMC

Naprava Flexible Memory Controller (FMC) vsebuje tri pomnilniške krmilnike:

  • krmilnik pomnilnika NOR Flash/PSRAM,
  • krmilnik pomnilnika NAND/Flash,
  • krmnilnik pomnilnika SDRAM.

Glavna naloga naprave FMC je pretvarjanje transakcij, ki prihajajo po AHB/AXI vodilu iz CPE-ja, v transakcije, ki ustrezajo protokolom zunanjih naprav in njihovim časovnim zahtevam.

Slika 8.1: FMC blok diagram

Iz stališča naprave FMC je zunanji pomnilnik razdeljen v 6 bank v velikosti 256MB. Slika 8.2 prikazuje omenjene banke z njihovimi začetnimi in končnimi naslovi. Vsi zunanji pomnilniki si s krmilnikom delijo naslovne, podatkovne in kontrolne signale. FMC na enkrat dostopa le do ene zunanje naprve, ki jo izbere s t.i. chip-select linijo. V nadaljevanju se bomo osredotočili na krmiljenje pomnilnika SDRAM. FMC SDRAM krmilnik podpira naprave do velikosti 256MB. Lahko izdaja 13-bitne naslove vrstice, 11-bitne naslove stolpca, ter dostopa do dveh ali štirih notranjih bank SDRAM-a. Dostopi so lahko dolžine 8, 16 ali 32-bitni.

Na razvojni plošči STM32H750 Discovery Kit imamo na voljo 128-megabitni SDRAM čip Micron MT48LC4M32B2, ki ima 1M x 32 x 4 banke. SDRAM čip je organiziran kot 4096 vrstic x 256 stolpcev x 32 bitov v vsaki izmed štirih bank. Za dostop mora torej FMC SDRAM krmnilnik izstavljati 12-bitne naslove vrstic (4096 vrstic), 8-bitne naslove stolpcev (256 stolpcev) in 2-bitni naslov banke (štiri banke).

Slika 8.2: FMC - razdelitev naslovnega prostora v banke

FMC SDRAM krmilnik vsebuje bralni FIFO medpomnilnik (angl. buffer), ki ima 6 vrstic po 32 bitov. Lahko se uporabi za branje podatkov vnaprej - krmilnik predvideva, da bo sledilo več READ ukazov v trenutno odprto vrstico. Podatki se shranijo v FIFO medpomnilnik. Prvi prebrani podatki se prenesejo na AHB/AXI vodilo, preostali pa shranijo v FIFO medpomnilnik. Podatki so shranjeni skupaj s 14-bitnim naslovnim “tag-om”, sk aterim se identificira podatek: 11 bitov za naslov stolpca ter 2 bita za oznako banke ter 1 bit za SDRAM napravo (2 možni napravi). V primeru, da kontroler prejme bralno zahtevo za podatek, ki se nahaja v FIFO medpomnilniku, krmilnik enostavno vrne podatek iz FIFO medpomnilnika.

FMC krmilnik ravno tako periodično izdaja osveževalne (auto-refresh) ukaze SDRAM napravi. Za to uporablja notranji števec, ki se inicializira na vrednos shranjeno v registru FMC_SDRTR. Ko števec doseže vrednost 0 se izstavi auto-refresh ukaz. Ta se izvede takoj, če SDRAM ni zaseden, sicer pa po zaključenem trenutnem dostopu.

Inicializacija

Kot ponavadi do sedaj, se bo inicializacija začela z inicializacijo GPIO pinov. Naslovni, podatkovni in kontrolni signali iz FMC krmilnika so namreč povezani na GPIO pine. Zato jih moramo, kot smo to storili pri UART, inicializirati za alternativno funckijo FMC. Pini, ki jih moramo inicializirati so:

  • pini 0, 1, 8 - 10, 14 in 15 na napravi GPIOD,
  • pini 0, 1 in 7 - 15 na napravi GPIOE,
  • pini 0 - 5 in 11 - 15 na napravi GPIOF,
  • pini 0, 1, 4, 5, 8, 15 na napravi GPIOG,
  • pini 5, 6 in 7 na napravi GPIOH.

To storimo z naslednjimi ukazi:

__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOF_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();

GPIO_InitTypeDef gpio_init_structure;
gpio_init_structure.Mode = GPIO_MODE_AF_PP;
gpio_init_structure.Pull = GPIO_PULLUP;
gpio_init_structure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
gpio_init_structure.Alternate = GPIO_AF12_FMC;
gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8| GPIO_PIN_9;
gpio_init_structure.Pin |= GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOD, &gpio_init_structure);
gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7| GPIO_PIN_8;
gpio_init_structure.Pin |= GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
gpio_init_structure.Pin |= GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOE, &gpio_init_structure);
gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2| GPIO_PIN_3;
gpio_init_structure.Pin |= GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12;
gpio_init_structure.Pin |= GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOF, &gpio_init_structure);
gpio_init_structure.Pin  = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
gpio_init_structure.Pin |= GPIO_PIN_8 | GPIO_PIN_15;
HAL_GPIO_Init(GPIOG, &gpio_init_structure);
gpio_init_structure.Pin   = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 ;
HAL_GPIO_Init(GPIOH, &gpio_init_structure);

Sledi inicializacija FMC SDRAM krmilnika, ki je razdeljena v dva strukturi:

  • strukturo tipa FMC_SDRAM_TimingTypeDef, v katero shranimo vse časovne nastavitve, ki so vezane na specifični SDRAM čip, ki ga uporabimo,
  • in strukturo tipa SDRAM_HandleTypeDef, v katero shranimo ostale nastavitve FMC krmilnika.

Ker so argumenti prve strukture zgolj preslikava lastnosti SDRAM čipa, se bomo poglobili predvsem v slednjo. Nastavitve v SDRAM_HandleTypeDef so:

  • SDBank: izberemo banko, ki jo bomo uporabljali (FMC_SDRAM_BANK1 - naslovi se začnejo na 0xC0000000, FMC_SDRAM_BANK2 - naslovi se začnejo na 0xD0000000),

  • ColumnBitsNumber: število bitov za naslov stolpca - FMC_SDRAM_COLUMN_BITS_NUM_8, FMC_SDRAM_COLUMN_BITS_NUM_9, FMC_SDRAM_COLUMN_BITS_NUM_10, FMC_SDRAM_COLUMN_BITS_NUM_11,

  • RowBitsNumber: število bitov za naslov vrstice - FMC_SDRAM_ROW_BITS_NUM_11, FMC_SDRAM_ROW_BITS_NUM_12, FMC_SDRAM_ROW_BITS_NUM_13,

  • MemoryDataWidth: širina podatkov - FMC_SDRAM_MEM_BUS_WIDTH_8, FMC_SDRAM_MEM_BUS_WIDTH_16, FMC_SDRAM_MEM_BUS_WIDTH_32,

  • InternalBankNumber: število bank v SDRAMu - FMC_SDRAM_INTERN_BANKS_NUM_2, FMC_SDRAM_INTERN_BANKS_NUM_4,

  • CASLatency: CAS latenca v številu ciklov - FMC_SDRAM_CAS_LATENCY_1, FMC_SDRAM_CAS_LATENCY_2. FMC_SDRAM_CAS_LATENCY_3,

  • WriteProtection: vklopi/izklopi pisalno zaščito - FMC_SDRAM_WRITE_PROTECTION_ENABLE, FMC_SDRAM_WRITE_PROTECTION_DISABLE,

  • SDClockPeriod: definira periodo ure SDRAM-a. Ta je lahko polovica ali tretjina HCLK - FMC_SDRAM_CLOCK_PERIOD_2, FMC_SDRAM_CLOCK_PERIOD_3,

  • ReadBurst: vklopi/izklopi “read burst” (uporaba FIFO medpomnilnika) - FMC_SDRAM_RBURST_ENABLE, FMC_SDRAM_RBURST_DISABLE.

__HAL_RCC_FMC_CLK_ENABLE();
FMC_SDRAM_TimingTypeDef  SDRAM_Timing = {0};
SDRAM_Timing.LoadToActiveDelay    = 2;
SDRAM_Timing.ExitSelfRefreshDelay = 7;
SDRAM_Timing.SelfRefreshTime      = 4;
SDRAM_Timing.RowCycleDelay        = 7;
SDRAM_Timing.WriteRecoveryTime    = 2;
SDRAM_Timing.RPDelay              = 2;
SDRAM_Timing.RCDDelay             = 2;

SDRAM_HandleTypeDef      hsdram = {0};
hsdram.Instance = FMC_SDRAM_DEVICE;
hsdram.Init.SDBank             = FMC_SDRAM_BANK2;
hsdram.Init.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_8;
hsdram.Init.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_12;
hsdram.Init.MemoryDataWidth    = FMC_SDRAM_MEM_BUS_WIDTH_32;
hsdram.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
hsdram.Init.CASLatency         = FMC_SDRAM_CAS_LATENCY_3;
hsdram.Init.WriteProtection    = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram.Init.SDClockPeriod      = FMC_SDRAM_CLOCK_PERIOD_3;
hsdram.Init.ReadBurst          = FMC_SDRAM_RBURST_DISABLE;
HAL_SDRAM_Init(&hsdram, &SDRAM_Timing);

Zadnji korak inicializacije je inicializacija samega SDRAM čipa. Za to inicializacijo moramo SDRAM čipu poslati nekaj ukazov. Te pošiljamo s pomočjo strukture FMC_SDRAM_CommandTypeDef. Koraki inicializacije so narejeni tako, kot zahtevajo navodila proizvajalca SDRAM čipa:

  • vklopimo SDRAM uro,
  • počakamo najmanj 100 mikrosekund preden pošljemo naslednji ukaz,
  • pošljemo ukaz PRECHARGE ALL, s tem postavimo vse banke v t.i. IDLE stanje,
  • izstavimo vsaj dva AUTO REFRESH ukaza,
  • nastavimo MODE register SDRAM čipa

Na koncu inicializacije še nastavimo števec za osveževanje.

#define SDRAM_TIMEOUT                    ((uint32_t)0xFFFF)
#define REFRESH_COUNT                    ((uint32_t)0x0603)

#define SDRAM_MODEREG_BURST_LENGTH_1             ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2             ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4             ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8             ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL      ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED     ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2              ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3              ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD    ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE     ((uint16_t)0x0200)

static void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram)
{
  FMC_SDRAM_CommandTypeDef command;
  __IO uint32_t tmpmrd =0;

  // 1. vklopimo uro
  command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
  command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
  command.AutoRefreshNumber = 1;
  command.ModeRegisterDefinition = 0;
  HAL_SDRAM_SendCommand(hsdram, &command, SDRAM_TIMEOUT);

  // pocakamo 100 us
  HAL_Delay(1);

  // posljemo precharge all ukaz
  command.CommandMode = FMC_SDRAM_CMD_PALL;
  command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
  command.AutoRefreshNumber = 1;
  command.ModeRegisterDefinition = 0;
  HAL_SDRAM_SendCommand(hsdram, &command, SDRAM_TIMEOUT);

  // posljemo auto refresh ukaz(e)
  command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
  command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
  command.AutoRefreshNumber = 8;
  command.ModeRegisterDefinition = 0;
  HAL_SDRAM_SendCommand(hsdram, &command, SDRAM_TIMEOUT);

  // nastavimo mode register SDRAMa
  tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1          |
                     SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL   |
                     SDRAM_MODEREG_CAS_LATENCY_3           |
                     SDRAM_MODEREG_OPERATING_MODE_STANDARD |
                     SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
  command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
  command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
  command.AutoRefreshNumber = 1;
  command.ModeRegisterDefinition = tmpmrd;
  HAL_SDRAM_SendCommand(hsdram, &command, SDRAM_TIMEOUT);

  // nastavimo stevec osvezevanja
  HAL_SDRAM_ProgramRefreshRate(hsdram, REFRESH_COUNT);
}

Dostop do SDRAM

Po inicializaciji dostopamo (pišemo ali beremo) podatke v SDRAMu z uporabo kazalcev. V primeru, da uporabljamo banko 2, dostopamo do naslovov med 0xD000 0000 in 0xDFFF FFFF. Če uporabimo banko 1 pa do naslovov med 0xC000 0000 in 0xCFFF FFFF.

volatile uint32_t* p = (volatile uint32_t*) 0xD0000000;
*p = 123;

//...

uint32_t a = *p;

Uporaba predpomnilnika

Uporabo ukaznega predpomnilnika (angl. instruction cache) in podatkovnega predpomnilnika (angl. data cache), lahko omogočimo s klicem spodnjih dveh ukazov, najbolj čimbolj na začetku programa.

SCB_EnableICache();
SCB_EnableDCache();

Priprava projekta

  • ko ustvarite projekt in se pojavi okno, ki vas vpraša ali želite uporabiti Memory Protection Unit (MPU), izberite No. V primeru, da ste izbrali Yes, v kodi zakomentirajte/izbrižite MPU_Config(),
  • iz knjižnice prenesite (kot smo to počeli za UART na vaji 5 in 7) datoteke Src/stm32h7xx_hal_sdram.c, Inc/stm32h7xx_hal_sdram.h, Src/stm32h7xx_ll_fmc.c in Inc/stm32h7xx_ll_fmc.h,
  • v datoteki Core/Inc/stm32h7xx_hal_conf.h odkomentirajte vrstico #define HAL_SDRAM_MODULE_ENABLED.

Naloga 8.1

Napišite funkcijo, ki najde največji element v podanem polju (polje podamo kot kazalec in število elementov). Funkcija naj ima sledeč podpis:

uint32_t find_max(uint32_t* addr, uint32_t size);

Nato uporabite dve polji s 30 tisoč elementi: eno naj bo shranjena v statičnem ramu - SRAMu (npr. globalna spremenljivka), drugo pa v SDRAMu. Nato izmerite čas klica funkcije find_max za sledeče primere:

  • polje je v SDRAMu, predpomnilnika in FIFO medpomnilnika ne uporabimo,
  • polje je v SRAMu, predpomnilnika ne uporabimo,
  • polje je v SDRAMu, uporabimo predpomnilnik, FIFO medpomnilnika pa ne,
  • polje je v SRAMu, uporabimo predpomnilnik,
  • polje je v SDRAMu, uporabimo predpomnilnika in FIFO medpomnilnika.

Vse rezultate zapišite v komentar kode. Za merjenje časa zopet uporabite funkcije iz vaje 6 in 7.