Skip to main content
Associate II
May 8, 2025
Solved

Trigger ADC with TIM2 TRGO not showing correct timing

  • May 8, 2025
  • 1 reply
  • 789 views

Im using the following code that is part of a metal detector I'm building. Im trying to trigger the ADC based on TRGO of TIM2 (I need dma / adc readings every 10us after the of each TX pulse). I expect the TIM2_IRQHandler interrupt to enter 10us (ADC_DELAY_US) after the TX pulse ends (TIM1_CC_IRQHandler). I added a buffer (buffer variable) to keep track of the milliseconds of the events but when I print it on gdb (print buffer) and then subtract tx_end_micros to adc_start_micros, I dont have 10us, as expected:

This is a print of the gdb:

 {{tx_end_micros = 2573435545, adc_start_micros = 2573437222, num_pulses = 2624525, num_pulses_tim2 = 2624526}, {tx_end_micros = 2573436545, adc_start_micros = 2573436542, 
 num_pulses = 2624526, num_pulses_tim2 = 2624527}}

Below is the full code:

#include <stdio.h>
#include "stm32f4xx.h"
#include <math.h>

// Function Prototypes
void SystemClock_Config(void);
void GPIO_Init(void);
void TIM1_PWM_Init(void);
void TIM5_Init(void);
void LED_Init(void);
void TIM2_TRGO_Init(void);
void ADC1_Trigger_TIM2_Setup(void);
uint16_t adc1_read(void);
float compute_average(void);
void Delay_us(uint32_t);
void send_uart(char *msg);
uint32_t micros(void);
void toggle_led(void);
void enviar_dados_adc_serial(void);
void reset_adc(void);

// Settings
#define ADC_DELAY_US 10 // Delay after TX pulse before ADC sampling
#define NUM_SAMPLES 64 // Number of ADC samples to capture

// For alignment in memory
volatile uint16_t adc_buffer[NUM_SAMPLES] __attribute__((aligned(4)));
volatile uint32_t tx_num_pulses = 0;
volatile uint8_t adc_capture_complete = 0;

typedef struct {
 uint32_t tx_end_micros;
 uint32_t adc_start_micros;
 uint32_t num_pulses;
 uint32_t num_pulses_tim2;
} TimingData;

volatile TimingData buffer[2];
volatile uint8_t current_buffer = 0;

volatile TimingData timing; // Global instance, accessible in both ISRs

volatile uint32_t last_time;
volatile uint32_t curr_time = 0;
volatile uint32_t delta = 0;

void delay_ms(int milliseconds) {
 volatile int i;
 for (i = 0; i < milliseconds * 1000; i++) {
 __NOP(); // No operation, just waste time
 }
}

void reset_adc() {
 // Reset DMA for next capture
 ADC1->CR2 &= ~ADC_CR2_DMA; // Desliga DMA
 ADC1->CR2 |= ADC_CR2_DMA; // Religa DMA
 DMA2_Stream0->CR &= ~DMA_SxCR_EN;
 while (DMA2_Stream0->CR & DMA_SxCR_EN);
 
 // Clear DMA flags
 DMA2->LIFCR |= DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0 |
 DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0 | DMA_LIFCR_CFEIF0;
 
 // Reset DMA counter
 DMA2_Stream0->NDTR = NUM_SAMPLES;
 // Re-enable DMA
 DMA2_Stream0->CR |= DMA_SxCR_EN;
}

void ADC1_Trigger_TIM2_Setup(void) {
 RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
 GPIOA->MODER |= (3 << (1 * 2)); // PA1 analog mode

 ADC1->CR2 = 0; // rests all (not using continuous capture)
 ADC1->CR2 |= ADC_CR2_ADON;
 Delay_us(1); // Small delay for ADC stabilization

 ADC1->SMPR2 = 0; // 3 cycles sampling time (channels 0-9)
 ADC1->SQR3 = 1; // Channel 1 (PA1)
 
 // Configure for TIM2_TRGO triggering
 ADC1->CR2 &= ~(0xF << ADC_CR2_EXTSEL_Pos); // Clear EXTSEL
 ADC1->CR2 |= (6 << ADC_CR2_EXTSEL_Pos); // TIM2_TRGO
 ADC1->CR2 |= (1 << ADC_CR2_EXTEN_Pos); // Rising edge

 // Configure DMA
 ADC1->CR2 |= ADC_CR2_DMA; // Enable DMA

 ADC1->CR2 &= ~ADC_CR2_CONT; // Desativa modo contínuo
}


