3-wire SPI

In regular SPI communication separate lines are used for MISO and MOSI data lines for full duplex communication. To use fewer wires some devices support 3-wire operation where data in both directions is being sent on the same data line in a half duplex configuration. As the data line is shared the master and the slave device must not drive the line at the same time, so each end will tri-state its output while the other is sending. A regular SPI clock must still be supplied to the device. The chip select pin of the slave must also be driven, either tied locally if there is only one slave on the bus, or controlled by the master. 


EFM32 implementation

The EFM32 can operate as both a 3-wire SPI slave and master by using the USART peripheral, but this article will focus on an a master configuration. The 3-wire SPI command protocol will be heavily dependent on the specific slave device that is used, but the example given below implements simple byte read and write functions for an ADXL345 accelerometer.


The RX and TX lines of the EFM32 can be connected together internally in the EFM32 by setting the LOOPBK bit in USARTn->CTRL. When this is done the USn_TX pin of the EFM32 should be connected to the data line of the slave as shown below.




Below is an init-function for setting up the USART to do 3-wire SPI:


#define CS_PORT   gpioPortD 
#define CS_PIN 3
#define CLK_PORT gpioPortD
#define CLK_PIN 2
#define DATA_PORT gpioPortD
#define DATA_PIN 0
#define SPI_USART_CLOCK cmuClock_USART1

void initSpi3Wire()
{ USART_InitSync_TypeDef usartConfig = USART_INITSYNC_DEFAULT;

/* Enabling clock to USART and GPIO */ CMU_ClockEnable(SPI_USART_CLOCK,true); CMU_ClockEnable(cmuClock_GPIO,true); usartConfig.clockMode = usartClockMode3; usartConfig.msbf = true;

/* Configure USART as SPI master */ USART_InitSync(SPI_USART,&usartConfig);

/* Enable internal lookback between TX and RX pin and enable Auto CS */ SPI_USART->CTRL |= USART_CTRL_LOOPBK | USART_CTRL_AUTOCS;


/* Configre GPIOs for SPI pins */ GPIO_PinModeSet(DATA_PORT, DATA_PIN, gpioModePushPull,0);/* Data */ GPIO_PinModeSet(CLK_PORT, CLK_PIN, gpioModePushPull,1);/* Clock */ GPIO_PinModeSet(CS_PORT, CS_PIN, gpioModePushPull,1);/* CS */ USART_Enable(SPI_USART, usartEnable);


Writing data


Writing a byte to the accelerometer is done by first writing the address byte and then writing the data to be written to that address:


void writeSpiByte(uint8_t addr,uint8_t data)
/* Write addr to TXDATA0 to be transmitted first, before TXDATA1 with data * value is sent */ SPI_USART->TXDOUBLE = addr << _USART_TXDOUBLE_TXDATA0_SHIFT | data << _USART_TXDOUBLE_TXDATA1_SHIFT;

/* Wait for TX to complete */


Reading data


Reading a byte from the accelerometer is done by first writing the address byte with a read command bit set as the 7th bit. The the master will tri-state the data pin and clock data back from the slave. 


When sending data the RXDATA registers of the master will normally shift in the data sent. As this data is normally not interesting we can block the receiver while sending data by writing a 1 to RXBLOCKEN in USARTn->CMD. Alternativelly we could also just discard the first byte in the RX buffer.


When the master has sent the address it will need to tristate the TX pin and unblock RX (if this has been blocked). This can either be done manually through the USARTn->CMD register or by setting some of the extra control bits (TXTRIAT and UBRXAT) in the TXDOUBLEX register. Setting these bits will both unblock RX and tri-state the TX line immediately after the first address byte has been sent. The master can then immediately continue to read back the data without additional USART configuration changes. 


An example for reading a byte in the ADXL345 is given below:

uint8_t readSpiByte(uint8_t addr)
{ SPI_USART->CMD = USART_CMD_RXBLOCKEN;/* Block RX while sending from master */ SPI_USART->CMD = USART_CMD_CLEARRX; /* Clear any old RX data */

/* Write data for sending read command with address and reading back data * TXDATA0 contains read command and addres. * TXDATA1 contains dummy data to be sent when clocking back read data. * The TXTRIAT0 and UBRXAT0 bits will tri-state the TX pin and unblock RX * after the first byte has been transmitted. * The TXTRIAT and UBRXAT bits really only need to be set for TXDATA0, but * because of errata USART_E101 on some devices, we set these bits for TXDATA1 * as well. This works also for devices that does not have this errata. */ SPI_USART->TXDOUBLEX =(addr |1<< READ_CMD_BIT)<< _USART_TXDOUBLEX_TXDATA0_SHIFT | USART_TXDOUBLEX_TXTRIAT0 | USART_TXDOUBLEX_UBRXAT0 |0x00<< _USART_TXDOUBLEX_TXDATA1_SHIFT | USART_TXDOUBLEX_TXTRIAT1 | USART_TXDOUBLEX_UBRXAT1;

/* Wait for valid RX DATA */
while(!(USART_StatusGet(SPI_USART)& USART_STATUS_RXDATAV)); SPI_USART->CMD = USART_CMD_TXTRIDIS; /* Turn off TX tri-stating */









  • Knowledge Base Articles
  • 32-bit MCUs
  • Hi,

    im trying to use this example as a starting point for my own project. I am using a Leopard Gecko STK and Simplicity Studio 3. When I am trying the compile the above code, it tells me that "READ_CMD_BIT" is undeclared (used in the readSpiByte() method above). What is it for and where do I set it?

    Thanks in advance


  • Hi @marc1,


    I believe the "READ_CMD_BIT" as it is used in the readSpiByte() function above is a bit position in the address byte that the particular accelerometer in this example is expecting.  In other words, the accelerometer is expecting you to first send an address, and depending on whether a particular bit in that address is set or cleared, it will know whether you intend to read or write to the device (and thus whether the accelerometer needs to transmit or receive on the next byte frame).  Thus, the READ_CMD_BIT is not something on the EFM32 that you need to set, and this functionality will be dependent on the slave device communication protocol.


    I hope this helps.