cancel
Showing results for 
Search instead for 
Did you mean: 

Bare metal SPI implementation problem

NicRoberts
Associate III

Hi,

 

I'm trying to get a bare metal implementation of SPI working on a STM32WB55 Nucleo board.

The transmit appears to work but I cant get the receive function to work. I'm using a loop back by connecting MOSI & MISO with a jumper.

When in debug I can see my Tx buffer getting loaded with the appropriate value but it never appears in the Rx buffer. When I step through the Tx code runs through as expected, in the Rx code though it looks like the SPI_FLAG_RXNE never gets set i.e. nothing ever arrives.
Ultimately I'm trying to connect with 2 x ADXL362s over the SPI bus.

I have attached a zip of the CubeIDE.

Any help with debugging this or pointers to how I go about that would be most appreciated. I'm getting code blindness trying to find the problem. 

11 REPLIES 11
Ozone
Principal

Can you see anything on the pin ?

And, I don't see any transmit in your receive routine.
You are aware that Tx and Rx happens simultaneously, and you need to do dummy transmits to receive anything ?


By the way, I would do the extra work and replace the numbers in the init function(s) with constants from the device header file.

Pavel A.
Evangelist III

When in debug I can see my Tx buffer getting loaded with the appropriate value but it never appears in the Rx buffer

Can it be because you keep the SPI registers opened in the debugger viewer? 


@Ozone wrote:

Can you see anything on the pin ?


Dont have a scope here unfortunately.
 

@Ozone wrote:

And, I don't see any transmit in your receive routine.
You are aware that Tx and Rx happens simultaneously, and you need to do dummy transmits to receive anything ?


The transmit function in spi.c
 
StatusTypeDef spi_transmit(SPI_HandleTypeDef *hspi, uint8_t *p_data, uint16_t size, uint32_t timeout)

 

I thought I was doing a transmit, wating for the Tx buffer to clear before continuing (would that be a dummy tx?) & then checking to see if anything is received. 

Is this to do with the Tx & Rx sharing the same data register, so I cant transmit & simultaneously receive? I thought they had their own separate buffers which share the DR have I got that wrong?

 


@Ozone wrote:

By the way, I would do the extra work and replace the numbers in the init function(s) with constants from the device header file.


Noted, I see the sense in that.

Is that a known issue with the CubeIDE debug?

I have run it just looking at tx_buffer & rx_buffer. tx_buffer loads but rx_buffer remains empty even though the code steps all the way through.

To receive SPI data, you must send SPI data. The bus is synchronous. Your receive function just waits around for RXNE to happen.

If you feel a post has answered your question, please click "Accept as Solution".

> Dont have a scope here unfortunately.

