
I have been working on an interface between an STM32G431 and a W25Q128FV SPI flash memory chip (128 Mbit/16MByte). The image above shows the memory chip on a breakout board with a surface mount capacitor next to it. Given the nature of breadboards and the wires used I’ve been running the memory interface at a reduced speed (1.3MHz). A PCB will be used at a later stage and which should allow for higher speeds.
Erasing the chip was presenting some problems. The code for erase was as follows:
void serial_flash::bulk_erase()
{
write_enable();
SPI->startTransaction();
SPI->transfer((uint8_t)0xc7);
SPI->stopTransaction();
while(read_status1() & 1); // wait until erase has completed
}
The chip must be put into “write” mode before the erase command (0xC7) is sent. The function exits when the “busy” bit in status register 1 is clear. While everything seemed ok, the function did not erase the chip. I took a closer look at the SPI bus using a logic analyzer. I have a very cheap logic analyzer which doesn’t have a very good trigger mechanism. My normal workaround for this is to put the area under test into an everlasting loop and then view the pins of interest on the logic analyzer. This is a problem for erase operations like this as the SPI flash chip has only so many erase cycles. As a precaution I change the command code to 0xd7 (not a supported command) which allowed me look at the SPI bus without harming the chip. I also commented out loop that polled the status register.

The write enable command (0x06) is plainly visible as is the “fake” chip erase command 0xd7. The CS line is driven low just before the 0x06 command and goes high some time after the 0xd7 command. This is not the correct way to erase this chip. The data sheet clearly states that the CS line must go high for a period after each command. It does not do this after the write enable command. The write_enable function is as follows:
void serial_flash::write_enable(void)
{
SPI->startTransaction();
SPI->transfer((uint8_t)0x06);
SPI->stopTransaction();
}
The stopTransaction function should drive the CS line high but it didn’t seem to be working. The relevant SPI code is:
void spi::stopTransaction(void)
{
volatile unsigned Timeout = 1000;
while (SPI1->SR & ((1 << 12) + (1 << 11)) ); // wait for fifo to empty
while (((SPI1->SR & (1 << 0))!=0)&&(Timeout--)); // Wait for RXNE
Timeout = 1000;
while (((SPI1->SR & (1 << 1))==0)&&(Timeout--)); // Wait for TXE
Timeout = 1000;
while (((SPI1->SR & (1 << 7))!=0)&&(Timeout--)); // Wait for Busy
SPI1->CR1 &= ~(1 << 6); // Disable SPI (SPE = 0)
}
This should have worked but it clearly didn’t. Thinking about the sequence of events involved in the bulk_erase function it occurred to me that the call to startTransaction just after the write_enable command may actually be happening before the SPI peripheral had a chance to raise the CS line. The SPI peripheral is routed through GPIO port A in this setup. I noticed that I could monitor the status of the CS pin by reading GPIOA’s input data register and hence wait for it to go high. The stopTransaction code was modified as follows:
void spi::stopTransaction(void)
{
volatile unsigned Timeout = 1000;
while (SPI1->SR & ((1 << 12) + (1 << 11)) ); // wait for fifo to empty
while (((SPI1->SR & (1 << 0))!=0)&&(Timeout--)); // Wait for RXNE
Timeout = 1000;
while (((SPI1->SR & (1 << 1))==0)&&(Timeout--)); // Wait for TXE
Timeout = 1000;
while (((SPI1->SR & (1 << 7))!=0)&&(Timeout--)); // Wait for Busy
SPI1->CR1 &= ~(1 << 6); // Disable SPI (SPE = 0)
while((GPIOA->IDR & (1 << 4))==0); // wait for CS to go high
}
This produced the following output from the logic analyzer:

A high pulse can now be seen between the two SPI commands. As a final test, I replaced the fake “0xD7” command with “0xC7” and presto: erases now work.
Hello Frank,
I did not try your code, but I have an idea, what could be the problem.
You used the SPI at a relatively slow bitrate and the CPU may run far-far faster. In the stoptransaction method you use apparently small timeout values when you are waiting for the clearing events. In my opinion, the while loops might actually finish before the actual SPI event occurs because reached the timeout.
Also when you switch off the SPI with the “SPI1->CR1 &= ~(1 << 6)" instruction, the SPI may not be ready yet.
I recommend to do the test again with far higher timeout values (for example 1000000).
Other idea: You can store the value of the SPI status register in variables after each loop and you can send these values out of an UART and see their value.
In this way you can check, is the timeout long enough.
Best regards,
Tamas
LikeLike
Hi Tamas, thanks for the comment. Your observation is correct, the CPU is running a lot faster than the SPI peripheral. I did indeed try longer delays. In the end I felt that reading the state of the pin was the best way of waiting for the SPI system to react which was independent of CPU speed.
LikeLike
Thanks for the suggestion. Lost in email for a long time I’m afraid 😨
LikeLike