piątek, 25 listopada 2016

[2] STM32F7 - SDRAM

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:

  1. #define SDRAM_START_ADR      ((uint32_t)0xC0000000)
  2. #define SDRAM_MAX_ADR         0x800000  // max=8MByte //Rozmiar pamięci
  3. #define SDRAM_MEMORY_WIDTH FMC_SDRAM_MEM_BUS_WIDTH_16
  4. #define SDCLOCK_PERIOD     FMC_SDRAM_CLOCK_PERIOD_2
  5. #define SDRAM_TIMEOUT      ((uint32_t)0xFFFF)
  6. #define REFRESH_COUNT      ((uint32_t)0x0603)
  7. #define SDRAM_MODEREG_BURST_LENGTH_1             ((uint16_t)0x0000)
  8. #define SDRAM_MODEREG_BURST_LENGTH_2             ((uint16_t)0x0001)
  9. #define SDRAM_MODEREG_BURST_LENGTH_4             ((uint16_t)0x0002)
  10. #define SDRAM_MODEREG_BURST_LENGTH_8             ((uint16_t)0x0004)
  11. #define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL      ((uint16_t)0x0000)
  12. #define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED     ((uint16_t)0x0008)
  13. #define SDRAM_MODEREG_CAS_LATENCY_2              ((uint16_t)0x0020)
  14. #define SDRAM_MODEREG_CAS_LATENCY_3              ((uint16_t)0x0030)
  15. #define SDRAM_MODEREG_OPERATING_MODE_STANDARD    ((uint16_t)0x0000)
  16. #define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
  17. #define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE     ((uint16_t)0x0200)

Dodatkowo należy stworzyć kilka zmiennych typów strukturalnych dla funkcji inicjalizujących:

  1. static SDRAM_HandleTypeDef sdramHandle;
  2. static FMC_SDRAM_TimingTypeDef Timing;
  3. static FMC_SDRAM_CommandTypeDef Command;
  4. 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:

  1. __weak void SDRAM_MSPINIT_PRIVATE(SDRAM_HandleTypeDef  *hsdram, void *Params)
  2. {
  3.   //Zegar dla FMC
  4.   __HAL_RCC_FMC_CLK_ENABLE();
  5.   //Wlaczenie zegarow dla GPIO
  6.   __HAL_RCC_GPIOC_CLK_ENABLE();
  7.   __HAL_RCC_GPIOD_CLK_ENABLE();
  8.   __HAL_RCC_GPIOE_CLK_ENABLE();
  9.   __HAL_RCC_GPIOF_CLK_ENABLE();
  10.   __HAL_RCC_GPIOG_CLK_ENABLE();
  11.   __HAL_RCC_GPIOH_CLK_ENABLE();
  12.   //Wspolne ustawienia dla pinow
  13.   gpio_init_structure.Mode      = GPIO_MODE_AF_PP;
  14.   gpio_init_structure.Pull      = GPIO_PULLUP;
  15.   gpio_init_structure.Speed     = GPIO_SPEED_FAST;
  16.   gpio_init_structure.Alternate = GPIO_AF12_FMC;
  17.   //GPIOC _ PIN3
  18.   gpio_init_structure.Pin   = GPIO_PIN_3;
  19.   HAL_GPIO_Init(GPIOC, &gpio_init_structure);
  20.   //GPIOD _ PIN0 | PIN1| PIN3 | PIN8 | PIN9 | PIN10 | PIN14 | PIN15
  21.   gpio_init_structure.Pin   = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_3 |
  22.                               GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15;
  23.   HAL_GPIO_Init(GPIOD, &gpio_init_structure);
  24.   //GPIOE _ PIN0 | PIN1| PIN7 | PIN8 | PIN9 | PIN10 | PIN11 | PIN12 | PIN13 | PIN14 | PIN15
  25.   gpio_init_structure.Pin   = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_7| GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 |
  26.                               GPIO_PIN_11 | GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
  27.   HAL_GPIO_Init(GPIOE, &gpio_init_structure);
  28.   //GPIOF _ PIN0 | PIN1| PIN2 | PIN3 | PIN4 | PIN5 | PIN11 | PIN12 | PIN13 | PIN14 | PIN15
  29.   gpio_init_structure.Pin   = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2| GPIO_PIN_3 | GPIO_PIN_4 |
  30.                               GPIO_PIN_5 | GPIO_PIN_11 | GPIO_PIN_12 |
  31.                               GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
  32.   HAL_GPIO_Init(GPIOF, &gpio_init_structure);
  33.   //GPIOG _ PIN0 | PIN1| PIN4 | PIN5 | PIN8 | PIN15
  34.   gpio_init_structure.Pin   = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4| GPIO_PIN_5 | GPIO_PIN_8 | GPIO_PIN_15;
  35.   HAL_GPIO_Init(GPIOG, &gpio_init_structure);
  36.   //GPIOH _ PIN3 | PIN5
  37.   gpio_init_structure.Pin   = GPIO_PIN_3 | GPIO_PIN_5;
  38.   HAL_GPIO_Init(GPIOH, &gpio_init_structure);
  39. }

