STM32F103 SPI weirdness - Page 1
Forum section: electronics / general

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 08, 2023, 02:34:14 pm
content: I hope someone has encountered this behavior too and can show some light on it.I'm currently working on a transmitter / receiver project with NRF905 modules connected to bluepill modules (STM32F103C8) and are writing bare metal code for it. The transmitter works like a charm and sends 10 bytes of data every 10ms. The receiver works but showed some weirdness. After power up and then turning on the transmitter the data was not received correctly, but after a variable amount of messages it aligned it self and the data came out perfect. Turning of the transmitter and then turning it on again showed it still working as intended. Only after a power cycle of the receiver it was wrong again for so many messages.So I started investigating by connecting a logic analyzer to the SPI signals and the NRF905 AM and DR (Address match and Data ready) signals. This showed normal behavior. Data read from the NRF905 showed as intended. The status byte as response to the NRF905 command and then my preamble 0xAA 0x55. The start of the data in my receive buffer on the other hand was not the intended preamble. It started with the status byte, that should have been discarded in my code.I wrote the SPI code based on what the reference manual (RM0008) for the STM32F1 series states. With debugging I could not figure out why it did not do what it supposed to do, so I added some GPIO toggles to do timing measurements. This showed that the RXNE bit goes high to early in the process. (Low in the screen captures below) I ended up with just checking on the busy flag to make it work reliably.I initialize the SPI peripheral with the following code.Code://SPI setup//CR1//BIDIMODE = 0 (2 line unidirectional communication)//BIDIOE = 0 (Not used since 2 line mode selected)//CRCEN = 0 (No CRC calculation needed with NRF905)//CRCNEXT = 0 (Not used)//DFF = 0 (8 bit frames)//RXONLY = 0 (Full duplex mode)//SSM = 0 (Software slave mode disabled)//SSI = 0 (Not used)//LSBFIRST = 0 (Most significant bit transmitted first)//SPE = 0 (This bit is set when data needs to be send or received. Controls the slave select pin)//BR = ??? (Need to determine a baud rate)//MSTR = 1 (Master device)//CPOL = 0 (Mode 0 selected)//CPHA = 0 (Mode 0 selected)//CR2//TXEIE = 0 (No transmit interrupt)//RXNEIE = 0 (No receive interrupt)//ERRIE = 0 (No error interrupt)//SSOE = 1 (Hardware slave control output enabled)//TXDMAEN = 0 (No transmit DMA enabled)//RXDMAEN = 0 (No receive DMA enabled)//Setup the NRF905 communication interface for master mode, clock/8 (4.5MHz), rising edge low start clock, 8 bit, msb first out//Active low hardware NSS (CSN on NRF905)SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_DIV8;SPI1->CR2 = SPI_CR2_SSOE;This is the code that behaves weird.Code:void nrf905_read_spi(uint8_t command, uint8_t *buffer, uint32_t length){//Start a communication session by enabling the peripheral, which sets the NSS line lowSPI1->CR1 |= SPI_CR1_SPE;//To start a session with the NRF905 a command needs to be sendSPI1->DR = command;//Make sure the transmit register is emptywhile((SPI1->SR & SPI_SR_TXE) == 0);//Transfer a null byte to clock in the next byte to be receivedSPI1->DR = 0;//Wait until the status byte is receivedwhile((SPI1->SR & SPI_SR_RXNE) == 0);//The first byte received is the status register and needs to be ignored//Use the buffer to read the byte into and later overwrite it with the actual data*buffer = SPI1->DR;//Need to wait for the command to be transfered before loading the next byte//and then receive all the data bytesfor(;length > 0;length--){//Make sure the transmit register is emptywhile((SPI1->SR & SPI_SR_TXE) == 0);//Transfer a null byte to clock in the byte to be receivedSPI1->DR = 0;//Wait until the current byte is receivedwhile((SPI1->SR & SPI_SR_RXNE) == 0);//Load it into the buffer*buffer = SPI1->DR;//Point to the next location in the bufferbuffer++;}//Wait until the transmit register is emptywhile((SPI1->SR & SPI_SR_TXE) == 0);//Wait until the peripheral is donewhile(SPI1->SR & SPI_SR_BSY);//Disable the peripheral to make the NSS line highSPI1->CR1 &= ~SPI_CR1_SPE;}This is the code that works without problems, but adds a ~750ns pause between the bytes.Code:void nrf905_read_spi(uint8_t command, uint8_t *buffer, uint32_t length){//Start a communication session by enabling the peripheral, which sets the NSS line lowSPI1->CR1 |= SPI_CR1_SPE;//To start a session with the NRF905 a command needs to be sendSPI1->DR = command;//Wait until the peripheral is donewhile(SPI1->SR & SPI_SR_BSY);//The first byte received is the status register and needs to be ignored//Use the buffer to read the byte into and later overwrite it with the actual data*buffer = SPI1->DR;//Loop to receive all the actual data bytesfor(;length > 0;length--){//Transfer a null byte to clock in the byte to be receivedSPI1->DR = 0;//Wait until the peripheral is donewhile(SPI1->SR & SPI_SR_BSY);//Load the received byte into the buffer*buffer = SPI1->DR;//Point to the next location in the bufferbuffer++;}//Disable the peripheral to make the NSS line highSPI1->CR1 &= ~SPI_CR1_SPE;}Attached is the part of the manual I used to base the code on, and also some screen captures of the logic analyzer data. The TXE, RXNE and BUSY signals in the screen captures are inverted due to setting the output before the wait loop and clearing the output after the wait loop.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 10:13:07 am
content: To check if it ain't a problem of the one bluepill module used in my setup, I tested it with 5 other bluepill modules, and they all show the same behavior.For some reason the first receiver not empty flag comes to early in the process

