14 lipca 2018

Pamięć SDRAM

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:

#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