waclawek.jan

Using USART instead of SPI

Blog Post created by waclawek.jan on Mar 4, 2018

In STM32, there is often a shortage of SPI modules, while USARTs/UARTs are relatively abundant. For example, the 'F20x/40x line has 3 SPIs and 4 USARTs + 2 UARTs. SPIs can also act as I2S, so in applications, where I2S are needed together with some high-speed SPI, it's not uncommon to run out of SPIs.

 

SPIs are, among other things, often used in master mode to control shift-registers, or shift-register-like peripherals. This is where USARTs in synchronous mode can come to help out.

 

The setup is relatively trivial: in GPIO, besides USART_RX and USART_TX a third pin has to be set to appropriate AF for USART_CK. In USART itself, baudrate has to be set in USART_BRR; and as a last step of setup TE/RE as appropriate, and UE, in USART_CR1 - these steps are identical to setting up the USART for UART. Don't bother setting parity etc. - these may work just as with USART, but it would be surprising if any shift-register-like peripheral would need them.  In USART_CR3, set DMAT/DMAR as appropriate, if you intend to use DMA - just as with UART.

 

The synchronous-specific part is in USART_CR2: CLKEN has to be set to enable the synchronous mode and the clock output onto USART_CK pin. CPOL/CPHA has to be adjusted as needed - they work exactly as in "normal" SPI, and there's even the timing chart in the USART synchronous mode subchapter of USART chapter in RM, if you are unsure which to choose.  There's a gotcha in form of LBCL bit - I fail to see any merit in not having this bit set.

 

So, an example setup of a transmitting-only USART used to control an OLED display with SPI-like interface may look like

#define OR |
#define OLED_SPI_USART USART3
 
OLED_SPI_USART->BRR = 16;  // ----> 2.625MHz
OLED_SPI_USART->CR2 = 0
    OR ( 0                       * USART_CR2_ADD_0    )  /* Address of the USART node */
    OR ( 0                       * USART_CR2_LBDL     )  /* LIN Break Detection Length */
    OR ( 0                       * USART_CR2_LBDIE    )  /* LIN Break Detection Interrupt Enable */
    OR ( 1                       * USART_CR2_LBCL     )  /* Last Bit Clock pulse */
    OR ( 0                       * USART_CR2_CPHA     )  /* Clock Phase */
    OR ( 0                       * USART_CR2_CPOL     )  /* Clock Polarity */
    OR ( 1                       * USART_CR2_CLKEN    )  /* Clock Enable */
    OR ( USART_CR2_STOP__1_BIT   * USART_CR2_STOP_0   )  /* Bit 0 */
    OR ( 0                       * USART_CR2_LINEN    )  /* LIN mode enable */
  ;
  OLED_SPI_USART->CR1 = 0
    OR ( 0                       * USART_CR1_SBK      )  /* Send Break */
    OR ( 0                       * USART_CR1_RWU      )  /* Receiver wakeup */
    OR ( 0                       * USART_CR1_RE       )  /* Receiver Enable */
    OR ( 1                       * USART_CR1_TE       )  /* Transmitter Enable */
    OR ( 0                       * USART_CR1_IDLEIE   )  /* IDLE Interrupt Enable */
    OR ( 0                       * USART_CR1_RXNEIE   )  /* RXNE Interrupt Enable */
    OR ( 0                       * USART_CR1_TCIE     )  /* Transmission Complete Interrupt Enable */
    OR ( 0                       * USART_CR1_TXEIE    )  /* Transmitter Empty Interrupt Enable */
    OR ( 0                       * USART_CR1_PEIE     )  /* PE Interrupt Enable */
    OR ( 0                       * USART_CR1_PS       )  /* Parity Selection - 0 = even, 1 = odd */
    OR ( 0                       * USART_CR1_PCE      )  /* Parity Control Enable */
    OR ( 0                       * USART_CR1_WAKE     )  /* Wakeup method - 0 = Idle Line, 1 = Address Mark */
    OR ( 0                       * USART_CR1_M        )  /* Word length - 0 = 8-data-bit, 1 = 9-data-bit */
    OR ( 1                       * USART_CR1_UE       )  /* USART Enable */
    OR ( 0                       * USART_CR1_OVER8    )  /* USART Oversampling by 8 enable */
  ;
  OLED_SPI_USART->CR3 = 0
    OR ( 1                       * USART_CR3_DMAT     )  /* DMA Enable Transmitter */
  ;

 

The runtime handling is again exactly just as with normal UART, whether polled, interrupt-driven or DMA-based.

 

 

Major differences to SPI and potential gotchas:

  • master only
  • there's no framing signal (NSS)
  • USART transmits LSB-first, only. This may be a major pain for certain chips with SPI-like interfaces, as SPI is usually MSB-first. Thanksfully, ARM has a bitswap instruction with an appropriate function in CMSIS; bitswapping a byte is then (__RBIT(X) >> 24)
  • Baudrate calculation applies just as with normal UART. This limits the achievable maximum data rate, as bit lengths are multiples of 8 input clock (APB clock) periods (if USART_CR1.OVER8 is set - otherwise multiples of 16 APB clocks, of course). Don't have hopes setting a below-1-fractional baudrate divider.
  • Maximum data rate limited further by the fact, that there are gaps between consecutive bytes (the clock is not continuous even with the tightest feeding of the data register). This is caused by the internal logic generating start and stop bits, and the clock is stopped during that time.
  • 8-bit data only. Oh, 9-bit mode probably works, too; but that's not that usual with the SPI-like protocols and chips.

 

JW

Outcomes