author: gpr
author_rank: Contributor
timestamp: November 09, 2023, 11:33:52 am
content: I'm not sure it will help, but you can try to access DR register as 8-bit by casting pointer to it, in both sender and receiver, something like that:Code:volatile uint8_t* dr_ptr = (volatile uint8_t*)&spi->DR;*dr_ptr = byte; // senderbyte = *dr_ptr; // receiver

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 11:45:32 am
content: Thanks for the response, but it ain't a problem of data width, it is a problem of timing.The peripheral signals that there is data available before receiving the last bit. Why this happens is the question. When using the busy flag to be sure the data is there it works without problems.

author: gpr
author_rank: Contributor
timestamp: November 09, 2023, 11:57:18 am
content: I remember some datasheets mentioning about accessing DR register with proper data width. I can imagine the situation when sender (despite being configured as 8bit in config register) writes 16-bit data to DR, and this yields clocking 16 bit of data in SPI protocol, which appears as two bytes on receiver side.

author: eutectique
author_rank: Frequent Contributor
timestamp: November 09, 2023, 11:59:26 am
content: Clock polarity is suspicious. From your register settings, it should be low when idle.

author: gpr
author_rank: Contributor
timestamp: November 09, 2023, 12:08:31 pm
content: Quote from: eutectique on November 09, 2023, 11:59:26 amClock polarity matters only when SPI is active and NSS is pulled low, nobody cares about the state of clock line when nss is high.I don't see anything wrong about it on screenshots.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 12:18:44 pm
content: Quote from: gpr on November 09, 2023, 11:57:18 amThe buffer from which the data is read is typed as 8 bits. The compiler takes care of only writing 8 bits in the SPI data register. Same goes for the receiver.Also the data I see in the logic analyzer SPI decoder is correct and in line with what I send.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 12:29:53 pm
content: Quote from: eutectique on November 09, 2023, 11:59:26 amQuote from: gpr on November 09, 2023, 12:08:31 pmgpr is correct about this. As soon as the SPI peripheral is disabled the signal lines are set to float. On enabling the clock line goes low, which sets the correct level for the chosen clock polarity. When looking at the signals on an oscilloscope one can see a capacitive charge curve from low to high on the signal lines. That is why they are delayed in the logic analyzer captures when CSN (nss) goes high.As mentioned before, when I use the SPI busy flag it all works fine. When using the TXE and RXNE flags things go sour. As can be seen in the logic analyzer data the first RXNE comes to soon causing it to read faulty data and on the second RXNE event it then reads the first received data byte, which should have been discarded. After that it reads the up following bytes and then misses the last one received.

