Skip to main content
PFlor.2
Senior II
February 13, 2024
Solved

STM32H743 external loader

  • February 13, 2024
  • 21 replies
  • 8683 views

I went through the ST External QSPI Loader how to and have verified my quadspi.c/h driver is good with the Winbond W25Q64 flash part we are using.  However after I create the .stldr and load it into the STM32 Programmer it will not read from the flash...get Error:data read failed.  I've attached the project if someone could help me out with what could be wrong?

This topic has been closed for replies.
Best answer by PFlor.2

Just to wrap up the solution to this LONG thread....

Initially I was using TIM6 for the timebase (based on application using FreeRTOS that cannot use SysTick) but the NVIC priority was set to 15.  I'm not sure if the priority was the problem or some timeout with the Loader_Src.c Init() function, but the ST Support solution to comment out __set_PRIMASK(0); that enabled interrupts just masked the issue and since the interrupts were no longer running the HAL_GetTick() never incremented so timeout values made no difference and I had HAL_Delay() overridden to use a loop of NOP.

PFlor2_0-1733955529552.png

By using SysTick for the Timebase and setting it's NVIC priority to 0 instead of 15 (default) I was able to use enable interrupts in Init() and use the standard HAL_Delay() and HAL_GetTick() just fine and almost everything worked just fine.  However, the Chip Erase still failed....Tesla was correct that this was a timeout issue.  Basically, I had a timeout value of 20000 (HAL_QSPI_TIMEOUT_DEFAULT_VALUE) I was using for most the QSPI functions.  I started using a larger value for erasing the chip, but I was using this larger timeout for issuing the erase chip command when in fact the HAL_QSPI_AutoPolling() is what needed the larger timeout value since that is what is waiting for the erase to complete.  Once I modified my AutoPolling function to use a larger timeout when doing a chip erase then that method started working as well.   Just multiple misunderstanding of how this loader works on my part on multiple fronts.

#define CSP_QPSI_TIMEOUT_DEFAULT_VALUE 20000
#define CSP_QPSI_MASSERASE_TIMEOUT_VALUE 65000