void TIM2_TRGO_Init(void) {
 RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

 TIM2->PSC = 84 - 1; // 1 µs per tick
 TIM2->ARR = ADC_DELAY_US - 1; // Count to delay value
 TIM2->CNT = 0;

 // Configure TIM2 to generate TRGO on Update event
 TIM2->CR2 &= ~TIM_CR2_MMS;
 TIM2->CR2 |= TIM_CR2_MMS_1; // MMS = 010 = Update Event

 // Configure TIM2 as slave of TIM1 in Trigger mode (starts when receiving trigger)
 TIM2->SMCR &= ~(TIM_SMCR_SMS | TIM_SMCR_TS); // Clear all
 TIM2->SMCR |= (0x6 << TIM_SMCR_SMS_Pos); // SMS=110 (Trigger Mode)
 TIM2->SMCR |= (0x0 << TIM_SMCR_TS_Pos); // TS=000 (ITR0 = TIM1)
 TIM2->SMCR |= (0x6 << TIM_SMCR_SMS_Pos); // SMS=110 (Trigger Mode)

 // Enable one-pulse mode so TIM2 stops after one cycle (form dma in non circular mode)
 // TIM2->CR1 |= TIM_CR1_OPM;

 // Enable update interrupt for TIM2
 TIM2->DIER |= TIM_DIER_UIE;
 NVIC_EnableIRQ(TIM2_IRQn);
}

