How to Handle Single Chip Select in STM32 SPI?

How to Handle Single Chip Select in STM32 SPI?

Previously, when using STM32’s SPI to control many external chips, there was only one chip select for a single SPI peripheral. How can we achieve independent chip selection for one master and multiple slaves?

SPI Bus Topology

Generally, the SPI bus is connected as shown in the figure below, with one master and multiple slaves.

How to Handle Single Chip Select in STM32 SPI?

As shown in the figure:

  • Each slave device has an independent chip select pin, and the master communicates with one slave at a time, which means selecting one slave.
  • MOSI/MISO/SCLK are connected in parallel.
  • MISO must be a tri-state buffer; when the slave device is not selected, this pin must be set to high impedance, not output mode, otherwise it will affect the bus!
  • For MOSI/SCLK, although they are connected in parallel, there is only one output and multiple inputs.

However, as you can see in STM32’s SPI peripherals, a single SPI only has one NSS signal. Taking the STM32F407’s SPI2 as an example:

So how can we achieve the previously mentioned one master and multiple slaves? Some friends suggest directly using GPIO to simulate it.

That’s right, simulating the SPI bus with GPIO is quite easy, but this approach cannot achieve high baud rates, requires CPU time, and is relatively inefficient! Using the SPI peripheral controller allows the lower-level bit stream transmission and reception to be handled by the peripheral controller, while simulating with GPIO requires CPU involvement.

So What to Do?

Daisy Chain Topology

How to Handle Single Chip Select in STM32 SPI?

This solution saves pins but requires shift control, and the efficiency is still lower compared to independent chip selection.

Independent Chip Select Topology

The SPI peripheral’s MOSI, MISO, and SCK can still be used as usual, but we do not use the chip select; we set it to general output mode and use other GPIOs to select the slave chips instead.

Let’s take a look at the code:

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hspi->Instance==SPI1)
  {
    __HAL_RCC_SPI1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**SPI1 GPIO Configuration
    PA5     ------> SPI1_SCK
    PA6     ------> SPI1_MISO
    PA7     ------> SPI1_MOSI
    PA15     ------> SPI1_NSS  but we do not use it here
    */
    GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

   /*__HAL_RCC_GPIOC_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);*/ 
  }
}

Initializing the SPI peripheral

#define SPI_CS1                        GPIO_PIN_1
#define SPI_CS1_PORT                   GPIOC
#define SPI_CS2                        GPIO_PIN_2
#define SPI_CS2_PORT                   GPIOC
#define SPI_CS3                        GPIO_PIN_3
#define SPI_CS3_PORT                   GPIOC
static void init_spi(SPI_HandleTypeDef * spi_handle)
{
  /* SPI1 parameter configuration*/
  spi_handle->Instance = SPI1;
  spi_handle->Init.Mode = SPI_MODE_MASTER;
  spi_handle->Init.Direction = SPI_DIRECTION_2LINES;
  spi_handle->Init.DataSize = SPI_DATASIZE_8BIT;
  spi_handle->Init.CLKPolarity = SPI_POLARITY_LOW;
  spi_handle->Init.CLKPhase = SPI_PHASE_1EDGE;
  spi_handle->Init.NSS = SPI_NSS_HARD_OUTPUT;
  spi_handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  spi_handle->Init.FirstBit = SPI_FIRSTBIT_MSB;
  spi_handle->Init.TIMode = SPI_TIMODE_DISABLE;
  spi_handle->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  spi_handle->Init.CRCPolynomial = 10;
  ASSERT (HAL_SPI_Init(spi_handle) != HAL_OK);

 GPIO_InitTypeDef  GPIO_InitStructure;

 __HAL_RCC_GPIOC_CLK_ENABLE();

 GPIO_InitStructure.Pin = SPI_CS1;
 GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStructure.Pull = GPIO_NOPULL;
 GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;
 HAL_GPIO_Init(SPI_CS1_PORT, &GPIO_InitStructure); 
  
 GPIO_InitStructure.Pin = SPI_CS2;
 HAL_GPIO_Init(SPI_CS2_PORT, &GPIO_InitStructure);  
 
 GPIO_InitStructure.Pin = SPI_CS3;
 HAL_GPIO_Init(SPI_CS3_PORT, &GPIO_InitStructure);   
}