A Salae logic clone for a few bucks and sigrok (https://sigrok.org/wiki/Main_Page) can go a long way.

> Is this to do with the Tx & Rx sharing the same data register, so I cant transmit & simultaneously receive? I thought they had their own separate buffers which share the DR have I got that wrong?

No, this is correct so far. Transmissions are synchronous, thus separate Rx/Tx registers are required.
While one item is clocked out by SCK on MOSI, another item is clocked in on MISO at the same time.
"Item" might be a byte, 16-bit word, or whatever size is possible.

However, these two items transferred simultaneously are not directly related.
To get a specific answer (value) from a SPI slave, you need to first fully transfer it, and then give slace time to process it. Only in the next transfer, the slave can answer with with the requested value (usually transferred by writing a dummy value).

I would recommend to study the protocol definition of the slave device carefully, this is what you need to implement.

Simple ("stateless") slave devices, e.g. without any internal registers, might not need multiple transfers. But I don't know any such device. And the  ADXL362 is surely not such a device either.

NicRoberts
Associate III

OK so I now have a Tx & Rx function that does both at the same time.
Same as previous except just making the call to the below function & not the two tx & rx functions.
This still doesn't work with the loop back (MOSI connected to MISO) though. 
Transmission occurs but never appears at the Rx buffer?
The condition 

/* Check RXNE flag */ if((hspi->Instance->SR & (SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U))

.. is never met. Rx count is larger than 0 so it looks like RXNE is never set i.e. always empty?

 

StatusTypeDef spi_transmit_receive(SPI_HandleTypeDef *hspi, uint8_t *p_tx_data, uint8_t *p_rx_data, uint16_t size, uint32_t timeout) { uint16_t initial_tx_count = size; uint32_t tmp_mode; SPI_StateTypeDef tmp_state; uint32_t tickstart; /* Variables used to alternate Rx and Tx during transfer */ uint32_t txallowed = 1U; StatusTypeDef error_code = DEV_OK; /* Initialise tickstart for timeout management */ tickstart = get_tick(); /* Initialise temporary variables */ tmp_state = hspi->State; tmp_mode = hspi->Init.Mode; initial_tx_count = size; /* Set transaction information */ hspi->ErrorCode = SPI_ERROR_NONE; hspi->pRxBufferPtr = (uint8_t *)p_rx_data; hspi->RxXferCount = size; hspi->RxXferSize = size; hspi->pTxBufferPtr = (uint8_t *)p_tx_data; hspi->TxXferCount = size; hspi->TxXferSize = size; /* Check if SPI enabled */ if((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE) { /* Enable SPI */ SET_BIT(hspi->Instance->CR1, SPI_CR1_SPE); } /* Tx & Rx in 16 bit mode */ if(hspi->Init.DataSize == SPI_DATASIZE_16BIT) { if((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_tx_count == 0x01U)) { hspi->Instance->DR = *((uint16_t *)hspi->pTxBufferPtr); hspi->pTxBufferPtr += sizeof(uint16_t); hspi->TxXferCount--; } while((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U)) { /* Check TXE flag */ if((hspi->Instance->SR & (SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U)) { hspi->Instance->DR = *((uint16_t *)hspi->pTxBufferPtr); hspi->pTxBufferPtr += sizeof(uint16_t); hspi->TxXferCount--; /* As next data is Rx then Tx not allowed */ txallowed = 0U; } /* Check RXNE flag */ if((hspi->Instance->SR & (SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U)) { *((uint16_t *)hspi->pRxBufferPtr) = (uint16_t)hspi->Instance->DR; hspi->pRxBufferPtr += sizeof(uint16_t); hspi->RxXferCount--; /* As next data is Tx then Tx allowed */ txallowed = 1U; } if(((get_tick() - tickstart) >= timeout) && (timeout != MAX_DELAY)) { error_code = DEV_TIMEOUT; hspi->State = SPI_STATE_READY; return error_code; } } } /* Tx & Rx in 8 bit mode */ else { if((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_tx_count == 0x01U)) { *((__IO uint8_t *)&hspi->Instance->DR) = (* hspi->pTxBufferPtr); hspi->pTxBufferPtr += sizeof(uint8_t); hspi->TxXferCount--; } while((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U)) { /* Check TXE flag */ if((hspi->Instance->SR & (SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U)) { *(__IO uint8_t *)&hspi->Instance->DR = (*hspi->pTxBufferPtr); hspi->pTxBufferPtr ++; hspi->TxXferCount--; /* As next data is Rx then Tx not allowed */ txallowed = 0U; } /* Check RXNE flag */ if((hspi->Instance->SR & (SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U)) { (*(uint8_t *)hspi->pRxBufferPtr) = hspi->Instance->DR; hspi->pRxBufferPtr ++; hspi->RxXferCount--; /* As next data is Tx then Tx allowed */ txallowed = 1U; } if(((get_tick() - tickstart) >= timeout) && (timeout != MAX_DELAY)) { error_code = DEV_TIMEOUT; hspi->State = SPI_STATE_READY; return error_code; } } } return error_code; }
View more

 

First, I don't have any STM32WB55 board, but I assume it is similiar to other STM32 MCUs. Although I noticed their SPI periphery units are not all identical, especially in regards to capabilities and flag treatment.

And second, I don't use Cube - for several reasons ... I just leave it at that.

If you currently have nothing to monitor the actual SPI transfers (scope or logic analyser), I would recomment to single-step through through a transmit, and watch the SPI flags. Without any SPI interrupts, disable them if necessary.

As soon as you write to DR register, the TxE flag should be set, because the value is immediately transferred to the output hardware. Then (micro- or milliseconds later), the RxNE flag should assert. Whatever the the state of the MISO pin at the relevant SCK transitions is will be clocked into the DR receive buffer.
Writes to DR always access the transmit buffer, while reads always access the receive buffer. You will need to read the register to see the value in the debugger.

Keep an eye on the status register (SPI_SR). Hopefully your IDE/Debugger is smart enough to decode the bits for you. Check for error conditions after the transmission.
And I would recommend to view the config register (SPI_CR1 and SPI_CR2) as well, decode the values, and compare with the settings you made. I presume the register names are the same as e.g. for the F40x here.

A debugger can be intrusive, as it will clear interrupt flags when reading ISR registers. That does not happen with "normal" status register reads.

If your RxNE flag is never set, your configuration is probably wrong - either on pin assignment level, or SPI peripheral level.

I've stepped through & it looks like the TXE flag is always set & the RXNE is always clear.

CR1 values change, disable-set register-enable, as expected. Though I may be confused about the BIDIMODE and the BIDIOE settings, I've set both bits to 0.

I see the value loaded into DR then SR indicates that the FIFO receive has some activity but doesn't indicate that RXNE. Then DR clears, I tried a 1ms delay & no delay but nothing shows up & RXNE remains 0.