Kolejnym elementem jest procedura pozwalająca na konfiguracje FMC:

  1. ErrorStatus SDRAM_INIT_PRIVATE(void)
  2. {
  3.   static ErrorStatus sdramstat = ERROR;
  4.   //Konfiguracja urzadzenia
  5.   sdramHandle.Instance = FMC_SDRAM_DEVICE;
  6.   //Czas skonfigurowany na 100MHz jako zegar dla SD, systemowy zegar 200MHz
  7.   Timing.LoadToActiveDelay    = 2;
  8.   Timing.ExitSelfRefreshDelay = 7;
  9.   Timing.SelfRefreshTime      = 4;
  10.   Timing.RowCycleDelay        = 7;
  11.   Timing.WriteRecoveryTime    = 2;
  12.   Timing.RPDelay              = 2;
  13.   Timing.RCDDelay             = 2;
  14.   sdramHandle.Init.SDBank             = FMC_SDRAM_BANK1;             //numer banku pamieci
  15.   sdramHandle.Init.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_8; //Ilosc bitow w adresie kolumny
  16.   sdramHandle.Init.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_12;   //Ilosc bitow w adresie wiersza
  17.   sdramHandle.Init.MemoryDataWidth    = SDRAM_MEMORY_WIDTH;            
  18.   sdramHandle.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4; //Liczba wewnetrznych bankow pamieci
  19.   sdramHandle.Init.CASLatency         = FMC_SDRAM_CAS_LATENCY_2;     //Czas dostepu w cyklach
  20.   sdramHandle.Init.WriteProtection    = FMC_SDRAM_WRITE_PROTECTION_DISABLE; //Wylaczenie blokowania pamieci, mozna wprowadzac dane
  21.   sdramHandle.Init.SDClockPeriod      = SDCLOCK_PERIOD;    
  22.   sdramHandle.Init.ReadBurst          = FMC_SDRAM_RBURST_ENABLE;   //Pozwala na pobieranie kolejnych komend podczas CAS Latency
  23.   sdramHandle.Init.ReadPipeDelay      = FMC_SDRAM_RPIPE_DELAY_0;   //Delay w cycklach przy odczycie danych
  24.   //Inicjalizacja FMC
  25.   if(HAL_SDRAM_Init(&sdramHandle, &Timing) == HAL_OK) { sdramstat = SUCCESS; }
  26.   //Incjalizacja kontrolera SDRAM
  27.   SDRAM_MSPINIT_PRIVATE(&sdramHandle, NULL);
  28.   //SDRAM procedura startowa
  29.   SDRAM_INITIALIZATION_PRIVATE(REFRESH_COUNT);
  30.   return sdramstat;
  31. }

