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.

1 ACCEPTED SOLUTION

Accepted Solutions

> 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

View solution in original post

14 REPLIES 14
MM..1
Chief II

I dont ask why you dont use more simpler HAL.

read stm32g4xx_ll_dma.h

I see twoo non standart

  1. you try modify SCK pin , your pin is alternate cant handle
  2. you enable NSS management but pin isnt alternate. Maybe this function is software management?
StefanHogg
Associate II

The decision of using LL driver instead of HAL is due to the less used flash memory.

  1. I modify the SCK pin because the SPI slave needs SCK low when being selected. To ensure that, I try to modify SCK when SPI is disabled.
  2. Ok, that's correct. It's non-standard, I will fix this.

Unfortunately, with both issues fixed the behavior is the same. The interrupt handlers are still not reached...

BTW:

Could it be something G4xx specific? I tried the same sequence with a STM32G031 and there it is working as expected.

StefanHogg
Associate II

Update: I confirmed that this is an interrupt issue.

If I just poll for the DMA transmission complete flags instead of using the DMA transmission interrupts, erverything will work as expected.

Any idea what avoids the interrupts to be triggered?

- interrupts not enabled in NVIC - read out NVIC registers to check

- current execution priority higher than interrupts' priority - incorrectly finished previous interrupt or incorrectly set up RTOS

 - globally disabled interrupts

Here is some general checklist. 

JW

StefanHogg
Associate II

The interrupts are enabled in DMA initialization:

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(),0, 0));
  NVIC_EnableIRQ(DMA1_Channel2_IRQn);
 
}

This is also reflected by the NVIC registers

0693W00000JPAhQQAX.jpgAdditionally I call __enable_irq() before my main while loop.

What struggles me, is the set bit in NVIC register "IABR0" which means that an interrupt is active.

I've cross-checked the name of my interrupt handlers "DMA1_Channel1_IRQHandler" and "DMA1_Channel2_IRQHandler". They match the handlers which are embedded in the interrupt vector table of the file "startup_stm32g431kbtx.s" embedded in this project.

So everything seems to be correct for calling the interrupt handlers, but they aren't triggered...

> What struggles me, is the set bit in NVIC register "IABR0" which means that an interrupt is active.

Well then probably it is. Where is the program counter?

> I've cross-checked the name of my interrupt handlers "DMA1_Channel1_IRQHandler" and "DMA1_Channel2_IRQHandler". They match the handlers which are embedded in the interrupt vector table of the file "startup_stm32g431kbtx.s" embedded in this project.

Never trust source code - processor executes binary, not the source code. Have a look at the vector table in binary and check that the proper vector is at proper position.

JW

StefanHogg
Associate II

> Well then probably it is. Where is the program counter?

The program counter is at 0x1FFF4B30.

0693W00000JPFS1QAP.jpg0693W00000JPFSBQA5.jpgWhat does this mean? It is not a "normal" program address...

> Never trust source code - processor executes binary, not the source code. Have a look at the vector table in binary and check that the proper vector is at proper position.

The interrupt vectors I'm interested in are at address 0x0000006C and 0x00000070.

0693W00000JPFSfQAP.jpgMy handler are located at addresses 0x08001280 and 0x080012CC (seen in map-file).

0693W00000JPFSuQAP.jpgIn the output hex file the interrupt vector looks like following picture.

0693W00000JPFT4QAP.jpgThe stored addresses in vector table are different by 1 compared to the addresses in map file. But I've checked that with other standard IRQHandlers like "HardFault_Handler" or "MemManage_Handler". There is the same difference, so from my point of view this seems to be ok.

Yes, the vector table is OK.

> The stored addresses in vector table are different by 1

Cortex-Mx runs permanently in Thumb mode (see some basic ARM history/overview) and bit 0 of code addresses signalize this.

> PC=0x1fff4b30

> What does this mean? It is not a "normal" program address...

This is in the system ROM area, a.k.a. bootloader. Check your BOOT0 pin and setting of the relevant option bits.

JW

StefanHogg
Associate II

> This is in the system ROM area, a.k.a. bootloader. Check your BOOT0 pin and setting of the relevant option bits.

The BOOT0 pin is connected to GND, so the device will boot from flash.

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