STM32 SPI slave inconsistent  - Page 1
Forum section: electronics / general

author: sam999
author_rank: Newbie
timestamp: March 29, 2021, 03:22:09 pm
content: Hi,I'm using an STM32F429 Discovery board. I have SPI3 configured as a full-duplex slave (currently used only to send) which sends data out to a Beaglebone as a Master. The Beagle supplies a 10MHz clock and SS to the Discovery board. For now, the clock is pulsed/polled every 2ms to get the header after which the data is clocked through, this loops.My issue is that every so often the MISO pin stays low after the header is correctly sent and no data is clocked though, as verified on an oscilloscope. This seemingly happens at random, sometimes it may be once every 100 or so transfers and other times happens after just a few transfers. I have played around with the GPIO settings such as speed and pull without success so far.As a side note, I don't understand why I need to specify the first 2 words / 32 bits as highlighted in the code below, in order to actually just clock through one 16 bit word. If I specify 1 word, no data is ever clocked through. However in the case of the header, 4 does mean four words are clocked though.The very simple code below is producing the problem.Code:uint16_t header = {1,2,3,4};uint16_t data = 32768;While(1){HAL_SPI_Init(&hspi3);if (hspi3.State != HAL_SPI_STATE_BUSY_TX){HAL_SPI_Transmit(&hspi3,(uint8_t*)&header,4,50);}if (hspi3.State != HAL_SPI_STATE_BUSY_TX){HAL_SPI_Transmit(&hspi3,(uint8_t*)&data,2,50);}HAL_SPI_DeInit(&hspi3);Hal_Delay(20);}-------------------------------------------------------------------------------------------------------------hspi3.Instance = SPI3;hspi3.Init.Mode = SPI_MODE_SLAVE;hspi3.Init.Direction = SPI_DIRECTION_2LINES;hspi3.Init.DataSize = SPI_DATASIZE_16BIT;hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;hspi3.Init.NSS = SPI_NSS_HARD_INPUT;hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi3.Init.TIMode = SPI_TIMODE_DISABLE;hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi3.Init.CRCPolynomial = 10;Could anyone please help with this?Thanks

author: julian1
author_rank: Frequent Contributor
timestamp: March 29, 2021, 08:21:15 pm
content: I think the guard stmts should block until the spi is ready to tx,eg. instead ofif (hspi3.State != HAL_SPI_STATE_BUSY_TX)it should be,while (hspi3.State == HAL_SPI_STATE_BUSY_TX);QuoteYes, googling for hal docs, it looks like the len is the word len, and not byte length. confusing because data is cast to byte pointer.So,HAL_SPI_Transmit(&hspi3,(uint8_t*)&data,2,50);should be,HAL_SPI_Transmit(&hspi3,(uint8_t*)&data,1,50);I haven't used Hal, but notice I sometimes have to wait for two flags, spi status register not busy and spi transfer finish, in order to sequence spi. Might be worth checking. Otherwise maybe try concatenating the header and the data, and send in one call.

