cancel
Showing results for 
Search instead for 
Did you mean: 

ADC1 not starting conversion with TIM2_TRGO trigger on STM32F407VET6 (bare metal, no HAL)

cristianosar
Associate II

I'm working on a bare-metal project using an STM32F407VET6 (generic “black board” from China - https://stm32-base.org/boards/STM32F407VET6-STM32-F4VE-V2.0.html). I'm trying to trigger ADC1 conversions using TIM2_TRGO (timer trigger output), but the ADC only works when using SWSTART. I've done extensive testing and debugging to isolate the problem. Here's what I found:

What works:
ADC1 works with SWSTART: I can read from channel 1 (PA1) manually and see the expected values.

LED debug: PD12 lights up if the ADC reads a value above 1000.

TIM2 generates update events: Confirmed by enabling the TIM2 UIF interrupt and toggling PD12 on each update.

Trigger configuration:

EXTSEL = 4 → TIM2_TRGO

EXTEN = 1 → rising edge

Forced trigger via TIM2->EGR |= TIM_EGR_UG does not trigger the ADC.

VREF+ is connected to 3.3V, and VREF- to GND, with a 100nF decoupling cap.

TIM2_TRGO is confirmed to work via IRQ debug (PD12 blinks).

DMA is configured (when enabled), but never triggers → PD12 never toggles.

 

Debug setup:

I'm using a LED on PD12 to debug, I comment / uncomment the code during the tests.

PA1 is directly connected to a 3.3v source on the board.


PD12: toggles if ADC value > 1000

PD12: toggles in TIM2_IRQHandler() → confirms update event

PD12: toggles in DMA2_Stream0_IRQHandler() → never fires (ADC not triggering)

 

Problem:
ADC1 never starts conversion from external trigger (TRGO), despite all relevant settings and confirmed timer behavior. No conversion ever occurs unless I manually call SWSTART.

 

Am I doing something wrong or is there any undocumented requirement for STM32F4 (F407 specifically) to accept external triggers like TIM2_TRGO?
Are there any known silicon errata or obscure setup steps that could block the trigger path to the ADC?

My code (made with the help of ChatGpt) is the following:

#include "stm32f4xx.h" void LED_Init(void); void TIM2_TRGO_Init(void); void ADC1_Trigger_TIM2_Setup(void); void DMA2_Stream0_ADC_Config(void); void LED_Init(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; GPIOD->MODER |= (1 << (12 * 2)); } void TIM2_TRGO_Init(void) { RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; TIM2->PSC = 84 - 1; // 1 MHz TIM2->ARR = 10000 - 1; // 10ms TIM2->EGR |= TIM_EGR_UG; // FORÇA UPDATE ANTES DE CR2 TIM2->CR2 &= ~TIM_CR2_MMS; // Limpa TIM2->CR2 |= TIM_CR2_MMS_1; // MMS = 010 (update event) TIM2->CR1 |= TIM_CR1_CEN; // Agora sim liga o timer TIM2->DIER |= TIM_DIER_UIE; // Habilita interrupção de update NVIC_EnableIRQ(TIM2_IRQn); // NVIC } void ADC1_Trigger_TIM2_Setup(void) { RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; GPIOA->MODER |= (3 << (1 * 2)); // PA1 analógico ADC1->CR2 = 0; ADC1->SQR3 = 1; ADC1->SMPR2 |= (7 << 3); // canal 1, 480 ciclos ADC1->CR2 |= (4 << ADC_CR2_EXTSEL_Pos); // TIM2_TRGO ADC1->CR2 |= (1 << ADC_CR2_EXTEN_Pos); // rising edge ADC1->CR2 |= ADC_CR2_ADON; NVIC_EnableIRQ(DMA2_Stream0_IRQn); // interrupt } int main(void) { LED_Init(); DMA2_Stream0_ADC_Config(); ADC1_Trigger_TIM2_Setup(); TIM2_TRGO_Init(); while (1) { if (ADC1->SR & ADC_SR_EOC) { uint16_t value = ADC1->DR; if (value > 1000) GPIOD->ODR |= (1 << 12); // debug: NEVER TOGGLES else GPIOD->ODR &= ~(1 << 12); // debug: NEVER TOGGLES } } } void TIM2_IRQHandler(void) { if (TIM2->SR & TIM_SR_UIF) { TIM2->SR &= ~TIM_SR_UIF; // DEBUG: Pisca PD12 ao disparar TRGO // GPIOD->ODR ^= (1 << 12); // debug: OK } } void DMA2_Stream0_ADC_Config(void) { RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // Habilita clock do DMA2 DMA2_Stream0->CR &= ~DMA_SxCR_EN; // Garante que o stream está desligado while (DMA2_Stream0->CR & DMA_SxCR_EN); // Espera desativação // Limpa flags de interrupção DMA2->LIFCR |= DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0 | DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0 | DMA_LIFCR_CFEIF0; // Configura o endereço do periférico (ADC1_DR) DMA2_Stream0->PAR = (uint32_t)&ADC1->DR; // Endereço do buffer na memória DMA2_Stream0->M0AR = (uint32_t)&adc_buffer[0]; // Número de transferências DMA2_Stream0->NDTR = 1; // Configura canal 0 (para ADC1) DMA2_Stream0->CR = (0 << DMA_SxCR_CHSEL_Pos) | // Canal 0 (1 << DMA_SxCR_MSIZE_Pos) | // Memória 16 bits (1 << DMA_SxCR_PSIZE_Pos) | // Periférico 16 bits (1 << DMA_SxCR_MINC_Pos) | // Incrementa memória (0 << DMA_SxCR_PINC_Pos) | // Não incrementa periférico (0 << DMA_SxCR_CIRC_Pos) | // Sem circular (1 se quiser contínuo) (0 << DMA_SxCR_DIR_Pos) | // Periférico -> Memória (1 << DMA_SxCR_TCIE_Pos); // Interrupção por transferência completa DMA2_Stream0->CR |= DMA_SxCR_EN; // Liga o DMA } void DMA2_Stream0_IRQHandler(void) { if (DMA2->LISR & DMA_LISR_TCIF0) { DMA2->LIFCR |= DMA_LIFCR_CTCIF0; // DEBUG: Pisca PD12 quando o DMA completar GPIOD->ODR ^= (1 << 12); // debug: NEVER TOGGLES } }
View more

Thanks for the help.

 

1 ACCEPTED SOLUTION

Accepted Solutions
cristianosar
Associate II

I've found the problem, the code above was missing 

DMA2_Stream0_ADC_Config

but the real problem was me setting the wrong bits of the external trigger:
changed from:

 
// ADC1->CR2 |= (4 << ADC_CR2_EXTSEL_Pos); // TIM2_TRGO

to

ADC1->CR2 |= (6 << ADC_CR2_EXTSEL_Pos); // TIM2_TRGO
 
Thanks all for the help. 

View solution in original post

1 REPLY 1
cristianosar
Associate II

I've found the problem, the code above was missing 

DMA2_Stream0_ADC_Config

but the real problem was me setting the wrong bits of the external trigger:
changed from:

 
// ADC1->CR2 |= (4 << ADC_CR2_EXTSEL_Pos); // TIM2_TRGO

to

ADC1->CR2 |= (6 << ADC_CR2_EXTSEL_Pos); // TIM2_TRGO
 
Thanks all for the help.