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)

  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)

  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?

up vote 4 down vote accepted

The slave device can only communicate when it is provided a clock from the master (your STM32). This complicates reading from the slave, because you have to cause the master to provide enough clock cycles for the slave to respond.

When you send an SPI command via SPI_I2S_SendData(), two transmission actually happen during the same eight clock pulses. The first is that your byte is clocked out of the MOSI line. But, at the same time, data is being clocked in to the microcontroller via the MISO line.

But since the slave doesn't get the full command until the end of these transactions, it doesn't present any data to the bus. This results in a received value of 0x00 or 0xFF.

Then you need to provide an additional eight clocks to allow the slave to return the actual value. With the STM32, this is done by sending a "dummy byte" to the slave.

Note that, in the first transmission, the master ignores whatever arrives from the slave. In the second transmission, the slave ignores whatever is sent by the master.

The code should look something like this:

// Activate the SS pin

// Send the command
SPI_I2S_SendData(SPI_BUS, txbyte);

// When the byte completes sending, the RXNE flag will get set
// and must be cleared by reading the Data Register.
// Notice that the value in the data register is ignored.
while (SPI_I2S_GetFlagStatus(SPI_BUS, SPI_I2S_FLAG_RXNE) == RESET);
SPI_I2S_ReceiveData(SPI_BUS); // Notice there is no variable assignment here!

// Now get the response. Load the data register (DR) with a dummy
// byte to start data reception.
SPI_I2S_SendData(SPI_BUS, 0);

// When the dummy byte completes sending, the RX buffer will contain the
// response byte. Get it.
while (SPI_I2S_GetFlagStatus(SPI_BUS, SPI_I2S_FLAG_RXNE) == RESET);
rxbyte = SPI_I2S_ReceiveData(SPI_BUS);

// Release the SS line
  • 1
    Some chips (maybe not this one) will preemptively send their status register during the address word. It costs essentially nothing, and it's not infrequently wanted, in which case only a single word's worth clocks is needed. – Chris Stratton Oct 23 at 0:08
  • Very true, @Chris :) I only wanted to present a basic, generic case. – bitsmack Oct 23 at 0:31

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.

  • Thanks Isdi! I suspected as much but wanted to verify. – Sam Hammamy Oct 22 at 22:41
  • Do you have an example of a professional / clean code that does this? I am a hobbyist but I am a soft. developer and want my code to be clean! – Sam Hammamy Oct 22 at 22:42
  • 1
    I'm a believer in writing code that the coder can understand 1 year after looking at it. I've seen some TI embedded stuff that makes an embedded programmer with 40 years cringe with the amount of pointer passing (my words not his) just so they're "unified" across their SimpleLink platform. One thing I would do is check the setup time for the chip select, I'm positive you don't need 100ms, probably closer to 100ns; but check the Hope datasheet to be sure. – isdi Oct 22 at 22:50

Your Answer


By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.