author: sam999
author_rank: Newbie
timestamp: March 30, 2021, 02:20:06 pm
content: Thanks for the reply, unfortunately that didn't help resolve the problem. Interestingly I'm seeing the problem happens much less frequently at a higher master CLK speed (17MHz). The beagle waits a few ms before clocking through the data so I don't think the issue is related to tight timings.If I change the length to 1 word I never get any data on the MISO line.I would much rather keep the header separate to the data since I use a variable offset to the data array as part of the HAL_SPI_Transmit.I feel like I must be missing something obvious here as this is a very simple implementation.Thanks

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 30, 2021, 05:03:52 pm
content: The problem, as I see it, is that you're basically handling communication as though it was a SPI master, but it's a SPI slave. I'm gonna elaborate.In SPI slave, obviously "data to be sent" must be ready (ie. written to the output register) *before* it's clocked out by the master. The way you did it, the data *after* the header is NOT guaranteed to be ready, because you're using two separate calls to HAL_SPI_Transmit(). This function is a blocking call and will return only when the transmission has been fully completed. Which means that when your first call to HAL_SPI_Transmit() returns, the header has been fully transmitted. Now if the master keeps on clocking for the "data" part, the first data word may not be ready to be sent by the slave - for instance if the next SPI clock happens just before the second call to HAL_SPI_Transmit() has had a chance to fill in the transmit buffer. Calling it with a 2-word buffer instead of 1 gives the SPI slave peripheral an opportunity to have its data ready when the master keeps clocking. Have you checked what the master receives in this case exactly?Successive calls to HAL_SPI_Transmit() like this works for master SPI, but for slave SPI, it's not the way to go.There are a couple ways to do this correctly. The easiest here would be to put both the header and the data in the same buffer in just one HAL_SPI_Transmit() call. Another would be to use interrupts - in this case you would probably have to use something else than HAL_SPI_Transmit().As a side note, your error handling here is bogus.- 'if (hspi3.State != HAL_SPI_STATE_BUSY_TX)' serves no purpose: HAL_SPI_Transmit() already checks that the 'State' is HAL_SPI_STATE_READY, and will immediately return an error if not.- If the 'State' is anything other than HAL_SPI_STATE_READY, the code will just not get executed; that might not be what you want.- Additionally, HAL_SPI_Transmit() is already a blocking call, so if all you use it this function to transmit data, there is no need to implement a waiting loop either. HAL_SPI_Transmit() will already wait for the buffer to be transmitted entirely before returning.All in all, remember than in SPI slave mode, data to be sent by the slave must be ready *before* the first clock pulse from the master. That should help you figure out how to do it properly.You could check out the "SPI_HalfDuplex_ComPolling" example project in the corresponding HAL subdirectory. Interestingly, they use the HAL for setting up the SPI peripheral, but they use registers directly for handlng data transmission.

author: sam999
author_rank: Newbie
timestamp: March 30, 2021, 07:21:07 pm
content: Thanks for the reply. I understand the slave must be ready and have tried delays between the master clocked header and data of between tens of us, into ms, and the result is the same. I don't think it's related to the slave not being ready.Quote from: SiliconWizard on March 30, 2021, 05:03:52 pmThe 2-word buffer always results in the data being correctly clocked though at the first master clock after the header, except for the times it stays high. (Sorry previous mistake - stays high rather than low). A one word buffer means the MISO line always stays high for some reason. The Beagle does not accept any data clocked though after the 5th clock (first after header), so the extra buffer doesn't give it any extra chances.I'm aware this whole implementation isn't the most efficient but I can't see why it shouldn't be working reliably. It was supposed to be an quick easy method of reliable comms for development, before moving on to more complicated modes like interrupt transmission which come with their own challenges.With all this in mind, I still can't see why it shouldn't be working.Thanks

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 30, 2021, 11:38:02 pm
content: Quote from: sam999 on March 30, 2021, 07:21:07 pmSo, am I understanding right that you're basically saying: it always works except when it doesn't?That definitely suggests you have a timing issue. With your approach, you are again assuming that the second call to the HAL_SPI_Transmit() will get the data ready for transmission BEFORE the master clocks it. How can you make sure of that?Is the master continuously clocking the header + data, or is there a pause between the header and the data from the master's POV? If it's clocking both continuously as I suspect, or the master is faster than your slave, then your approach can't work reliably. You definitely need to have one word of data buffered in advance.You can do it by polling if you don't want to use interrupts yet, but you can't do it reliably with two calls to HAL_SPI_Transmit() as I said. You'll need to fill the transmit register manually. I suggest either taking a look at the example project I mentioned above, or a look at the HAL source code for the SPI peripheral. You'll get to see how to do it.Now if you want something very simple only using the HAL as you seem to say, you can send the whole packet with just one call to HAL_SPI_Transmit() as I suggested earlier. If you still want to be able to manage the header separately from a programming POV, then just write a small utility function that will prepare a buffer to send, writing the header and the "data" separately to it, and then passing the contructed buffer to HAL_SPI_Transmit().

author: sam999
author_rank: Newbie
timestamp: March 31, 2021, 01:04:12 pm
content: Quote from: SiliconWizard on March 30, 2021, 11:38:02 pmI make sure of that by allowing gratuitous amounts of time in the order of ms between header clocks and data clock from the master, but the bug is just as prevalent.Quote from: SiliconWizard on March 30, 2021, 11:38:02 pmThe Beagle polls for the header every 2ms by pulsing the SCK line until the slave is ready and starts to toggle the MISO line with the first header word. When the Beagle gets a valid first word it then provides three further clocks in short succession for the full header. All is fine at this point. The Beagle then waits/ pauses (up to ms) before pulsing the SCK line again for the data word.Thanks for the other suggestions, I will look into these if needs be, I'm just reluctant to drop this simple implementation without understanding the real issue.

