cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G431 SPI & DMA interrupt issue

StefanHogg
Associate II

On a STM32G431K8 I'm using the DMA1 to handle SPI1 data transfer (channel 1 for data receiption and channel 2 for data transmission).

I'm using STM32CubeMX for code generation, resulting in following SPI1 and DMA1 initialization:

/**
  * @brief SPI1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI1_Init(void)
{
 
  /* USER CODE BEGIN SPI1_Init 0 */
 
  /* USER CODE END SPI1_Init 0 */
 
  LL_SPI_InitTypeDef SPI_InitStruct = {0};
 
  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
 
  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);
 
  LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA);
  /**SPI1 GPIO Configuration
  PA5   ------> SPI1_SCK
  PA6   ------> SPI1_MISO
  PA7   ------> SPI1_MOSI
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_5;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
  GPIO_InitStruct.Pin = LL_GPIO_PIN_6;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
  GPIO_InitStruct.Pin = LL_GPIO_PIN_7;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_5;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
  /* SPI1 DMA Init */
 
  /* SPI1_RX Init */
  LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_SPI1_RX);
  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);
  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL);
  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);
  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);
  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_BYTE);
  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_BYTE);
 
  /* SPI1_TX Init */
  LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_2, LL_DMAMUX_REQ_SPI1_TX);
  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_2, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PRIORITY_LOW);
  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MODE_NORMAL);
  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PERIPH_NOINCREMENT);
  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MEMORY_INCREMENT);
  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_2, LL_DMA_PDATAALIGN_BYTE);
  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_2, LL_DMA_MDATAALIGN_BYTE);
 
  /* USER CODE BEGIN SPI1_Init 1 */
  // Enable DMA transfer complete/error interrupts
  LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
  LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1);
  LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_2);
  LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_2);
 
  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
  SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
  SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;
  SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;
  SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;
  SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
  SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV8;
  SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
  SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
  SPI_InitStruct.CRCPoly = 7;
  LL_SPI_Init(SPI1, &SPI_InitStruct);
  LL_SPI_SetStandard(SPI1, LL_SPI_PROTOCOL_MOTOROLA);
  LL_SPI_EnableNSSPulseMgt(SPI1);
  /* USER CODE BEGIN SPI1_Init 2 */
  LL_SPI_SetRxFIFOThreshold(SPI1, LL_SPI_RX_FIFO_TH_QUARTER);
  /* USER CODE END SPI1_Init 2 */
 
}
 
/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{
 
  /* Init with LL driver */
  /* DMA controller clock enable */
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMAMUX1);
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
 
  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  NVIC_SetPriority(DMA1_Channel1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
  NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  /* DMA1_Channel2_IRQn interrupt configuration */
  NVIC_SetPriority(DMA1_Channel2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),1, 0));
  NVIC_EnableIRQ(DMA1_Channel2_IRQn);
 
}

According to the chapter "Communication using DMA" of the Reference Manual, following code represents the SPI communication handling.

    // Prepare SPI request data
    au8SpiReqBuffer[0] = 0x80;
    au8SpiReqBuffer[1] = 0x3D;
    au8SpiReqBuffer[2] = 0x40;
    au8SpiReqBuffer[3] = 0x10;
 
    // Configure the DMA functional parameters for SPI transmission
    LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_2,
                           (uint32_t)au8SpiReqBuffer,
                           LL_SPI_DMA_GetRegAddr(SPI1),
                           LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_2));
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_2, 4);
    // Configure the DMA functional parameters for SPI reception
    LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1,
                           LL_SPI_DMA_GetRegAddr(SPI1),
                           (uint32_t)au8SpiRespBuffer,
                           LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1));
    LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, 4);
 
    // Send SPI request
    // Reset transfer state
    tSpiTransferState = TRANSFER_WAIT;
    // Enable DMA RX Buffer
    LL_SPI_EnableDMAReq_RX(SPI1);
    // Enable DMA Channels Tx and Rx
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_2);
    // Enable DMA TX Buffer
    LL_SPI_EnableDMAReq_TX(SPI1);
    // Pull SPI CLK line low while selecting SPI slave
    LL_GPIO_ResetOutputPin(SPI1_SCK_GPIO_Port, SPI1_SCK_Pin);
    // Select SPI slave (pull slave select line low)
    LL_GPIO_ResetOutputPin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin);
    LL_SPI_Enable(SPI1);
 
    // Wait for SPI transmission completed
    while (TRANSFER_WAIT == tSpiTransferState)
    {
      ;
    }
 
    while (LL_SPI_TX_FIFO_EMPTY != LL_SPI_GetTxFIFOLevel(SPI1))
    {
      ;
    }
    while (0 != LL_SPI_IsActiveFlag_BSY(SPI1))
    {
      ;
    }
 
    // Pull SPI CLK line low while de-selecting SPI slave
    LL_SPI_Disable(SPI1);
    LL_GPIO_ResetOutputPin(SPI1_SCK_GPIO_Port, SPI1_SCK_Pin);
    // De-select SPI slave (pull slave select line high)
    LL_GPIO_SetOutputPin(SPI1_NSS_GPIO_Port, SPI1_NSS_Pin);
    // Disable DMA TX Buffer
    LL_SPI_DisableDMAReq_TX(SPI1);
    // Disable DMA RX Buffer
    LL_SPI_DisableDMAReq_RX(SPI1);

For getting the SPI tansmission/receiption completed, I'm using the DMA channel interrupts.

