cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H563 and SPI DMA Transfer issue

PFlor.2
Senior

I'm having an issue when using the DMA with SPI.  I'm using the SPI for a 320x240 LCD and want the DMA to help with blocking while transferring large amounts of data with touchGFX.

I'm able to paint the screen one color just using the SPI but when I try to use the hdmatx I can't get it to complete and hangs on waiting for the DMA state to be READY (see below)....

 

void ST7789v_Fill_Color(uint16_t color)
{
  uint16_t i;

  ST7789v_SetWindow(0, 0, ST7789_WIDTH - 1, ST7789_HEIGHT - 1);

  CS_L();

  uint32_t size = ST7789_WIDTH * ST7789_HEIGHT * 2;
  uint16_t chunk_size;
  uint16_t buffer[size/2];
  uint8_t *buf;

  memset(buffer, color, sizeof(buffer));

  if (size > 0) {
    DC_H();

    LCD_WR_REG(ST7789_CMD_RAMWR);

    while (size > 0) {
      buf = (uint8_t *)buffer;
      chunk_size = size > 8192 ? 8192 : size;

      if (DMA_MIN_SIZE <= size) {
        if (HAL_SPI_Transmit_DMA(&hspi3, buf, chunk_size ) != HAL_OK) {
          Error_Handler();
        }
        while (hspi3.hdmatx->State != HAL_DMA_STATE_READY);
      }
      else {
        if (HAL_SPI_Transmit(&hspi3, buf, chunk_size, HAL_MAX_DELAY) != HAL_OK) {
          Error_Handler();
        }
      }
      buf += chunk_size;
      size -= chunk_size;
    }
  }
  CS_H();
}

 

This code hangs at while (hspi3.hdmatx->State != HAL_DMA_STATE_READY); and the state still shows busy.

below is my SPI and DMA configuration.

 

