poniedziałek, 3 lipca 2017

[15] STM32F7 - Discovery - QSPI, zewnętrzna pamięć Flash

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]:



Pamięć do STM32 jest podłączona w następujący sposób:

  • Pin PB2 - QSPI_CLK (Pin 6 C)
  • Pin PB6 - QSPI_CS (Pin 1 S#)
  • Pin PD11 - QSPI_IO_0(Pin 5 DQ0)
  • Pin PD12 - QSPI_IO_1(Pin 2 DQ1)
  • Pin PE2 - QSPI_IO_2(Pin 3 DQ2)
  • Pin PD13 - QSPI_IO_3(Pin 4 DQ3)


Cube Mx:


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:

  1. #define QSPI_CS_PIN                GPIO_PIN_6
  2. #define QSPI_CS_GPIO_PORT          GPIOB
  3. #define QSPI_CLK_PIN               GPIO_PIN_2
  4. #define QSPI_CLK_GPIO_PORT         GPIOB
  5. #define QSPI_D0_PIN                GPIO_PIN_11
  6. #define QSPI_D0_GPIO_PORT          GPIOD
  7. #define QSPI_D1_PIN                GPIO_PIN_12
  8. #define QSPI_D1_GPIO_PORT          GPIOD
  9. #define QSPI_D2_PIN                GPIO_PIN_2
  10. #define QSPI_D2_GPIO_PORT          GPIOE
  11. #define QSPI_D3_PIN                GPIO_PIN_13
  12. #define QSPI_D3_GPIO_PORT          GPIOD

Następnie zdefiniowałem statusy dla przeprowadzonych operacji podczas korzystania z QSPI:

  1. /* QSPI operation status */
  2. #define QSPI_OK            ((uint8_t)0x00)
  3. #define QSPI_ERROR         ((uint8_t)0x01)
  4. #define QSPI_BUSY          ((uint8_t)0x02)
  5. #define QSPI_NOT_SUPPORTED ((uint8_t)0x04)
  6. #define QSPI_SUSPENDED     ((uint8_t)0x08)

Kolejnym elementem są zdefiniowane rozmiary pamięci oraz maksymalne czasy dla poszczególnych sektorów:

  1. /* Defines declarations for external flash on board STM32F7-Disco */
  2. #define N25Q128A_FLASH_SIZE                  0x1000000 /* 128 MBits => 16MBytes => 16,777,216 bytes */
  3. #define N25Q128A_SECTOR_SIZE                 0x10000   /* 256 sectors of 64KBytes       */
  4. #define N25Q128A_SUBSECTOR_SIZE              0x1000    /* 4096 subsectors of 4kBytes    */
  5. #define N25Q128A_PAGE_SIZE                   0x100     /* 65536 pages of 256 bytes      */
  6. #define N25Q128A_SUBSECTOR_CNT               4096      /* 4096 subsectors               */
  7. #define N25Q128A_DUMMY_CYCLES_READ_QUAD      10
  8. #define N25Q128A_BULK_ERASE_MAX_TIME         250000
  9. #define N25Q128A_SECTOR_ERASE_MAX_TIME       3000
  10. #define N25Q128A_SUBSECTOR_ERASE_MAX_TIME    800

Definicje operacji, rejestrów i komend:

  1. /* Flag Status Register */
  2. #define N25Q128A_FSR_PRERR                   ((uint8_t)0x02)    /*!< Protection error */
  3. #define N25Q128A_FSR_PGSUS                   ((uint8_t)0x04)    /*!< Program operation suspended */
  4. #define N25Q128A_FSR_VPPERR                  ((uint8_t)0x08)    /*!< Invalid voltage during program or erase */
  5. #define N25Q128A_FSR_PGERR                   ((uint8_t)0x10)    /*!< Program error */
  6. #define N25Q128A_FSR_ERERR                   ((uint8_t)0x20)    /*!< Erase error */
  7. #define N25Q128A_FSR_ERSUS                   ((uint8_t)0x40)    /*!< Erase operation suspended */
  8. #define N25Q128A_FSR_READY                   ((uint8_t)0x80)    /*!< Ready or command in progress */
  9. /* Registers */
  10. #define N25Q128A_SR_WIP                      ((uint8_t)0x01)
  11. #define N25Q128A_VCR_NB_DUMMY                ((uint8_t)0xF0)
  12. #define N25Q128A_SR_WREN                     ((uint8_t)0x02)
  13. /* Register Operations */
  14. #define READ_FLAG_STATUS_REG_CMD             0x70
  15. #define WRITE_STATUS_REG_CMD                 0x01
  16. /* Commands */
  17. /* Reset operations */
  18. #define RESET_ENABLE_CMD                     0x66
  19. #define RESET_MEMORY_CMD                     0x99
  20. /* Identification Operations */
  21. #define READ_ID_CMD                          0x9E
  22. #define READ_ID_CMD2                         0x9F
  23. #define MULTIPLE_IO_READ_ID_CMD              0xAF
  24. #define READ_SERIAL_FLASH_DISCO_PARAM_CMD    0x5A
  25. /* Read Operations */
  26. #define READ_CMD                             0x03
  27. #define FAST_READ_CMD                        0x0B
  28. #define DUAL_OUT_FAST_READ_CMD               0x3B
  29. #define DUAL_INOUT_FAST_READ_CMD             0xBB
  30. #define QUAD_OUT_FAST_READ_CMD               0x6B
  31. #define QUAD_INOUT_FAST_READ_CMD             0xEB
  32. #define READ_VOL_CFG_REG_CMD                 0x85
  33. #define WRITE_VOL_CFG_REG_CMD                0x81
  34. #define READ_STATUS_REG_CMD                  0x05
  35. #define WRITE_STATUS_REG_CMD                 0x01
  36. #define WRITE_ENABLE_CMD                     0x06
  37. #define WRITE_DISABLE_CMD                    0x04
  38. #define SUBSECTOR_ERASE_CMD                  0x20
  39. #define SECTOR_ERASE_CMD                     0xD8
  40. #define BULK_ERASE_CMD                       0xC7
  41. #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:

  1. uint8_t  __attribute__((unused)) QSPI_Flash_Init(void);
  2. uint8_t QSPI_Flash_Erase_Complete(void);
  3. uint8_t QSPI_Flash_Erase_SubSector(uint32_t subsector_nr);
  4. uint8_t QSPI_Flash_Read_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* data_buf);
  5. uint8_t QSPI_Flash_Write_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* data_buf);
  6. uint8_t QSPI_Flash_Write_SubSector(uint32_t subsectorNumber, uint8_t* data_buf, uint32_t size);
  7. uint8_t QSPI_Flash_Write_Page(uint32_t startAddr, uint8_t* data_buf);
  8. uint8_t QSPI_Flash_Read_Unique_ID(uint8_t *dataBuffer);
  9. uint8_t QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi);        /* Used after initialization in main function */
  10. 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:

  1. /* QUADSPI init function */
  2. static void MX_QUADSPI_Init(void)
  3. {
  4.   hqspi.Instance = QUADSPI;
  5.   hqspi.Init.ClockPrescaler = 2;
  6.   hqspi.Init.FifoThreshold = 4;
  7.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
  8.   hqspi.Init.FlashSize = 23;
  9.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
  10.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
  11.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;
  12.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
  13.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)
  14.   {
  15.     Error_Handler();
  16.   }
  17. }

