Mikrokontroler STM32F429ZI jest wyposażony w 256KB pamięci RAM. To całkiem sporo jak na mikrokontroler - z drugiej strony pamięci zawsze brakuje, więc przydałoby się więcej. Na płytce ewaluacyjnej znajdziemy 8MB (czyli 64Mb) pamięci SDRAM IS42S16400J. Więcej o układzie jest w nocie katalogowej: http://www.issi.com/WW/pdf/42-45S16400J.pdf
Aby skorzystać z tej dodatkowej pamięci użyjemy modułu FMC, czyli Flexible Memory Controller.
Konfiguracja w pierwszej chwili może wydawać się skomplikowana, ale jak ją raz przejdziemy używanie pamięci jest bardzo proste - zaczynając od adresu 0xd0000000 mamy do dyspozycji 8 megabajtów pamięci. Dostęp do niej jest wolniejszy niż do wbudowanej pamięci RAM, ale poza tym działa identycznie.
Ponieważ kod konfiguracyjny FMC jest nieco zawiły, najlepiej wydzielić go do osobnego modułu. Jego interfejs będzie w pliku sdram.h:
Mamy więc stałą z adresem pamięci oraz jedną funkcję która ją zainicjalizuje.
Plik sdram.c zawiera implementację i jest nieco bardziej złożony.
Po pierwsze plik nagłówkowy stm32f4xx.h okazuje się niewystarczający. Nie ma w nim definicji związanych z naszym modelem pamięci, ale brakuje również niektórych definicji dla rejestrów FMC. Ich definicje są częściowo przeniesione z przykładów dostarczonych do zestawu discovery.
Kolejna sprawa to ustawienie funkcji dla pinów. Pamięć wykorzystuje całkiem sporo pinów, aby ułatwić sobie zadanie przygotowałem funkcję konfigurującą pin:
Teraz mogę skonfigurować wszystkie potrzebne piny - są opisane w dokumentacji płytki discovery oraz nocie katalogowej mikrokontrolera:
Na koniec to co najważniejsze, czyli sama inicjalizacja pamięci:
Opis wymaganych kroków do inicjalizacji FMC znajdziemy w dokumentacji mikrokontrolera. Natomiast dokumentacja pamięci dostarcza informacji o timingach.
Użyte przeze mnie są nieco inne niż w przypadku kodu przykładowego dla płytki ewaluacyjnej - wydaje mi się, że policzyłem je dobrze, program wydaje się działać poprwanie. Ale zastrzegam, że każdy się może mylić i nie daję gwarancji na powyższy kod.
Skoro pamięć już działa, można napisać program główny - który przy okazji przetestuje zapis i odczyt z pamięci.
Skoro pamięc SDRAM już działa można uruchamiać kolejne moduły. Na celowniku wyświetlacz, miejsce na bufor obrazu już przygotowane.
Aby skorzystać z tej dodatkowej pamięci użyjemy modułu FMC, czyli Flexible Memory Controller.
Konfiguracja w pierwszej chwili może wydawać się skomplikowana, ale jak ją raz przejdziemy używanie pamięci jest bardzo proste - zaczynając od adresu 0xd0000000 mamy do dyspozycji 8 megabajtów pamięci. Dostęp do niej jest wolniejszy niż do wbudowanej pamięci RAM, ale poza tym działa identycznie.
Ponieważ kod konfiguracyjny FMC jest nieco zawiły, najlepiej wydzielić go do osobnego modułu. Jego interfejs będzie w pliku sdram.h:
#ifndef __SDRAM__ #define __SDRAM__ #define SDRAM_ADDR 0xd0000000 void sdram_init(void); #endif // __SDRAM__
Mamy więc stałą z adresem pamięci oraz jedną funkcję która ją zainicjalizuje.
Plik sdram.c zawiera implementację i jest nieco bardziej złożony.
Po pierwsze plik nagłówkowy stm32f4xx.h okazuje się niewystarczający. Nie ma w nim definicji związanych z naszym modelem pamięci, ale brakuje również niektórych definicji dla rejestrów FMC. Ich definicje są częściowo przeniesione z przykładów dostarczonych do zestawu discovery.
#define FMC_SDCMR_MODE_NORMAL 0 #define FMC_SDCMR_MODE_CLK_CFG_EN 1 #define FMC_SDCMR_MODE_PALL 2 #define FMC_SDCMR_MODE_AUTO_REFRESH 3 #define FMC_SDCMR_MODE_LMR 4 #define FMC_SDCMR_MODE_SELF_REFRESH 5 #define FMC_SDCMR_MODE_POWER_DOWN 6 #define FMC_SDCMR_NRFS_CYCLES(n) (((n) - 1) << 5) #define FMC_SDCMR_LMR_MRD(n) ((n) << 9) #define FMC_SDRTR_COUNT_VALUE(n) ((n) << 1) #define FMC_SDCR_COLS_8b 0x00000000 #define FMC_SDCR_COLS_9b 0x00000001 #define FMC_SDCR_COLS_10b 0x00000002 #define FMC_SDCR_COLS_11b 0x00000003 #define FMC_SDCR_ROWS_11b 0x00000000 #define FMC_SDCR_ROWS_12b 0x00000004 #define FMC_SDCR_ROWS_13b 0x00000008 #define FMC_SDCR_DATA_8b 0x00000000 #define FMC_SDCR_DATA_16b 0x00000010 #define FMC_SDCR_DATA_32b 0x00000020 #define FMC_SDCR_BANKS_2 0x00000000 #define FMC_SDCR_BANKS_4 0x00000040 #define FMC_SDCR_CAS_1 0x00000080 #define FMC_SDCR_CAS_2 0x00000100 #define FMC_SDCR_CAS_3 0x00000180 #define FMC_SDCR_WP 0x00000200 #define FMC_SDCR_CLK_DIV2 0x00000800 #define FMC_SDCR_CLK_DIV3 0x00000c00 #define FMC_SDTR_TMRD_VALUE(n) (((n) - 1) << 0) #define FMC_SDTR_TXSR_VALUE(n) (((n) - 1) << 4) #define FMC_SDTR_TRAS_VALUE(n) (((n) - 1) << 8) #define FMC_SDTR_TRC_VALUE(n) (((n) - 1) << 12) #define FMC_SDTR_TWR_VALUE(n) (((n) - 1) << 16) #define FMC_SDTR_TRP_VALUE(n) (((n) - 1) << 20) #define FMC_SDTR_TRCD_VALUE(n) (((n) - 1) << 24) #define SDRAM_LMR_BURST_1 0x0000 #define SDRAM_LMR_BURST_2 0x0001 #define SDRAM_LMR_BURST_4 0x0002 #define SDRAM_LMR_BURST_8 0x0003 #define SDRAM_LMR_BURST_PAGE 0x0007 #define SDRAM_LMR_BURST_INTERLEAVED 0x0008 #define SDRAM_LMR_CAS_2 0x0020 #define SDRAM_LMR_CAS_3 0x0030 #define SDRAM_LMR_WR_BURST_SINGLE 0x0200
Kolejna sprawa to ustawienie funkcji dla pinów. Pamięć wykorzystuje całkiem sporo pinów, aby ułatwić sobie zadanie przygotowałem funkcję konfigurującą pin:
static void pin_config(GPIO_TypeDef* port, uint8_t pin, uint32_t func) { if (pin < 8) { port->AFR[0] |= func << (pin * 4); port->AFR[0] |= func << (pin * 4); } else { port->AFR[1] |= func << ((pin - 8) * 4); port->AFR[1] |= func << ((pin - 8) * 4); } port->MODER |= 2 << (pin * 2); port->OSPEEDR |= 2 << (pin * 2); }
Teraz mogę skonfigurować wszystkie potrzebne piny - są opisane w dokumentacji płytki discovery oraz nocie katalogowej mikrokontrolera:
static void init_gpio(void) { pin_config(GPIOG, 8, 12); // SDCLK pin_config(GPIOG, 15, 12); // SDNCAS pin_config(GPIOF, 11, 12); // SDNRAS pin_config(GPIOB, 5, 12); // SDCKE1 pin_config(GPIOB, 6, 12); // SDNE1 pin_config(GPIOC, 0, 12); // SDNWE pin_config(GPIOE, 0, 12); // NBL0 pin_config(GPIOE, 1, 12); // NBL1 pin_config(GPIOF, 0, 12); // A0 pin_config(GPIOF, 1, 12); // A1 pin_config(GPIOF, 2, 12); // A2 pin_config(GPIOF, 3, 12); // A3 pin_config(GPIOF, 4, 12); // A4 pin_config(GPIOF, 5, 12); // A5 pin_config(GPIOF, 12, 12); // A6 pin_config(GPIOF, 13, 12); // A7 pin_config(GPIOF, 14, 12); // A8 pin_config(GPIOF, 15, 12); // A9 pin_config(GPIOG, 0, 12); // A10 pin_config(GPIOG, 1, 12); // A11 pin_config(GPIOG, 4, 12); // BA0 pin_config(GPIOG, 5, 12); // BA1 pin_config(GPIOD, 14, 12); // D0 pin_config(GPIOD, 15, 12); // D1 pin_config(GPIOD, 0, 12); // D2 pin_config(GPIOD, 1, 12); // D3 pin_config(GPIOE, 7, 12); // D4 pin_config(GPIOE, 8, 12); // D5 pin_config(GPIOE, 9, 12); // D6 pin_config(GPIOE, 10, 12); // D7 pin_config(GPIOE, 11, 12); // D8 pin_config(GPIOE, 12, 12); // D9 pin_config(GPIOE, 13, 12); // D10 pin_config(GPIOE, 14, 12); // D11 pin_config(GPIOE, 15, 12); // D12 pin_config(GPIOD, 8, 12); // D13 pin_config(GPIOD, 9, 12); // D14 pin_config(GPIOD, 10, 12); // D15 }
Na koniec to co najważniejsze, czyli sama inicjalizacja pamięci:
void sdram_init(void) { init_gpio(); RCC->AHB3ENR |= RCC_AHB3ENR_FMCEN; // SDCLK 84 MHz => 11.9 ns // TMRD: 2 clk // TXSR: 70 ns => 6 // TRAS: 42 ns => 4 // TRC: 63 ns => 6 // TWR: 2 clk // TRP: 15 ns => 2 // TRCD: 15 ns => 2 FMC_Bank5_6->SDCR[0] = FMC_SDCR_CLK_DIV2; FMC_Bank5_6->SDCR[1] = FMC_SDCR_CLK_DIV2 | FMC_SDCR_CAS_2 | FMC_SDCR_BANKS_4 | FMC_SDCR_ROWS_12b | FMC_SDCR_COLS_8b | FMC_SDCR_DATA_16b; FMC_Bank5_6->SDTR[0] = FMC_SDTR_TRC_VALUE(6) | FMC_SDTR_TRP_VALUE(2); FMC_Bank5_6->SDTR[1] = FMC_SDTR_TMRD_VALUE(2) | FMC_SDTR_TXSR_VALUE(6) | FMC_SDTR_TRAS_VALUE(4) | FMC_SDTR_TWR_VALUE(2) | FMC_SDTR_TRCD_VALUE(2); /* Configure a clock configuration enable command */ while (FMC_Bank5_6->SDSR & FMC_SDSR_BUSY); FMC_Bank5_6->SDCMR = FMC_SDCMR_MODE_CLK_CFG_EN | FMC_SDCMR_CTB2; /* Insert 100 us delay */ for (volatile uint32_t dly = 0; dly < 16800; dly++) ; /* Configure a PALL (precharge all) command */ while (FMC_Bank5_6->SDSR & FMC_SDSR_BUSY); FMC_Bank5_6->SDCMR = FMC_SDCMR_MODE_PALL | FMC_SDCMR_CTB2; /* Configure a Auto-Refresh command, 4 auto-refresh cycles */ while (FMC_Bank5_6->SDSR & FMC_SDSR_BUSY); FMC_Bank5_6->SDCMR = FMC_SDCMR_MODE_AUTO_REFRESH | FMC_SDCMR_CTB2 | FMC_SDCMR_NRFS_CYCLES(4); while (FMC_Bank5_6->SDSR & FMC_SDSR_BUSY); FMC_Bank5_6->SDCMR = FMC_SDCMR_MODE_AUTO_REFRESH | FMC_SDCMR_CTB2 | FMC_SDCMR_NRFS_CYCLES(4); /* Program the external memory mode register */ while (FMC_Bank5_6->SDSR & FMC_SDSR_BUSY); uint32_t lmr = SDRAM_LMR_BURST_2 | SDRAM_LMR_CAS_2 | SDRAM_LMR_WR_BURST_SINGLE; FMC_Bank5_6->SDCMR = FMC_SDCMR_MODE_LMR | FMC_SDCMR_CTB2 | FMC_SDCMR_LMR_MRD(lmr); /* Set the refresh rate counter */ FMC_Bank5_6->SDRTR |= FMC_SDRTR_COUNT_VALUE(1292); while (FMC_Bank5_6->SDSR & FMC_SDSR_BUSY); }
Opis wymaganych kroków do inicjalizacji FMC znajdziemy w dokumentacji mikrokontrolera. Natomiast dokumentacja pamięci dostarcza informacji o timingach.
Użyte przeze mnie są nieco inne niż w przypadku kodu przykładowego dla płytki ewaluacyjnej - wydaje mi się, że policzyłem je dobrze, program wydaje się działać poprwanie. Ale zastrzegam, że każdy się może mylić i nie daję gwarancji na powyższy kod.
Skoro pamięć już działa, można napisać program główny - który przy okazji przetestuje zapis i odczyt z pamięci.
#include <stdint.h> #include <stdbool.h> #include "stm32f429xx.h" #include "pll.h" #include "sdram.h" int main(int argc, char *argv[]) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOCEN | RCC_AHB1ENR_GPIODEN | RCC_AHB1ENR_GPIOEEN | RCC_AHB1ENR_GPIOFEN | RCC_AHB1ENR_GPIOGEN; pll_init(); sdram_init(); GPIOG->MODER |= GPIO_MODER_MODE13_0|GPIO_MODER_MODE14_0; GPIOG->BSRR = GPIO_BSRR_BS13; GPIOG->BSRR = GPIO_BSRR_BR14; for (int i = 0; i < 1024; i++) *(uint32_t*) (SDRAM_ADDR + 4 * i) = i; for (int i = 0; i < 1024; i++) { uint32_t x = *(uint32_t*)(SDRAM_ADDR + 4 * i); if (x != i) while (1); } GPIOG->BSRR = GPIO_BSRR_BS14; while (1) { } return 0; }
Skoro pamięc SDRAM już działa można uruchamiać kolejne moduły. Na celowniku wyświetlacz, miejsce na bufor obrazu już przygotowane.
Brak komentarzy:
Prześlij komentarz