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.
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:
- void SPI2_Init()
- {
- Struktury dla GPIO oraz SPI
- GPIO_InitTypeDef GPIO_InitStruct;
- SPI_HandleTypeDef SPIHandle;
- //Wybranie SPI do ustawien
- SPIHandle.Instance = SPI2;
- //Wlaczenie zegara dla GPIOA & GPIOB
- __GPIOA_CLK_ENABLE();
- __GPIOB_CLK_ENABLE();
- //Wlaczenie zegara dla SPI1
- __HAL_RCC_SPI2_CLK_ENABLE();
- //Do konfiguracji wybralem SPI2 z pinami:
- //PB13 - SCK
- //PB14 - MISO
- //PB15 - MOSI
- GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
- GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- //Konfiguracja pinu CS, ja wybralem GPIOB1
- GPIO_InitStruct.Pin = GPIO_PIN_1;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
- //Dzielnik czestotliwosci dla SPI2, czyli APB2
- SPIHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
- SPIHandle.Init.FirstBit = SPI_FIRSTBIT_MSB; //MSB transmitowany jako pierwszy
- SPIHandle.Init.Mode = SPI_MODE_MASTER; //Transmisja w trybie master
- SPIHandle.Init.Direction = SPI_DIRECTION_2LINES; //Transmisja w obu kierunkach
- SPIHandle.Init.DataSize = SPI_DATASIZE_8BIT; //Ramki danych 8 bitowe
- //Wylaczenie obliczania CRC
- SPIHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
- SPIHandle.Init.CRCPolynomial = 7; //Dla CRC
- SPIHandle.Init.TIMode = SPI_TIMODE_DISABLE; //TIMMode wylaczony
- SPIHandle.Init.CLKPolarity = SPI_POLARITY_LOW; //Zegar na LOW
- //Dane trasmitowane na pierwszym zboczu sygnalu SCK
- SPIHandle.Init.CLKPhase = SPI_PHASE_1EDGE;
- SPIHandle.Init.NSS = SPI_NSS_SOFT; //Programowa obsluga pinu CS
- //Wylaczenie SPI
- __HAL_SPI_DISABLE(&SPIHandle);
- //Init SPI
- HAL_SPI_Init(&SPIHandle);
- //Wlaczenie SPI
- __HAL_SPI_ENABLE(&SPIHandle);
- }
Pierwszy elementem są definicje poszczególnych adresów rejestrów pamięci flash:
- #define FLASH_CMD_READ_STATUS_REGISTER 0x05
- #define FLASH_CMD_READ_INFORMATION_REGISTER 0x2B
- #define FLASH_CMD_READ_IDENT 0x9F
- #define FLASH_CMD_WRITE_ENABLE 0x06
- #define FLASH_CMD_PAGE_PROGRAM 0x02
- #define FLASH_CMD_READ_DATA 0x03
- #define FLASH_CMD_READ_HIGH_SPEED 0x0B
- #define FLASH_CMD_SECTOR_ERASE 0x20
- #define FLASH_CMD_BLOCK64_ERASE 0xD8
- #define FLASH_CMD_CHIP_ERASE1 0xC7
- #define FLASH_CMD_CHIP_ERASE2 0x60
- #define FLASH_SOFTWARE_RESET_DEVICE 0xF0
- #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;
- uint8_t FLASH_STATUS_REGISTER_READ( void )
- {
- //Zmienna przechowujaca wartosc zwrocona
- uint8_t readData;
- //Stan niski na linie CS
- FLASH_CS_PIN_LOW;
- //Przeslanie adresu 0x05 do pamieci flash,
- //poprzez zdefiniowany SPI
- SPI_SendFunction(FLASH_MEM_SPI, FLASH_CMD_READ_STATUS_REGISTER);
- //Wpisanie do zmiennej otrzymanej wartosci
- readData = SPI_ReceiveFunction(FLASH_SPI);
- //Stan wysoki na lini CS
- FLASH_CS_PIN_HIGH;
- return readData;
- }
Kolejna funkcja ma za zadanie odczytać wartość rejestru, zawierający identyfikator układu.
- uint32_t FLASH_READ_ID()
- {
- uint32_t readData = 0;
- //Stan niski na pin CS
- FLASH_CS_PIN_LOW;
- //Wyslanie danych do rejestru 0x9F
- SPI_SendFunction(FLASH_MEM_SPI, FLASH_CMD_READ_IDENT);
- //Odczytanie danych i wpisanie ich do zmiennej
- readData |= SPI_ReceiveFunction( FLASH_SPI ) << 16;
- readData |= SPI_ReceiveFunction( FLASH_SPI ) << 8;
- readData |= SPI_ReceiveFunction( FLASH_SPI );
- //Stan wysoki na pin CS
- FLASH_CS_PIN_HIGH;
- //Czekanie na zakończenie operacji
- while( ( flashReadStatusReg() & 0x01 ) == 0x01 )
- {
- __NOP();
- }
- return readData;
- }
Kolejnym elementem programu jest funkcja odczytujące dane z rejestru informacyjnego:
- //Odczytanie rejestru informacyjnego
- uint8_t flashReadInfoReg(void)
- {
- uint8_t readData;
- FLASH_CS_PIN_LOW;
- //Wyslanie danych przez SPI
- SPI_SendFunction(FLASH_SPI, FLASH_CMD_READ_INFORMATION_REGISTER );
- ucTmp = SPI_ReceiveFunction( FLASH_SPI );
- FLASH_CS_PIN_HIGH;
- return readData;
- }
Następna funkcja wykonuje reset danych w ukladzie:
- void flash_hard_reset(void)
- {
- //Odczekanie na ustawienie statusu
- while(FLASH_STATUS_REGISTER_READ() & 1);
- FLASH_CS_PIN_LOW;
- //Przeslanie komendy do ukladu
- SPI_SendFunction(FLASH_SPI, FLASH_SOFTWARE_RESET_DEVICE);
- FLASH_CS_PIN_HIGH;
- //Odczekać przynajmniej 50 ms
- Delayms(50);
- while(FLASH_STATUS_REGISTER_READ() & 1);
- }
Aby zaprogramować bądź wyczyścić dane w pamięci należy odpowiednio ustawić rejestr który pozwoli dokonać wprowadzenia danych do układu:
- //Zezwolenie na wprowadzanie danych
- uint8_t FLASH_DATA_WRITE_ENABLE( void )
- {
- //Przed rozpoczęciem wprowadzania danych do tego rejestru
- //trzeba odczekac na odpowiednie ustawienie statusu
- while( ( FLASH_STATUS_REGISTER_READ() & 0x01 ) == 0x01 )
- { __NOP(); }
- //Jesli pin CS jest obsługiwany programowo, to należy go ustawić
- //Na stan niski
- FLASH_WP_PIN_LOW;
- //Stan niski na pin CS
- FLASH_CS_PIN_LOW;
- //Przeslanie adresu rejestru pozwolającego na dokonanie zapisu
- SPI_SendFunction(FLASH_SPI, FLASH_CMD_WRITE_ENABLE);
- //Ustawienie stanu wysokiego
- FLASH_CS_PIN_HIGH;
- //Ustawienie stanu wysokiego na pinie WP
- FLASH_WP_PIN_HIGH;
- while( ( FLASH_STATUS_REGISTER_READ() & 0x02 ) != 0x02 )
- { __NOP(); }
- return 0;
- }
Kolejna funkcja pozwoli na zaprogramowanie jednej strony z danymi, czyli wgranie 256 bitów danych.
- uint8_t FLASH_PAGE_PROGRAM( uint32_t address, uint8_t * data, uint16_t dataSize )
- {
- uint16_t i;
- //Pin CS na LOW
- FLASH_CS_PIN_LOW;
- //FLASH_CMD_PAGE_PROGRAM = 0x02
- SPI_SendFunction(FLASH_SPI, FLASH_CMD_PAGE_PROGRAM);
- SPI_SendFunction( FLASH_SPI, ( address >> 16 ) * 0xFF );
- SPI_SendFunction( FLASH_SPI, ( address >> 8 ) & 0xFF );
- SPI_SendFunction( FLASH_SPI, address & 0xFF );
- for( i = 0; i < dataSize; i++ )
- {
- SPI_SendFunction(FLASH_SPI,data[i]);
- }
- FLASH_CS_PIN_HIGH;
- return 0;
- }
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:
- void FLASH_PAGES_READ(unsigned char *p, int adress_p, const int page_number)
- {
- int i = 0;
- unsigned char *readpage = p;
- uint8_t data;
- while(flash_read_status() & 1);
- FLASH_CS_PIN_LOW;
- //Wyslanie komendy do odczytu danych
- SPI_SendFunction(FLASH_SPI, FLASH_CMD_READ_DATA);
- SPI_SendFunction(FLASH_SPI, ((adress_p >> 16) * 0xff));
- SPI_SendFunction(FLASH_SPI, ((adress_p >> 8) & 0xff));
- SPI_SendFunction(FLASH_SPI, adress_p & 0xff);
- //Czekanie na zakonczenie przesylania danych
- while(flash_read_status() & 1);
- //odczytanie danych, wprowadzenie ich do zmiennych
- for(i = 0; i <page_number*256; i++)
- { *readpage++ = SPI_ReceiveFunction(FLASH_SPI); }
- FLASH_CS_PIN_HIGH;
- }
Odczyt 8 bitów:
- //Odczytanie danych
- uint8_t FLASH_READ_8_B( uint32_t address_d )
- {
- uint8_t dat;
- FLASH_CS_PIN_LOW;
- SPI_SendFunction( FLASH_SPI, FLASH_CMD_READ_DATA );
- SPI_SendFunction( FLASH_SPI, ( address_d >> 16 ) * 0xFF );
- SPI_SendFunction( FLASH_SPI, ( address_d >> 8 ) & 0xFF );
- SPI_SendFunction( FLASH_SPI, address_d & 0xFF );
- dat = SPI_ReceiveFunction( FLASH_SPI );
- FLASH_CS_PIN_HIGH;
- return dat;
- }
Odczyt 32 bitów:
- uint32_t FLASH_READ_32_B( uint32_t address_d )
- {
- uint32_t dat;
- FLASH_CS_PIN_LOW;
- SPI_SendFunction( FLASH_SPI, FLASH_CMD_READ_DATA );
- SPI_SendFunction( FLASH_SPI, ( address >> 16 ) * 0xFF );
- SPI_SendFunction( FLASH_SPI, ( address >> 8 ) & 0xFF );
- SPI_SendFunction( FLASH_SPI, address & 0xFF );
- dat = ( uint32_t )SPI_ReceiveFunction( FLASH_SPI ) << 0;
- dat |= ( uint32_t )SPI_ReceiveFunction( FLASH_SPI ) << 8;
- dat |= ( uint32_t )SPI_ReceiveFunction( FLASH_SPI ) << 16;
- dat |= ( uint32_t )SPI_ReceiveFunction( FLASH_SPI ) << 24;
- FLASH_CS_PIN_HIGH;
- return dat;
- }
Można też przygotować funkcje pozwalającą na szybkie odczytanie danych z układu.
- void FLASH_FAST_READ_DATA(unsigned char *p,int pn,const int n_pages)
- {
- int address;
- unsigned char *rp = p;
- FLASH_CS_PIN_LOW;
- SPI_SendFunction(FLASH_SPI, CMD_READ_HIGH_SPEED);
- SPI_SendFunction( FLASH_SPI, ( address >> 16 ) * 0xFF );
- SPI_SendFunction( FLASH_SPI, ( address >> 8 ) & 0xFF );
- SPI_SendFunction( FLASH_SPI, address & 0xFF );
- //Przeslanie tzw. dummy byte, albo 0x00 albo 0xFF
- SPI_SendFunction( FLASH_SPI, 0x00 );
- for(int i = 0;i < n_pages * 256;i++)
- {
- *rp++ = SPI_ReceiveFunction( FLASH_SPI );
- }
- FLASH_CS_PIN_HIGH;
- }
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.