/* SPI3 init function */
void MX_SPI3_Init(void)
{

  /* USER CODE BEGIN SPI3_Init 0 */

  /* USER CODE END SPI3_Init 0 */

  /* USER CODE BEGIN SPI3_Init 1 */

  /* USER CODE END SPI3_Init 1 */
  hspi3.Instance = SPI3;
  hspi3.Init.Mode = SPI_MODE_MASTER;
  hspi3.Init.Direction = SPI_DIRECTION_2LINES_TXONLY;
  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_4;
  hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi3.Init.CRCPolynomial = 0x7;
  hspi3.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
  hspi3.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
  hspi3.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
  hspi3.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
  hspi3.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
  hspi3.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
  hspi3.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
  hspi3.Init.IOSwap = SPI_IO_SWAP_DISABLE;
  hspi3.Init.ReadyMasterManagement = SPI_RDY_MASTER_MANAGEMENT_INTERNALLY;
  hspi3.Init.ReadyPolarity = SPI_RDY_POLARITY_HIGH;
  if (HAL_SPI_Init(&hspi3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI3_Init 2 */

  /* USER CODE END SPI3_Init 2 */
}


    /* SPI3 DMA Init */
    /* GPDMA1_REQUEST_SPI3_TX Init */
    handle_GPDMA1_Channel0.Instance = GPDMA1_Channel0;
    handle_GPDMA1_Channel0.Init.Request = GPDMA1_REQUEST_SPI3_TX;
    handle_GPDMA1_Channel0.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
    handle_GPDMA1_Channel0.Init.Direction = DMA_MEMORY_TO_PERIPH;
    handle_GPDMA1_Channel0.Init.SrcInc = DMA_SINC_INCREMENTED;
    handle_GPDMA1_Channel0.Init.DestInc = DMA_DINC_FIXED;
    handle_GPDMA1_Channel0.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
    handle_GPDMA1_Channel0.Init.DestDataWidth = DMA_DEST_DATAWIDTH_BYTE;
    handle_GPDMA1_Channel0.Init.Priority = DMA_LOW_PRIORITY_HIGH_WEIGHT;
    handle_GPDMA1_Channel0.Init.SrcBurstLength = 1;
    handle_GPDMA1_Channel0.Init.DestBurstLength = 1;
    handle_GPDMA1_Channel0.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
    handle_GPDMA1_Channel0.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
    handle_GPDMA1_Channel0.Init.Mode = DMA_NORMAL;
    if (HAL_DMA_Init(&handle_GPDMA1_Channel0) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(spiHandle, hdmatx, handle_GPDMA1_Channel0);

    if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel0, DMA_CHANNEL_NPRIV) != HAL_OK)
    {
      Error_Handler();
    }

    /* SPI3 interrupt Init */
    HAL_NVIC_SetPriority(SPI3_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(SPI3_IRQn);
  /* USER CODE BEGIN SPI3_MspInit 1 */

  /* USER CODE END SPI3_MspInit 1 */
  }

 

 Can anyone help me identify what I'm missing??

 

Thanks!

7 REPLIES 7
Saket_Om
ST Employee

Hello @PFlor.2 

Could you share with us the content of error flags on the DMA and SPI sides?

Additionally, did you enable the NVIC DMA interrupt?

 

    HAL_NVIC_SetPriority(GPDMA1_Channel0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);

 

If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar

There are no errors that occur, in the code snippet below the Error_Handler() is not reached (returns HAL_OK).  The code hangs on the while loop waiting for the DMA state to change from BUSY to READY.

        if (HAL_SPI_Transmit_DMA(&hspi3, buf, chunk_size ) != HAL_OK) {
          Error_Handler();
        }
        while (hspi3.hdmatx->State != HAL_DMA_STATE_READY);

both the SPI3 and DMA interrupts are enabled and set to priority 5, that's the lowest the .ioc allows them to be set.

 

If I just use the SPI as seen below without the DMA, data is transmitted to LCD just fine...

        if (HAL_SPI_Transmit(&hspi3, buf, chunk_size, HAL_MAX_DELAY) != HAL_OK) {
          Error_Handler();
        }

 

Ok some new information…some progress but more questions.

I tried changing the interrupt priority to 0 for both the DMA and SPI which created some interesting results.

  1. Just running it this way it appears to have successfully transmitted the first block but no longer hangs.  Somehow it must’ve went through the loop to send the other blocks but only actually transmitted the first block (I tested this with blocks of 65535 and 8192 bytes, each time just a single block is written to LCD).  For 65535 block just over a third of the display is the desired color, for 8192 just a small portion at top of LCD.  How could the code execute for all blocks of loop without hanging on the DMA state, but only write the first block?
  2. Using the debugger if I set a breakpoint after the while (hspi3.hdmatx->State != HAL_DMA_STATE_READY)  and step through each block they are all written fine.  Complete LCD is painted color as desired.
  3. I tried multiple levels of interrupt priority between 0 and 5 and get same results.  Even if I change SPI back to 5 but set the DMA to 4 it still does the same thing, but no longer hangs on while (hspi3.hdmatx->State != HAL_DMA_STATE_READY
static void ST7789v_WriteData(uint8_t *buff, uint32_t buff_size)
{
  DC_H();

  // split data in small chunks because HAL can't send more than 64K at once

  while (buff_size > 0) {
    uint16_t chunk_size = buff_size > 8192 ? 8192 : buff_size;
    #ifdef USE_DMA
//      if (DMA_MIN_SIZE <= buff_size)
//      {
        HAL_SPI_Transmit_DMA(&hspi3, buff, chunk_size);
        while (hspi3.hdmatx->State != HAL_DMA_STATE_READY)
        {}
//      }
//      else
//        HAL_SPI_Transmit(&hspi3, buff, chunk_size, HAL_MAX_DELAY);
    #else
      HAL_SPI_Transmit(&hspi3, buff, chunk_size, HAL_MAX_DELAY);
    #endif
    buff += chunk_size;
    buff_size -= chunk_size;
  }
}
void ST7789v_Fill_Color(uint16_t color)
{
  ST7789v_SetWindow(0, 0, ST7789_WIDTH - 1, ST7789_HEIGHT - 1);

  CS_L();

  LCD_WR_REG(ST7789_CMD_RAMWR);

  uint32_t size = ST7789_WIDTH * ST7789_HEIGHT;
  uint16_t buffer[size];

  for (uint32_t idx = 0; idx < size; idx++) {
    buffer[idx] = color;
  }

  ConvHL((uint8_t *)buffer, size*2);
  ST7789v_WriteData((uint8_t *)buffer, size*2);

  CS_H();
}

Hello @PFlor.2 

 I would like to suggest the following possible solutions:

  1. Configuring Interrupt Priorities:

Please try configuring the interrupt priorities as follows:

 

/* Enable and set SPIx Interrupt */
HAL_NVIC_SetPriority(SPIx_IRQn, 0x01, 0);
HAL_NVIC_EnableIRQ(SPIx_IRQn);

/* NVIC configuration for DMA transfer complete interrupt */
HAL_NVIC_SetPriority(GPDMA1_Channeltx_IRQn, 0x02, 0);
HAL_NVIC_EnableIRQ(GPDMA1_Channeltx_IRQn);

/* NVIC configuration for DMA transfer complete interrupt */
HAL_NVIC_SetPriority(GPDMA1_Channelrx_IRQn, 0x02, 0);
HAL_NVIC_EnableIRQ(GPDMA1_Channelrx_IRQn);

 

  1. Changing the While Loop Check:

In addition to checking the DMA state, please add a check on the SPI state to ensure that the SPI communication is complete before proceeding.

 Alos, add a check on the transmit return value of  HAL_SPI_Transmit_DMA() to ensure it is equal to HAL_OK.

 

if (HAL_SPI_Transmit_DMA(&hSPIx, ...) != HAL_OK) {
    /* Handle error */
}
while (hSPIx.hdmatx->State != HAL_DMA_STATE_READY);
while (hSPIx.State == HAL_SPI_STATE_BUSY_TX); /* wait end of transfer */

 

  1. Lowering the SPI Baud Rate:

Please try lowering the SPI baud rate to match the slave device specifications.

If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar

Ok this seems to work, just a few questions...

1)  Setting HDMA priority to 2 and SPI3 priority to 1 works as you suggested.  My .ioc won't let me set any priority level below 5 (not sure what is set that doesn't allow that) so I set all priority 5 up to 8 and set SPI3 to 5 and HDMA to 6 expected to achieve the same results as SPi3 set to 1 and HDMA set to 2.  Why does this not work the same?

2)  Why should I have to wait for SPI to complete after the DMA state is READY?  Shouldn't the DMA state wait for the SPI to complete?  This seems to defeat the purpose of the DMA, no?

3) The SPI3 baud rate was set to 15MBps and the slave device specifications is slight about that, I had been setting my prescaler to 4 instead of 2 for a 7.5MBps just to make sure.

Hello @PFlor.2 

 

  1. Could you share the .ioc file with us so that we can assist you more effectively? Which version of STM32CubeMX you are using?

  2. The DMA will raise its TC flag when it finishes transferring data from memory to the TXDR. However, the SPI will still be busy until it finishes transmitting data from the TXDR to the slave device. Therefore, the check should be done on the SPI side.

If your question is answered, please close this topic by clicking "Accept as Solution".

Thanks
Omar

Follow-up of the discussion related to STM32CubeMX in STM32H563: Not able to set NVIC priorities properly for HDMA & SPI. IOC file is attached there as well.

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.