0
\$\begingroup\$

I want to read my IMU with DMA with only SPI DMA Rx and normal SPI Tx the code works but when I use SPI DMA TX the State of the SPI handler stays on HAL_SPI_STATE_BUSY_TX;

I'm using the STM32CubeIDE 1.5.0 with the newest firmware.

SPI INIT:

void MX_SPI3_Init(void)
{

  hspi3.Instance = SPI3;
  hspi3.Init.Mode = SPI_MODE_MASTER;
  hspi3.Init.Direction = SPI_DIRECTION_2LINES;
  hspi3.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi3.Init.NSS = SPI_NSS_SOFT;
  hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi3.Init.CRCPolynomial = 7;
  hspi3.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi3.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
  if (HAL_SPI_Init(&hspi3) != HAL_OK)
  {
    Error_Handler();
  }

}

void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  /* USER CODE BEGIN SPI3_MspInit 0 */

  /* USER CODE END SPI3_MspInit 0 */
    /* SPI3 clock enable */
    __HAL_RCC_SPI3_CLK_ENABLE();

    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    /**SPI3 GPIO Configuration
    PB2     ------> SPI3_MOSI
    PC10     ------> SPI3_SCK
    PC11     ------> SPI3_MISO
    */
    GPIO_InitStruct.Pin = IMU_MOSI_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_SPI3;
    HAL_GPIO_Init(IMU_MOSI_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = IMU_SCK_Pin|IMU_MISO_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF6_SPI3;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    /* SPI3 DMA Init */
    /* SPI3_RX Init */
    hdma_spi3_rx.Instance = DMA1_Stream2;
    hdma_spi3_rx.Init.Channel = DMA_CHANNEL_0;
    hdma_spi3_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_spi3_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi3_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi3_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi3_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi3_rx.Init.Mode = DMA_NORMAL;
    hdma_spi3_rx.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_spi3_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi3_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi3_rx);

    /* SPI3_TX Init */
    hdma_spi3_tx.Instance = DMA1_Stream5;
    hdma_spi3_tx.Init.Channel = DMA_CHANNEL_0;
    hdma_spi3_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_spi3_tx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi3_tx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi3_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_spi3_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_spi3_tx.Init.Mode = DMA_NORMAL;
    hdma_spi3_tx.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_spi3_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi3_tx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi3_tx);

  /* USER CODE BEGIN SPI3_MspInit 1 */

  /* USER CODE END SPI3_MspInit 1 */
  }
}

Interrupt code:

void EXTI9_5_IRQHandler(void)
{
  /* USER CODE BEGIN EXTI9_5_IRQn 0 */
    uint8_t tmp[1];
    if (IMU_init_ok == true){
        tmp[0] = ACCEL_XOUT_H|0x80;
        // IMU SPI NSS LOW
        HAL_GPIO_WritePin(IMU_NSS_GPIO_Port, IMU_NSS_Pin, GPIO_PIN_RESET);
        
        HAL_SPI_Transmit_DMA(&hspi3,(uint8_t *)tmp, 1); //HAL_SPI_Transmit(&hspi3,(uint8_t *)tmp, 1, HAL_MAX_DELAY); works
        while(HAL_SPI_GetState(&hspi3) != HAL_SPI_STATE_READY);
        HAL_SPI_Receive_DMA(&hspi3, (uint8_t *)IMU_BUF, 14);

    }
  /* USER CODE END EXTI9_5_IRQn 0 */
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
  /* USER CODE BEGIN EXTI9_5_IRQn 1 */

  /* USER CODE END EXTI9_5_IRQn 1 */
}

