cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G070: 11 ADC channels + DMA with TIM15 TRGO trigger — DMA IRQ not firing

pradeepwagre
Associate III

Setup Description

  • MCU: STM32G070RE

  • Peripherals:

    • ADC1 sampling 11 channels (IN0–IN8, IN17, IN18).

    • DMA1 Channel1 configured circular, PERIPH→MEMORY, increment on memory.

    • Timer 15 used to generate a 3 ms integration window (for TPIC8101 knock sensor).

      • CC1 = start of window (INT pin LOW).

      • CC2 = end of window (INT pin HIGH) and also used as TRGO (OC2REF rising edge) to trigger ADC conversions.(ADC should only come into picture when INT pin goes high)

  • DMA buffer: uint16_t adc_raw_buffer[NUM_CHANNELS] where NUM_CHANNELS = 11.


What Works

  • TIM15 interrupts (CC1 and CC2) are firing as expected.

  • GPIO pin toggles correctly at CC1 and CC2.

  • TRGO is configured as LL_TIM_TRGO_OC2REF.


What Does Not Work

  • DMA1_Channel1_IRQHandler never fires.

  • The DMA buffer (adc_raw_buffer[]) remains unchanged.

  • It looks like the ADC never actually starts conversions on TRGO.                                                                             

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{
    /* Clock first */
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

    /* NVIC for DMA1 Channel 1 */
    NVIC_SetPriority(DMA1_Channel1_IRQn, 1);
    NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}

