Skip to main content
Explorer
August 27, 2025
Question

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

  • August 27, 2025
  • 6 replies
  • 784 views

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);
}

 

    This topic has been closed for replies.

    6 replies

    Super User
    August 27, 2025

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

    JW

    Explorer
    August 27, 2025

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

    Super User
    August 27, 2025

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

    Explorer
    August 27, 2025

    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 

    Super User
    August 27, 2025

    > 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

     

    Explorer
    August 27, 2025

    share the snippet if possible.

    Super User
    August 27, 2025

    > 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

    Explorer
    August 27, 2025

    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.

    Super User
    August 27, 2025

    Maybe you misunderstood the purpose of this forum.

    We don't provide ready-made code, we provide explanation.

    JW

    Explorer
    August 28, 2025

    I am also looking for the same. At least the solution given should be nearer for what I am looking.