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

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.