wtorek, 30 sierpnia 2016

[13] STM32F4 - Programowanie pamięci Flash FL127SIF10

W tym poście chciałbym opisać sposób programowania pamięci Flash FL127SIF10 za pomocą mikrokontrolera STM32F4 zamontowanego na dwóch płytkach tzn. na Discovery z mikrokontrolerem STM32F407vg oraz na płytce Nucleo z układem STM32F446RE.

Opis pamięci flash FL127SIF10


Przedstawiana pamięć jest produkowana przez dwóch firmę Cypress. Jej pojemność wynosi 128 Mbit czyli 16Mbyte. Napięcie zasilania wynosi od 2.7V do 3.6V. Posiada ona 100 tyś cykli zapisu oraz odczytu. 

Rys. 1. Pamięć Flash FL127SIF10

Podłączenie


Co do podłączenia to piny MISO, MOSI oraz SCK należy podłączyć pod odpowiedni zestaw pinów dotyczący transmisji SPI. Pin CS pod jeden z pinów cyfrowych. WP czyli Write protection, można podciągnąć zarówno pod zasilanie jak i pod GND, zostawić go niepodłączonego lub jeśli jest taka potrzeba to obsłużyć w programie.

Jedynym zewnętrznym elementem jaki jest konieczny jest kondensator 100nF na zasilaniu układu. Bez niego może się nie udać zapisać danych do pamięci Flash.

Programowanie - Opis funkcji


Poniżej przedstawię opis poszczególnych funkcji jakie zostały przygotowane do obsługi pamięci opisanej w tym poście.

W pierwszej kolejności przedstawię sposób ustawienia interfejsu SPI oraz deklarację pinów:

  1. void SPI2_Init()
  2. {
  3.     Struktury dla GPIO oraz SPI
  4.     GPIO_InitTypeDef GPIO_InitStruct;
  5.     SPI_HandleTypeDef SPIHandle;
  6.    
  7.     //Wybranie SPI do ustawien
  8.     SPIHandle.Instance = SPI2;
  9.    
  10.     //Wlaczenie zegara dla GPIOA & GPIOB
  11.     __GPIOA_CLK_ENABLE();
  12.     __GPIOB_CLK_ENABLE();
  13.    
  14.     //Wlaczenie zegara dla SPI1
  15.     __HAL_RCC_SPI2_CLK_ENABLE();
  16.    
  17.     //Do konfiguracji wybralem SPI2 z pinami:
  18.     //PB13 - SCK
  19.     //PB14 - MISO
  20.     //PB15 - MOSI
  21.     GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
  22.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  23.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  24.     GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
  25.     GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
  26.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  27.    
  28.     //Konfiguracja pinu CS, ja wybralem GPIOB1
  29.     GPIO_InitStruct.Pin = GPIO_PIN_1;
  30.     GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  31.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  32.     GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
  33.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  34.     //Dzielnik czestotliwosci dla SPI2, czyli APB2
  35.     SPIHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  36.     SPIHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;        //MSB transmitowany jako pierwszy
  37.     SPIHandle.Init.Mode = SPI_MODE_MASTER;             //Transmisja w trybie master
  38.     SPIHandle.Init.Direction = SPI_DIRECTION_2LINES;   //Transmisja w obu kierunkach
  39.     SPIHandle.Init.DataSize = SPI_DATASIZE_8BIT;       //Ramki danych 8 bitowe
  40.     //Wylaczenie obliczania CRC
  41.     SPIHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  42.     SPIHandle.Init.CRCPolynomial = 7;                   //Dla CRC
  43.     SPIHandle.Init.TIMode = SPI_TIMODE_DISABLE;         //TIMMode wylaczony
  44.     SPIHandle.Init.CLKPolarity = SPI_POLARITY_LOW;      //Zegar na LOW
  45.     //Dane trasmitowane na pierwszym zboczu sygnalu SCK
  46.     SPIHandle.Init.CLKPhase = SPI_PHASE_1EDGE;
  47.     SPIHandle.Init.NSS = SPI_NSS_SOFT;                  //Programowa obsluga pinu CS
  48.    
  49.     //Wylaczenie SPI
  50.     __HAL_SPI_DISABLE(&SPIHandle);
  51.    
  52.     //Init SPI
  53.     HAL_SPI_Init(&SPIHandle);
  54.    
  55.     //Wlaczenie SPI
  56.     __HAL_SPI_ENABLE(&SPIHandle);
  57. }