Thus, we can add chip select signals before and after the original SPI transmission functions:

typedef enum 
{  
 SPI_CH_1=0,
 SPI_CH_2,
 SPI_CH_3,
 SPI_CH_LAST,
} SPI_CH;
static HAL_StatusTypeDef SPI_Select(SPI_CH ch)
{
   switch (ch)
   {
     case SPI_CH_1:
       HAL_GPIO_WritePin(SPI_CS1_PORT,SPI_CS1,GPIO_PIN_RESET);
       break;
       
     case SPI_CH_2:
       HAL_GPIO_WritePin(SPI_CS2_PORT,SPI_CS2,GPIO_PIN_RESET);
       break;
       
     case SPI_CH_3:
       HAL_GPIO_WritePin(SPI_CS3_PORT,SPI_CS3,GPIO_PIN_RESET);
       break;       
     
     default:
       return HAL_ERROR;
   }  
   return HAL_OK;
}
static HAL_StatusTypeDef SPI_DeSelect(SPI_CH ch)
{
   switch (ch)
   {
     case SPI_CH_1:
       HAL_GPIO_WritePin(SPI_CS1_PORT,SPI_CS1,GPIO_PIN_SET);
       break;
       
     case SPI_CH_2:
       HAL_GPIO_WritePin(SPI_CS2_PORT,SPI_CS2,GPIO_PIN_SET);
       break;
       
     case SPI_CH_3:
       HAL_GPIO_WritePin(SPI_CS3_PORT,SPI_CS3,GPIO_PIN_SET);
       break;       
     
     default:
       return HAL_ERROR;
   }
   return HAL_OK;
}

HAL_StatusTypeDef SPI_TransmitReceive(SPI_CH ch,
                    SPI_HandleTypeDef *hspi, 
                    uint8_t *pTxData, 
                    uint8_t *pRxData, 
                    uint16_t Size,
                    uint32_t Timeout)
{
   HAL_StatusTypeDef ret; 
   if(ch>=SPI_CH_LAST)
     return HAL_ERROR;  
   
   SPI_Select(ch);
   ret = HAL_SPI_TransmitReceive(hspi,pTxData,pRxData,Size,Timeout);
   SPI_DeSelect(ch);
   
   return ret;
}

HAL_StatusTypeDef SPI_Transmit(SPI_CH ch,
                 SPI_HandleTypeDef *hspi, 
                 uint8_t *pData, 
                 uint16_t Size, 
                 uint32_t Timeout)
{
   HAL_StatusTypeDef ret; 
   if(ch>=SPI_CH_LAST)
     return HAL_ERROR;  
   
   SPI_Select(ch);
   ret = HAL_SPI_Transmit(hspi,pData,Size,Timeout);
   SPI_DeSelect(ch);
   
   return ret;  
}

In this way, a single SPI peripheral can control multiple slave chips. If you’re interested, you might want to try this approach.

How to Handle Single Chip Select in STM32 SPI?

1. None

2. Why are schematics always poorly drawn? These tips are essential to know.

3. How to display IP location on embedded devices?

4. RISC-V is aimed at MCU/MPU, RTOS, but faces challenges…

5. A method for self-updating firmware for microcontrollers!

6. The XuanTie Cup RISC-V Application Innovation Competition has officially started, registration is now open!

How to Handle Single Chip Select in STM32 SPI?

Disclaimer: This article is a network reprint, and the copyright belongs to the original author. If there are any copyright issues, please contact us, and we will confirm the copyright based on the materials you provide and pay remuneration or delete the content accordingly.

Leave a Comment