/**
  * @brief This function handles DMA1 channel1 global interrupt.
  */
void DMA1_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */
  if (0 != LL_DMA_IsActiveFlag_TC1(DMA1))
  {
    LL_DMA_ClearFlag_TC1(DMA1);
    // Disable DMA1 Rx Channel
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
    tSpiTransferState = TRANSFER_COMPLETE;
  }
  else if (0 != LL_DMA_IsActiveFlag_TE1(DMA1))
  {
    LL_DMA_ClearFlag_TE1(DMA1);
    tSpiTransferState = TRANSFER_ERROR;
  }
  /* USER CODE END DMA1_Channel1_IRQn 0 */
 
  /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */
 
  /* USER CODE END DMA1_Channel1_IRQn 1 */
}
 
/**
  * @brief This function handles DMA1 channel2 global interrupt.
  */
void DMA1_Channel2_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel2_IRQn 0 */
  if (0 != LL_DMA_IsActiveFlag_TC2(DMA1))
  {
    LL_DMA_ClearFlag_TC2(DMA1);
    // Disable DMA2 Tx Channel
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_2);
  }
  else if (0 != LL_DMA_IsActiveFlag_TE2(DMA1))
  {
    LL_DMA_ClearFlag_TE2(DMA1);
    tSpiTransferState = TRANSFER_ERROR;
  }
 
  /* USER CODE END DMA1_Channel2_IRQn 0 */
 
  /* USER CODE BEGIN DMA1_Channel2_IRQn 1 */
 
  /* USER CODE END DMA1_Channel2_IRQn 1 */
}

My problem is that the program hangs up at the wait for SPI transmission completed

// Wait for SPI transmission completed
while (TRANSFER_WAIT == tSpiTransferState)
{
  ;
}

The DMA interrupt handlers (neither channel 1 nor channel 2) are not called...

Nevertheless, if I halt the MCU, everything seems to be done correctly.

DMA channels are configured correctly (including the transmission complete interrupts enabled)

0693W00000JOcKcQAL.jpg0693W00000JOcLaQAL.jpgAnd the transmission complete flags on the DMA channels are set.

0693W00000JOcQQQA1.jpgAlso the SPI status register looks good.

0693W00000JOcLpQAL.jpgAnd even the response buffer shows the actually received data.

0693W00000JOcQkQAL.jpgThe only strange thing is that the DMA interrupt handlers are not triggered...

Anyone with an idea what I'm missing?

Best Regards

PS: Attached you could find the overall STM32CubeIDE project.

14 REPLIES 14
ssipa.1
Associate II

you disable all interrupts?

and did you enable it back?

StefanHogg
Associate II

The interrupt "sequence" is to start from scratch at power-on.

Then I enable DMA1_Channel1_IRQn and DMA1_Channel2_IRQn.

Afterwards I enable TansmissionComplete and TransmissionError interrupts for DMA1 Channel 1 & 2.

At the end of the configuration phase I once enable all interrupts by calling __enable_irq().

And meanwhile the main while loop the bootloader gets triggered while waiting for DMA TransmissionComplete interrupt...

> What could be the reason, that in my application the bootloader will be entered?

I don't know. Check SCB->VTOR.

Check your BOOT0 pin and setting of the relevant option bits.

Check SYSCFG_MEMRMP.

JW

StefanHogg
Associate II

> I don't know. Check SCB->VTOR.

Thank you for this useful hint.

I've checked it and it was 0x00000000.

Meanwhile looking for SCB->VTOR, I've found following code snippets in file system_stm32g4xx.c:

#if defined(USER_VECT_TAB_ADDRESS)
/*!< Uncomment the following line if you need to relocate your vector Table
     in Sram else user remap will be done in Flash. */
/* #define VECT_TAB_SRAM */
#if defined(VECT_TAB_SRAM)
#define VECT_TAB_BASE_ADDRESS   SRAM_BASE       /*!< Vector Table base address field.
                                                     This value must be a multiple of 0x200. */
#define VECT_TAB_OFFSET         0x00000000U     /*!< Vector Table base offset field.
                                                     This value must be a multiple of 0x200. */
#else
#define VECT_TAB_BASE_ADDRESS   FLASH_BASE      /*!< Vector Table base address field.
                                                     This value must be a multiple of 0x200. */
#define VECT_TAB_OFFSET         0x00000000U     /*!< Vector Table base offset field.
                                                     This value must be a multiple of 0x200. */
#endif /* VECT_TAB_SRAM */
#endif /* USER_VECT_TAB_ADDRESS */
/**
  * @brief  Setup the microcontroller system.
  * @param  None
  * @retval None
  */
 
void SystemInit(void)
{
  /* FPU settings ------------------------------------------------------------*/
  #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    SCB->CPACR |= ((3UL << (10*2))|(3UL << (11*2)));  /* set CP10 and CP11 Full Access */
  #endif
 
  /* Configure the Vector Table location add offset address ------------------*/
#if defined(USER_VECT_TAB_ADDRESS)
  SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#endif /* USER_VECT_TAB_ADDRESS */
}

If I define USER_VECT_TAB_ADDRESS, then SCB->VTOR will be 0x08000000 and the program will work as expected and also the interrupt handlers are triggered as expected.

That still does not explain, why is FLASH not mapped at 0, as it should after normal boot.

I'd recommend you to double-check the option bytes, they may be set to some unusual value.

JW