author: tru
author_rank: Regular Contributor
timestamp: March 31, 2021, 02:31:08 pm
content: Perhaps something unrelated, but shouldn't the header variable be an array?So this:uint16_t header = {1,2,3,4};Should be:uint16_t header = {1,2,3,4};

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 31, 2021, 05:09:43 pm
content: Quote from: sam999 on March 31, 2021, 01:04:12 pmIf there is such a "long" pause between the two blocks on the master's side, then I agree your simple approach should work. If you want to fully debug this, I suggest toggling GPIOs both on the slave's side and on the master's side around each block (header, data), and look at them with a scope. That will help you see the real sequence of events, and maybe there's something you have overlooked.

author: JustMeHere
author_rank: Frequent Contributor
timestamp: April 09, 2021, 12:25:41 am
content: I had a problem like this a long time ago. Make sure your CS pins don't float. I always bias them to not selected with pull up/down resistors now.This really helps while programming and booting.

author: DavidAlfa
author_rank: Super Contributor
timestamp: April 09, 2021, 08:16:56 am
content: I had a lot of problems with spi slave modes.Any noise or glitch will start a transaction, hanging the spi peripheral into busy state.Before calling hal transmit, check BSY bit in SR. If busy, it means it received at least one clock, and it's waiting more.That means you lost clocks or got noise in the clock line. Reset the spi hw and re-init.I tried everything to reset the SPI, nothing would clear the Busy flag.After 3 days tearing my hair apart, someone suggested using RCC Reset register (Didn't knew it existed), worked like a charm.https://community.st.com/s/question/0D53W00000gfktLSAQ/solved-unable-to-reset-spi-busy-flag-in-slave-mode-by-any-meansYou also need more checks:- Ensure clock polarity and phase are correct.- Check that the return of Hal transmit is HAL_OK.- Is the 50mS timeout enough?- NSS must be high when starting the HAL transfer, and go low before starting the clocks.If you start the SPI transmit and it's already low, it will fail (at least in my tests).Also, you're using polling mode, so all is done by software. 10MHz will send a byte every 0.8uS.The MCU must be able to check the flags, load the data, and get everything ready in that time.If you run the spi too fast in non DMA mode, it will likely cause buffer underrun.Lower the speed to few KHz and try again, to discard too high speed.If you are using only one spi device, you can get rid of the NSS pin.I use this code to ensure the SPI is ready before receiving/transmitting, and reset the peripheral if not ready.NSS is not used in my case (one 1 master, 1 slave)I set a 1.5mS timer that will reset the SPI if no clock is received in that time (Once a frame starts, max time between bytes is 1mS).Did my own ISR, so every byte received I reset the timer.Once the timer overflows, it restores the SPI to receiving state, also checks the BSY Flag and if so, completely resets the SPI.It works flawless. RCC Reset was the only way to completely reset the SPI H/W no matter its state.Code:if((hspi1.State>HAL_SPI_STATE_READY) && (hspi1.State<HAL_SPI_STATE_ERROR)){// Handler BUSY in any mode (But not error or reset);hspi1.State=HAL_SPI_STATE_READY;// Force ready state}if((hspi1.Instance->SR & SPI_SR_BSY) || (hspi1.State!=HAL_SPI_STATE_READY)){// If peripheral is actually busy or handler not readyhspi1.State=HAL_SPI_STATE_RESET;// Force reset state (HAL_SPI_Init will fail if not in reset state)__HAL_RCC_SPI1_FORCE_RESET();// Reset SPI1asm("nop\nnop\nnop\nnop");// Wait few clocks just in casewhile(hspi1.Instance->SR & SPI_SR_BSY);// Wait until Busy is gone__HAL_RCC_SPI1_RELEASE_RESET();// Release resetasm("nop\nnop\nnop\nnop");// Wait few clocks just in casewhile(hspi1.Instance->SR & SPI_SR_BSY);// Check againif (HAL_SPI_Init(&hspi1) != HAL_OK){ Error_Handler(); }// Re-init SPI}