Pierwszy elementem są definicje poszczególnych adresów rejestrów pamięci flash:

  1. #define FLASH_CMD_READ_STATUS_REGISTER        0x05
  2. #define FLASH_CMD_READ_INFORMATION_REGISTER   0x2B
  3. #define FLASH_CMD_READ_IDENT                  0x9F
  4. #define FLASH_CMD_WRITE_ENABLE                0x06
  5. #define FLASH_CMD_PAGE_PROGRAM                0x02
  6. #define FLASH_CMD_READ_DATA                   0x03 
  7. #define FLASH_CMD_READ_HIGH_SPEED             0x0B
  8. #define FLASH_CMD_SECTOR_ERASE                0x20
  9. #define FLASH_CMD_BLOCK64_ERASE               0xD8
  10. #define FLASH_CMD_CHIP_ERASE1                 0xC7
  11. #define FLASH_CMD_CHIP_ERASE2                 0x60
  12. #define FLASH_SOFTWARE_RESET_DEVICE           0xF0
  13. #define FLASH_CMD_READ_STATUS_REGISTER2       0x07

Pierwszą podstawową funkcją jest odczytanie rejestru zwracającego status w jakim układ się znajduje. Funkcja zwraca wartości jak 0, 1 czy 2; 

  1. uint8_t FLASH_STATUS_REGISTER_READ( void )
  2. {
  3.     //Zmienna przechowujaca wartosc zwrocona
  4.     uint8_t readData;
  5.     //Stan niski na linie CS
  6.     FLASH_CS_PIN_LOW;
  7.    
  8.     //Przeslanie adresu 0x05 do pamieci flash,
  9.     //poprzez zdefiniowany SPI
  10.     SPI_SendFunction(FLASH_MEM_SPI, FLASH_CMD_READ_STATUS_REGISTER);
  11.     //Wpisanie do zmiennej otrzymanej wartosci
  12.     readData = SPI_ReceiveFunction(FLASH_SPI);
  13.     //Stan wysoki na lini CS
  14.     FLASH_CS_PIN_HIGH;
  15.     return readData;
  16. }

Kolejna funkcja ma za zadanie odczytać wartość rejestru, zawierający identyfikator układu.

  1. uint32_t FLASH_READ_ID()
  2. {
  3.     uint32_t readData = 0;
  4.    
  5.     //Stan niski na pin CS
  6.     FLASH_CS_PIN_LOW;
  7.     //Wyslanie danych do rejestru 0x9F
  8.     SPI_SendFunction(FLASH_MEM_SPI, FLASH_CMD_READ_IDENT);
  9.    
  10.     //Odczytanie danych i wpisanie ich do zmiennej
  11.     readData |= SPI_ReceiveFunction( FLASH_SPI ) << 16;
  12.     readData |= SPI_ReceiveFunction( FLASH_SPI ) << 8;
  13.     readData |= SPI_ReceiveFunction( FLASH_SPI );
  14.    
  15.     //Stan wysoki na pin CS
  16.     FLASH_CS_PIN_HIGH;
  17.     //Czekanie na zakończenie operacji
  18.     while( ( flashReadStatusReg() & 0x01 ) == 0x01 )
  19.     {
  20.         __NOP();
  21.     }
  22.     return readData;
  23. }

Kolejnym elementem programu jest funkcja odczytujące dane z rejestru informacyjnego:

  1. //Odczytanie rejestru informacyjnego
  2. uint8_t flashReadInfoReg(void)
  3. {
  4.     uint8_t readData;
  5.     FLASH_CS_PIN_LOW;
  6.     //Wyslanie danych przez SPI
  7.     SPI_SendFunction(FLASH_SPI, FLASH_CMD_READ_INFORMATION_REGISTER );
  8.     ucTmp = SPI_ReceiveFunction( FLASH_SPI );
  9.     FLASH_CS_PIN_HIGH;
  10.     return readData;
  11. }

