0
votes

I am currently practicing using SPI+DMA to send data to a SPI display. The data sequence of the display is as following:

[pull CS low]->[pull D/C low]->[1 SPI byte of CMD]->[pull D/C high]->[n SPI byte of data]->[pull CS high]. Where the D/C pin is a GPIO pin.

My idea is that first pull CS and D/C low, then send 1 byte of CMD through HAL_SPI_Transmit_IT(); and pull the D/C pin high and start the DMA transfer in the SPI interrupt routine. And the CS pin will be pull high in DMA TxComplete interrupt.

My SPI is set with data length of 8 bits, and DMA setting is memory to peripheral and increment mode.

I am using cubeMX to generate code, and here is roughly my code:

uint8_t displayData[DIS_DATA_BUFF_SIZE];

int main(void)
{
    ...init stuff from cubeMX
    cmdBuffer[0].cmd = 0xAA;
    cmdBuffer[0].amountOfData = 10;
    cmdBuffer[0].pDataStart = displayData;

    while (1)
    {
        HAL_Delay(500);

        cmdBuffer[0].status = IN_USE;
        pExecuteCmd = &cmdBuffer[0];

        SPI_START();
        DIS_CMD_MODE_ON();
        HAL_SPI_Transmit_IT(&hspi2, &pExecuteCmd->cmd, 1);
    }
}

And here is my SPI interrupt routine

void SPI2_IRQHandler(void)
{
  /* USER CODE BEGIN SPI2_IRQn 0 */
    uint8_t startDMA = 0;
    if(__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE)){
        if(pExecuteCmd->status == EXE_CMD){
            DIS_CMD_MODE_OFF();
            if(pExecuteCmd->amountOfData == 0){
                SPI_END();
                pExecuteCmd->status = EMPTY;
            }else{
                pExecuteCmd->status = EXE_DATA;
                startDMA = 1;
            }
        }
        else if(pExecuteCmd->status == IN_USE){
             pExecuteCmd->status = EXE_CMD;
        }
    }

  /* USER CODE END SPI2_IRQn 0 */
    HAL_SPI_IRQHandler(&hspi2);
    if(startDMA)
    {
    
        HAL_SPI_Transmit_DMA(&hspi2, pExecuteCmd->pDataStart,
                    pExecuteCmd->amountOfData);
    }
  /* USER CODE BEGIN SPI2_IRQn 1 */

  /* USER CODE END SPI2_IRQn 1 */
}

And here is my last part of DMA interrupt routine

void DMA1_Channel5_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel5_IRQn 0 */
    if(__HAL_DMA_GET_FLAG(&hdma_spi2_tx, DMA_FLAG_TC5)){
        SPI_END();
        pExecuteCmd->status = EMPTY;
    }


  /* USER CODE END DMA1_Channel5_IRQn 0 */
    HAL_DMA_IRQHandler(&hdma_spi2_tx);
  /* USER CODE BEGIN DMA1_Channel5_IRQn 1 */

  /* USER CODE END DMA1_Channel5_IRQn 1 */
}

In my current attempt, the main starts the spi CMD transmitting, and I expect that the DMA transmitting will be triggered by HAL_SPI_Transmit_DMA(). But the DMA can only be started once, which is the very first transmitting. And then HAL_SPI_Transmit_DMA() seems like return HAL_BUSY due to hspi->State != HAL_SPI_STATE_READY.

I am not sure where I did wrong. Can any one provide any hint, what is the proper way to drive interrupt based DMA transmission?

Thanks.

Update1

I got a strange result after looking into it. Since I only have a logic analyzer as my debugging tool, I placed pin toggling as debugging massage. I put one in the SPI_IRQHandler as follow:

void SPI2_IRQHandler(void)
{
/* USER CODE BEGIN SPI2_IRQn 0 */
    uint8_t startDMA = 0;
    if(__HAL_SPI_GET_FLAG(&hspi2, SPI_FLAG_TXE)){
        if(pExecuteCmd->status == EXE_CMD){
            DIS_CMD_MODE_OFF();
            if(pExecuteCmd->amountOfData == 0){
                SPI_END();
                pExecuteCmd->status = EMPTY;
            }else{
                pExecuteCmd->status = EXE_DATA;
                startDMA = 1;
            }
        }
        else if(pExecuteCmd->status == IN_USE){
            pExecuteCmd->status = EXE_CMD;
        }
    }
    /* USER CODE END SPI2_IRQn 0 */
    HAL_SPI_IRQHandler(&hspi2);

    if(startDMA)
    {
        if(hspi2.State == HAL_SPI_STATE_READY){

            HAL_GPIO_TogglePin(DIS_NRST_GPIO_Port, DIS_NRST_Pin);
            HAL_GPIO_TogglePin(DIS_NRST_GPIO_Port, DIS_NRST_Pin);
            //^^^^^^^toggle pin showing the state is READY^^^^^//
            HAL_SPI_Transmit_DMA(&hspi2, pExecuteCmd->pDataStart,
                            pExecuteCmd->amountOfData);
        }
    }
}