Funkcja przygotowana w bibliotece prezentuje się następująco:

  1. uint8_t  QSPI_Flash_Init(void)
  2. {
  3.     hqspi.Instance = QUADSPI;
  4.     HAL_QSPI_DeInit(&hqspi);
  5.     Statiic_QSPI_MspInit(&hqspi);
  6.     hqspi.Init.ClockPrescaler     = 2;
  7.     hqspi.Init.FifoThreshold      = 4;
  8.     hqspi.Init.SampleShifting     = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
  9.     hqspi.Init.FlashSize          = POSITION_VAL(N25Q128A_FLASH_SIZE) - 1;
  10.     hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
  11.     hqspi.Init.ClockMode          = QSPI_CLOCK_MODE_0;
  12.     hqspi.Init.FlashID            = QSPI_FLASH_ID_1;
  13.     hqspi.Init.DualFlash          = QSPI_DUALFLASH_DISABLE;
  14.     HAL_QSPI_Init(&hqspi);
  15.     /* QSPI reset */
  16.     if(QSPI_ResetMemory(&hqspi) != QSPI_OK){
  17.         return QSPI_NOT_SUPPORTED;
  18.     }
  19.     /* Wait before config */
  20.     if(QSPI_DummyCyclesCfg(&hqspi) != QSPI_OK){
  21.         return QSPI_NOT_SUPPORTED;
  22.     }
  23.     return QSPI_OK;
  24. }