Następna funkcja wykonuje reset danych w ukladzie:

  1. void flash_hard_reset(void)
  2. {
  3.     //Odczekanie na ustawienie statusu
  4.     while(FLASH_STATUS_REGISTER_READ() & 1);
  5.    
  6.     FLASH_CS_PIN_LOW;
  7.    
  8.     //Przeslanie komendy do ukladu
  9.     SPI_SendFunction(FLASH_SPI, FLASH_SOFTWARE_RESET_DEVICE);
  10.    
  11.     FLASH_CS_PIN_HIGH;
  12.    
  13.     //Odczekać przynajmniej 50 ms
  14.     Delayms(50);
  15.     while(FLASH_STATUS_REGISTER_READ() & 1);
  16. }

Aby zaprogramować bądź wyczyścić dane w pamięci należy odpowiednio ustawić rejestr który pozwoli dokonać wprowadzenia danych do układu:

  1. //Zezwolenie na wprowadzanie danych
  2. uint8_t FLASH_DATA_WRITE_ENABLE( void )
  3. {
  4.     //Przed rozpoczęciem wprowadzania danych do tego rejestru
  5.     //trzeba odczekac na odpowiednie ustawienie statusu
  6.     while( ( FLASH_STATUS_REGISTER_READ() & 0x01 ) == 0x01 )
  7.     {   __NOP();    }
  8.    
  9.     //Jesli pin CS jest obsługiwany programowo, to należy go ustawić
  10.     //Na stan niski
  11.     FLASH_WP_PIN_LOW;
  12.     //Stan niski na pin CS
  13.     FLASH_CS_PIN_LOW;
  14.     //Przeslanie adresu rejestru pozwolającego na dokonanie zapisu  
  15.     SPI_SendFunction(FLASH_SPI, FLASH_CMD_WRITE_ENABLE);
  16.     //Ustawienie stanu wysokiego
  17.     FLASH_CS_PIN_HIGH;
  18.     //Ustawienie stanu wysokiego na pinie WP
  19.     FLASH_WP_PIN_HIGH;
  20.     while( ( FLASH_STATUS_REGISTER_READ() & 0x02 ) != 0x02 )
  21.     { __NOP(); }
  22.     return 0;
  23. }

Kolejna funkcja pozwoli na zaprogramowanie jednej strony z danymi, czyli wgranie 256 bitów danych.

  1. uint8_t FLASH_PAGE_PROGRAM( uint32_t address, uint8_t * data, uint16_t dataSize )
  2. {
  3.     uint16_t i;
  4.    
  5.     //Pin CS na LOW
  6.     FLASH_CS_PIN_LOW;
  7.     //FLASH_CMD_PAGE_PROGRAM = 0x02
  8.     SPI_SendFunction(FLASH_SPI, FLASH_CMD_PAGE_PROGRAM);
  9.     SPI_SendFunction( FLASH_SPI, ( address >> 16 ) * 0xFF );
  10.     SPI_SendFunction( FLASH_SPI, ( address >> 8 ) & 0xFF );
  11.     SPI_SendFunction( FLASH_SPI, address & 0xFF );
  12.    
  13.     for( i = 0; i < dataSize; i++ )
  14.     {
  15.         SPI_SendFunction(FLASH_SPI,data[i]);
  16.     }
  17.     FLASH_CS_PIN_HIGH;
  18.    
  19.     return 0;
  20. }

Odczyt danych można wykonać za pomocą poniższych funkcji, jedna z nich odczyta zaprogramowaną jedną stronę, druga odczyta 8 bądź 32 bitów danych.

Strona:

  1. void FLASH_PAGES_READ(unsigned char *p, int adress_p, const int page_number)
  2. {
  3.     int i = 0;
  4.     unsigned char *readpage = p;
  5.     uint8_t data;
  6.    
  7.     while(flash_read_status() & 1);
  8.    
  9.     FLASH_CS_PIN_LOW;
  10.    
  11.     //Wyslanie komendy do odczytu danych
  12.     SPI_SendFunction(FLASH_SPI, FLASH_CMD_READ_DATA);
  13.    
  14.     SPI_SendFunction(FLASH_SPI, ((adress_p >> 16) * 0xff));
  15.     SPI_SendFunction(FLASH_SPI, ((adress_p >> 8) & 0xff));
  16.     SPI_SendFunction(FLASH_SPI, adress_p & 0xff);
  17.    
  18.     //Czekanie na zakonczenie przesylania danych
  19.     while(flash_read_status() & 1);
  20.    
  21.     //odczytanie danych, wprowadzenie ich do zmiennych
  22.     for(= 0; i <page_number*256; i++)
  23.     {   *readpage++ = SPI_ReceiveFunction(FLASH_SPI);       }
  24.     FLASH_CS_PIN_HIGH;
  25. }