And also placed another pin toggling in the end of HAL_SPI_Transmit_DMA(). I put it in the end of the function.

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
{
  HAL_StatusTypeDef errorcode = HAL_OK;

  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE(hspi->Init.Direction));

  /* Process Locked */
  __HAL_LOCK(hspi);

  if(hspi->State != HAL_SPI_STATE_READY)
  {
    errorcode = HAL_BUSY;
    goto error;
  }

  if((pData == NULL) || (Size == 0U))
  {

    errorcode = HAL_ERROR;
    goto error;
  }

  /* Set the transaction information */
  hspi->State       = HAL_SPI_STATE_BUSY_TX;
  hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  hspi->pTxBuffPtr  = (uint8_t *)pData;
  hspi->TxXferSize  = Size;
  hspi->TxXferCount = Size;

  /* Init field not used in handle to zero */
  hspi->pRxBuffPtr  = (uint8_t *)NULL;
  hspi->TxISR       = NULL;
  hspi->RxISR       = NULL;
  hspi->RxXferSize  = 0U;
  hspi->RxXferCount = 0U;

  /* Configure communication direction : 1Line */
  if(hspi->Init.Direction == SPI_DIRECTION_1LINE)
  {
    SPI_1LINE_TX(hspi);
  }

#if (USE_SPI_CRC != 0U)
  /* Reset CRC Calculation */
  if(hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)
  {
    SPI_RESET_CRC(hspi);
  }
#endif /* USE_SPI_CRC */

  /* Set the SPI TxDMA Half transfer complete callback */
  hspi->hdmatx->XferHalfCpltCallback = SPI_DMAHalfTransmitCplt;

  /* Set the SPI TxDMA transfer complete callback */
  hspi->hdmatx->XferCpltCallback = SPI_DMATransmitCplt;

  /* Set the DMA error callback */
  hspi->hdmatx->XferErrorCallback = SPI_DMAError;

  /* Set the DMA AbortCpltCallback */
  hspi->hdmatx->XferAbortCallback = NULL;

  /* Enable the Tx DMA Stream */
  HAL_DMA_Start_IT(hspi->hdmatx, (uint32_t)hspi->pTxBuffPtr, (uint32_t)&hspi->Instance->DR, hspi->TxXferCount);

  /* Check if the SPI is already enabled */
  if((hspi->Instance->CR1 &SPI_CR1_SPE) != SPI_CR1_SPE)
  {
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(hspi);
  }

  /* Enable the SPI Error Interrupt Bit */
  SET_BIT(hspi->Instance->CR2, SPI_CR2_ERRIE);

  /* Enable Tx DMA Request */
  SET_BIT(hspi->Instance->CR2, SPI_CR2_TXDMAEN);

error :
  /* Process Unlocked */
  __HAL_UNLOCK(hspi);
  HAL_GPIO_WritePin(DIS_DC_GPIO_Port, DIS_DC_Pin, RESET);
  HAL_GPIO_WritePin(DIS_DC_GPIO_Port, DIS_DC_Pin, SET);
  return errorcode;
}

As result: the DMA transmission only works at the very first time, then no data is transferred through DMA. AND I only get once of DIS_DC_Pin toggled, with many times toggling of DIS_NRST_Pin. Which means, that the process did entered the if(hspi2.State == HAL_SPI_STATE_READY) in the interrupt routine, but not into the HAL_SPI_Transmit_DMA()???

screen shot of logic analyzer

How can this happen?

1
Does your SPI output any data after calling HAL_SPI_Transmit_IT? I hope your solution with HAL_Delay is only temporary. It is not very efficient ot use interrupts and then hope for them to be finished after a fixed delay.A.R.C.
Hi, this code is only for testing the mechanism, not meant to be the final implementation. Yes, the HAL_SPI_Transmit_IT() did transmit the 0xAA as intended. But for the HAL_SPI_Transmit_DMA(), it only works at the very first interrupt after boot up, then HAL_SPI_Transmit_DMA does not do anything more, it returns HAL_BUSY.MinShu Huang
By the way, I tried the HAL_SPI_Transmit_DMA() separately in the main, the function itself work, it sends out the amount of data, which is assigned. But it does not work when I put it in the interrupt routine as I described in my post.MinShu Huang
Have you tried debugging the HAL_SPI_IRQHandler() function? This function is responsible for setting the HAL status to e.g. HAL_OK or HAL_BUSY. If the status after this function is not ok, your transmit dma function will not work.A.R.C.
Thanks for your input, I did more debugging, I have an update in the original post, could you have a look?MinShu Huang

1 Answers

0
votes

I had the same issue in a statemachine where I wanted the SPI transfers to run "in background". I had/have a SPI Tx-Rx statemachine where the next Tx/Rx is triggered by the DMA Rx/Tx Complete Callback Interrupt of the last transmission at a time.

The symptoms were the same: after the first call of the SPI_Tx_DMA, SPI_Rx_DMA sequence - consisting exactly of one Tx and one Rx - the SPI seems to be stuck and reported a "HAL_BUSY" error, when I tried the next Tx.

The solution was to check the SPI DMA settings in CubeMX and change the DMA-Mode for the RX DMA Channel from Circular to Normal. I have no idea why I had initially chosen "circular" - mostly probably because it seemed somehow "plausible" for me, when I set up SPI in CubeMX the first time ...

I did not investigate further, so I can not provide a sophisticated answer, what's going on behind the scenes ...