uint8_t CSP_QSPI_Erase_Chip(void) {

 QSPI_CommandTypeDef sCommand = { 0 };

 if (QSPI_WriteEnable() != HAL_OK) {
 return HAL_ERROR;
 }

 /* Erasing Sequence --------------------------------- */
 sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
 sCommand.Instruction = CHIP_ERASE_CMD;
 sCommand.AddressMode = QSPI_ADDRESS_NONE;
 sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
 sCommand.DataMode = QSPI_DATA_NONE;
 sCommand.DummyCycles = 0;
 sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
 sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
 sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

 if (HAL_QSPI_Command(&hqspi, &sCommand, CSP_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
 return HAL_ERROR;
 }

 if (QSPI_AutoPollingMemReady(CSP_QPSI_MASSERASE_TIMEOUT_VALUE) != HAL_OK) {
 return HAL_ERROR;
 }

 return HAL_OK;

}

 

21 replies

Tesla DeLorean
Guru
February 13, 2024

Are you sure the Winbond W25Q64 part you're using supports Writing Status Register 2 with it's own command?

The 0x6B 1-1-4 command for memory mapping is apt to fail if QE isn't set.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
Tesla DeLorean
Guru
February 13, 2024

 

// sourcer32@gmail.com https://paypal.me/cliveone
//****************************************************************************

static uint8_t QSPI_ReadStatus(QSPI_HandleTypeDef *hqspi, uint32_t Reg, uint32_t *pStatusReg) // WINBOND
{
 QSPI_CommandTypeDef sCommand = {0};
 uint32_t data = 0;

 sCommand.NbData = 1;

 switch(Reg)
 {
 case 0 : sCommand.Instruction = READ_STATUS_REG1_CMD; break; // REG1
 case 1 : sCommand.Instruction = READ_STATUS_REG2_CMD; break; // REG2
 case 2 : sCommand.Instruction = READ_STATUS_REG3_CMD; break; // REG3
 case 0x9F : // READID
 sCommand.Instruction = 0x9F;
 sCommand.NbData = 3;
 break;
 default:
 return(QSPI_ERROR);
 }

 sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
 sCommand.AddressMode = QSPI_ADDRESS_NONE;
 sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
 sCommand.DataMode = QSPI_DATA_1_LINE;
 sCommand.DummyCycles = 0;

 sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
 sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
 sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

 if (HAL_QSPI_Command(hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
 {
 return(QSPI_ERROR);
 }

 if (HAL_QSPI_Receive(hqspi, (void *)&data, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
 {
 return(QSPI_ERROR);
 }

 if (pStatusReg)
 *pStatusReg = data;

 return(QSPI_OK);
}

//****************************************************************************

static uint8_t QSPI_WriteStatus(QSPI_HandleTypeDef *hqspi, uint32_t Reg, uint32_t StatusReg)
{
 QSPI_CommandTypeDef sCommand = {0};

 sCommand.NbData = 1;

 switch(Reg)
 {
#if 1 // WINBOND
 case 0 : // REG1
 sCommand.Instruction = WRITE_STATUS_REG1_CMD;
 sCommand.NbData = 2; // REG1 and REG2 as 16-bit
 break;
#else
 case 0 : sCommand.Instruction = WRITE_STATUS_REG1_CMD; break; // REG1
 case 1 : sCommand.Instruction = WRITE_STATUS_REG2_CMD; break; // REG2
 case 2 : sCommand.Instruction = WRITE_STATUS_REG3_CMD; break; // REG3
#endif
 default:
 return(QSPI_ERROR);
 }

 sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
 sCommand.AddressMode = QSPI_ADDRESS_NONE;
 sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
 sCommand.DataMode = QSPI_DATA_1_LINE;
 sCommand.DummyCycles = 0;

 sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
 sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
 sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

 /* Enable write operations */
 if (QSPI_WriteEnable(hqspi) != QSPI_OK)
 {
 return(QSPI_ERROR);
 }

 /* Send the command */
 if (HAL_QSPI_Command(hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
 {
 return(QSPI_ERROR);
 }

 /* Transmission of the data */
 if (HAL_QSPI_Transmit(hqspi, (void *)&StatusReg, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
 {
 return(QSPI_ERROR);
 }

 return(QSPI_OK);
}

//****************************************************************************

static uint8_t QSPI_ConfigQuad(QSPI_HandleTypeDef *hqspi) // WINBOND
{
 uint32_t Id, Reg1, Reg2;

 if (QSPI_ReadStatus(hqspi, 0x9F, &Id) == QSPI_OK)
 {
 Id &= 0xFFFF;

 if ((Id != 0x40EF) && (Id != 0x60EF)) // WINBOND CHECK
 return(QSPI_ERROR);
 }
 else
 {
 return(QSPI_ERROR);
 }

 if (QSPI_ReadStatus(hqspi, 0, &Reg1) != QSPI_OK)
 return(QSPI_ERROR);

 if (QSPI_ReadStatus(hqspi, 1, &Reg2) != QSPI_OK)
 return(QSPI_ERROR);

#ifdef DEBUG_UART //printf("REG1:%02X REG2:%02X\n", Reg1, Reg2);
 OutString("REG1:");
 OutString(DumpHexEx(Reg1, 2));
 OutString(" REG2:");
 OutString(DumpHexEx(Reg2, 2));
 OutChar('\n');
#endif // DEBUG_UART

 // Unfudge parts where invalid MICRON, MACRONIX and WINBOND sequences have been registered
#if defined(USE_W25Q256) || defined(USE_W25Q512) || defined(USE_W25Q01) || defined(USE_W25Q02)
 if (((Reg2 & 0x42) != 0x02) || ((Reg1 & 0x3C) != 0))
 {
 Reg2 |= 2; // Set QE
 Reg2 &= ~0x40; // COM=0
 Reg1 &= ~0x3C; // Clear BP0,BP1,BP2,BP3
#else
 if (((Reg2 & 0x42) == 0) || ((Reg1 & 0x1C) != 0)) // W25Q64 / W25Q128
 {
 Reg2 |= 2; // Set QE
 Reg2 &= ~0x40; // COM=0
 Reg1 &= ~0x1C; // Clear BP0,BP1,BP2
#endif

 if (QSPI_WriteStatus(hqspi, 0, ((Reg2 << | Reg1) != QSPI_OK) // WINBOND WRITES BOTH
 return(QSPI_ERROR);

 /* Configure automatic polling mode to wait the memory is ready */
 if (QSPI_AutoPollingMemReady(hqspi, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK)
 {
 //puts("Fail Quad");
 return(QSPI_ERROR);
 }

 if (QSPI_ReadStatus(hqspi, 0, &Reg1) != QSPI_OK)
 return(QSPI_ERROR);

 if (QSPI_ReadStatus(hqspi, 1, &Reg2) != QSPI_OK)
 return(QSPI_ERROR);

#ifdef DEBUG_UART //printf("REG1:%02X REG2:%02X\n", Reg1, Reg2);
 OutString("REG1:");
 OutString(DumpHexEx(Reg1, 2));
 OutString(" REG2:");
 OutString(DumpHexEx(Reg2, 2));
 OutChar('\n');
#endif // DEBUG_UART
 }

 return(QSPI_OK);
}

//****************************************************************************

 

if (QSPI_WriteStatus(hqspi, 0, ((Reg2 << 8) | Reg1) != QSPI_OK) // WINBOND WRITES BOTH

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
PFlor.2
PFlor.2Author
Senior II
February 13, 2024

Do I need something like this?  Should QSPI_Config_Quad() called from CSP_QUADSPI_Init() or where is it used?

PFlor.2
PFlor.2Author
Senior II
March 19, 2024

Found a resolution for this issue...

Apparently the enabling interrupts in the Init() function of the Loader_Src.c provided in the ST repository for the H7 external loader was hanging after it entered this function the first time.  Made the modifications below per ST suggestion...

PFlor2_0-1710859554743.png 

Once this was done however the HAL_Delay() in my QuadSPI drivers would hang (made sense since interrupts are disabled I guess) so I also had to include a local HAL_Delay() function that used NOP instead of ticks.

I added this in my quadspi.c file...

PFlor2_1-1710859743199.png

ST support is still looking into whey the enabling of interrupts is hanging in this case.

 

Tesla DeLorean
Guru
March 19, 2024

They need to move to a model that doesn't use interrupts.

If the board is running at 64 MHz, you can enable TIM2 / TIM5, with PSC=64000-1, ARR=0xFFFFFFFF, and you can pull the millisecond count from TIM->CNT

Using HAL_Delay() will make things slow. The HAL_GetTick() should be used to provide a bounding timeout, but functions should return as soon as the device is ready / finished.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
PFlor.2
PFlor.2Author
Senior II
December 9, 2024

Tesla I have a mostly working external loader I developed for the Windbond W25Q64JV but for some reason the "Erase Chip" function for the external flash fails with the STM32Programmer as seen below...

PFlor2_0-1733777190465.png

 

This function works fine with the loader you provided for me (CLIVEONE-W25Q64_STM32H7XX-PB2-PB6-PD11-PF9-PE2-PA1.stldr) back in May 2023.  Could you share your W25Q64 driver code with me so I can see what is wrong with mine?

PFlor.2
PFlor.2AuthorBest answer
Senior II
December 11, 2024

Just to wrap up the solution to this LONG thread....

Initially I was using TIM6 for the timebase (based on application using FreeRTOS that cannot use SysTick) but the NVIC priority was set to 15.  I'm not sure if the priority was the problem or some timeout with the Loader_Src.c Init() function, but the ST Support solution to comment out __set_PRIMASK(0); that enabled interrupts just masked the issue and since the interrupts were no longer running the HAL_GetTick() never incremented so timeout values made no difference and I had HAL_Delay() overridden to use a loop of NOP.

PFlor2_0-1733955529552.png

By using SysTick for the Timebase and setting it's NVIC priority to 0 instead of 15 (default) I was able to use enable interrupts in Init() and use the standard HAL_Delay() and HAL_GetTick() just fine and almost everything worked just fine.  However, the Chip Erase still failed....Tesla was correct that this was a timeout issue.  Basically, I had a timeout value of 20000 (HAL_QSPI_TIMEOUT_DEFAULT_VALUE) I was using for most the QSPI functions.  I started using a larger value for erasing the chip, but I was using this larger timeout for issuing the erase chip command when in fact the HAL_QSPI_AutoPolling() is what needed the larger timeout value since that is what is waiting for the erase to complete.  Once I modified my AutoPolling function to use a larger timeout when doing a chip erase then that method started working as well.   Just multiple misunderstanding of how this loader works on my part on multiple fronts.

#define CSP_QPSI_TIMEOUT_DEFAULT_VALUE 20000
#define CSP_QPSI_MASSERASE_TIMEOUT_VALUE 65000


uint8_t CSP_QSPI_Erase_Chip(void) {

 QSPI_CommandTypeDef sCommand = { 0 };

 if (QSPI_WriteEnable() != HAL_OK) {
 return HAL_ERROR;
 }

 /* Erasing Sequence --------------------------------- */
 sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
 sCommand.Instruction = CHIP_ERASE_CMD;
 sCommand.AddressMode = QSPI_ADDRESS_NONE;
 sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
 sCommand.DataMode = QSPI_DATA_NONE;
 sCommand.DummyCycles = 0;
 sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
 sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
 sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

 if (HAL_QSPI_Command(&hqspi, &sCommand, CSP_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
 return HAL_ERROR;
 }

 if (QSPI_AutoPollingMemReady(CSP_QPSI_MASSERASE_TIMEOUT_VALUE) != HAL_OK) {
 return HAL_ERROR;
 }

 return HAL_OK;

}

 

Associate II
February 20, 2025

Hello,

I am usig STM32H730ZBT6 with S25FL064LABNFI041 external Flashe and facing the same "Failed to erase memory" error. I have tried everything commented in this post and some others, but without success.

I upload my project for the case someone could check it.

I have already spent 3 days with this issue and I can't afford more, but it is essential for carrying out or projects, so any help will be really grateful.

Thank you in advance and regards.

Egoitz.