Uruchomienie pinów w bibliotece, bez użycia funkcji wygenerowanej z Cuba wygląda następująco:

  1. __weak void Statiic_QSPI_MspInit(QSPI_HandleTypeDef *hqspi, void *Params)
  2. {
  3.   GPIO_InitTypeDef gpio_init_structure;
  4.   //QSPI enable Clock
  5.   __HAL_RCC_QSPI_CLK_ENABLE();
  6.   //QSPI Reset
  7.   __HAL_RCC_QSPI_FORCE_RESET();
  8.   __HAL_RCC_QSPI_RELEASE_RESET();
  9.   //Enable Clocks
  10.   PORT_CLOCK_ENABLE(QSPI_CS_GPIO_PORT);
  11.   PORT_CLOCK_ENABLE(QSPI_CLK_GPIO_PORT);
  12.   PORT_CLOCK_ENABLE(QSPI_D0_GPIO_PORT);
  13.   PORT_CLOCK_ENABLE(QSPI_D1_GPIO_PORT);
  14.   PORT_CLOCK_ENABLE(QSPI_D2_GPIO_PORT);
  15.   PORT_CLOCK_ENABLE(QSPI_D3_GPIO_PORT);
  16.   // QSPI CS GPIO pin configuration
  17.   gpio_init_structure.Pin       = QSPI_CS_PIN;
  18.   gpio_init_structure.Mode      = GPIO_MODE_AF_PP;
  19.   gpio_init_structure.Pull      = GPIO_PULLUP;
  20.   gpio_init_structure.Speed     = GPIO_SPEED_HIGH;
  21.   gpio_init_structure.Alternate = GPIO_AF10_QUADSPI;
  22.   HAL_GPIO_Init(QSPI_CS_GPIO_PORT, &gpio_init_structure);
  23.   // QSPI CLK GPIO pin configuration
  24.   gpio_init_structure.Pin       = QSPI_CLK_PIN;
  25.   gpio_init_structure.Pull      = GPIO_NOPULL;
  26.   gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
  27.   HAL_GPIO_Init(QSPI_CLK_GPIO_PORT, &gpio_init_structure);
  28.   // QSPI D0 GPIO pin configuration
  29.   gpio_init_structure.Pin       = QSPI_D0_PIN;
  30.   gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
  31.   HAL_GPIO_Init(QSPI_D0_GPIO_PORT, &gpio_init_structure);
  32.   // QSPI D1 GPIO pin configuration
  33.   gpio_init_structure.Pin       = QSPI_D1_PIN;
  34.   gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
  35.   HAL_GPIO_Init(QSPI_D1_GPIO_PORT, &gpio_init_structure);
  36.   // QSPI D2 GPIO pin configuration
  37.   gpio_init_structure.Pin       = QSPI_D2_PIN;
  38.   gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
  39.   HAL_GPIO_Init(QSPI_D2_GPIO_PORT, &gpio_init_structure);
  40.   // QSPI D3 GPIO pin configuration
  41.   gpio_init_structure.Pin       = QSPI_D3_PIN;
  42.   gpio_init_structure.Alternate = GPIO_AF9_QUADSPI;
  43.   HAL_GPIO_Init(QSPI_D3_GPIO_PORT, &gpio_init_structure);
  44.   // NVIC configuration for QSPI interrupt
  45.   HAL_NVIC_SetPriority(QUADSPI_IRQn, 0x0F, 0);
  46.   HAL_NVIC_EnableIRQ(QUADSPI_IRQn);
  47. }

