ADC1 not starting conversion with TIM2_TRGO trigger on STM32F407VET6 (bare metal, no HAL)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
2025-04-16 8:34 AM - edited 2025-04-16 5:37 PM
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.
Solved! Go to Solution.
- Labels:
-
ADC
-
DMA
-
STM32F4 Series
Accepted Solutions
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
2025-04-16 5:39 PM
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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Email to a Friend
- Report Inappropriate Content
2025-04-16 5:39 PM
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