author: gpr
author_rank: Contributor
timestamp: November 09, 2023, 12:40:02 pm
content: Quote from: pcprogrammer on November 09, 2023, 12:18:44 pmCompiler follows C rules and uses destination variable to determine data width, for DR it is 32-bit. So, without cast it will generate 32-bit write to DR register. You can look at generated assembly or use some online compilers.Again, I'm not saying this is the root of problem for your case, but in my code I'd eliminate possibility of potential issues here by using a cast.

author: eutectique
author_rank: Frequent Contributor
timestamp: November 09, 2023, 12:51:28 pm
content: According to the reference manual,SPI_DRis 16-bit register. Only ST knows how 8-bit or 32-bit accesses are implemented.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 12:58:17 pm
content: Quote from: gpr on November 09, 2023, 12:40:02 pmI will check what the compiler makes of it, but I'm not worried about it causing issues. The reference manual describes it as a 16 bit register and when the peripheral is set to 8 bits mode the top 8 bits are forced to 0. I assume the bus into the peripheral drops the top 16 bits when a 32 bit write is done and clears the top bits when a 32 bit read is done. Writing 16 bits when in 8 bits mode causes no problems going by the manual.But there in lies the problem. I'm going by the manual while writing my code and it gives weird results

author: gpr
author_rank: Contributor
timestamp: November 09, 2023, 01:32:06 pm
content: A few more thoughts:1. You are not always checking TXE before accessing DR. From datasheet:Code:Note that TXE flag should be ‘1 before any attempt to write the Tx buffer is made.2.http://efton.sk/STM32/gotcha/g66.htmlSPI master Tx outputs 16 clocks, even if its Data Size set to 8 bitI find those stm32 gotchas collection very useful, you can search for all spi-related topics here. Don't blindly trust datasheets.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 01:38:03 pm
content: Curious as I am, I decided to try interrupts. With careful placing of the interrupt enable and disable I managed to get it working, but it is not really continuous. There are gaps between the bytes and the overall time taken is almost the same as with the single check on the busy flag.In the capture the TXE and RXNE signals are generated by the interrupt routine, so when the TXE flag is set by the hardware the interrupt routine is called and this sets the GPIO high on entry and low on exit.The code used:Code:volatile uint32_t receive_counter;volatile uint32_t discard_first_byte;volatile uint8_t *buffer_pointer;void nrf905_read_spi(uint8_t command, uint8_t *buffer, uint32_t length){//Set the number of bytes to receive in the interrupt routinereceive_counter = length;//Signal that the first byte needs to be discardeddiscard_first_byte = 1;//Set the pointer to where the interrupt routine should store the databuffer_pointer = buffer;//Start a communication session by enabling the peripheral, which sets the NSS line lowSPI1->CR1 |= SPI_CR1_SPE;//To start a session with the NRF905 a command needs to be sendSPI1->DR = command;//Enable the needed interruptsSPI1->CR2 |= SPI_CR2_TXEIE | SPI_CR2_RXNEIE;//Wait for the bytes to be received. The interrupt routine lowers the counter,//so when it becomes zero the last byte is receivedwhile(receive_counter);//Make sure the peripheral is donewhile(SPI1->SR & SPI_SR_BSY);//Disable the needed interruptsSPI1->CR2 &= ~(SPI_CR2_TXEIE | SPI_CR2_RXNEIE);//Disable the peripheral to make the NSS line highSPI1->CR1 &= ~SPI_CR1_SPE;}void nrf905_irq_handler(void){register uint8_t data;//Check if the transmitter empty is flaggedif(SPI1->SR & SPI_SR_TXE){//Set pin for time measurementGPIOB->ODR |= (1 << 15);//Check here if a new transmission is needed depending on the receive counterif(receive_counter > 1){//Load a byte to keep the clock going and to clear the interruptSPI1->DR = 0;}else{//Done so ignore further interruptsSPI1->CR2 &= ~SPI_CR2_TXEIE;}//Clear pin for time measurementGPIOB->ODR &= ~(1 << 15);}//Check if data reception is flaggedif(SPI1->SR & SPI_SR_RXNE){//Set pin for time measurementGPIOB->ODR |= (1 << 14);//Read the data to clear the interruptdata = SPI1->DR;//See if the data is wantedif((discard_first_byte == 0) && (receive_counter)){//if so write it in the buffer*buffer_pointer = data;//Select the next buffer locationbuffer_pointer++;//Decrement the receive counterreceive_counter--;}//Clear the flag to signal first byte has been discardeddiscard_first_byte = 0;//Clear pin for time measurementGPIOB->ODR &= ~(1 << 14);}}

author: gpr
author_rank: Contributor
timestamp: November 09, 2023, 01:42:41 pm
content: You might also look at st hal driver:https://github.com/STMicroelectronics/stm32f1xx_hal_driver/blob/3f810594a401e7b17771e6ad1d7c86bdfbb45cb0/Src/stm32f1xx_hal_spi.c#L1316-L1341

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 01:49:23 pm
content: Quote from: gpr on November 09, 2023, 01:32:06 pmTrue, but when using the busy flag one can be sure that the transmission has finished and therefore the TXE flag will be set. Same goes for the RXNE flag, that when the peripheral is finished there will be data in the receive buffer. Based on the mode used that is. I'm only talking about full duplex master mode with a single slave.Quote from: gpr on November 09, 2023, 01:32:06 pmInteresting. That is a good tip. I knew about the data packing for the USB peripheral, but not for the SPI in the F0 and F3 series. Since this is a F1 it is not an issue, but I will adapt my code to make it more robust.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 01:53:18 pm
content: Quote from: gpr on November 09, 2023, 01:42:41 pmI hate the HAL stuff. Way to much crap for doing simple things, and it most likely looks like what is done in Arduino land. Send a byte, check TXE, check RXNE, check BUSY and then read a byte. (Like how it is done in this examplehttps://klinikarduino.github.io/stm32f103_spl/part_5/index.html#examplecode)

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 02:42:33 pm
content: While playing with the interrupt setup, I found that when I enabled the RXNE interrupt before enabling the SPI peripheral and sending the command byte it triggered the interrupt directly, as if there was data in the receive buffer already. So I tried this with earlier code that uses the TXE and RXNE flags to read the data register after enabling the peripheral, but before sending the first byte. This does make a difference. It started working, but, I guess, due to an interrupt routine it fails eventually on a data overrun.The attached captures show this happening. At the end due to the overrun the last byte is not seen and the code hangs on the RXNE wait loop. The first RXNE wait loop is stretched causing the overrun. In the one with a gap the interrupt seems to be in the TXE wait loop and there it does not matter.To be safe and unencumbered by other interrupt routines, I will use the code based on the BUSY flag. Single byte per transaction without risk of overrun due to interrupt routine delays.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 03:13:17 pm
content: Bummer, the busy flag needs some time after writing to the data register to become high. I removed the code to set and clear the GPIO lines for the timing measurements, and it failed to work properly. To overcome this I added the wait on the TXE flag back in and it is now working without problems.Without the GPIO control it takes a bit less time to get the data. ~26us which is nothing on the 10ms interval. With the two timing signals generated it was ~33us.The code:Code:void nrf905_read_spi(uint8_t command, uint8_t *buffer, uint32_t length){//Start a communication session by enabling the peripheral, which sets the NSS line lowSPI1->CR1 |= SPI_CR1_SPE;//To start a session with the NRF905 a command needs to be send*(volatile uint8_t *)&SPI1->DR = command;//Wait until the transmit register is empty. Is needed because the busy flag needs some cycles to become onewhile((SPI1->SR & SPI_SR_TXE) == 0);//Wait until the peripheral is donewhile(SPI1->SR & SPI_SR_BSY);//The first byte received is the status register and needs to be ignored//Use the buffer to read the byte into and later overwrite it with the actual data*buffer = SPI1->DR;//Need to wait for the command to be transfered before loading the next byte//and then receive all the data bytesfor(;length > 0;length--){//Transfer a null byte to clock in the byte to be received*(volatile uint8_t *)&SPI1->DR = 0;//Wait until the transmit register is empty. Is needed because the busy flag needs some cycles to become onewhile((SPI1->SR & SPI_SR_TXE) == 0);//Wait until the peripheral is donewhile(SPI1->SR & SPI_SR_BSY);//Load it into the buffer*buffer = SPI1->DR;//Point to the next location in the bufferbuffer++;}//Disable the peripheral to make the NSS line highSPI1->CR1 &= ~SPI_CR1_SPE;}

author: mikerj
author_rank: Super Contributor
timestamp: November 09, 2023, 05:37:31 pm
content: All of those while loops without any timeouts are a big invitation to have your code hang.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 09, 2023, 07:27:02 pm
content: Correct and it actually happens in case of an overrun situation, but it is in development phase testing the basic premise, so I don't care about that yet.There is more then one way to cope with it. Either a counter to limit the number of loops, a check against system timer ticks, a watchdog timer, etc.

author: wek
author_rank: Frequent Contributor
timestamp: November 10, 2023, 09:21:44 am
content: It would be interesting to find outwhat chipare you using, and also try your code on an STM32F103.JW

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 10, 2023, 10:20:05 am
content: Quote from: wek on November 10, 2023, 09:21:44 amAccording to the ID read by openocd and markers on the chip they are genuine STM chips.Unfortunately I'm not as sharp anymore as I used to be so things take longer, but I figured out what my problem is in this case. On startup of the receiver module a configuration is written to the NRF905. This leaves the SPI module in a state where there is data in the receive buffer and the RXNE flag is high due to it. My expectation was that the flags would be cleared on disabling or enabling the SPI peripheral, but they remain as they were.When using the Arduino setup with SPI.transfer() this does not happen because for every byte (word) transferred it also reads the data register which clears the flags.I wrote my own SPI write function that only writes to the data register and this leaves the RXNE flag in a high state. So reading the data register at the end of that function should clear the flags and everything should be ok.The code as it was.Code:void nrf905_write_spi(uint8_t command, uint8_t *data, uint32_t length){//Start a communication session by enabling the peripheralSPI1->CR1 |= SPI_CR1_SPE;//To start a session with the NRF905 a command needs to be send*(volatile uint8_t *)&SPI1->DR = command;//Transfer all the remaining data bytesfor(;length > 0;length--){//Wait until the register is emptywhile((SPI1->SR & SPI_SR_TXE) == 0);//Transfer the current byte*(volatile uint8_t *)&SPI1->DR = *data;//Point to the next byte to transferdata++;}//Wait until the register is emptywhile((SPI1->SR & SPI_SR_TXE) == 0);//Wait until the peripheral is donewhile(SPI1->SR & SPI_SR_BSY);//Disable the peripheral to clear the NSS lineSPI1->CR1 &= ~SPI_CR1_SPE;}For the while loops in this case adding timeouts is not necessary, because when the build in hardware fails (SPI peripheral, clock system, internal buses) it does not matter that the code hangs. Without proper working hardware the whole software is useless.It is a question of risk assessment.

author: temperance
author_rank: Super Contributor
timestamp: November 10, 2023, 10:37:49 am
content: QuotePossibly derailing the thread... If so I will start a new topic.I use the same method and just have the watchdog expire when a flag doesn't behave as it should because the peripheral being addressed is not responding anymore.For many simple embedded systems this is acceptable. But how do you professional programmers solve this problem where this is not acceptable without writing tons of code to catch each possible peripheral error state? I've been thinking about this many times and it quickly leads to a lot of code which is difficult to test.

author: wek
author_rank: Frequent Contributor
timestamp: November 10, 2023, 10:43:50 am
content: > According to the ID read by openocd and markers on the chip they are genuine STM chips.Which ID?I'm not sure either is a proof of genuinity (contrary, I'm quite sure the second (markers) isn't). The copycats make effort to appear as identical as possible. There was a thread here about distinguishing the various bluepills, but I don't have a link, I'm not interested.> This leaves the SPI module in a state where there is data in the receive buffer and the RXNE flag is high due to it.So, simply, software error. That explains a lot.> My expectation was that the flags would be cleared on disabling or enabling the SPI peripheral, but they remain as theyNice catch!Exact effect of SPI_CR1.SPE (and similarly enable bits in other peripherals) is not that well described in the manual. If you want to safely return to "after reset" state, a sure bet is to... reset the peripheral... using its dedicated RCC_AxBxRSTR bit.JW

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 10, 2023, 11:50:18 am
content: Quote from: wek on November 10, 2023, 10:43:50 amopenocd returns this info.Info : device id = 0x20036410Info : flash size = 64kbytesAlso have several that show 128KB of flash.I once received bluepills with CS32 chips on them and they use a different ID.Quote from: wek on November 10, 2023, 10:43:50 amWheter the ones I have are really genuine does not matter that much to me. As long as they work in my projects, I'm happy. Nothing commercial going onQuote from: wek on November 10, 2023, 10:43:50 amYep, but that is always a problem with this kind of bugs, figuring out why things go wrong.Quote from: wek on November 10, 2023, 10:43:50 amThanks, and the reset also came to my mind, but was not sure anymore how it is done in the STM32F1 range. Was about to open up the manual to look it up. Used it on the Allwinner F1C100s so was aware of the existence.

author: peter-h
author_rank: Super Contributor
timestamp: November 10, 2023, 12:33:01 pm
content: QuoteI spent many many hours on the 32F417 and found the same.The basic problem is that the peripherals on these chips are completely asynchronous to the CPU (they are various 3rd party "IP blobs" added to the ARM-licensed "IP-blob" which is the CPU) plus they also run at a much slower clock, and wait states (possibly dozens of them) are generated internally. but not always when they should be; it looks like ST made it work right with interrupts or DMA, while a tight polling loop always breaks.The solution is indeed to perform some "NOP" on the said peripheral, and reading some register which has no effect will waste the right amount of time, no matter how slowly the peripheral is running.Also most if not all STM32 SPI registers are actually 16 bit; this is necessary to support the 16 bit SPI mode (which incidentally is sometimes handy). Also see herehttps://www.eevblog.com/forum/microcontrollers/32f417-spi-running-at-one-third-the-speed-it-should/

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 10, 2023, 12:50:08 pm
content: A problem with the STM documentation is that it does not tell the complete story, but I have seen worse. One just have to deal with it and learn the hard way how things work.Have done DMA based and just yesterday interrupt based SPI, and it is not that difficult, but it is getting to know the quirks, and note them down for future use. Did not need the latter when I was youngThe benefit of using DMA or interrupts over polling is no or less risk on overrun due to other interrupt interference.

author: peter-h
author_rank: Super Contributor
timestamp: November 10, 2023, 04:16:11 pm
content: Also noticed you use NSS.Not many people have used hardware NSS, due to inflexible timing. It may work if talking to just one device.https://stackoverflow.com/questions/35780290/how-can-i-use-hardware-nss-spi-on-stm32f4

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 11, 2023, 07:45:51 am
content: Yes, the hardware NSS works fine for me. All that is connected is a NRF905 module, which needs the NSS line to go high between sessions. This is the easiest solution for my setup.The interrupt solution, without the setting of the GPIO for timing measurement shows a continuous data flow on the logic analyzer. The SPI clock is only 4.5MHz, so not as high as what you use on the F4. The NRF905 can handle up to 10MHz, but I like to play it safe, and for my setup it does not matter. It is a replacement for an old 27MHz 4 channel remote control, where the repetition rate is 20ms. With 10ms it is already an improvement

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 11, 2023, 01:40:29 pm
content: Yes, it will do that gap in full duplex mode.If you want the fastest RX, set it to bidirectional half-duplex (BIDIMODE=1), RX only mode (BIDIOE=0).Plus you will save a pin, as the same pin will do SDI and SDO, the majority of devices will be fine with shorting SDO and SDI.Normally SDO is Hi-Z when not transmitting, and SDI state doesn't matter when doing so.This mode doesn't require sending data to receive it, only a loop checking RXNE and reading DR when 1..This has its own issues too, the spi peripheral goes frenzy mode, starts sending clock non-stop, you'll have to cope with the throughput, but it will also send extra clocks, as when you stop it it will have already send some clocks for the new byte.I remember having serious hw issues with the SPI peripheral in this mode, can't tell if it was in a F103, a F411 or a F429... or my own fault (But I think it wasn't, I spent lots of hours on it and found plenty of similar issues in ST forum).I recall is having to use RCC peripheral reset to recover it, as it held busy forever under certain conditions, and disabling / enabling / reconfiguring SPI had no effect.Edit: Found it:https://community.st.com/t5/stm32-mcus-products/solved-unable-to-reset-spi-busy-flag-in-slave-mode-by-any-means/td-p/161597Plus always enable the pull resistor for the SPI clock pin, as it also goes Hi-Z when disabling SPI, causing unexpected issues:https://community.st.com/t5/stm32-mcus-products/solved-sck-idling-high-when-spi-module-is-stopped/td-p/66426

author: peter-h
author_rank: Super Contributor
timestamp: November 11, 2023, 01:58:16 pm
content: You need to disable the SPI to get NSS=1, IIRC. It is a strange thing to do.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 12, 2023, 06:27:08 am
content: @DavidAlfa.Thanks, the pulldown option sounds good for keeping the clock line low. With hardware NSS it does not really matter, although I saw the logic analyzer choke on it once in a while, but did not notice it in my receiver.Enabling the SPI module before lowering the software NSS line is also an option to avoid the problem.With the use of the RXNE flag instead of the BSY flag it does not have a gap when the transmission is done properly, but due to other interrupt routines it gave overrun problems. Using the SPI interrupts with proper priority overcomes this problem.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 12, 2023, 01:35:26 pm
content: Are you running SPI RX in ISR mode? Getting an IRQ for every received byte?The overhead and latency will be awful, you'll easily get overrun errors.If the received data is small and you have enough free ram, what about DMA?Or just receive the entire data within the ISR, as long as it doesn't stay there for a lot of time.

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 12, 2023, 05:13:45 pm
content: I tested it with both TX and RX via an interrupt routine and it worked without a hitch. The SPI is only running on 4.5MHz so ~1.8us per byte. On 72MHz CPU clock not a very big problem. Could do DMA, but why bother when it works as intended.The setup I'm going to use is polling on the BSY flag to make sure a byte is in and no overrun can happen. Gives the gap between the bytes, but that is not a problem. For a remote control it is fine on a 10ms interval, but I could push it to 5ms. Not much faster due to the transmission time of the NRF905. The original 27MHz version has a 20ms interval.Once the basics are done I will tweak the CPU clock down to reduce on power consumption.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 12, 2023, 05:24:25 pm
content: 500KHz ISR is pretty heavy!On a 72MHz CPU, you have only 144 clocks.That includes ISR overhead, context saving/restoring, you ISR code, higher priority ISRs...ARM instructions execute in 1, 2, 3 4 clocks, not forgetting branches causing cache miss, adding flash access delay.If you plan to lower the CPU clock, it can get only worse.All this for 10 bytes? Just receive them all inside the ISR, setting the highest priority to avoid disturbances, it's just 18us!

author: pcprogrammer
author_rank: Super Contributor
timestamp: November 12, 2023, 08:52:35 pm
content: I see your point, and the benefit of handling it in an interrupt routine with the highest priority is that it blocks out everything else, so no interference during reception. The same would be to do it in the main loop with interrupts disabled during the reception. DMA would indeed even be better, but for this simple system there is no need to complicate things.My initial problem was due to a misconception about what happens when the peripheral is disabled after transmission and then enabled for reception. I expected the flags to be cleared, but they remain as they were, which caused the RXNE to be seen from the previous action.The system setup is on the one hand the transmitter that only sends a message every 10ms, which leads to the receiver only receiving a message every 10ms. After the data ready signal of the NRF905 there is plenty of time to read the data. Does not matter if it takes 18 or 28us or even longer. The only other thing the main loop has to do is to read the analog inputs for servo feedback. The motor control is done in a timer interrupt routine. All in all nothing special.

author: DavidAlfa
author_rank: Super Contributor
timestamp: November 12, 2023, 09:06:59 pm
content: And you can't afford a 20us stall every 5-10ms? That's nothing.Are you the master, right?You get a IRQ signal from a pin in the device, indicating new data available, so you start the SPI thing to receive the bytes?Unless your main loop is slow, probably you can skip the interrupts at all.