static void MX_ADC1_Init(void)
{
    /* --- Clocks --- */
	LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC);
    /**ADC1 GPIO Configuration
    PA0   ------> ADC1_IN0
    PA1   ------> ADC1_IN1
    PA2   ------> ADC1_IN2
    PA3   ------> ADC1_IN3
    PA4   ------> ADC1_IN4
    PA5   ------> ADC1_IN5
    PA6   ------> ADC1_IN6
    PA7   ------> ADC1_IN7
    PC4   ------> ADC1_IN17
    PC5   ------> ADC1_IN18
    PB0   ------> ADC1_IN8
    */
    GPIO_InitStruct.Pin = PS1_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(PS1_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = PS2_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(PS2_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = Knock_Sense_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(Knock_Sense_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = Temp_Sense1_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(Temp_Sense1_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = Temp_Sense2_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(Temp_Sense2_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = CSense1_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(CSense1_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = CSense2_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(CSense2_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = CSense3_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(CSense3_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = Tht_Psn_Ana_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(Tht_Psn_Ana_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = Gas_Leakage_Ana_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(Gas_Leakage_Ana_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = Batt_Fail_Pin;
    GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
    LL_GPIO_Init(Batt_Fail_GPIO_Port, &GPIO_InitStruct);
    /* --- ADC core --- */
    LL_ADC_InitTypeDef adc = {0};
    adc.Clock         = LL_ADC_CLOCK_SYNC_PCLK_DIV2;      // 64/2 = 16 MHz
    adc.Resolution    = LL_ADC_RESOLUTION_12B;
    adc.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
    adc.LowPowerMode  = LL_ADC_LP_MODE_NONE;
    LL_ADC_Init(ADC1, &adc);

    /* --- Regular group (TRGO from TIM15 CC2) --- */
    LL_ADC_REG_InitTypeDef reg = {0};
    reg.TriggerSource    = LL_ADC_REG_TRIG_EXT_TIM15_TRGO;
    reg.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
    reg.ContinuousMode   = LL_ADC_REG_CONV_SINGLE;
    reg.DMATransfer      = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
    reg.Overrun          = LL_ADC_REG_OVR_DATA_PRESERVED;
    LL_ADC_REG_Init(ADC1, &reg);
    LL_ADC_REG_SetTriggerEdge(ADC1, LL_ADC_REG_TRIG_EXT_RISING);
    /* --- Sequencer: 11 channels --- */
    LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_FIXED);
    LL_ADC_REG_SetSequencerScanDirection(ADC1, LL_ADC_REG_SEQ_SCAN_DIR_FORWARD);
    LL_ADC_REG_SetSequencerChannels(ADC1,
          LL_ADC_CHANNEL_0  | LL_ADC_CHANNEL_1  | LL_ADC_CHANNEL_2  |
          LL_ADC_CHANNEL_3  | LL_ADC_CHANNEL_4  | LL_ADC_CHANNEL_5  |
          LL_ADC_CHANNEL_6  | LL_ADC_CHANNEL_7  | LL_ADC_CHANNEL_8  |
          LL_ADC_CHANNEL_17 | LL_ADC_CHANNEL_18);

    LL_ADC_SetSamplingTimeCommonChannels(ADC1,
        LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_160CYCLES_5);

    /* --- DMA1 Channel 1 for ADC --- */
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
    /* Direction PERIPH -> MEMORY (you missed this earlier) */
    LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1,     LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
    LL_DMA_SetPeriphAddress (DMA1, LL_DMA_CHANNEL_1, (uint32_t)&ADC1->DR);
    LL_DMA_SetMemoryAddress (DMA1, LL_DMA_CHANNEL_1, (uint32_t)adc_raw_buffer);
    LL_DMA_SetDataLength    (DMA1, LL_DMA_CHANNEL_1, NUM_CHANNELS);   // 11
    LL_DMA_SetPeriphSize    (DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD);
    LL_DMA_SetMemorySize    (DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_HALFWORD);
    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_SetMode          (DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);
    LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_VERYHIGH);
    /* Enable TC (and TE if you want) interrupts at channel level */
    LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
    LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1);
    /* Clear any stale flags before enabling */
    LL_DMA_ClearFlag_GI1(DMA1); LL_DMA_ClearFlag_TC1(DMA1); LL_DMA_ClearFlag_TE1(DMA1);
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);

    /* --- ADC power: regulator + calibration + enable --- */
    LL_ADC_EnableInternalRegulator(ADC1);
    for (volatile uint32_t d=0; d<(SystemCoreClock/100000U); ++d) { __NOP(); } // ~1ms

    /* IMPORTANT: ADC must be disabled here (it is) when starting calibration */
    LL_ADC_StartCalibration(ADC1);
    while (LL_ADC_IsCalibrationOnGoing(ADC1)) { /* small wait */ }

    LL_ADC_Enable(ADC1);
    while (!LL_ADC_IsActiveFlag_ADRDY(ADC1)) { /* small wait */ }
    /* Do NOT call StartConversion when using external trigger; TIM15 CC2 will fire it */
}

void TIM15_Init_3ms_Window(void)
{
  /* INT/HOLD pin (example PB1) */
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);
  LL_GPIO_SetPinMode (GPIOB, LL_GPIO_PIN_1, LL_GPIO_MODE_OUTPUT);
  LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_1, LL_GPIO_SPEED_FREQ_HIGH);
  LL_GPIO_SetPinPull (GPIOB, LL_GPIO_PIN_1, LL_GPIO_PULL_NO);
  LL_GPIO_SetOutputPin(GPIOB, LL_GPIO_PIN_1);  /* idle HIGH */

  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_TIM15);

  /* 1 us tick */
  uint32_t psc = (SystemCoreClock / 1000000UL);
  LL_TIM_SetPrescaler   (TIM15, psc - 1);
  LL_TIM_SetAutoReload  (TIM15, 4999);                      /* 9 ms period */
  LL_TIM_SetCounterMode (TIM15, LL_TIM_COUNTERMODE_UP);
  LL_TIM_EnableARRPreload(TIM15);

  /* CC1 at 0 us -> pull LOW (ISR) */
  LL_TIM_OC_SetMode(TIM15, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_TOGGLE);
  LL_TIM_OC_SetCompareCH1(TIM15, 0);
  LL_TIM_OC_EnablePreload(TIM15, LL_TIM_CHANNEL_CH1);
  LL_TIM_CC_EnableChannel(TIM15, LL_TIM_CHANNEL_CH1);
  LL_TIM_EnableIT_CC1(TIM15);

  /* CC2 at 3000 us -> set HIGH (ISR) & TRGO edge for ADC */
  /* Use PWM2 so OC2REF has a RISING edge at compare */
  LL_TIM_OC_SetMode(TIM15, LL_TIM_CHANNEL_CH2, LL_TIM_OCMODE_PWM2);
  LL_TIM_OC_SetCompareCH2(TIM15, 3000);
  LL_TIM_OC_EnablePreload(TIM15, LL_TIM_CHANNEL_CH2);
  LL_TIM_CC_EnableChannel(TIM15, LL_TIM_CHANNEL_CH2);
  LL_TIM_EnableIT_CC2(TIM15);

  /* TRGO on OC2REF (ADC listens for rising edge) */
  LL_TIM_SetTriggerOutput(TIM15, LL_TIM_TRGO_OC2REF);

  /* NVIC + start */
  NVIC_SetPriority(TIM15_IRQn, 2);
  NVIC_EnableIRQ(TIM15_IRQn);

  LL_TIM_GenerateEvent_UPDATE(TIM15);    /* load PSC/ARR/CCR */
  LL_TIM_SetCounter(TIM15, 0);
  LL_TIM_EnableCounter(TIM15);
}

 

9 REPLIES 9
waclawek.jan
Super User

Read out and check/post content of TIM, ADC, DMAMUX and DMA registers.

JW

pradeepwagre
Associate III

I didn't get you. I have already shared Initialization code in chat.

TDK
Super User

The DMAMUX needs to be configured to tie the ADC to that DMA channel.

If you feel a post has answered your question, please click "Accept as Solution".
pradeepwagre
Associate III

share the snippet if possible.

Can you share the example?already doing this part. n STM32G070 (G0x0) there is no DMAMUX, and there’s nothing to tie.
The ADC to DMA mapping is fixed in hardware: ADC1 → DMA1 Channel 1.

* --- DMA1 Channel 1 for ADC --- */
    LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
    /* Direction PERIPH -> MEMORY (you missed this earlier) */
    LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1,     LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
    LL_DMA_SetPeriphAddress (DMA1, LL_DMA_CHANNEL_1, (uint32_t)&ADC1->DR);
    LL_DMA_SetMemoryAddress (DMA1, LL_DMA_CHANNEL_1, (uint32_t)adc_raw_buffer);
    LL_DMA_SetDataLength    (DMA1, LL_DMA_CHANNEL_1, NUM_CHANNELS);   // 11
    LL_DMA_SetPeriphSize    (DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD);
    LL_DMA_SetMemorySize    (DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_HALFWORD);
    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_SetMode          (DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);
    LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_VERYHIGH);
    /* Enable TC (and TE if you want) interrupts at channel level */
    LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
    LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1);
    /* Clear any stale flags before enabling */
    LL_DMA_ClearFlag_GI1(DMA1); LL_DMA_ClearFlag_TC1(DMA1); LL_DMA_ClearFlag_TE1(DMA1);
    LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1); 