void DMA2_Stream0_ADC_Config(void) {
 RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // Enable DMA2 clock

 // Make sure stream is disabled
 DMA2_Stream0->CR &= ~DMA_SxCR_EN;
 while (DMA2_Stream0->CR & DMA_SxCR_EN); // Wait for deactivation

 // Clear interrupt flags
 DMA2->LIFCR |= DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0 |
 DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0 | DMA_LIFCR_CFEIF0;

 // Configure peripheral address (ADC1_DR)
 DMA2_Stream0->PAR = (uint32_t)&ADC1->DR;
 // Buffer address in memory
 DMA2_Stream0->M0AR = (uint32_t)&adc_buffer;
 // Number of transfers
 DMA2_Stream0->NDTR = NUM_SAMPLES;
 
 // Configure for channel 0 (ADC1)
 DMA2_Stream0->CR =
 (0 << DMA_SxCR_CHSEL_Pos) | // Channel 0
 (1 << DMA_SxCR_MSIZE_Pos) | // Memory 16 bits
 (1 << DMA_SxCR_PSIZE_Pos) | // Peripheral 16 bits
 (1 << DMA_SxCR_MINC_Pos) | // Increment memory
 (0 << DMA_SxCR_PINC_Pos) | // Don't increment peripheral
 (0 << DMA_SxCR_CIRC_Pos) | // Normal mode (not circular) circular 1
 (0 << DMA_SxCR_DIR_Pos) | // Peripheral -> Memory
 (1 << DMA_SxCR_TCIE_Pos); // Transfer complete interrupt

 // Enable DMA interrupt
 NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

void toggle_led() {
 GPIOD->ODR |= (1 << 12); // Turn LED on
 Delay_us(10);
 GPIOD->ODR &= ~(1 << 12); // Turn LED off
}

void uart_init(void) {
 // Enable clocks for GPIOA and USART1
 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
 RCC->APB2ENR |= RCC_APB2ENR_USART1EN;

 // Configure PA9 and PA10 for alternate function (AF7)
 // TX = PA9, RX = PA10
 GPIOA->MODER &= ~((3 << (9 * 2)) | (3 << (10 * 2))); // clear bits
 GPIOA->MODER |= (2 << (9 * 2)) | (2 << (10 * 2)); // alternate function mode

 GPIOA->AFR[1] &= ~((0xF << ((9 - 8) * 4)) | (0xF << ((10 - 8) * 4)));
 GPIOA->AFR[1] |= (7 << ((9 - 8) * 4)) | (7 << ((10 - 8) * 4)); // AF7 (USART1)

 // Configure USART1: 115200 baud, 8N1
 USART1->BRR = 0x2D9; // For 115200 baud with PCLK2 = 84 MHz
 USART1->CR1 = USART_CR1_TE | USART_CR1_UE; // Enable transmitter and USART
}

void send_uart(char *msg) {
 while (*msg) {
 while (!(USART1->SR & USART_SR_TXE)); // wait for empty buffer
 USART1->DR = *msg++;
 }
}

int main(void) {
 // Configure system clock
 SystemClock_Config();

 // Initialize UART for debug via serial
 uart_init();

 // Initialize GPIO (PE13 as TIM1_CH3 Alternate Function)
 GPIO_Init();

 LED_Init();

 TIM5_Init();

 // Initialize TIM1 for PWM on PE13
 TIM1_PWM_Init();

 // Initialize TIM2 for ADC triggering
 TIM2_TRGO_Init();

 // Configure ADC and DMA
 DMA2_Stream0_ADC_Config();
 ADC1_Trigger_TIM2_Setup();

 while (1) {
 if (adc_capture_complete) { 
 // Reset flag
 adc_capture_complete = 0;
 
 /* // Reset DMA for next capture
 ADC1->CR2 &= ~ADC_CR2_DMA; // Desliga DMA
 ADC1->CR2 |= ADC_CR2_DMA; // Religa DMA
 DMA2_Stream0->CR &= ~DMA_SxCR_EN;
 while (DMA2_Stream0->CR & DMA_SxCR_EN);
 
 // Clear DMA flags
 DMA2->LIFCR |= DMA_LIFCR_CTCIF0 | DMA_LIFCR_CHTIF0 |
 DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0 | DMA_LIFCR_CFEIF0;
 
 // Reset DMA counter
 DMA2_Stream0->NDTR = NUM_SAMPLES;
 // Re-enable DMA
 DMA2_Stream0->CR |= DMA_SxCR_EN; */
 }
 }
}

void SystemClock_Config(void) {
 // 1. Enable HSE (High-Speed External Clock, typically 8 MHz)
 RCC->CR |= RCC_CR_HSEON; 
 while (!(RCC->CR & RCC_CR_HSERDY)); // Wait for HSE to stabilize
 
 // 2. Configure PLL to multiply frequency
 RCC->PLLCFGR = (8 << RCC_PLLCFGR_PLLM_Pos) | // HSE 8MHz /8 = 1MHz
 (336 << RCC_PLLCFGR_PLLN_Pos) | // 1MHz × 336 = 336MHz
 (0 << RCC_PLLCFGR_PLLP_Pos) | // 00 = divide by 2 → 168MHz
 (7 << RCC_PLLCFGR_PLLQ_Pos) | // USB clock = 336/7 = 48MHz
 RCC_PLLCFGR_PLLSRC_HSE; // Use HSE as source

 // 3. Activate PLL
 RCC->CR |= RCC_CR_PLLON;
 while (!(RCC->CR & RCC_CR_PLLRDY)); // Wait for PLL to stabilize
 
 // 4. Configure bus frequencies to avoid overclock
 RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB Prescaler = 1 (168 MHz)
 RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; // APB1 Prescaler = 4 (42 MHz)
 RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2 Prescaler = 2 (84 MHz)
 
 FLASH->ACR |= FLASH_ACR_LATENCY_5WS | FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN;

 // 6. Switch System Clock to PLL
 RCC->CFGR |= RCC_CFGR_SW_PLL;
 while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // Wait for switch

 // Configure ADC clock
 ADC->CCR &= ~ADC_CCR_ADCPRE; // Clear ADCPRE bits
 ADC->CCR |= (3 << ADC_CCR_ADCPRE_Pos); // 11b → divide APB2 por 8 → ADCCLK = 84 / 8 = 10.5 MHz :white_heavy_check_mark:

 SystemCoreClockUpdate();
}

void GPIO_Init(void) {
 // Enable clocks for GPIOs A and E
 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; // Enable GPIOA clock (for PA8)
 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; // Enable GPIOE clock

 // Configure PE13 as alternate function (TIM1_CH3)
 GPIOE->MODER &= ~(3 << (13 * 2));
 GPIOE->MODER |= (2 << (13 * 2)); // Alternate function mode
 GPIOE->OSPEEDR |= (3 << (13 * 2)); // High speed for PE13
 
 GPIOE->AFR[1] &= ~(0xF << ((13 - 8) * 4)); // Clear AFR[1] bits for PE13
 GPIOE->AFR[1] |= (1 << ((13 - 8) * 4)); // Set AF1 for PE13 (TIM1_CH3)

 // Configure PA8 as alternate function (TIM1_CH1)
 GPIOA->MODER &= ~(3 << (8 * 2));
 GPIOA->MODER |= (2 << (8 * 2)); // Alternate function mode
 GPIOA->OSPEEDR |= (3 << (8 * 2)); // High speed
 GPIOA->AFR[1] &= ~(0xF << ((8 - 8) * 4)); // Clear AF bits
 GPIOA->AFR[1] |= (1 << ((8 - 8) * 4)); // AF1 (TIM1_CH1)

 // Configure PA1 as analog input for ADC
 GPIOA->MODER |= GPIO_MODER_MODER1; // Analog mode (0b11)

 // Debug pin (set PE1 as debug pin)
 GPIOE->MODER |= (1 << (1 * 2)); // PE1 as output
 GPIOE->ODR &= ~(1 << 1); // Start low
}

// Timer used only for debug timing
void TIM5_Init(void) {
 // Enable clock for TIM5
 RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
 // APB1 is 42MHz, but timer clock is 2x that (84MHz)
 // For 1 µs resolution: 84MHz / 84 = 1MHz
 TIM5->PSC = 84 - 1;
 // Max auto-reload (32 bits)
 TIM5->ARR = 0xFFFFFFFF;
 // Start counting
 TIM5->CR1 |= TIM_CR1_CEN;
 // Reset TIM5 counter
 TIM5->CNT = 0;
}

uint32_t micros(void) {
 return TIM5->CNT;
}

void TIM1_PWM_Init(void) {
 // Enable TIM1 clock on APB2 bus
 RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;

 // Set timer frequency (reduce clock to 1 MHz, so 1 tick = 1 µs)
 TIM1->PSC = 168-1; // Prescaler (divides timer clock)
 // Set PWM period (1ms = 1000 µs)
 TIM1->ARR = 1000 - 1; // Define maximum count (PWM period)
 // Set duty cycle (100 µs)
 TIM1->CCR3 = 100; // TX pulse width
 TIM1->CCR1 = 500; // 500µs pulse width (for PA8)

 // Configure TIM1 Channel 3 for PWM1 mode
 TIM1->CCMR2 &= ~TIM_CCMR2_OC3M; // Clear output mode bits for Channel 3
 TIM1->CCMR2 |= (6 << TIM_CCMR2_OC3M_Pos); // Set Channel 3 to PWM1 mode
 TIM1->CCMR2 |= TIM_CCMR2_OC3PE; // Enable Preload for CCR3
 // Configure polarity (active high)
 TIM1->CCER &= ~TIM_CCER_CC3P;
 TIM1->CCER |= TIM_CCER_CC3E; // Enable PWM output on Channel 3

 // Configure Channel 1 (for PA8 charge pump pulse) as PWM Mode 1
 TIM1->CCMR1 &= ~TIM_CCMR1_OC1M;
 TIM1->CCMR1 |= (6 << TIM_CCMR1_OC1M_Pos); // PWM Mode 1
 TIM1->CCMR1 |= TIM_CCMR1_OC1PE; // Enable preload
 // Configure polarity (active high) and enable output
 TIM1->CCER &= ~TIM_CCER_CC1P; // Active high
 TIM1->CCER |= TIM_CCER_CC1E; // Enable output
 
 // Configure TIM1 to generate TRGO on OC3REF signal (when TX pulse ends)
 TIM1->CR2 &= ~TIM_CR2_MMS;
 //TIM1->CR2 |= TIM_CR2_MMS_1 | TIM_CR2_MMS_0; // MMS = 011 = OC3REF as trigger
 // TIM1->CR2 |= (TIM_CR2_MMS_1 | TIM_CR2_MMS_0);
 TIM1->CR2 |= (0x3 << TIM_CR2_MMS_Pos); // MMS=011 (OC3REF = PWM end)

 // Enable interrupt at compare match (when CCR3 is reached)
 TIM1->DIER |= TIM_DIER_CC3IE;
 
 // Enable both compare and update interrupts
 TIM1->DIER |= TIM_DIER_CC3IE | TIM_DIER_UIE;
 NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn); // Enable update interrupt
 NVIC_EnableIRQ(TIM1_CC_IRQn); // Enable compare interrupt

 // Start TIM1 counter, generating PWM
 TIM1->CR1 |= TIM_CR1_CEN;
 // Enable PWM output on physical pins (only for advanced timers like TIM1 and TIM8)
 TIM1->BDTR |= TIM_BDTR_MOE;
}

