Vaja 9: 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.
Iz stališča naprave FMC je zunanji pomnilnik razdeljen v 6 bank v velikosti 256MB. Slika 10.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 naprave, 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).
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”, s katerim 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 vrednost 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 vrsticami:
();
__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.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;
gpio_init_structure(GPIOD, &gpio_init_structure);
HAL_GPIO_Init.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;
gpio_init_structure(GPIOE, &gpio_init_structure);
HAL_GPIO_Init.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;
gpio_init_structure(GPIOF, &gpio_init_structure);
HAL_GPIO_Init.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5;
gpio_init_structure.Pin |= GPIO_PIN_8 | GPIO_PIN_15;
gpio_init_structure(GPIOG, &gpio_init_structure);
HAL_GPIO_Init.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 ;
gpio_init_structure(GPIOH, &gpio_init_structure); HAL_GPIO_Init
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 na0xC0000000
,FMC_SDRAM_BANK2
- naslovi se začnejo na0xD0000000
),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= {0};
FMC_SDRAM_TimingTypeDef 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_Timing
= {0};
SDRAM_HandleTypeDef 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;
hsdram(&hsdram, &SDRAM_Timing); HAL_SDRAM_Init
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
Ob koncu inicializacije nastavimo še š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 commanduint32_t tmpmrd =0;
__IO
// 1. vklopimo uro
.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
command.AutoRefreshNumber = 1;
command.ModeRegisterDefinition = 0;
command(hsdram, &command, SDRAM_TIMEOUT);
HAL_SDRAM_SendCommand
// pocakamo 100 us
(1);
HAL_Delay
// posljemo precharge all ukaz
.CommandMode = FMC_SDRAM_CMD_PALL;
command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
command.AutoRefreshNumber = 1;
command.ModeRegisterDefinition = 0;
command(hsdram, &command, SDRAM_TIMEOUT);
HAL_SDRAM_SendCommand
// posljemo auto refresh ukaz(e)
.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
command.AutoRefreshNumber = 8;
command.ModeRegisterDefinition = 0;
command(hsdram, &command, SDRAM_TIMEOUT);
HAL_SDRAM_SendCommand
// nastavimo mode register SDRAMa
= (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |
tmpmrd |
SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
SDRAM_MODEREG_CAS_LATENCY_3 |
SDRAM_MODEREG_OPERATING_MODE_STANDARD ;
SDRAM_MODEREG_WRITEBURST_MODE_SINGLE.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK2;
command.AutoRefreshNumber = 1;
command.ModeRegisterDefinition = tmpmrd;
command(hsdram, &command, SDRAM_TIMEOUT);
HAL_SDRAM_SendCommand
// nastavimo stevec osvezevanja
(hsdram, REFRESH_COUNT);
HAL_SDRAM_ProgramRefreshRate}
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, najbolje ob 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 6 in 8) datoteke
Src/stm32h7xx_hal_sdram.c
,Inc/stm32h7xx_hal_sdram.h
,Src/stm32h7xx_ll_fmc.c
inInc/stm32h7xx_ll_fmc.h
, - v datoteki
Core/Inc/stm32h7xx_hal_conf.h
odkomentirajte vrstico#define HAL_SDRAM_MODULE_ENABLED
.
Naloga
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.