2024-02-13 11:31 AM
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?
2024-02-13 11:45 AM
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.
2024-02-13 12:19 PM - edited 2024-02-13 03:53 PM
Most of the Winbond W25Qxx series support a Write Status Registers command that writes both SR1 and SR2 as a pair of bytes. A smaller subset support WRSR1 and WRSR2 as individual commands.
Given the broad array of stock / parts, and the difficulty in determining which you have mounted, its probably inadvisable to use WRSR2 unless you know it works. You could probably read-back to check, but these and other QSPI frequently don't have a status reporting BAD / INVALID command.
Probably supported on the W25Q64JV, but is that what you are using? Please indicate part marking.
2024-02-13 12:29 PM - edited 2024-09-13 06:42 AM
// 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 << 8) | 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)8) 8) | Reg1) != QSPI_OK) // WINBOND WRITES BOTH
2024-02-13 12:32 PM
sorry I replied on the wrong post earlier...tried to copy it here but wouldn't take. Couldn't delete the reply on the other post either, ugh...
Yes, the part is the W25Q64JVSIQ. As I mentioned in the other reply, I've been using this in memory mapped mode with TouchGFX in my application for some time now so I'm pretty confident the CSP_QSPI_EnableMemoryMappedMode() is working. If I'm understanding you correctly, I believe the memory mapped mode would not work if this was the issue.
2024-02-13 12:39 PM
In NOR_FLASH mode the Read is done via a memory mapped read of the memory via the ST-LINK
In SPI_FLASH mode the Read is done via Read() functionality of the External Loader.
The reading could give a failure/error if Init() returns an error code, perhaps review that via Verbose Logging 3, or output diagnostics / telemetry from the loader. Or if the memory mapping isn't doing it's job properly. See data returned / dumped.
I don't use CubeMX, main() or vector tables / interrupts in my loaders.
2024-02-13 01:22 PM
Do I need something like this? Should QSPI_Config_Quad() called from CSP_QUADSPI_Init() or where is it used?
2024-02-13 03:58 PM
This is what I'm using, it is provided as an example.
Not sure this is where your issue is, or not.
Either your Init() code is failing, or the memory mapping is failing, per your description of the fault.
Log the output from STM32 Cube Programmer, with level three setting, and attach that, might be easier to diagnose.
From within the Loader you can provide telemetry via a UART to understand the interactions from that perspective.
2024-02-15 04:12 PM
I did have something wrong in my initialization...so now when I connect the programmer the loader will read the flash at 0x90000000 successfully! However when I press the READ button after that it fails again and when I try to program it fails on the erase external flash. See attached log. So it seems it enter the memory mapped mode after initialization ok but then maybe pressing READ causes it to exit memory mapped mode and it fails.
2024-02-15 05:05 PM
Watch how Init() handles a second call. Memory Mapping needs a QSPI Abort or DeInit to terminate, and then restart