cancel
Showing results for 
Search instead for 
Did you mean: 

Why would SPI send 16 bits instead of 8?

SHobb.1
Associate II

I have configured my STM32G071 SPI peripheral to transmit 8 bits with the following code:

void spi_config(void)
{
  RCC->APBENR2 |= RCC_APBENR2_SPI1EN;   //Enable SPI Clock
 
  //Configure for SPI mode (not I2S mode)
  SPI1->I2SCFGR &= ~SPI_I2SCFGR_I2SMOD;
 
  //MSB first, Master, Clock Phase: 1, Clock Polarity: 1
  //Baud rate (SPI Clock: ~1MHz.  Input clock = 64MHz --> fpclk/64);
  //Full duplex (not Rx only), Config for SW ctrl of SS
  SPI1->CR1 &= ~(SPI_CR1_RXONLY | SPI_CR1_LSBFIRST | SPI_CR1_BR);
  SPI1->CR1 |= (SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_MSTR | SPI_CR1_CPOL | SPI_CR1_CPHA);
 
  //Data format (8 bits) - make sure not to set disallowed value
  SPI1->CR2 |= SPI_CR2_DS_2 | SPI_CR2_DS_1 | SPI_CR2_DS_0;
  SPI1->CR2 &= ~SPI_CR2_DS_3;
 
//  SPI1->CR2 |= SPI_CR2_SSOE;        //Needed if not using SW SS (to avoid SPI1 being forced into slave mode
  SPI1->CR1 |= SPI_CR1_SPE;           //Enable SPI
 
  __IO uint32_t tempRd = SPI1->SR;    //Clear all flags by reading status register
  (void)tempRd;                       //Avoid compiler warning
}

Using the debugger, I have confirmed that the DS bits in CR2 are properly being set to 0111 (8 bits). However, when I scope my MOSI and SCK lines, I see 16 bits being transferred: first the 8 bits I expect, then 8 bits of 0 (8 additional SCK cycles with MOSI held low).

My transmit function is:

bool spi_transmit(uint8_t *pData, uint8_t len, uint32_t timeout)
{
  uint8_t dataIdx = 0;
  uint32_t startTick = rcc_msGetTicks();
 
  SPI1->CR1 |= SPI_CR1_SPE;   //Enable SPI, if not
 
  while(dataIdx < len)
  {
    if(SPI1->SR & SPI_SR_TXE)   //TX Empty
    {
      SPI1->DR = pData[dataIdx];
      dataIdx++;
    }
    else
    {
      if((rcc_msGetTicks() - startTick) >= timeout)
      {
        return(false);
      }
    }
  }
 
  while(SPI1->SR & SPI_SR_BSY) //Wait for busy flag
  {
    if((rcc_msGetTicks() - startTick) >= timeout)
    {
      return(false);
    }
  }
 
  //Clear overrun condition --> DR, SR - see RM0444, p. 1390
  __IO uint32_t tempRd = SPI1->DR;
  tempRd = SPI1->SR;
  (void)tempRd;
 
  return(true);
}

When I call it from main, I set the len argument to 1. Through testing, I have confirmed that the while loop in the transmit function is operating properly (only executing once). However, there are still 16 SCLK cycles and 8 extra 0's transmitted.

Fun facts:

RM0444, p. 1185 says bit 11 of SPIx_CR1 is a CRCL bit (CRC length). Earlier RMs (i.e., RM0008 for the F103RB) calls this bit "DFF" for Data Frame Format. The Debugger calls this bit DFF (for the STM32G081). In any event, it is set to '0' (which should be correct for 8-bit data).

Any suggestions for why my SPI peripheral is sending 16 bits?

7 REPLIES 7
Uwe Bonnes
Principal III

You need to explicit size the access to DR

*(uint8_t*)&SPI1->DR = pData[dataIdx];

Keywords to search in the SPI chapter of RM are "data packing".

JW

Yes, it needs the width, otherwise it packs the FIFO with a bonus byte

We might also want to use volatile in this context, as this also stops the compiler from doing stupid things with optimization or folding..

*(volatile uint8_t*)&SPI1->DR = pData[dataIdx];

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
SHobb.1
Associate II

That did it. Thanks for the advice.

SHobb.1
Associate II

Follow-up question: the page linked to by Jan mentions, "Consequence of data packing on data reading (i.e. Rx) are less conspicuous, but still can lead to confusing errors."

After reading the appropriate section of the RM, I am still not 100% clear how to read the data register if I only want 8 bits. Would I do the same casting? Here is an updated rx/tx function:

bool spi_transmitReceive(uint8_t *pDataTx, uint8_t *pDataRx, uint8_t len, uint32_t timeout)
{
  uint8_t dataIdx = 0;
  bool isTransmit = 1;
  uint32_t startTick = rcc_msGetTicks();
 
  SPI1->CR1 |= SPI_CR1_SPE;   //Enable SPI, if not
 
  //While loop: transmit, then switch to receive mode, manage timeout
  while(dataIdx < len)
  {
    if(SPI1->SR & SPI_SR_TXE && isTransmit)   //Transmit dummy data
    {
      *(volatile uint8_t*)&SPI1->DR = pDataTx[dataIdx];
      isTransmit = false;      //Reset flag
    }
 
    if(SPI1->SR & SPI_SR_RXNE)             //Receive
    {
      pDataRx[dataIdx] = *(volatile uint8_t*)&SPI1->DR;
      dataIdx++;
      isTransmit = true;      //Set flag
    }
    else                                  //Manage timeout
    {
      if((rcc_msGetTicks() - startTick) >= timeout)
      {
        return(false);
      }
    }
  }
 
  while(SPI1->SR & SPI_SR_BSY) //Wait for busy flag
   {
     if((rcc_msGetTicks() - startTick) >= timeout)
     {
       return(false);
     }
   }
 
   //Clear overrun condition --> DR, SR - see RM0444, p. 1390
   __IO uint32_t tempRd = SPI1->DR;
   tempRd = SPI1->SR;
   (void)tempRd;
 
   return(true);
}

Characteristically for ST, the description is rather messy, but basically if you want to fall back to the traditional "normal" single-byte handling, you should both use the cast when reading from the data register, AND set FRXTH. You should read the section in SPI chapter dealing with FIFO and data packing.

The read access must be always aligned with the RXFIFO

threshold configured by the FRXTH bit in SPIx_CR2 register.

I don't quite understand the need for timeout but that is just me. Also, my RM0444 has 1390 pages, so p.1390 does not really contain any comment on overrun/flush, and I also don't quite understand it.

JW

SHobb.1
Associate II

Jan,

Thanks again. I read that register description three times today and somehow discounted the FRXTH bit. I guess it was because I somehow [wrongly] assumed I wasn't using the Rx FIFO. After setting that bit, everything now works (Rx and Tx). The timeout is some boilerplate code I use in lots of my functions - due to my propensity for writing code that gets stuck... As far as the receiver flush, I guess you are right - technically I could check the OVR bit. But just doing the sequence as is (described on p. 1183 of the RM - my comment about p. 1390 is wrong) handles the case anyhow.

Also, that "STM32 Gotchas" page is a gold mine. Thank you for that as well.