void LED_Init(void) {
 RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
 GPIOD->MODER |= (1 << (12 * 2));
 GPIOD->ODR &= ~(1 << 12); // Ensure LED starts off
}

uint16_t adc1_read(void) {
 ADC1->CR2 |= ADC_CR2_SWSTART; // Start conversion
 while (!(ADC1->SR & ADC_SR_EOC)); // Wait until conversion complete
 return ADC1->DR; // Read result
}

/* === Interrupt Handlers === */

// TIM1 update interrupt handler (debug: used only for debug of TX pulse start with LED)
void TIM1_UP_TIM10_IRQHandler(void) {
 if (TIM1->SR & TIM_SR_UIF) {
 TIM1->SR &= ~TIM_SR_UIF;
 // TX pulse start logic here if needed
 }
}

// TIM1 compare interrupt handler (at end of TX pulse)
void TIM1_CC_IRQHandler(void) {
 if (TIM1->SR & TIM_SR_CC3IF) {
 TIM1->SR &= ~TIM_SR_CC3IF;
 tx_num_pulses++;
 
 buffer[current_buffer].tx_end_micros = micros();
 buffer[current_buffer].num_pulses = tx_num_pulses;
 uint8_t buf = current_buffer ^ 1;
 current_buffer = buf;
 }
}