Already 

waclawek.jan
Super User

> I have already shared Initialization code in chat.

The mcu works out of the registers content rather than source code. There may be e.g. errors in the library you are using.

> STM32G070 (G0x0) there is no DMAMUX, and there’s nothing to tie.
> The ADC to DMA mapping is fixed in hardware: ADC1 → DMA1 Channel 1.

Where do you have this information from, AI?

Have you had a look at the Reference Manual?

waclawekjan_0-1756312043743.png

As TDK said, you need to set DMAMUX channel related to the DMA channel you are using, the numbering does not match so it's DMAMUX channel 0. At the beginning of DMAMUX chapter you'll see the table of triggers, so enter the number related to given ADC to DMAMUX_CxCR.DMAREQ_ID, that's all. There certainly are LL functions and defines for these things, you can find them in Cube, which is open source.

Or maybe TDK will provide you with ready made solution, but do you want to rely on that?

JW

I am sorry but it didn't intend to hurt anyone. As a reference I prefer to use it. By the way I can't see any code share by you.

> n STM32G070 (G0x0) there is no DMAMUX, and there’s nothing to tie.

Did AI tell you that? The Reference Manual says it's there. See the "Request Generator" part.

TDK_0-1756312301041.png

 

TDK_1-1756312453744.png

 

 

Here's a working example:

https://github.com/STMicroelectronics/STM32CubeG0/blob/master/Projects/NUCLEO-G070RB/Examples/ADC/ADC_MultiChannelSingleConversion/Src/main.c

 

If you feel a post has answered your question, please click "Accept as Solution".

Thank you for giving this solution but it didn't help me. I need a selective channels for adc conversion and they are 11 in number. The solution you gave goes upto 8 channels only. I am trying to use configurable sequence and not fixed sequence.