Ten post chciałbym poświęcić na opisanie sposobu zaprogramowania pamięci SDRAM zamontowanej na płytce STM32F7 Discovery. Jest to model MT48LC432B2B5-6A firmy Micron. Zastosowana kość posiada 128Mb pamięci, dla mikrokontrolera jest dostępne 64Mb. Linie DQ16 do DQ31 są podłączone do rezystorów podciągających do masy.
Rys. 1. Schemat podłączenia [UM1907]
Pamięć sterowana jest poprzez układ FMC (ang. Flexible Memmory Controler). Maksymalny ustawiony dla niego zegar nie może przekraczać ponad połowę maksymalnej częstotliwości taktowania systemu, czyli 100MHz, lub 1/3 częstotliwości taktowania. Wartość 100MHz spowodowana jest częstotliwością taktowania układu SDRAM
Pamięć do których uzyskuje się dostęp podzielona jest na cztery banki pamięci. Każdy bank składa się z 256 kolumn i 4096 rzędów. Każda komórka składa się z 32 bitów. Ograniczony dostęp do 64Mb jest spowodowany nie wykorzystywaniem wszystkich linii, o czym wspomniałem wcześniej.
Programowanie
Teraz przejdę przez poszczególne etapy programowania tej pamięci:
Najpierw należy zdefiniować wszystkie potrzebne adresy oraz wartości jako #define:
- #define SDRAM_START_ADR ((uint32_t)0xC0000000)
- #define SDRAM_MAX_ADR 0x800000 // max=8MByte //Rozmiar pamięci
- #define SDRAM_MEMORY_WIDTH FMC_SDRAM_MEM_BUS_WIDTH_16
- #define SDCLOCK_PERIOD FMC_SDRAM_CLOCK_PERIOD_2
- #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)
Dodatkowo należy stworzyć kilka zmiennych typów strukturalnych dla funkcji inicjalizujących:
- static SDRAM_HandleTypeDef sdramHandle;
- static FMC_SDRAM_TimingTypeDef Timing;
- static FMC_SDRAM_CommandTypeDef Command;
- static GPIO_InitTypeDef gpio_init_structure;
Proces inicjalizacji peryferiów wygląda następująco. W pierwsze kolejności włączane są GPIO oraz zegary. Następnie wywoływane jest wprowadzenie danych do FMC.
Definicja funkcji wygląda następująco:
- __weak void SDRAM_MSPINIT_PRIVATE(SDRAM_HandleTypeDef *hsdram, void *Params)
- {
- //Zegar dla FMC
- __HAL_RCC_FMC_CLK_ENABLE();
- //Wlaczenie zegarow dla GPIO
- __HAL_RCC_GPIOC_CLK_ENABLE();
- __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();
- //Wspolne ustawienia dla pinow
- gpio_init_structure.Mode = GPIO_MODE_AF_PP;
- gpio_init_structure.Pull = GPIO_PULLUP;
- gpio_init_structure.Speed = GPIO_SPEED_FAST;
- gpio_init_structure.Alternate = GPIO_AF12_FMC;
- //GPIOC _ PIN3
- gpio_init_structure.Pin = GPIO_PIN_3;
- HAL_GPIO_Init(GPIOC, &gpio_init_structure);
- //GPIOD _ PIN0 | PIN1| PIN3 | PIN8 | PIN9 | PIN10 | PIN14 | PIN15
- gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_3 |
- GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15;
- HAL_GPIO_Init(GPIOD, &gpio_init_structure);
- //GPIOE _ PIN0 | PIN1| PIN7 | PIN8 | PIN9 | PIN10 | PIN11 | PIN12 | PIN13 | PIN14 | PIN15
- gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7| GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 |
- GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
- HAL_GPIO_Init(GPIOE, &gpio_init_structure);
- //GPIOF _ PIN0 | PIN1| PIN2 | PIN3 | PIN4 | PIN5 | PIN11 | PIN12 | PIN13 | PIN14 | PIN15
- gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2| GPIO_PIN_3 | GPIO_PIN_4 |
- GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 |
- GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
- HAL_GPIO_Init(GPIOF, &gpio_init_structure);
- //GPIOG _ PIN0 | PIN1| PIN4 | PIN5 | PIN8 | PIN15
- gpio_init_structure.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4| GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15;
- HAL_GPIO_Init(GPIOG, &gpio_init_structure);
- //GPIOH _ PIN3 | PIN5
- gpio_init_structure.Pin = GPIO_PIN_3 | GPIO_PIN_5;
- HAL_GPIO_Init(GPIOH, &gpio_init_structure);
- }
Kolejnym elementem jest procedura pozwalająca na konfiguracje FMC:
- ErrorStatus SDRAM_INIT_PRIVATE(void)
- {
- static ErrorStatus sdramstat = ERROR;
- //Konfiguracja urzadzenia
- sdramHandle.Instance = FMC_SDRAM_DEVICE;
- //Czas skonfigurowany na 100MHz jako zegar dla SD, systemowy zegar 200MHz
- Timing.LoadToActiveDelay = 2;
- Timing.ExitSelfRefreshDelay = 7;
- Timing.SelfRefreshTime = 4;
- Timing.RowCycleDelay = 7;
- Timing.WriteRecoveryTime = 2;
- Timing.RPDelay = 2;
- Timing.RCDDelay = 2;
- sdramHandle.Init.SDBank = FMC_SDRAM_BANK1; //numer banku pamieci
- sdramHandle.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_8; //Ilosc bitow w adresie kolumny
- sdramHandle.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_12; //Ilosc bitow w adresie wiersza
- sdramHandle.Init.MemoryDataWidth = SDRAM_MEMORY_WIDTH;
- sdramHandle.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; //Liczba wewnetrznych bankow pamieci
- sdramHandle.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_2; //Czas dostepu w cyklach
- sdramHandle.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE; //Wylaczenie blokowania pamieci, mozna wprowadzac dane
- sdramHandle.Init.SDClockPeriod = SDCLOCK_PERIOD;
- sdramHandle.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; //Pozwala na pobieranie kolejnych komend podczas CAS Latency
- sdramHandle.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_0; //Delay w cycklach przy odczycie danych
- //Inicjalizacja FMC
- if(HAL_SDRAM_Init(&sdramHandle, &Timing) == HAL_OK) { sdramstat = SUCCESS; }
- //Incjalizacja kontrolera SDRAM
- SDRAM_MSPINIT_PRIVATE(&sdramHandle, NULL);
- //SDRAM procedura startowa
- SDRAM_INITIALIZATION_PRIVATE(REFRESH_COUNT);
- return sdramstat;
- }
Teraz czas na procedurę startową, która składa się z 5 określonych kroków:
- void SDRAM_INITIALIZATION_PRIVATE(uint32_t RefreshCount)
- {
- __IO uint32_t tmp = 0;
- //Krok 1: Konfiguracja zegarow
- Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
- Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
- Command.AutoRefreshNumber = 1;
- Command.ModeRegisterDefinition = 0;
- //Wyslanie komendy
- HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
- //Krok 2: Opoznienie 100us minimum, wyslane 1ms Systick
- HAL_Delay(1);
- //Krok 3: Konfiguracja PALL (precharge all)
- Command.CommandMode = FMC_SDRAM_CMD_PALL;
- Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
- Command.AutoRefreshNumber = 1;
- Command.ModeRegisterDefinition = 0;
- //Wyslanie komendy
- HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
- //Krok 4: Komenda Auto Refresh
- Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
- Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
- Command.AutoRefreshNumber = 8;
- Command.ModeRegisterDefinition = 0;
- //Przeslanie komendy
- HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
- //Krok 5: Programowanie trybu dla zewnetrznej pamieci
- tmp = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 | SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL | SDRAM_MODEREG_CAS_LATENCY_2 |SDRAM_MODEREG_OPERATING_MODE_STANDARD | SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
- Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
- Command.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
- Command.AutoRefreshNumber = 1;
- Command.ModeRegisterDefinition = tmp;
- //Przeslanie komendy
- HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
- //Krok 6: Ustawienie licznika odswiezania
- HAL_SDRAM_ProgramRefreshRate(&sdramHandle, RefreshCount);
- }
Trzy funkcje przedstawione powyżej mogą zostać zdefiniowane jako funkcje funkcje prywatne w pliku biblioteki. Wszystkie te funkcje można połączyć w jedną, która już będzie przekazywana do dalszej części programu:
- ErrorStatus SDRAM_INIT(void)
- {
- ErrorStatus stat=ERROR;
- uint16_t data1;
- static uint8_t init_pass=0; //Jako statyczna, musi być pamiętana
- //Inicjalizacje musza byc wykonane tylko jeden raz
- if(init_pass != 0)
- {
- if(init_pass==1) { return(SUCCESS); }
- else { return(ERROR); }
- }
- //FMC_konfiguracja
- stat=SDRAM_INIT_PRIVATE();
- if(stat!=SUCCESS) { return(ERROR); }
- //Zapis oraz odczyt danych z pamięci
- SDRAM_WRITE_16b(0x00, 0x7BC5);
- data1 = SDRAM_READ_16b(0x00);
- //Sprawdzenie danych
- if(0x7BC5 == data1) { stat = SUCCESS; }
- //Zaznaczenie że inicjalizacja przeszla pomyslnie
- if(SUCCESS = stat)
- //Zapisanie wartosci startowej
- if(stat==SUCCESS) { init_pass=1; }
- else { init_pass=2; }
- return(stat);
- }
Jeśli chodzi o zapis oraz odczyt danych to funkcje można zakodować zwyczajnie, z parametrem inline, bądź całą procedurę zawrzeć w #define:
- inline uint8_t SDRAM_READ_8B(uint32_t adres)
- {
- return(*(__IO uint8_t*)(SDRAM_START_ADR + adres));
- }
- inline uint8_t SDRAM_READ_16B(uint32_t adres)
- {
- return(*(__IO uint16_t*)(SDRAM_START_ADR + adres));
- }
- inline uint8_t SDRAM_READ_32B(uint32_t adres)
- {
- return(*(__IO uint32_t*)(SDRAM_START_ADR + adres));
- }
Zapis:
- inline void SDRAM_WRITE_8b(uint32_t adres, uint8_t zmienna)
- {
- (*(_IO uint8_t*) (SDRAM_START_ADR + adres) = zmienna);
- }
- inline void SDRAM_WRITE_16b(uint32_t adres, uint8_t zmienna)
- {
- (*(_IO uint16_t*) (SDRAM_START_ADR + adres) = zmienna);
- }
- inline void SDRAM_WRITE_32b(uint32_t adres, uint8_t zmienna)
- {
- (*(_IO uint32_t*) (SDRAM_START_ADR + adres) = zmienna);
- }
Dodatkowo do pamięci SDRAM zapisu można dokonać poprzez DMA z wykorzystam funkcji z biblioteki HAL'a:
Jeśli chodzi o konfigurację z użyciem środowiska CUBEMx, to należy kontroler FMC włączyć z następującymi ustawieniami:
Zegar należy ustawić następująco:
Konfiguracja FMC przeniesiona z wcześniejszych funkcji do cuba wygląda następująco:
- ErrorStatus SDRAM_READ_BUFFER_32b(uint32_t* ptrBuffer, uint32_t startAdres, uint32_t lenBuffer)
- {
- if(HAL_SDRAM_Read_32b(&sdramHandle, (uint32_t *)(startAdres+SDRAM_START_ADR), ptrBuffer, lenBuffer) != HAL_OK)
- { return ERROR; }
- else{ return SUCCESS; }
- }
- ErrorStatus SDRAM_WRITE_BUFFER_32b_DMA(uint32_t* ptrBuffer, uint32_t startAdr, uint32_t lenBuffer)
- {
- if(HAL_SDRAM_Write_DMA(&sdramHandle, (uint32_t *)(startAdr+SDRAM_START_ADR), ptrBuffer, lenBuffer) != HAL_OK)
- { return ERROR; }
- else { return SUCCESS; }
- }
CUBE MX
Jeśli chodzi o konfigurację z użyciem środowiska CUBEMx, to należy kontroler FMC włączyć z następującymi ustawieniami:
Rys. 2. Inicjalizacja FMC
Zegar należy ustawić następująco:
Rys. 3. Konfiguracja zegarów
Konfiguracja FMC przeniesiona z wcześniejszych funkcji do cuba wygląda następująco:
Rys 4. Konfiguracja FMC