void DMA1_Stream2_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Stream2_IRQn 0 */
    if(IMU_init_ok == true){

        IMU_r_gyro[0] = (IMU_BUF[8] << 8) | IMU_BUF[9];
        IMU_r_gyro[1] = (IMU_BUF[10] << 8) | IMU_BUF[11];
        IMU_r_gyro[2] = (IMU_BUF[12] << 8) | IMU_BUF[13];

        for(int i = 0; i<3; i++){
            IMU_ypr[i] = IMU_r_gyro[i] / 65.5;
        }

        IMU_r_accel[0] = (IMU_BUF[0] << 8) | IMU_BUF[1];
        IMU_r_accel[1] = (IMU_BUF[2] << 8) | IMU_BUF[3];
        IMU_r_accel[2] = (IMU_BUF[4] << 8) | IMU_BUF[5];

        for(int i = 0; i<3; i++){
            IMU_accel[i] =  IMU_r_accel[i] / 4096.0;
        }

        //NSS HIGH IMU SPI
        HAL_GPIO_WritePin(IMU_NSS_GPIO_Port, IMU_NSS_Pin, GPIO_PIN_SET);

    }
  /* USER CODE END DMA1_Stream2_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_spi3_rx);
  /* USER CODE BEGIN DMA1_Stream2_IRQn 1 */

  /* USER CODE END DMA1_Stream2_IRQn 1 */
}
\$\endgroup\$
8
  • \$\begingroup\$ Back up and try some provided example, as is, unmodified. Use a scope or logic analyzer to see that it does what it claims to - SPI doesn't depend on a target ACK so it won't really care if the expected peripheral of the example is missing. Contemplate the diff vs. your current attempt, and see if each change is warranted. Also consider if DMA is actually worth the bother. \$\endgroup\$ Commented Dec 14, 2020 at 1:16
  • \$\begingroup\$ Where in RAM is your transmit buffer located? If it's in the DTCM area (0x2000000-0x2001FFFF) then that's your problem. The standard DMA controllers can't access either the DTCM or ITCM areas - these are only accessible by the Core and the MDMA. \$\endgroup\$
    – brhans
    Commented Dec 14, 2020 at 1:38
  • \$\begingroup\$ @brhans if it's that my RAM is located in this area. (RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K) but witch area should I choose now. \$\endgroup\$ Commented Dec 14, 2020 at 2:01
  • \$\begingroup\$ Any of the other RAM banks will work. AXI-SRAM at 0x24000000, SRAM1 at 0x30000000, SRAM2 at 0x30020000, or SRAM4 at 38000000. You'll also find if/when you get around to using the Ethernet or USB modules (maybe some others too) that those can't access buffers in the DTCM either. \$\endgroup\$
    – brhans
    Commented Dec 14, 2020 at 2:47
  • \$\begingroup\$ @brhans when I change the address in the linker files FLASH.ld and RAM.ld it get into a hard fault. (RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K to RAM (xrw) : ORIGIN = 0x24000000, LENGTH = 256K) in both files. \$\endgroup\$ Commented Dec 14, 2020 at 17:19

3 Answers 3

3
\$\begingroup\$

This may not be an universal answer, but I had a very similar problem with DMA transfer w/ SPI on STM32G4.

It turned out that the STM32CubeIDE does not properly track dependencies between components when generating the main() function initialization code, resulting in the following init sequence:

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  // [...]
  MX_SPI2_Init();
  // [...]
  MX_DMA_Init();
  // [...]
  /* USER CODE BEGIN 2 */

This, in turn, caused an invisible error in MX_SPI2_Init(), which (in HAL_SPI_Init) initializes DMAMUX and DMA channel. Without running MX_DMA_Init first, those peripherials still didn't have their clocks enabled, resulting in empty writes to their corresponding pages.

A one-time solution is to simply change the order, s.t. DMA is initialized first, e.g.:

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_DMA_Init();
  MX_GPIO_Init();
  // [...]
  MX_SPI2_Init();
  // [...]
  /* USER CODE BEGIN 2 */

However every time you change the .ioc configuration (e.g. another DMA channel is added), you need to remember to fix the order. I ended up puting the MX_DMA_INIT() call in the user section and redefining MX_DMA_Init() to a no-op for the generated block as such:

  // Manually init DMA first, then make MX_DMA_Init into a noop,
  MX_DMA_Init();
  #define MX_DMA_Init() do {} while(0)
  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  // [...]
  MX_SPI2_Init();
  // [...]
  MX_DMA_Init();
  // [...]
  /* USER CODE BEGIN 2 */
  #undef MX_DMA_Init
\$\endgroup\$
2
  • 1
    \$\begingroup\$ You saved my day. Thank you so much. \$\endgroup\$ Commented Feb 22, 2023 at 6:41
  • \$\begingroup\$ This was my case as well, on a STM32H7, and now it works, thank you. Noticeably, I had another older project with the same MCU, and the order, instead, was correct (first DMA, then SPI init). That's strange. \$\endgroup\$
    – next-hack
    Commented May 18, 2023 at 11:45
0
\$\begingroup\$

You may find that the SPI transfer by DMA works the very first time. In this case, the HAL SPI DMA setup is waiting for some interrupt handlers (DMA transfer complete) to reset the internal BUSY state. You may find that by enabling the interrupts, the HAL state is able to reset your BUSY state back to READY, after that initial transfer completes. It may be that your firmware setup does not desire any DMA-transfer-complete interrupt as this may wakeup your processor when you do not wish this to occur. In that case, I would suggest modifying the HAL setup so it assumes the transfer completes, based on your system timing interval, and then can commence the next transfer without this dependency.

\$\endgroup\$
0
\$\begingroup\$

Are you sure you're giving SPI time to do its thing?

Your MCU is a lot faster than SPI, if you're calling SPI send functions while others are not complete you well get this issue. Since it's DMA, the MCU is not going to sit around waiting for it to be done. I had the same issue recently, even though it may seem like you're not calling SPI functions back to back, keep in mind how fast your MCU is executing code - it may be that it does call the next SPI function before the first one is done.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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