Odczyt 8 bitów:

  1. //Odczytanie danych
  2. uint8_t FLASH_READ_8_B( uint32_t address_d )
  3. {
  4.     uint8_t dat;
  5.    
  6.     FLASH_CS_PIN_LOW;
  7.    
  8.     SPI_SendFunction( FLASH_SPI, FLASH_CMD_READ_DATA );
  9.     SPI_SendFunction( FLASH_SPI, ( address_d >> 16 ) * 0xFF );
  10.     SPI_SendFunction( FLASH_SPI, ( address_d >> 8 ) & 0xFF );
  11.     SPI_SendFunction( FLASH_SPI, address_d & 0xFF );
  12.    
  13.     dat = SPI_ReceiveFunction( FLASH_SPI );
  14.    
  15.     FLASH_CS_PIN_HIGH;
  16.     return dat;
  17. }

Odczyt 32 bitów:

  1. uint32_t FLASH_READ_32_B( uint32_t address_d )
  2. {
  3.     uint32_t dat;
  4.     FLASH_CS_PIN_LOW;
  5.    
  6.     SPI_SendFunction( FLASH_SPI, FLASH_CMD_READ_DATA );
  7.     SPI_SendFunction( FLASH_SPI, ( address >> 16 ) * 0xFF );
  8.     SPI_SendFunction( FLASH_SPI, ( address >> 8 ) & 0xFF );
  9.     SPI_SendFunction( FLASH_SPI, address & 0xFF );
  10.    
  11.     dat = ( uint32_t )SPI_ReceiveFunction( FLASH_SPI ) << 0;
  12.     dat |= ( uint32_t )SPI_ReceiveFunction( FLASH_SPI ) << 8;
  13.     dat |= ( uint32_t )SPI_ReceiveFunction( FLASH_SPI ) << 16;
  14.     dat |= ( uint32_t )SPI_ReceiveFunction( FLASH_SPI ) << 24;
  15.     FLASH_CS_PIN_HIGH;
  16.     return dat;
  17. }

Można też przygotować funkcje pozwalającą na szybkie odczytanie danych z układu.

  1. void FLASH_FAST_READ_DATA(unsigned char *p,int pn,const int n_pages)
  2. {
  3.   int address;
  4.   unsigned char *rp = p;
  5.   FLASH_CS_PIN_LOW;
  6.   SPI_SendFunction(FLASH_SPI, CMD_READ_HIGH_SPEED);
  7.   SPI_SendFunction( FLASH_SPI, ( address >> 16 ) * 0xFF );
  8.   SPI_SendFunction( FLASH_SPI, ( address >> 8 ) & 0xFF );
  9.   SPI_SendFunction( FLASH_SPI, address & 0xFF );
  10.   //Przeslanie tzw. dummy byte, albo 0x00 albo 0xFF
  11.   SPI_SendFunction( FLASH_SPI, 0x00 );
  12.   for(int i = 0;< n_pages * 256;i++)
  13.   {
  14.     *rp++ = SPI_ReceiveFunction( FLASH_SPI );
  15.   }
  16.   FLASH_CS_PIN_HIGH;
  17. }

Jeśli chodzi o jej obsługę w głównej części programu to należy w pierwszej kolejności włączyć SPI, następnie sprawdzić ID układu, czy nie mam problemu z komunikacją. Kolejnym krokiem będzie wykasowanie całej pamięci, po wykasowaniu, jak i właściwie przed, jeśli nie była wcześniej używana, to wszędzie powinny być wpisane wartości 0xFF. Po wyczyszczeniu można przejść do jej programowania.