Po włączeniu należy jeszcze pamięć zresetować i poczekać na zakończenie operacji:

  1.   if(P_QSPI_ResetMemory(&hqspi) != QSPI_OK)     { /* Error QSPI_NOT_SUPPORTED */    }
  2.   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:

  1. uint8_t QSPI_Flash_Read_Unique_ID(uint8_t* data_buf)
  2. {
  3.     #if DEBUG_USART == 1
  4.     char tmpUsartBuffer[40] = {0};
  5.     #endif
  6.    
  7.     QSPI_CommandTypeDef command;
  8.     command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  9.     command.Instruction       = READ_ID_CMD;
  10.     command.AddressMode       = QSPI_ADDRESS_1_LINE;
  11.     command.AddressSize       = QSPI_ADDRESS_24_BITS;
  12.     command.Address           = 0x000000;
  13.     command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  14.     command.DataMode          = QSPI_DATA_1_LINE;
  15.     command.DummyCycles       = 0;
  16.     command.NbData            = 2;
  17.     command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  18.     command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  19.     command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
  20.     if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  21.     #if DEBUG_USART == 1
  22.         HAL_UART_Transmit(&huart1, (uint8_t*)"ERROR1\r\n",8,0x1000);
  23.     #endif
  24.         return QSPI_ERROR;
  25.     }
  26.     if (HAL_QSPI_Receive(&hqspi, data_buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  27.     #if DEBUG_USART == 1
  28.         HAL_UART_Transmit(&huart1, (uint8_t*)"ERROR2\r\n",8,0x1000);
  29.     #endif
  30.         return QSPI_ERROR;
  31.     }
  32.     #if DEBUG_USART == 1
  33.    
  34.     sprintf(tmpUsartBuffer, "Unique ID [HEX]\r\n");
  35.     HAL_UART_Transmit(&huart1, (uint8_t*)tmpUsartBuffer,strlen(tmpUsartBuffer),0x1000);
  36.     for(uint8_t loop = 0; loop<sizeof(data_buf); loop++)
  37.     {
  38.         sprintf(tmpUsartBuffer, (uint8_t*)"[%u] 0x%2X\r\n", loop, (uint8_t)data_buf[loop]);
  39.         HAL_UART_Transmit(&huart1, (uint8_t*)tmpUsartBuffer,strlen(tmpUsartBuffer),0x1000);
  40.     }
  41.    
  42.     #endif
  43.     return QSPI_OK;
  44. }

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:

  1. uint8_t QSPI_Flash_Erase_Complete(void)
  2. {
  3.   QSPI_CommandTypeDef command;
  4.   // Initialize the erase command
  5.   command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  6.   command.Instruction       = BULK_ERASE_CMD;
  7.   command.AddressMode       = QSPI_ADDRESS_NONE;
  8.   command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  9.   command.DataMode          = QSPI_DATA_NONE;
  10.   command.DummyCycles       = 0;
  11.   command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  12.   command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  13.   command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
  14.   // Enable write operations
  15.   if(Static_QSPI_WriteEnable(&hqspi) != QSPI_OK){
  16.       return QSPI_ERROR;
  17.   }
  18.   // Send the command
  19.   if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  20.       return QSPI_ERROR;
  21.   }
  22.   // Configure automatic polling mode to wait for end of erase
  23.   if(Static_QSPI_AutoPollingMemReady(&hqspi, N25Q128A_BULK_ERASE_MAX_TIME) != QSPI_OK){
  24.       return QSPI_ERROR;
  25.   }
  26.   return QSPI_OK;
  27. }

Czyszczenie całego sektora:

  1. uint8_t QSPI_Flash_Erase_Sector(uint32_t sectorNumber)
  2. {
  3.   QSPI_CommandTypeDef command;
  4.   if(sectorNumber>=N25Q128A_SECTOR_CNT) {
  5.       return QSPI_ERROR;
  6.   }
  7.   // Initialize the erase command
  8.   command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  9.   command.Instruction       = SECTOR_ERASE_CMD;
  10.   command.AddressMode       = QSPI_ADDRESS_1_LINE;
  11.   command.AddressSize       = QSPI_ADDRESS_24_BITS;
  12.   command.Address           = sectorNumber;
  13.   command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  14.   command.DataMode          = QSPI_DATA_NONE;
  15.   command.DummyCycles       = 0;
  16.   command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  17.   command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  18.   command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
  19.   // Enable write operations
  20.   if(Static_QSPI_WriteEnable(&hqspi) != QSPI_OK){
  21.       return QSPI_ERROR;
  22.   }
  23.   // Send the command
  24.   if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  25.       return QSPI_ERROR;
  26.   }
  27.   // Configure automatic polling mode to wait for end of erase
  28.   if(Static_QSPI_AutoPollingMemReady(&hqspi, N25Q128A_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK){
  29.       return QSPI_ERROR;
  30.   }
  31.   return QSPI_OK;
  32. }