Teraz czas na procedurę startową, która składa się z 5 określonych kroków:

  1. void SDRAM_INITIALIZATION_PRIVATE(uint32_t RefreshCount)
  2. {
  3.   __IO uint32_t tmp = 0;
  4.   //Krok 1: Konfiguracja zegarow
  5.   Command.CommandMode            = FMC_SDRAM_CMD_CLK_ENABLE;
  6.   Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  7.   Command.AutoRefreshNumber      = 1;
  8.   Command.ModeRegisterDefinition = 0;
  9.   //Wyslanie komendy
  10.   HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
  11.   //Krok 2: Opoznienie 100us minimum, wyslane 1ms Systick
  12.   HAL_Delay(1);
  13.   //Krok 3: Konfiguracja PALL (precharge all)
  14.   Command.CommandMode            = FMC_SDRAM_CMD_PALL;
  15.   Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  16.   Command.AutoRefreshNumber      = 1;
  17.   Command.ModeRegisterDefinition = 0;
  18.   //Wyslanie komendy
  19.   HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
  20.   //Krok 4: Komenda Auto Refresh
  21.   Command.CommandMode            = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
  22.   Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  23.   Command.AutoRefreshNumber      = 8;
  24.   Command.ModeRegisterDefinition = 0;
  25.   //Przeslanie komendy
  26.   HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
  27.   //Krok 5: Programowanie trybu dla zewnetrznej pamieci
  28.  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;
  29.   Command.CommandMode            = FMC_SDRAM_CMD_LOAD_MODE;
  30.   Command.CommandTarget          = FMC_SDRAM_CMD_TARGET_BANK1;
  31.   Command.AutoRefreshNumber      = 1;
  32.   Command.ModeRegisterDefinition = tmp;
  33.   //Przeslanie komendy
  34.   HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
  35.   //Krok 6: Ustawienie licznika odswiezania
  36.   HAL_SDRAM_ProgramRefreshRate(&sdramHandle, RefreshCount);
  37. }

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:

  1. ErrorStatus SDRAM_INIT(void)
  2. {
  3.   ErrorStatus stat=ERROR;
  4.   uint16_t data1;
  5.   static uint8_t init_pass=0; //Jako statyczna, musi być pamiętana
  6.   //Inicjalizacje musza byc wykonane tylko jeden raz
  7.   if(init_pass != 0)
  8.   {
  9.     if(init_pass==1) { return(SUCCESS); }
  10.     else { return(ERROR); }
  11.   }
  12.   //FMC_konfiguracja
  13.   stat=SDRAM_INIT_PRIVATE();
  14.   if(stat!=SUCCESS) { return(ERROR); }
  15.   //Zapis oraz odczyt danych z pamięci
  16.   SDRAM_WRITE_16b(0x00, 0x7BC5);
  17.   data1 = SDRAM_READ_16b(0x00);
  18.   //Sprawdzenie danych
  19.   if(0x7BC5 == data1) { stat = SUCCESS; }
  20.   //Zaznaczenie że inicjalizacja przeszla pomyslnie
  21.   if(SUCCESS = stat)
  22.   //Zapisanie wartosci startowej
  23.   if(stat==SUCCESS) { init_pass=1; }
  24.   else { init_pass=2; }
  25.   return(stat);
  26. }

Jeśli chodzi o zapis oraz odczyt danych to funkcje można zakodować zwyczajnie, z parametrem inline, bądź całą procedurę zawrzeć w #define: 

  1. inline uint8_t SDRAM_READ_8B(uint32_t adres)
  2. {
  3.     return(*(__IO uint8_t*)(SDRAM_START_ADR + adres));
  4. }
  5. inline uint8_t SDRAM_READ_16B(uint32_t adres)
  6. {
  7.     return(*(__IO uint16_t*)(SDRAM_START_ADR + adres));
  8. }
  9. inline uint8_t SDRAM_READ_32B(uint32_t adres)
  10. {
  11.     return(*(__IO uint32_t*)(SDRAM_START_ADR + adres));
  12. }

Zapis:

  1. inline void SDRAM_WRITE_8b(uint32_t adres, uint8_t zmienna)
  2. {
  3.   (*(_IO uint8_t*) (SDRAM_START_ADR + adres) = zmienna);
  4. }
  5. inline void SDRAM_WRITE_16b(uint32_t adres, uint8_t zmienna)
  6. {
  7.   (*(_IO uint16_t*) (SDRAM_START_ADR + adres) = zmienna);
  8. }
  9. inline void SDRAM_WRITE_32b(uint32_t adres, uint8_t zmienna)
  10. {
  11.   (*(_IO uint32_t*) (SDRAM_START_ADR + adres) = zmienna);
  12. }

Dodatkowo do pamięci SDRAM zapisu można dokonać poprzez DMA z wykorzystam funkcji z biblioteki HAL'a:

  1. ErrorStatus SDRAM_READ_BUFFER_32b(uint32_t* ptrBuffer, uint32_t startAdres, uint32_t lenBuffer)
  2. {
  3.   if(HAL_SDRAM_Read_32b(&sdramHandle, (uint32_t *)(startAdres+SDRAM_START_ADR), ptrBuffer, lenBuffer) != HAL_OK)
  4.   { return ERROR; }
  5.   else{ return SUCCESS; }
  6. }
  7. ErrorStatus SDRAM_WRITE_BUFFER_32b_DMA(uint32_t* ptrBuffer, uint32_t startAdr, uint32_t lenBuffer)
  8. {
  9.   if(HAL_SDRAM_Write_DMA(&sdramHandle, (uint32_t *)(startAdr+SDRAM_START_ADR), ptrBuffer, lenBuffer) != HAL_OK)
  10.   { return ERROR; }
  11.   else { return SUCCESS; }
  12. }


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