Electronic – STM32 SPI not working as I expect it should based on online reading

microcontrollerrfm69spistm32f103c8t6

I am using an STM32F103C8 to connect to a Hope RF95W transceiver IC for the purpose of learning.

I am only trying to read the chip version register, then write a config register that has a 0x00 reset value and then read it to make sure my write code works.

The RF95W datasheet says about SPI transfer:

SINGLE access: an address byte followed by a data byte is sent for a
write access whereas an address byte is sent and a read byte is
received for the read access. The NSS pin goes low at the beginning of
the frame and goes high after the data byte.

MOSI is generated by the master on the falling edge of SCK and is
sampled by the slave (i.e. this SPI interface) on the rising edge of
SCK. MISO is generated by the slave on the falling edge of SCK.

A transfer is always started by the NSS pin going low. MISO is high
impedance when NSS is high. The first byte is the address byte. It is
comprises:

  • A wnr bit, which is 1 for write access and 0 for read
    access.

  • Then 7 bits of address, MSB first.

To me this means that reading a register requires only one transfer. Sending the address of the register, then waiting for the SPI_I2S_FLAG_RXNE flag, then the value of this register will be in the SPI data register.

However what's happening is I need two sequences of these write/flag/read operations to get the value:

uint8_t read_register(uint8_t address){

  uint8_t data;
  GPIO_ResetBits(GPIOA, GPIO_Pin_3); // slave select (low)
  delay_ms(100);

  while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
  SPI_I2S_SendData(SPI1, address); // send  

  while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
  data = SPI_I2S_ReceiveData(SPI1); // read received

  while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
  SPI_I2S_SendData(SPI1, address); // send

  while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
  data = SPI_I2S_ReceiveData(SPI1); // read received

  GPIO_SetBits(GPIOA, GPIO_Pin_3); // slave deselect (high)
  return data; 
}

Only by having these send/receive and then another send/receive can I get the expected value from the register. If don't make the second send, the receive returns a 0x00.

Writing is a little more straightforward, because it's a write of address, followed by a data write.

void write_register()
{
  uint8_t numRead1 = 0;

  GPIO_ResetBits(GPIOA, GPIO_Pin_3); // slave select (low)
  delay_ms(100);

  while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
  SPI_I2S_SendData(SPI1, 0x40 | 0x80); // send 
  while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
  SPI_I2S_SendData(SPI1, 0x7E); // send
  while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
  numRead1 = SPI_I2S_ReceiveData(SPI1); // read received

  GPIO_SetBits(GPIOA, GPIO_Pin_3); // slave deselect (high)
}

In this case, I expect numRead1 to be 0x00 which is the default value of the register I am writing to. A subsequent call to read_register with the same address returns the value I wrote to it.

But I am sure there's a fundamental mistake in what I am doing which if not corrected will lead to bigger problems down the road.

Any thoughts?

Best Answer

The Hope chip has to get the address of the register you wish to read, that happens on the first byte transfer, at which point it's loaded into the Hope SPI MISO register to be transmitted as the next (dummy) byte is sent out by the master (since it's a read operation).

Alternatively the next byte transmitted out can be another read/write operation, but the pump has to be primed so to speak, with reads being sent back on the next byte SPI transaction.