Czyszczenie pod-sektora:

  1. uint8_t QSPI_Flash_Erase_SubSector(uint32_t subsectorNumber)
  2. {
  3.   uint32_t subsectorAdress = 0;
  4.   QSPI_CommandTypeDef command;
  5.   if(subsectorNumber>=N25Q128A_SUBSECTOR_CNT) {
  6.       return QSPI_ERROR;
  7.   }
  8.   subsectorAdress = subsectorNumber * N25Q128A_SUBSECTOR_SIZE;
  9.   // Initialize the erase command
  10.   command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  11.   command.Instruction       = SUBSECTOR_ERASE_CMD;
  12.   command.AddressMode       = QSPI_ADDRESS_1_LINE;
  13.   command.AddressSize       = QSPI_ADDRESS_24_BITS;
  14.   command.Address           = subsectorAdress;
  15.   command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  16.   command.DataMode          = QSPI_DATA_NONE;
  17.   command.DummyCycles       = 0;
  18.   command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  19.   command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  20.   command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
  21.   // Enable write operations
  22.   if(Static_QSPI_WriteEnable(&hqspi) != QSPI_OK){
  23.       return QSPI_ERROR;
  24.   }
  25.   // Send the command
  26.   if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  27.       return QSPI_ERROR;
  28.   }
  29.   // Configure automatic polling mode to wait for end of erase
  30.   if(Static_QSPI_AutoPollingMemReady(&hqspi, N25Q128A_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK){
  31.       return QSPI_ERROR;
  32.   }
  33.   return QSPI_OK;
  34. }

