2023-11-11 10:06 AM
Hello,
I have been trying to make a driver for the Winbond(W25N01GV) NAND flash over QSPI but I've been failing so far. Can somebody give me some ideas about what I am doing wrong?
#define UNPACK_UINT16_TO_2_BYTES(num) {(uint8_t) (((num) & 0xFF00) >> 8), (uint8_t) ((num) & 0x00FF)}
#define PACK_2_BYTES_TO_UINT16(bytes) ((((uint16_t) *(bytes)) << 8) + *((bytes)+1))
bool isBusy(void)
{
uint8_t reg_data = 0;
QSPI_CommandTypeDef com;
com.InstructionMode = QSPI_INSTRUCTION_1_LINE; // QSPI_INSTRUCTION_...
com.Instruction = W25N01GV_STATUS_REG;
com.AddressMode = QSPI_ADDRESS_1_LINE;
com.AddressSize = QSPI_ADDRESS_8_BITS;
com.Address = SR3_STATUS_REGISTER_ADDRESS;
com.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
com.AlternateBytes = QSPI_ALTERNATE_BYTES_NONE;
com.AlternateBytesSize = QSPI_ALTERNATE_BYTES_NONE;
com.DummyCycles = 0;
com.DataMode = QSPI_DATA_1_LINE;
com.NbData = 1;
com.DdrMode = QSPI_DDR_MODE_DISABLE;
//com.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
com.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &com, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return true;
}
if (HAL_QSPI_Receive(&hqspi, ®_data, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return true;
}
bool bai = (reg_data & 0x01U);
return bai;
//return false;
}
uint8_t QspiFlashProgramExecute(uint16_t address)
{
while(isBusy())
HAL_Delay(1);
QSPI_CommandTypeDef command;
uint8_t block_add[2] = UNPACK_UINT16_TO_2_BYTES(address);
command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
command.Instruction = 0x10U; // Program Execute
command.AddressMode = QSPI_ADDRESS_NONE;
command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
command.DummyCycles = 8;
command.DataMode = QSPI_DATA_1_LINE;
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, 1000U) != HAL_OK)
{
//UartSendText(&uart3, "ERROR HAL_QSPI_Command in QspiFlashProgramExecute\r");
return QSPI_ERROR;
}
if (HAL_QSPI_Transmit(&hqspi, block_add, 1000U) != HAL_OK)
{
//UartSendText(&uart3, "ERROR HAL_QSPI_Transmit in QspiFlashProgramData\r");
return QSPI_ERROR;
}
return QSPI_OK;
}
bool W25N_WriteEnable(bool enable)
{
while(isBusy())
HAL_Delay(1);
QSPI_CommandTypeDef com;
com.InstructionMode = QSPI_INSTRUCTION_1_LINE; // QSPI_INSTRUCTION_...
com.Instruction = enable ? W25N01GB_WRITE_ENABLE : W25N01GB_WRITE_DISABLE;
com.AddressMode = QSPI_ADDRESS_NONE;
com.AddressSize = QSPI_ADDRESS_NONE;
//com.Address = 0x0U;
com.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
com.AlternateBytes = QSPI_ALTERNATE_BYTES_NONE;
com.AlternateBytesSize = QSPI_ALTERNATE_BYTES_NONE;
com.DummyCycles = 0;
com.DataMode = QSPI_DATA_NONE;
com.NbData = 0;
com.DdrMode = QSPI_DDR_MODE_DISABLE;
//com.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
com.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &com, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return QSPI_ERROR;
}
HAL_Delay(1);
return QSPI_OK;
}
/******************************************************ERASE*******************************************************/
uint8_t W25QSPIBlockErase(uint16_t blockAddress)
{
QSPI_CommandTypeDef sCommand = { 0 };
uint8_t ucSplitAddr[2] = UNPACK_UINT16_TO_2_BYTES(blockAddress);
blockAddress = blockAddress * W25N01GB_PAGE_BLOCK_COUNT_PER_PAGE;
/* Enable write operations */
while(isBusy())
HAL_Delay(1);
if (W25N_WriteEnable(true) != QSPI_OK)
{
return QSPI_ERROR;
}
/* Initialize the erase command */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.Instruction = W25N01GB_BLOCK_ERASE_CMD; //D8h
sCommand.AddressMode = QSPI_ADDRESS_NONE;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DataMode = QSPI_DATA_1_LINE;
sCommand.NbData = 2;
sCommand.DummyCycles = 8;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
//sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Send the command */
if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return QSPI_ERROR;
}
/*Send the Address*/
if (HAL_QSPI_Transmit(&hqspi, (uint8_t*) ucSplitAddr, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return QSPI_ERROR;
}
while (W25Q_IsBusy() == W25Q_BUSY)
HAL_Delay(1);
return QSPI_OK;
}
//this function will write to a page from it's first address
uint8_t W25N0_QuadWritePage(uint16_t page, uint8_t *pData, uint16_t size)
{
if(size > W25N01GV_PAGE_SIZE)
{
return QSPI_ERROR;
}
QSPI_CommandTypeDef sCommand;
while (isBusy())
HAL_Delay(1);
/* Enable write operations */
if (W25N_WriteEnable(true) != QSPI_OK)
{
return QSPI_ERROR;
}
/* Initialize the program command */
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.Instruction = QUAD_PAGE_PROG_CMD; //32h
sCommand.AddressMode = QSPI_ADDRESS_1_LINE;
sCommand.AddressSize = QSPI_ADDRESS_16_BITS;
sCommand.Address = page;
sCommand.DataMode = QSPI_DATA_4_LINES;
sCommand.DummyCycles = 0;
sCommand.NbData = size;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
/* Configure the command */
if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return QSPI_ERROR;
}
while (W25Q_IsBusy() == W25Q_BUSY)
HAL_Delay(1);
/* Transmission of the data */
if (HAL_QSPI_Transmit(&hqspi, (uint8_t*) pData, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return QSPI_ERROR;
}
while (W25Q_IsBusy() == W25Q_BUSY)
HAL_Delay(1);
QspiFlashProgramExecute(page);
return QSPI_OK;
}
uint8_t W25N0_QuadReadPage(uint16_t pageAddr, uint8_t *pData, uint16_t size)
{
if (size > W25N01GV_PAGE_SIZE)
{
return QSPI_ERROR;
}
QSPI_CommandTypeDef sCommand = { 0 };
uint8_t block_add[2] = UNPACK_UINT16_TO_2_BYTES(pageAddr);
while (isBusy())
HAL_Delay(1);
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.Instruction = W25N01GV_PAGE_READ_CMD; //13h
sCommand.AddressMode = QSPI_ADDRESS_NONE;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DataMode = QSPI_DATA_1_LINE;
sCommand.NbData = 2;
sCommand.DummyCycles = 8;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
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_Transmit(&hqspi, block_add, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return QSPI_ERROR;
}
while (W25Q_IsBusy() == W25Q_BUSY)
HAL_Delay(1);
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
sCommand.Instruction = W25N01GV_QUAD_INOUT_READ_CMD; //0xEB
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
sCommand.AddressSize = QSPI_ADDRESS_16_BITS;
sCommand.Address = pageAddr;
sCommand.DummyCycles = 4; //4
sCommand.DataMode = QSPI_DATA_4_LINES;
sCommand.NbData = size;
sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;
sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return QSPI_ERROR;
}
/* Reception of the data */
if (HAL_QSPI_Receive(&hqspi, (uint8_t*) pData, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
return QSPI_ERROR;
}
}
Solved! Go to Solution.
2023-11-13 09:44 AM
Well, it seems that my code works with some slight adjustments but it was a hardware problem after all.
Thank you @Tesla DeLorean for mentioning flight controller examples to compare to my code. After reverse engineering one of them, I found out that I was sending everything right.
For other people reading later, here is the repo: https://github.com/cleanflight/cleanflight/blob/master/src/main/drivers/flash_w25n01g.c
2023-11-11 11:56 AM - edited 2023-11-11 12:09 PM
Hello @Dracu and welcome to the ST Community :smiling_face_with_smiling_eyes:.
I suggest you to use those examples and compare them to your code taking into account the differences in parameters linked to the specifications of your application (Memory caracteristiques and configurations,...)
Best regards.
STTwo-32
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2023-11-11 12:10 PM
I'd strongly advise clearing auto/local variables so you don't get random junk from the stack breaking things in the library.
QSPI_CommandTypeDef command = {0}; // clear autos
2023-11-13 05:51 AM
Hello @Dracu ,
Could please check if this NAND memory is supported by Quad-SPI interface?
NAND memory are supported if the command format is in line with the QUADSPI controller frame format (AN4760):
But, according to the memory datasheet, Block Erase (D8h) command needs the address after the dummy bytes.
Thank you.
Kaouthar
To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
2023-11-13 09:33 AM
Hello @KDJEM.1 ,
That is why I am sending the address after the dummy clocks with:
HAL_QSPI_Transmit(&hqspi, block_add, 1000U) != HAL_OK)
2023-11-13 09:44 AM
Well, it seems that my code works with some slight adjustments but it was a hardware problem after all.
Thank you @Tesla DeLorean for mentioning flight controller examples to compare to my code. After reverse engineering one of them, I found out that I was sending everything right.
For other people reading later, here is the repo: https://github.com/cleanflight/cleanflight/blob/master/src/main/drivers/flash_w25n01g.c
2023-11-13 09:59 AM
For context I mentioned that here https://community.st.com/t5/stm32-mcus-products/stm32h750bx-qspi-issue-with-w25n01gvzeig-nand-flash-driver/m-p/606986#M227208
The NAND being perhaps more appropriate for mass-storage applications, but still a bit up-hill as the erase block size, and wear management still something to struggle with on an MCU. At a chip level eMMC handles a lot of that whilst avoiding socket / connectivity with MicroSD cars. NOR Flash QSPI are more popular for XIP, many have a 4KB erase block which can be viable with FatFS instead of typical 512-byte, but typically rather slow for the erase/write cycles.
2023-12-14 04:17 AM
hello,
I have been struggling to make a driver for the Winbond(W25N01GV) NAND flash over QSPI as you have but I've been failing so far. Can you please send me your working driver to compare with my work? Or take a look at my driver and tell me what am I doing wrong?