W tym poście chciałbym poruszyć temat wykorzystania QSPI na płytce discovery do komunikacji z pamięcią Nor Flash. Kość umieszczona na płytce discovery to 128 Mbit pamięć N25Q128A13EF840E od Microna.
Układ został podłączony do mikrokontrolera w następujący sposób [STM32F7 Datasheet]:
Układ został podłączony do mikrokontrolera w następujący sposób [STM32F7 Datasheet]:
Poniżej zaprezentuję sposób przygotowania programu za pomocą środowiska CubeMx:
Włączenie QSPI:
Ustawienie QSPI:
Programowanie:
Przygotowanie funkcji jest oparte o biblioteki BSP udostępnione przez firmę ST.
Najpierw omówię plik .h. Na samej górze znajdują się w nim deklaracje pinów, które są nieużywane w przypadku gdy nie korzysta się z procedury inicjalizacyjnej z Cuba:
- #define QSPI_CS_PIN GPIO_PIN_6
- #define QSPI_CS_GPIO_PORT GPIOB
- #define QSPI_CLK_PIN GPIO_PIN_2
- #define QSPI_CLK_GPIO_PORT GPIOB
- #define QSPI_D0_PIN GPIO_PIN_11
- #define QSPI_D0_GPIO_PORT GPIOD
- #define QSPI_D1_PIN GPIO_PIN_12
- #define QSPI_D1_GPIO_PORT GPIOD
- #define QSPI_D2_PIN GPIO_PIN_2
- #define QSPI_D2_GPIO_PORT GPIOE
- #define QSPI_D3_PIN GPIO_PIN_13
- #define QSPI_D3_GPIO_PORT GPIOD
Następnie zdefiniowałem statusy dla przeprowadzonych operacji podczas korzystania z QSPI:
- /* QSPI operation status */
- #define QSPI_OK ((uint8_t)0x00)
- #define QSPI_ERROR ((uint8_t)0x01)
- #define QSPI_BUSY ((uint8_t)0x02)
- #define QSPI_NOT_SUPPORTED ((uint8_t)0x04)
- #define QSPI_SUSPENDED ((uint8_t)0x08)
Kolejnym elementem są zdefiniowane rozmiary pamięci oraz maksymalne czasy dla poszczególnych sektorów:
- /* Defines declarations for external flash on board STM32F7-Disco */
- #define N25Q128A_FLASH_SIZE 0x1000000 /* 128 MBits => 16MBytes => 16,777,216 bytes */
- #define N25Q128A_SECTOR_SIZE 0x10000 /* 256 sectors of 64KBytes */
- #define N25Q128A_SUBSECTOR_SIZE 0x1000 /* 4096 subsectors of 4kBytes */
- #define N25Q128A_PAGE_SIZE 0x100 /* 65536 pages of 256 bytes */
- #define N25Q128A_SUBSECTOR_CNT 4096 /* 4096 subsectors */
- #define N25Q128A_DUMMY_CYCLES_READ_QUAD 10
- #define N25Q128A_BULK_ERASE_MAX_TIME 250000
- #define N25Q128A_SECTOR_ERASE_MAX_TIME 3000
- #define N25Q128A_SUBSECTOR_ERASE_MAX_TIME 800
Definicje operacji, rejestrów i komend:
- /* Flag Status Register */
- #define N25Q128A_FSR_PRERR ((uint8_t)0x02) /*!< Protection error */
- #define N25Q128A_FSR_PGSUS ((uint8_t)0x04) /*!< Program operation suspended */
- #define N25Q128A_FSR_VPPERR ((uint8_t)0x08) /*!< Invalid voltage during program or erase */
- #define N25Q128A_FSR_PGERR ((uint8_t)0x10) /*!< Program error */
- #define N25Q128A_FSR_ERERR ((uint8_t)0x20) /*!< Erase error */
- #define N25Q128A_FSR_ERSUS ((uint8_t)0x40) /*!< Erase operation suspended */
- #define N25Q128A_FSR_READY ((uint8_t)0x80) /*!< Ready or command in progress */
- /* Registers */
- #define N25Q128A_SR_WIP ((uint8_t)0x01)
- #define N25Q128A_VCR_NB_DUMMY ((uint8_t)0xF0)
- #define N25Q128A_SR_WREN ((uint8_t)0x02)
- /* Register Operations */
- #define READ_FLAG_STATUS_REG_CMD 0x70
- #define WRITE_STATUS_REG_CMD 0x01
- /* Commands */
- /* Reset operations */
- #define RESET_ENABLE_CMD 0x66
- #define RESET_MEMORY_CMD 0x99
- /* Identification Operations */
- #define READ_ID_CMD 0x9E
- #define READ_ID_CMD2 0x9F
- #define MULTIPLE_IO_READ_ID_CMD 0xAF
- #define READ_SERIAL_FLASH_DISCO_PARAM_CMD 0x5A
- /* Read Operations */
- #define READ_CMD 0x03
- #define FAST_READ_CMD 0x0B
- #define DUAL_OUT_FAST_READ_CMD 0x3B
- #define DUAL_INOUT_FAST_READ_CMD 0xBB
- #define QUAD_OUT_FAST_READ_CMD 0x6B
- #define QUAD_INOUT_FAST_READ_CMD 0xEB
- #define READ_VOL_CFG_REG_CMD 0x85
- #define WRITE_VOL_CFG_REG_CMD 0x81
- #define READ_STATUS_REG_CMD 0x05
- #define WRITE_STATUS_REG_CMD 0x01
- #define WRITE_ENABLE_CMD 0x06
- #define WRITE_DISABLE_CMD 0x04
- #define SUBSECTOR_ERASE_CMD 0x20
- #define SECTOR_ERASE_CMD 0xD8
- #define BULK_ERASE_CMD 0xC7
- #define EXT_QUAD_IN_FAST_PROG_CMD 0x12
W pliku nagłówkowym zdefiniowałem także funkcje globalne, które będą dostępne dla całego projektu:
- uint8_t __attribute__((unused)) QSPI_Flash_Init(void);
- uint8_t QSPI_Flash_Erase_Complete(void);
- uint8_t QSPI_Flash_Erase_SubSector(uint32_t subsector_nr);
- uint8_t QSPI_Flash_Read_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* data_buf);
- uint8_t QSPI_Flash_Write_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* data_buf);
- uint8_t QSPI_Flash_Write_SubSector(uint32_t subsectorNumber, uint8_t* data_buf, uint32_t size);
- uint8_t QSPI_Flash_Write_Page(uint32_t startAddr, uint8_t* data_buf);
- uint8_t QSPI_Flash_Read_Unique_ID(uint8_t *dataBuffer);
- uint8_t QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi); /* Used after initialization in main function */
- uint8_t QSPI_DummyCyclesCfg(QSPI_HandleTypeDef *hqspi); /* Used after initialization in main function */
QSPI_Flash_Init zdefiniowany w tym pliku jest prze zemnie nie wykorzystywany ponieważ, wykorzystuje procedurę inicjalizacyjną wygenerowaną przez program CubeMx.
Funkcja włączająca QSPI, która została wygenerowana z programu CubeMx, wygląda następująco:
- /* QUADSPI init function */
- static void MX_QUADSPI_Init(void)
- {
- hqspi.Instance = QUADSPI;
- hqspi.Init.ClockPrescaler = 2;
- hqspi.Init.FifoThreshold = 4;
- hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
- hqspi.Init.FlashSize = 23;
- hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
- hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
- hqspi.Init.FlashID = QSPI_FLASH_ID_1;
- hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
- if (HAL_QSPI_Init(&hqspi) != HAL_OK)
- {
- Error_Handler();
- }
- }
Funkcja przygotowana w bibliotece prezentuje się następująco:
- uint8_t QSPI_Flash_Init(void)
- {
- hqspi.Instance = QUADSPI;
- HAL_QSPI_DeInit(&hqspi);
- Statiic_QSPI_MspInit(&hqspi);
- hqspi.Init.ClockPrescaler = 2;
- hqspi.Init.FifoThreshold = 4;
- hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
- hqspi.Init.FlashSize = POSITION_VAL(N25Q128A_FLASH_SIZE) - 1;
- hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
- hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
- hqspi.Init.FlashID = QSPI_FLASH_ID_1;
- hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
- HAL_QSPI_Init(&hqspi);
- /* QSPI reset */
- if(QSPI_ResetMemory(&hqspi) != QSPI_OK){
- return QSPI_NOT_SUPPORTED;
- }
- /* Wait before config */
- if(QSPI_DummyCyclesCfg(&hqspi) != QSPI_OK){
- return QSPI_NOT_SUPPORTED;
- }
- return QSPI_OK;
- }
Uruchomienie pinów w bibliotece, bez użycia funkcji wygenerowanej z Cuba wygląda następująco:
- __weak void Statiic_QSPI_MspInit(QSPI_HandleTypeDef *hqspi, void *Params)
- {
- GPIO_InitTypeDef gpio_init_structure;
- //QSPI enable Clock
- __HAL_RCC_QSPI_CLK_ENABLE();
- //QSPI Reset
- __HAL_RCC_QSPI_FORCE_RESET();
- __HAL_RCC_QSPI_RELEASE_RESET();
- //Enable Clocks
- PORT_CLOCK_ENABLE(QSPI_CS_GPIO_PORT);
- PORT_CLOCK_ENABLE(QSPI_CLK_GPIO_PORT);
- PORT_CLOCK_ENABLE(QSPI_D0_GPIO_PORT);
- PORT_CLOCK_ENABLE(QSPI_D1_GPIO_PORT);
- PORT_CLOCK_ENABLE(QSPI_D2_GPIO_PORT);
- PORT_CLOCK_ENABLE(QSPI_D3_GPIO_PORT);
- // QSPI CS GPIO pin configuration
- gpio_init_structure.Pin = QSPI_CS_PIN;
- gpio_init_structure.Mode = GPIO_MODE_AF_PP;
- gpio_init_structure.Pull = GPIO_PULLUP;
- gpio_init_structure.Speed = GPIO_SPEED_HIGH;
- gpio_init_structure.Alternate = GPIO_AF10_QUADSPI;
- HAL_GPIO_Init(QSPI_CS_GPIO_PORT, &gpio_init_structure);
- // QSPI CLK GPIO pin configuration
- gpio_init_structure.Pin = QSPI_CLK_PIN;
- gpio_init_structure.Pull = GPIO_NOPULL;
- gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
- HAL_GPIO_Init(QSPI_CLK_GPIO_PORT, &gpio_init_structure);
- // QSPI D0 GPIO pin configuration
- gpio_init_structure.Pin = QSPI_D0_PIN;
- gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
- HAL_GPIO_Init(QSPI_D0_GPIO_PORT, &gpio_init_structure);
- // QSPI D1 GPIO pin configuration
- gpio_init_structure.Pin = QSPI_D1_PIN;
- gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
- HAL_GPIO_Init(QSPI_D1_GPIO_PORT, &gpio_init_structure);
- // QSPI D2 GPIO pin configuration
- gpio_init_structure.Pin = QSPI_D2_PIN;
- gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
- HAL_GPIO_Init(QSPI_D2_GPIO_PORT, &gpio_init_structure);
- // QSPI D3 GPIO pin configuration
- gpio_init_structure.Pin = QSPI_D3_PIN;
- gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
- HAL_GPIO_Init(QSPI_D3_GPIO_PORT, &gpio_init_structure);
- // NVIC configuration for QSPI interrupt
- HAL_NVIC_SetPriority(QUADSPI_IRQn, 0x0F, 0);
- HAL_NVIC_EnableIRQ(QUADSPI_IRQn);
- }
Po włączeniu należy jeszcze pamięć zresetować i poczekać na zakończenie operacji:
- if(P_QSPI_ResetMemory(&hqspi) != QSPI_OK) { /* Error QSPI_NOT_SUPPORTED */ }
- if(P_QSPI_DummyCyclesCfg(&hqspi) != QSPI_OK) { /* Error QSPI_NOT_SUPPORTED */ }
Funkcja odczytująca dane dotyczące id pamięci wygląda następująco:
- uint8_t QSPI_Flash_Read_Unique_ID(uint8_t* data_buf)
- {
- #if DEBUG_USART == 1
- char tmpUsartBuffer[40] = {0};
- #endif
- QSPI_CommandTypeDef command;
- command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
- command.Instruction = READ_ID_CMD;
- command.AddressMode = QSPI_ADDRESS_1_LINE;
- command.AddressSize = QSPI_ADDRESS_24_BITS;
- command.Address = 0x000000;
- command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
- command.DataMode = QSPI_DATA_1_LINE;
- command.DummyCycles = 0;
- command.NbData = 2;
- command.DdrMode = QSPI_DDR_MODE_DISABLE;
- command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
- command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
- if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- #if DEBUG_USART == 1
- HAL_UART_Transmit(&huart1, (uint8_t*)"ERROR1\r\n",8,0x1000);
- #endif
- return QSPI_ERROR;
- }
- if (HAL_QSPI_Receive(&hqspi, data_buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- #if DEBUG_USART == 1
- HAL_UART_Transmit(&huart1, (uint8_t*)"ERROR2\r\n",8,0x1000);
- #endif
- return QSPI_ERROR;
- }
- #if DEBUG_USART == 1
- sprintf(tmpUsartBuffer, "Unique ID [HEX]\r\n");
- HAL_UART_Transmit(&huart1, (uint8_t*)tmpUsartBuffer,strlen(tmpUsartBuffer),0x1000);
- for(uint8_t loop = 0; loop<sizeof(data_buf); loop++)
- {
- sprintf(tmpUsartBuffer, (uint8_t*)"[%u] 0x%2X\r\n", loop, (uint8_t)data_buf[loop]);
- HAL_UART_Transmit(&huart1, (uint8_t*)tmpUsartBuffer,strlen(tmpUsartBuffer),0x1000);
- }
- #endif
- return QSPI_OK;
- }
Dane są odbierane od czytnika i wypisywane na ekranie przez USART1.
Pamięć może być czyszczona przez wymazanie całego sektora, pod sektora lub całości. Nie ma możliwości czyszczenia pojedynczej strony z danymi. Aby bez problemów można było zapisać w trakcie pracy nowe dane do układu, to należy wymazać cały sektor danych.
Wymazywanie całej pamięci:
- uint8_t QSPI_Flash_Erase_Complete(void)
- {
- QSPI_CommandTypeDef command;
- // Initialize the erase command
- command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
- command.Instruction = BULK_ERASE_CMD;
- command.AddressMode = QSPI_ADDRESS_NONE;
- command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
- command.DataMode = QSPI_DATA_NONE;
- command.DummyCycles = 0;
- command.DdrMode = QSPI_DDR_MODE_DISABLE;
- command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
- command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
- // Enable write operations
- if(Static_QSPI_WriteEnable(&hqspi) != QSPI_OK){
- return QSPI_ERROR;
- }
- // Send the command
- if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- // Configure automatic polling mode to wait for end of erase
- if(Static_QSPI_AutoPollingMemReady(&hqspi, N25Q128A_BULK_ERASE_MAX_TIME) != QSPI_OK){
- return QSPI_ERROR;
- }
- return QSPI_OK;
- }
- uint8_t QSPI_Flash_Erase_Sector(uint32_t sectorNumber)
- {
- QSPI_CommandTypeDef command;
- if(sectorNumber>=N25Q128A_SECTOR_CNT) {
- return QSPI_ERROR;
- }
- // Initialize the erase command
- command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
- command.Instruction = SECTOR_ERASE_CMD;
- command.AddressMode = QSPI_ADDRESS_1_LINE;
- command.AddressSize = QSPI_ADDRESS_24_BITS;
- command.Address = sectorNumber;
- command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
- command.DataMode = QSPI_DATA_NONE;
- command.DummyCycles = 0;
- command.DdrMode = QSPI_DDR_MODE_DISABLE;
- command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
- command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
- // Enable write operations
- if(Static_QSPI_WriteEnable(&hqspi) != QSPI_OK){
- return QSPI_ERROR;
- }
- // Send the command
- if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- // Configure automatic polling mode to wait for end of erase
- if(Static_QSPI_AutoPollingMemReady(&hqspi, N25Q128A_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK){
- return QSPI_ERROR;
- }
- return QSPI_OK;
- }
Czyszczenie pod-sektora:
- uint8_t QSPI_Flash_Erase_SubSector(uint32_t subsectorNumber)
- {
- uint32_t subsectorAdress = 0;
- QSPI_CommandTypeDef command;
- if(subsectorNumber>=N25Q128A_SUBSECTOR_CNT) {
- return QSPI_ERROR;
- }
- subsectorAdress = subsectorNumber * N25Q128A_SUBSECTOR_SIZE;
- // Initialize the erase command
- command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
- command.Instruction = SUBSECTOR_ERASE_CMD;
- command.AddressMode = QSPI_ADDRESS_1_LINE;
- command.AddressSize = QSPI_ADDRESS_24_BITS;
- command.Address = subsectorAdress;
- command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
- command.DataMode = QSPI_DATA_NONE;
- command.DummyCycles = 0;
- command.DdrMode = QSPI_DDR_MODE_DISABLE;
- command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
- command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
- // Enable write operations
- if(Static_QSPI_WriteEnable(&hqspi) != QSPI_OK){
- return QSPI_ERROR;
- }
- // Send the command
- if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- // Configure automatic polling mode to wait for end of erase
- if(Static_QSPI_AutoPollingMemReady(&hqspi, N25Q128A_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK){
- return QSPI_ERROR;
- }
- return QSPI_OK;
- }
Aby odczytać dane należy posłużyć się następującą funkcją:
- uint8_t QSPI_Flash_Read_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* data_buf)
- {
- QSPI_CommandTypeDef command;
- if(startAddr>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
- if((startAddr+size)>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
- if(size==0) { return QSPI_ERROR; }
- // Initialize the read command
- command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
- command.Instruction = QUAD_INOUT_FAST_READ_CMD;
- command.AddressMode = QSPI_ADDRESS_4_LINES;
- command.AddressSize = QSPI_ADDRESS_24_BITS;
- command.Address = startAddr;
- command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
- command.DataMode = QSPI_DATA_4_LINES;
- command.DummyCycles = N25Q128A_DUMMY_CYCLES_READ_QUAD;
- command.NbData = size;
- command.DdrMode = QSPI_DDR_MODE_DISABLE;
- command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
- command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
- // Configure the command
- if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- // Reception of the data
- if(HAL_QSPI_Receive(&hqspi, data_buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- return QSPI_OK;
- }
Aby wykonać zapis danych należy do funkcji podać adres startowy, ilość danych oraz wskaźnik do nich. Należy pamiętać przy tym, że aby wykonać zapis to należy zezwolić poprzez ustawienie bitu WriteEnable. Następnie należy wyczyścić miejsce do którego będzie wykonywany zapis, po tym możliwe jest już prowadzenie danych do układu. Należy pamiętać, że najmniejsza możliwa ilość danych do zapisu to 256 bajtów (0x100) czyli jedna strona.
Funkcja zapisująca dane:
- uint8_t QSPI_Flash_Write_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* dataBuffer)
- {
- QSPI_CommandTypeDef command;
- uint32_t end_addr = 0;
- uint32_t current_size = 0;
- uint32_t current_addr = 0;
- if(startAddr>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
- if((startAddr+size)>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
- if(size==0) { return QSPI_ERROR; }
- current_addr = 0;
- while (current_addr <= startAddr) { current_addr += N25Q128A_PAGE_SIZE; }
- current_size = current_addr - startAddr;
- // set variables
- if(current_size > size) { current_size = size; }
- current_addr = startAddr;
- end_addr = startAddr + size;
- /* Initialize the program command */
- command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
- command.Instruction = EXT_QUAD_IN_FAST_PROG_CMD;
- command.AddressMode = QSPI_ADDRESS_4_LINES;
- command.AddressSize = QSPI_ADDRESS_24_BITS;
- command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
- command.DataMode = QSPI_DATA_4_LINES;
- command.DummyCycles = 0;
- command.DdrMode = QSPI_DDR_MODE_DISABLE;
- command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
- command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
- /* Write all pages of data */
- do{
- command.Address = current_addr;
- command.NbData = current_size;
- /* Enable write mode */
- if(Static_QSPI_WriteEnable(&hqspi) != QSPI_OK){
- return QSPI_ERROR;
- }
- /* Configure the command */
- if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- /* Transmission of the data */
- if(HAL_QSPI_Transmit(&hqspi, dataBuffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- /* Configure automatic polling mode to wait for end of program */
- if(Static_QSPI_AutoPollingMemReady(&hqspi, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK){
- return QSPI_ERROR;
- }
- /* Update the address and size variables for next page programming */
- current_addr += current_size;
- dataBuffer += current_size;
- current_size = ((current_addr + N25Q128A_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : N25Q128A_PAGE_SIZE;
- } while (current_addr < end_addr);
- return QSPI_OK;
- }
Za pomocą powyższej funkcji każda ilość danych może zostać zapisana do pamięci. Wykorzystałem ją w kolejnych funkcjach pozwalających na zapis większej ilości danych wraz z kasowaniem oraz sprawdzaniem.
Funkcja zapisująca cały pod sektor podanymi danymi:
- uint8_t QSPI_Flash_Write_SubSector(uint32_t subsectorNumber, uint8_t* dataBuffer)
- {
- char usartBuffer[40] = {0};
- uint32_t start_addr = 0;
- uint8_t readDataSubsector[N25Q128A_SUBSECTOR_SIZE] = {0};
- uint8_t dataRead2[1] = {0};
- /* Clear selected subsector */
- if(QSPI_Flash_Erase_SubSector(subsectorNumber) != QSPI_OK){
- sprintf(usartBuffer,"Error %lu", subsectorNumber);
- HAL_UART_Transmit(&huart1, (uint8_t*)usartBuffer,strlen(usartBuffer),0x1000);
- return QSPI_ERROR;
- }
- /* Calculate start address */
- start_addr = N25Q128A_SUBSECTOR_SIZE * subsectorNumber;
- QSPI_Flash_Read_Block_Byte(start_addr, 0x01, dataRead2);
- sprintf(usartBuffer,"Adress %lu. 0x%X \r\n", subsectorNumber, dataRead2[0]);
- HAL_UART_Transmit(&huart1, (uint8_t*)usartBuffer,strlen(usartBuffer),0x1000);
- /* Write data into flash */
- if(QSPI_Flash_Write_Block_Byte(start_addr, N25Q128A_SUBSECTOR_SIZE, dataBuffer) != QSPI_OK){
- return QSPI_ERROR;
- }
- /* Read data from flash*/
- if(QSPI_Flash_Read_Block_Byte(start_addr, N25Q128A_SUBSECTOR_SIZE, readDataSubsector) != QSPI_OK){
- return QSPI_ERROR;
- }
- /* Check if data was correctly write into flash */
- if(memcmp(dataBuffer,readDataSubsector,N25Q128A_SUBSECTOR_SIZE) == 0 ){
- return QSPI_OK;
- }
- else{
- return QSPI_ERROR;
- }
- /* return operation status */
- return QSPI_OK;
- }
Kolejna funkcja zapisuje całą stronę z danymi w podanej lokalizacji. W związku z tym, że pamięć można wymazywać jedynie po całym pod-sektorze (0x1000), a każde wgranie pamięci musi być poprzedzone jej czyszczeniem, to należy stosować odpowiednie mechanizmy kontroli pozostałych danych:
- uint8_t QSPI_Flash_Write_Page(uint32_t startAddr, uint8_t* data_buf)
- {
- uint32_t subsector_number = 0;
- uint32_t addres_with_sub_data = 0;
- uint8_t copy_of_subsector_data[N25Q128A_SUBSECTOR_SIZE] = {0};
- uint8_t tmp_buffer_with_data[N25Q128A_SUBSECTOR_SIZE] = {0};
- /* calculate subsector number */
- subsector_number = startAddr/N25Q128A_SUBSECTOR_SIZE;
- addres_with_sub_data = N25Q128A_SUBSECTOR_SIZE * subsector_number;
- /* Read data from flash */
- if(QSPI_Flash_Read_Block_Byte(addres_with_sub_data, N25Q128A_SUBSECTOR_SIZE, copy_of_subsector_data) != QSPI_OK){
- return QSPI_ERROR;
- }
- uint32_t addres = (startAddr - addres_with_sub_data);
- /* Erase subsector */
- if(QSPI_Flash_Erase_SubSector(subsector_number) != QSPI_OK){ return QSPI_ERROR; }
- /* write data to appropriate memory place */
- for(uint32_t loop = 0; loop < N25Q128A_PAGE_SIZE; loop++)
- {
- copy_of_subsector_data[addres + loop] = data_buf[loop];
- }
- /* Write data */
- if(QSPI_Flash_Write_Block_Byte(addres_with_sub_data, N25Q128A_SUBSECTOR_SIZE, copy_of_subsector_data) != QSPI_OK){
- return QSPI_ERROR;
- }
- /* Check if data are correct: Read data again then check */
- if(QSPI_Flash_Read_Block_Byte(addres_with_sub_data, N25Q128A_SUBSECTOR_SIZE, tmp_buffer_with_data) != QSPI_OK){
- return QSPI_ERROR;
- }
- if(memcmp(copy_of_subsector_data,tmp_buffer_with_data,N25Q128A_SUBSECTOR_SIZE) != 0 ){ return QSPI_ERROR; }
- /* return status */
- return QSPI_OK;
- }
W tym przypadku kopiowany jest cały pod-sektor danych, następnie w wybrane miejsce dane są dopisywane po czym następuje wpisanie danych do pamięci. Po takiej operacji dane są odczytywane z pamięci do bufora tymczasowego i następuję sprawdzenie czy zostały one poprawnie wprowadzone do flasha.
Funkcja odczytująca dane prezentuje się następująco:
- uint8_t QSPI_Flash_Read_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* dataBuffer)
- {
- QSPI_CommandTypeDef command;
- if(startAddr>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
- if((startAddr+size)>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
- if(size==0) { return QSPI_ERROR; }
- // Initialize the read command
- command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
- command.Instruction = QUAD_INOUT_FAST_READ_CMD;
- command.AddressMode = QSPI_ADDRESS_4_LINES;
- command.AddressSize = QSPI_ADDRESS_24_BITS;
- command.Address = startAddr;
- command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
- command.DataMode = QSPI_DATA_4_LINES;
- command.DummyCycles = N25Q128A_DUMMY_CYCLES_READ_QUAD;
- command.NbData = size;
- command.DdrMode = QSPI_DDR_MODE_DISABLE;
- command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
- command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
- // Configure the command
- if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- // Reception of the data
- if(HAL_QSPI_Receive(&hqspi, dataBuffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
- return QSPI_ERROR;
- }
- return QSPI_OK;
- }
Podobnie jak podstawowa funkcja zapisu ona odczyta podaną ilość danych i zapisze ją w buforze.
Wyłączenie QSPI wygląda następująco:
- __weak void BSP_QSPI_MspDeInit(QSPI_HandleTypeDef *hqspi)
- {
- /* Disable the NVIC for QSPI */
- HAL_NVIC_DisableIRQ(QUADSPI_IRQn);
- /* Disable peripherals and GPIO Clocks */
- HAL_GPIO_DeInit(QSPI_CS_GPIO_PORT, QSPI_CS_PIN);
- HAL_GPIO_DeInit(QSPI_CLK_GPIO_PORT, QSPI_CLK_PIN);
- HAL_GPIO_DeInit(QSPI_D0_GPIO_PORT, QSPI_D0_PIN);
- HAL_GPIO_DeInit(QSPI_D1_GPIO_PORT, QSPI_D1_PIN);
- HAL_GPIO_DeInit(QSPI_D2_GPIO_PORT, QSPI_D2_PIN);
- HAL_GPIO_DeInit(QSPI_D3_GPIO_PORT, QSPI_D3_PIN);
- /* Reset peripherals */
- __HAL_RCC_QSPI_FORCE_RESET();
- __HAL_RCC_QSPI_RELEASE_RESET();
- /* Disable the QuadSPI memory interface clock */
- __HAL_RCC_QSPI_CLK_DISABLE();
- }
W funkcji wyłączane są przerwania, poszczególne piny są zwalniane po czym QSPI jest resetowany i następuje wyłączenie zegarów.
Jeśli pamięć jest czyszczona za pomocą zdefiniowanych komend to dane znajdujące się w komórkach mają wartość 0xFF.
Pozostałe funkcje zdefiniowane w bibliotece są funkcjami statycznymi (poza QSPI_DummyCyclesCfg oraz QSPI_ResetMemory), służą one do ustawienia parametrów pracy, resetu bądź czekania na odpowiedź.
Przykładowa pętla główna programu:
- int main(void)
- {
- /* USER CODE BEGIN 1 */
- char usartBuffer[50] = {0};
- uint8_t dataWrite4[0x100] = {0x34};
- uint8_t dataRead4[0x100] = {0x00};
- /* USER CODE END 1 */
- /* MPU Configuration----------------------------------------------------------*/
- MPU_Config();
- /* Enable I-Cache-------------------------------------------------------------*/
- SCB_EnableICache();
- /* Enable D-Cache-------------------------------------------------------------*/
- SCB_EnableDCache();
- /* MCU Configuration----------------------------------------------------------*/
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
- HAL_Init();
- /* Configure the system clock */
- SystemClock_Config();
- /* Initialize all configured peripherals */
- MX_GPIO_Init();
- MX_RTC_Init();
- MX_USART1_UART_Init();
- //MX_QUADSPI_Init();
- //if(QSPI_ResetMemory(&hqspi) != QSPI_OK) { return QSPI_NOT_SUPPORTED; }
- // if(QSPI_DummyCyclesCfg(&hqspi) != QSPI_OK) { return QSPI_NOT_SUPPORTED; }
- //WRITE_REG(QUADSPI->LPTR, 0xFFF);
- /* USER CODE BEGIN 2 */
- GPIO_LED_INIT();
- /* Initial quadSpi */
- QSPI_Flash_Init();
- WRITE_REG(QUADSPI->LPTR, 0xFFF);
- for(uint32_t i = 0; i<6; i++)
- {
- QSPI_Flash_Erase_SubSector(i);
- }
- for(uint32_t i = 0; i<40; i++)
- {
- QSPI_Flash_Write_Page(i*0x1000, dataWrite4);
- QSPI_Flash_Read_Block_Byte(i*0x1000, 0x100, dataRead4);
- if(memcmp(dataWrite4,dataRead4,0x100) == 0 ){
- sprintf(usartBuffer,"OK OK OK OK\r\n");
- HAL_UART_Transmit(&huart1, (uint8_t*)usartBuffer,strlen(usartBuffer),0x1000);
- }
- else{
- sprintf(usartBuffer,"Error Error Error!!!\r\n");
- HAL_UART_Transmit(&huart1, (uint8_t*)usartBuffer,strlen(usartBuffer),0x1000);
- }
- }
- /* USER CODE END 2 */
- /* Infinite loop */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
- /* USER CODE BEGIN 3 */
- LED_HIGH(LED_PORT, LED_PIN);
- HAL_Delay(1000);
- LED_LOW(LED_PORT, LED_PIN);
- HAL_Delay(1000);
- }
- /* USER CODE END 3 */
- }