STM32F4 SPI code only works in debug mode - Page 1
Forum section: electronics / general

author: syntax333
author_rank: Regular Contributor
timestamp: June 08, 2021, 07:29:21 am
content: Hi I am trying to read ID of an external FLASH.The code which reads it is shown below.Code:uint16_t FLASH_ReadID(void){uint16_t tempData = 0x0000;//Read IDGPIOB->BSRR |= GPIO_BSRR_BR2; //Reset FLASH CSwhile(!(SPI1->SR & SPI_SR_TXE));SPI1->DR = 0x9E;while(!(SPI1->SR & SPI_SR_TXE));SPI1->DR = 0xFF;while((SPI1->SR & SPI_SR_BSY));tempData = (SPI1->DR << 8);while(!(SPI1->SR & SPI_SR_TXE));SPI1->DR = 0xFF;while((SPI1->SR & SPI_SR_BSY));tempData |= SPI1->DR;GPIOB->BSRR |= GPIO_BSRR_BS2; //Set FLASH CSreturn tempData;}After reading 2byte ID (0x20, 0xBA)I am transmitting the ID information to PC through USART. I use a serial terminal program which called Hterm. Problem is when I run the code, the FLASH ID returns as 0x00, 0x20. I set SPI baudrate as 42Mbps. When I set the baudrate to lower speeds output becomes 0x00, 0x00. After getting 0x00, 0x00 if I call the function one more time I get 0x80, 0x80.The most interesting part is when I debug the code it seems to return correct value of 0x20, 0xBA.Then I updated the code as below.Code:uint16_t FLASH_ReadID(void){uint16_t tempData = 0x0000;//Read IDGPIOB->BSRR |= GPIO_BSRR_BR2; //Reset FLASH CSSPI1->DR = 0x9E;while(!(SPI1->SR & SPI_SR_TXE));SPI1->DR = 0xFF;while(!(SPI1->SR & SPI_SR_RXNE));tempData = (SPI1->DR << 8);while(!(SPI1->SR & SPI_SR_TXE));SPI1->DR = 0xFF;while(!(SPI1->SR & SPI_SR_RXNE));tempData |= SPI1->DR;while((SPI1->SR & SPI_SR_BSY));GPIOB->BSRR |= GPIO_BSRR_BS2; //Set FLASH CSreturn tempData;}And I scoped the SPI line. The screenshot shown below.In scope everything seems to be fine but when I try to read the registers first I read 0x00, 0x20 and when I recall the function again I read 0xBA, 0x00 and if I recall the function again I read 0x20, 0x00. From this point whenever I call the function I read only 0x20,0x00. I am confused.Why does the code work properly when in debug mode step by step?

author: syntax333
author_rank: Regular Contributor
timestamp: June 08, 2021, 09:03:05 am
content: Instead of polling RXNE and TXE I decided to use RXNE interrupt for receiving data. It seems to work correct but I wonder why polling does not work?

author: Siwastaja
author_rank: Super Contributor
timestamp: June 08, 2021, 03:51:42 pm
content: Didn't look at the manual as part number isn't provided, but assuming your F4 has an SPI FIFO - which is very handy thing by the way - the first read of the data register will give you the data of the first SPI access operation even if you have generated more traffic after that. But from the code it seems you want to ignore the first reply. If this is the case, poll until RXNE (RX FIFO not empty), then read out the FIFO and ignore the result (in C, you can just write SPI1->DR;)after every time you have sent a word. SPI is a protocol that guarantees exact same amount of data in and out so the number of writes and reads should always match.If the thing doesn't have a FIFO or the FIFO depth is set as one word, then you can choose to ignore by not reading out, which would cause an overrun - just make sure the thing doesn't error out on overrun, some peripherals have that feature!For quite some time now I have liked the SPI master pattern of "reverse timeout", or writing to SPI then setting a timer to trig an interrupt after the (easily precalculated) time it should take for SPI to have reply in the RX FIFO. Then check the RXFIFO level and if it differs from the expected amount, generate an error, otherwise read out and use the result.

author: julian1
author_rank: Frequent Contributor
timestamp: June 08, 2021, 05:33:29 pm
content: Code://Read IDGPIOB->BSRR |= GPIO_BSRR_BR2; //Reset FLASH CS....GPIOB->BSRR |= GPIO_BSRR_BS2; //Set FLASH CS}Is the flash CS being toggled correctly?

author: syntax333
author_rank: Regular Contributor
timestamp: June 08, 2021, 05:54:30 pm
content: Quote from: Siwastaja on June 08, 2021, 03:51:42 pmI am using STM32F405. You are probably correct. Peripheral expects me to read the data even though I am not going to use it. For example after the transmission of command for Reading ID of FLASH (0x9E) I have to read the data register. It seems strange because I used to use SPI3 instead of SPI1 and SPI3 didn't has this type of mechanism. I didn't had to read after each transmission in SPI3.Is there a standard way to ignore the data? I don't like the compiler giving me a warning...

author: Siwastaja
author_rank: Super Contributor
timestamp: June 08, 2021, 05:59:20 pm
content: GCC doesn't give a warning when you access a volatile variable (as the peripheral registers are) without doing anything with the value, so justSPI->DR;works fine. It communicates the action and intent well.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 08, 2021, 06:07:51 pm
content: Although now that I look at the reference manual, the F405/415 series has an SPI peripheral which has no FIFO; it has just a single byte buffer. So if you don't read it out, OVERRUN happens. The refman explains the behavior exceptionally clearly:"In this case, the receive buffer contents are not updated with the newly received data fromthe transmitter device. A read operation to the SPI_DR register returns the previouscorrectly received data. All other subsequently transmitted half-words are lost."If I would be implementing such simple SPI peripheral, I would always update the register with the latest received data in case of overrun so you could ignore things by not reading out, but ST though overrun is clearly an error, not intended operation. Don't let it overrun, read out before write. With SPI, this is trivial because you are the master, spurious RX simply cannot happen. In a nutshell, wait for TXE, write, wait for RXNE, read, rinse and repeat.

author: syntax333
author_rank: Regular Contributor
timestamp: June 08, 2021, 07:08:30 pm
content: Quote from: Siwastaja on June 08, 2021, 06:07:51 pmYeah, I expected data register to have the latest received data. However, I still couldn't understand why I can get the latest data when I run the code in debug mode step by step. Maybe overrun bit clears itself after awhile?

author: Silenos
author_rank: Regular Contributor
timestamp: June 08, 2021, 09:21:21 pm
content: Quote from: syntax333 on June 08, 2021, 07:08:30 pmBecause debugger spuriously reads (bus read access) DR for you, and it accidentally, prematurely fixes the overrun condition.You have stepped onto a case where peripheral hardware is vulnerable to spurious r/w accesses as these change its state independenlty from software and cause "wtf is that debug works, release doesn't" (or vice versa) kind of misbehaviour.

author: Siwastaja
author_rank: Super Contributor
timestamp: June 09, 2021, 01:15:26 pm
content: This is the typical debugger trap you fell into. There are much much more than just SPI DR, in many peripherals just reading out a register is considered an operation (clearing interrupt flags by reading status register being another typical example). In FIFOs, if you think about it, this is a very logical and efficient choice: each read pops a new value from the FIFO without the need to acknowledge the read with some separate interface. But this means debugger will mess things up unless you are careful with it and know how to use it "properly".Arduiino Printf^tm debugging strategy doesn't have this issue; you clearly and explicitly choose where you read the peripheral register, can store it to a variable to be printed out later / logged into a telemetry system, and nothing is randomly affecting the functionality of your program.