void TIM2_IRQHandler(void) {
 // Check if update interrupt occurred
 if (TIM2->SR & TIM_SR_UIF) {
 TIM2->SR &= ~TIM_SR_UIF; // Clear interrupt flag
 
 buffer[current_buffer].adc_start_micros = micros();
 buffer[current_buffer].num_pulses_tim2 = tx_num_pulses;
 }
}

void DMA2_Stream0_IRQHandler(void) {
 if (DMA2->LISR & DMA_LISR_TCIF0) {
 // Clear the transfer complete flag
 DMA2->LIFCR |= DMA_LIFCR_CTCIF0;
 adc_capture_complete = 1;
 }
}

// --- Utilities ---
void Delay_us(uint32_t us) {
 volatile uint32_t count = us * (SystemCoreClock / 1000000) / 5;
 while (count--);
}

 

Best answer by Sarra.S

Hello @cristianosar

With TIM2->PSC = 84 - 1; and TIM2->ARR = ADC_DELAY_US - 1, this configuration should give you a 10 µs delay,  if your timer clock is 84 MHz 

Also, If you want TIM2 to stop after one cycle, this line (TIM2->CR1 |= TIM_CR1_OPM) should be uncommented.

1 reply

Sarra.SBest answer
ST Employee
May 12, 2025

Hello @cristianosar

With TIM2->PSC = 84 - 1; and TIM2->ARR = ADC_DELAY_US - 1, this configuration should give you a 10 µs delay,  if your timer clock is 84 MHz 

Also, If you want TIM2 to stop after one cycle, this line (TIM2->CR1 |= TIM_CR1_OPM) should be uncommented.

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
Associate II
May 22, 2025

Thanks!