Aby odczytać dane należy posłużyć się następującą funkcją:

  1. uint8_t QSPI_Flash_Read_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* data_buf)
  2. {
  3.   QSPI_CommandTypeDef command;
  4.   if(startAddr>=N25Q128A_FLASH_SIZE)        { return QSPI_ERROR; }
  5.   if((startAddr+size)>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
  6.   if(size==0)                               { return QSPI_ERROR; }
  7.   // Initialize the read command
  8.   command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  9.   command.Instruction       = QUAD_INOUT_FAST_READ_CMD;
  10.   command.AddressMode       = QSPI_ADDRESS_4_LINES;
  11.   command.AddressSize       = QSPI_ADDRESS_24_BITS;
  12.   command.Address           = startAddr;
  13.   command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  14.   command.DataMode          = QSPI_DATA_4_LINES;
  15.   command.DummyCycles       = N25Q128A_DUMMY_CYCLES_READ_QUAD;
  16.   command.NbData            = size;
  17.   command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  18.   command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  19.   command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
  20.   // Configure the command
  21.   if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  22.       return QSPI_ERROR;
  23.   }
  24.   // Reception of the data
  25.   if(HAL_QSPI_Receive(&hqspi, data_buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  26.       return QSPI_ERROR;
  27.   }
  28.   return QSPI_OK;
  29. }

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:

  1. uint8_t QSPI_Flash_Write_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* dataBuffer)
  2. {
  3.   QSPI_CommandTypeDef command;
  4.   uint32_t end_addr = 0;
  5.   uint32_t current_size = 0;
  6.   uint32_t current_addr = 0;
  7.   if(startAddr>=N25Q128A_FLASH_SIZE)        { return QSPI_ERROR; }
  8.   if((startAddr+size)>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
  9.   if(size==0)                               { return QSPI_ERROR; }
  10.   current_addr = 0;
  11.   while (current_addr <= startAddr)         {   current_addr += N25Q128A_PAGE_SIZE; }
  12.   current_size = current_addr - startAddr;
  13.   // set variables
  14.   if(current_size > size)   {   current_size = size;    }
  15.   current_addr = startAddr;
  16.   end_addr = startAddr + size;
  17.   /* Initialize the program command */
  18.   command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  19.   command.Instruction       = EXT_QUAD_IN_FAST_PROG_CMD;
  20.   command.AddressMode       = QSPI_ADDRESS_4_LINES;
  21.   command.AddressSize       = QSPI_ADDRESS_24_BITS;
  22.   command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  23.   command.DataMode          = QSPI_DATA_4_LINES;
  24.   command.DummyCycles       = 0;
  25.   command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  26.   command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  27.   command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
  28.   /* Write all pages of data */
  29.   do{
  30.       command.Address = current_addr;
  31.       command.NbData  = current_size;
  32.     /* Enable write mode */
  33.     if(Static_QSPI_WriteEnable(&hqspi) != QSPI_OK){
  34.         return QSPI_ERROR;
  35.     }
  36.    
  37.     /* Configure the command */
  38.     if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  39.         return QSPI_ERROR;
  40.     }
  41.    
  42.     /* Transmission of the data */
  43.     if(HAL_QSPI_Transmit(&hqspi, dataBuffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  44.         return QSPI_ERROR;
  45.     }
  46.    
  47.     /* Configure automatic polling mode to wait for end of program */
  48.     if(Static_QSPI_AutoPollingMemReady(&hqspi, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK){
  49.         return QSPI_ERROR;
  50.     }
  51.    
  52.     /* Update the address and size variables for next page programming */
  53.     current_addr += current_size;
  54.     dataBuffer += current_size;
  55.     current_size = ((current_addr + N25Q128A_PAGE_SIZE) > end_addr) ? (end_addr - current_addr) : N25Q128A_PAGE_SIZE;
  56.   } while (current_addr < end_addr);
  57.   return QSPI_OK;
  58. }

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:

  1. uint8_t QSPI_Flash_Write_SubSector(uint32_t subsectorNumber, uint8_t* dataBuffer)
  2. {
  3.     char usartBuffer[40] = {0};
  4.     uint32_t start_addr = 0;
  5.     uint8_t readDataSubsector[N25Q128A_SUBSECTOR_SIZE] = {0};
  6.     uint8_t dataRead2[1] = {0};
  7.     /* Clear selected subsector */
  8.     if(QSPI_Flash_Erase_SubSector(subsectorNumber) != QSPI_OK){
  9.         sprintf(usartBuffer,"Error %lu", subsectorNumber);
  10.         HAL_UART_Transmit(&huart1, (uint8_t*)usartBuffer,strlen(usartBuffer),0x1000);
  11.         return QSPI_ERROR;
  12.     }
  13.     /* Calculate start address */
  14.     start_addr = N25Q128A_SUBSECTOR_SIZE * subsectorNumber;
  15.     QSPI_Flash_Read_Block_Byte(start_addr, 0x01, dataRead2);
  16.     sprintf(usartBuffer,"Adress %lu. 0x%X \r\n", subsectorNumber, dataRead2[0]);
  17.     HAL_UART_Transmit(&huart1, (uint8_t*)usartBuffer,strlen(usartBuffer),0x1000);
  18.     /* Write data into flash */
  19.     if(QSPI_Flash_Write_Block_Byte(start_addr, N25Q128A_SUBSECTOR_SIZE, dataBuffer) != QSPI_OK){
  20.         return QSPI_ERROR;
  21.     }
  22.     /* Read data from flash*/
  23.     if(QSPI_Flash_Read_Block_Byte(start_addr, N25Q128A_SUBSECTOR_SIZE, readDataSubsector) != QSPI_OK){
  24.         return QSPI_ERROR;
  25.     }
  26.     /* Check if data was correctly write into flash */
  27.     if(memcmp(dataBuffer,readDataSubsector,N25Q128A_SUBSECTOR_SIZE) == 0 ){
  28.         return QSPI_OK;
  29.     }
  30.     else{
  31.         return QSPI_ERROR;
  32.     }
  33.     /* return operation status */
  34.     return QSPI_OK;
  35. }

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:

  1. uint8_t QSPI_Flash_Write_Page(uint32_t startAddr, uint8_t* data_buf)
  2. {
  3.     uint32_t subsector_number = 0;
  4.     uint32_t addres_with_sub_data = 0;
  5.     uint8_t copy_of_subsector_data[N25Q128A_SUBSECTOR_SIZE] = {0};
  6.     uint8_t tmp_buffer_with_data[N25Q128A_SUBSECTOR_SIZE] = {0};
  7.     /* calculate subsector number */
  8.     subsector_number = startAddr/N25Q128A_SUBSECTOR_SIZE;
  9.     addres_with_sub_data = N25Q128A_SUBSECTOR_SIZE * subsector_number;
  10.     /* Read data from flash */
  11.     if(QSPI_Flash_Read_Block_Byte(addres_with_sub_data, N25Q128A_SUBSECTOR_SIZE, copy_of_subsector_data) != QSPI_OK){
  12.         return QSPI_ERROR;
  13.     }
  14.     uint32_t addres = (startAddr - addres_with_sub_data);
  15.     /* Erase subsector */
  16.     if(QSPI_Flash_Erase_SubSector(subsector_number) != QSPI_OK){ return QSPI_ERROR; }
  17.     /* write data to appropriate memory place */
  18.     for(uint32_t loop = 0; loop < N25Q128A_PAGE_SIZE; loop++)
  19.     {
  20.         copy_of_subsector_data[addres + loop] = data_buf[loop];
  21.     }
  22.     /* Write data */
  23.     if(QSPI_Flash_Write_Block_Byte(addres_with_sub_data, N25Q128A_SUBSECTOR_SIZE, copy_of_subsector_data) != QSPI_OK){
  24.         return QSPI_ERROR;
  25.     }
  26.     /* Check if data are correct: Read data again then check */
  27.     if(QSPI_Flash_Read_Block_Byte(addres_with_sub_data, N25Q128A_SUBSECTOR_SIZE, tmp_buffer_with_data) != QSPI_OK){
  28.         return QSPI_ERROR;
  29.     }
  30.     if(memcmp(copy_of_subsector_data,tmp_buffer_with_data,N25Q128A_SUBSECTOR_SIZE) != 0 ){ return QSPI_ERROR; }
  31.     /* return status */
  32.     return QSPI_OK;
  33. }

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:

  1. uint8_t QSPI_Flash_Read_Block_Byte(uint32_t startAddr, uint32_t size, uint8_t* dataBuffer)
  2. {
  3.   QSPI_CommandTypeDef command;
  4.   if(startAddr>=N25Q128A_FLASH_SIZE)        { return QSPI_ERROR; }
  5.   if((startAddr+size)>=N25Q128A_FLASH_SIZE) { return QSPI_ERROR; }
  6.   if(size==0)                               { return QSPI_ERROR; }
  7.   // Initialize the read command
  8.   command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  9.   command.Instruction       = QUAD_INOUT_FAST_READ_CMD;
  10.   command.AddressMode       = QSPI_ADDRESS_4_LINES;
  11.   command.AddressSize       = QSPI_ADDRESS_24_BITS;
  12.   command.Address           = startAddr;
  13.   command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  14.   command.DataMode          = QSPI_DATA_4_LINES;
  15.   command.DummyCycles       = N25Q128A_DUMMY_CYCLES_READ_QUAD;
  16.   command.NbData            = size;
  17.   command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  18.   command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
  19.   command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
  20.   // Configure the command
  21.   if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  22.       return QSPI_ERROR;
  23.   }
  24.   // Reception of the data
  25.   if(HAL_QSPI_Receive(&hqspi, dataBuffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){
  26.       return QSPI_ERROR;
  27.   }
  28.   return QSPI_OK;
  29. }

Podobnie jak podstawowa funkcja zapisu ona odczyta podaną ilość danych i zapisze ją w buforze.

Wyłączenie QSPI wygląda następująco:

  1. __weak void BSP_QSPI_MspDeInit(QSPI_HandleTypeDef *hqspi)
  2. {
  3.   /* Disable the NVIC for QSPI */
  4.   HAL_NVIC_DisableIRQ(QUADSPI_IRQn);
  5.   /* Disable peripherals and GPIO Clocks */
  6.   HAL_GPIO_DeInit(QSPI_CS_GPIO_PORT, QSPI_CS_PIN);
  7.   HAL_GPIO_DeInit(QSPI_CLK_GPIO_PORT, QSPI_CLK_PIN);
  8.   HAL_GPIO_DeInit(QSPI_D0_GPIO_PORT, QSPI_D0_PIN);
  9.   HAL_GPIO_DeInit(QSPI_D1_GPIO_PORT, QSPI_D1_PIN);
  10.   HAL_GPIO_DeInit(QSPI_D2_GPIO_PORT, QSPI_D2_PIN);
  11.   HAL_GPIO_DeInit(QSPI_D3_GPIO_PORT, QSPI_D3_PIN);
  12.   /* Reset peripherals */
  13.   __HAL_RCC_QSPI_FORCE_RESET();
  14.   __HAL_RCC_QSPI_RELEASE_RESET();
  15.   /* Disable the QuadSPI memory interface clock */
  16.   __HAL_RCC_QSPI_CLK_DISABLE();
  17. }

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:

  1. int main(void)
  2. {
  3.   /* USER CODE BEGIN 1 */
  4.   char usartBuffer[50] = {0};
  5.   uint8_t dataWrite4[0x100] = {0x34};
  6.   uint8_t dataRead4[0x100] = {0x00};
  7.   /* USER CODE END 1 */
  8.   /* MPU Configuration----------------------------------------------------------*/
  9.   MPU_Config();
  10.   /* Enable I-Cache-------------------------------------------------------------*/
  11.   SCB_EnableICache();
  12.   /* Enable D-Cache-------------------------------------------------------------*/
  13.   SCB_EnableDCache();
  14.   /* MCU Configuration----------------------------------------------------------*/
  15.   /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  16.   HAL_Init();
  17.   /* Configure the system clock */
  18.   SystemClock_Config();
  19.   /* Initialize all configured peripherals */
  20.   MX_GPIO_Init();
  21.   MX_RTC_Init();
  22.   MX_USART1_UART_Init();
  23.   //MX_QUADSPI_Init();
  24.   //if(QSPI_ResetMemory(&hqspi) != QSPI_OK)     {   return QSPI_NOT_SUPPORTED;  }
  25.   // if(QSPI_DummyCyclesCfg(&hqspi) != QSPI_OK) {   return QSPI_NOT_SUPPORTED;  }
  26.   //WRITE_REG(QUADSPI->LPTR, 0xFFF);
  27.   /* USER CODE BEGIN 2 */
  28.   GPIO_LED_INIT();
  29.   /* Initial quadSpi */
  30.   QSPI_Flash_Init();
  31.   WRITE_REG(QUADSPI->LPTR, 0xFFF);
  32.   for(uint32_t i = 0; i<6; i++)
  33.   {
  34.       QSPI_Flash_Erase_SubSector(i);
  35.   }
  36.   for(uint32_t i = 0; i<40; i++)
  37.   {
  38.       QSPI_Flash_Write_Page(i*0x1000, dataWrite4);
  39.       QSPI_Flash_Read_Block_Byte(i*0x1000, 0x100, dataRead4);
  40.       if(memcmp(dataWrite4,dataRead4,0x100) == 0 ){
  41.           sprintf(usartBuffer,"OK OK OK OK\r\n");
  42.           HAL_UART_Transmit(&huart1, (uint8_t*)usartBuffer,strlen(usartBuffer),0x1000);
  43.       }
  44.       else{
  45.           sprintf(usartBuffer,"Error Error Error!!!\r\n");
  46.           HAL_UART_Transmit(&huart1, (uint8_t*)usartBuffer,strlen(usartBuffer),0x1000);
  47.       }
  48.   }
  49.   /* USER CODE END 2 */
  50.   /* Infinite loop */
  51.   /* USER CODE BEGIN WHILE */
  52.   while (1)
  53.   {
  54.   /* USER CODE END WHILE */
  55.   /* USER CODE BEGIN 3 */
  56.         LED_HIGH(LED_PORT, LED_PIN);
  57.         HAL_Delay(1000);
  58.         LED_LOW(LED_PORT, LED_PIN);
  59.         HAL_Delay(1000);
  60.   }
  61.   /* USER CODE END 3 */
  62. }

Pliki z projektem można znaleźć na dysku Google pod tym linkiem.