Using STM32 ADC with Timer Trigger option it helps us to control the sampling time of the ADC. It also becomes important when we need to sample Audio or while making some waveform analysis options. In this tutorial we are going to extend our previous STM32 ADC+USART example, where we implemented the ADC basic single channel single conversion mode and take the conversion and send it over USART at 230400 baudrate. We used 168Mhz CPU clock for STM32F4 Discovery board. We are going to keep all that configurations remain the same in this tutorial as well.

Setting Clocks to 168Mhz

First of all, lets make the clock configurations for our microcontroller. In STM32F4 Discovery board where we have STM32F407VG microcontroller we have option to run that microcontroller at full speed which is 168Mhz. We can achieve this task with the help of following code

   //enable power interface clock source
    RCC->APB1ENR |= RCC_APB1ENR_PWREN; 
    PWR->CR |= PWR_CR_VOS;

 
 #define PLL_N 168 //SYSTEM CLOCK SPEED (FCY (MHz))
 #define PLL_N 180 //SYSTEM CLOCK SPEED (FCY (MHz))
 #define HSI 16000000 //INTERAL OSC FREQUENCY 
        //Fcy = Fxtal x PLL_N/(PLL_P x PLL_M)
 #define PLL_M (HSI/2000000) 
 #define PLL_P 2
 #define PLL_Q 7

    // HCLK = SYSCLK / 1
    //CORE CLOCK = 180MHZ
    RCC->CFGR |= RCC_CFGR_HPRE_DIV1;  
    
    // PCLK2 = HCLK / 2
   
    /*
 //PERIPHERAL CLOCK 2 = 168HZ/4 = 42MHZ, THIS IS BECAUSE THE SPI 
     MODULES (AND POSSIBLY OTHERS) DO NOT OPERATE PROPERLY WHEN PCLK > 42MHZ


    */
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV4;     
    // PCLK1 = HCLK / 4
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;  

    // Configure the main PLL
   RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) | (PLL_Q << 24);

    // Enable the main PLL
    RCC->CR |= RCC_CR_PLLON;

    // Wait till the main PLL is ready
    while(!(RCC->CR & RCC_CR_PLLRDY));
   
   // Configure Flash prefetch, Instruction cache, Data cache and wait state
    FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

    // Select the main PLL as system clock source
    RCC->CFGR &=~ RCC_CFGR_SW;
    RCC->CFGR |= RCC_CFGR_SW_PLL;

    // Wait till the main PLL is used as system clock source
    while ((RCC->CFGR & RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);          
    SystemCoreClockUpdate();Code language: PHP (php)

This code is same like we did in our previous tutorial and will remain the same in our future tutorials as well whenever we need the clock to be set at full speed.

ADC Initialization with Timer Trigger Option

Now we need to modify our adc_init() function. We need to change the bits in CR2 register of the ADC1. For this we need to modify bits from 24-> 29th bit in ADC1->CR2 register. As you can see in the reference manual it shows like this

Here you can see that bit 29 and bit 28 is used to enable the Exernal trigger options. The reason to use two bits is to select the edge of the trigger pulse. So you have option to select rising, falling or both and if you keep these bits 00 it will disable the external select option.

Next in the EXTSEL[3:0] bits, You can define the trigger option. Because we are going to use the TIMER 2 TRGO pulse, So we need to put 0110 into these bits. Which make the selection of TIM2->TRGO signal. If you are concerned about the complete list of the Trigger groups it could be found under the same page in reference manual like follows

adc_extsel bits for adc external trigger groups

Now our adc_init() function may look like this

void adc_init(void)
{
 RCC->AHB1ENR|=RCC_AHB1ENR_GPIOCEN; //GPIOC clock enable
 GPIOC->MODER|=(3u<<(2*0));    //ADC input pin is analogue mode 
 RCC->APB2ENR|=RCC_APB2ENR_ADC1EN;  //ADC clock enable
 ADC1->SQR1&=~ADC_SQR1_L;       
        
        //set number of conversions per sequence to 1
 ADC1->SQR3&=~ADC_SQR3_SQ1;      
        
        //clear channel select bits
 ADC1->SQR3|=10;   //set channel
 ADC1->CR1 &= ~ADC_CR1_SCAN; //SCAN mode disabled 
 ADC1->CR2 &= ~ADC_CR2_CONT; //Disable continuous conversion
     
 ADC1->SMPR1 |= ADC_SMPR1_SMP10;
 
 //Trigger on Rising edge
        ADC1->CR2 &=  ~(1<<29);
 ADC1->CR2 |= (1<<28); 
 
        //External Event TIM2 TRGO [0110]
 const uint32_t TIM2_TRGO = ADC_CR2_EXTSEL_2 | ADC_CR2_EXTSEL_1; 
 ADC1->CR2 &= ~(ADC_CR2_EXTSEL);   // remove all selections
 ADC1->CR2 |= TIM2_TRGO;
 
 ADC1->CR2|=ADC_CR2_ADON;       
        //enable ADC 
 ADC1->CR2|=ADC_CR2_SWSTART;  
}
Code language: PHP (php)

Timer 2 Initialization With TRGO option

Once we set our ADC, the next thing that we need to do is to Initialize our Timer and enable the TRGO pulse. Because we are using TIMER 2 which is attached to APB1 and it runs at (168/4)*2 = 84Mhz Clock. We can select the appropriate timings using these clock information. If we put the value 8399 which is actual (8400 – 1) in the prescalar, it will give us 10Khz pulse. Next we put 10K in auto reload register and it will give us 1hz signal. We slowed down the sampling just to make sure everything is working as expected. So now our timer initialization looks like this.

void TIM2_Configuration(void){


    // enable TIM2 clock (bit0)
    RCC->APB1ENR |= (1 << 0);     

    TIM2->PSC = 8399; //For 84Mhz
    TIM2->ARR = 10000; //it will get one second delay

 /* Reset the MMS Bits */
  TIM2->CR2 &= (uint16_t)~TIM_CR2_MMS;
  /* Select the TRGO source */
  TIM2->CR2 |=  TIM_CR2_MMS_1; //UPDATE EVENT  

    // Update Interrupt Enable
    TIM2->DIER |= (1 << 0);

    // enable TIM2 IRQ from NVIC
    NVIC_EnableIRQ(TIM2_IRQn);

    // Enable Timer 2 module (CEN, bit0)
    TIM2->CR1 |= (1 << 0);
 
 
}
Code language: PHP (php)

As you may noticed that MMS bits are used to enable the TRGO pulse generation. Rest is similar to do the TIMER related stuff as we are use too doing when creating simple timing intervals.

Finally, our TIM2_IRQ looks like this


/*************************************************
* timer 2 interrupt handler
*************************************************/
void TIM2_IRQHandler(void)
{  
 // clear interrupt status
 if (TIM2->DIER & 0x01) {
  if (TIM2->SR & 0x01) {
   TIM2->SR &= ~(1U << 0);
  }
 }

    //Optionally we may toggle the LED to notice the intervals
    GPIOD->ODR ^= (1 << 13);

    
}Code language: PHP (php)

So that’s all for this tutorial, If everything setup properly you are ready to auto sample the ADC with timer trigger option. Just change the timings of TIM2 in Prescalar and ARR register and you are good to go.

Acknowledgement

Special thanks to @Arsenal who helped me to figure out my core mistake while creating this code. Here is the forum link if you wish to see his complete answer.

Also I would like to mention this Github stm32f4 bare metal repository who helped me a lot to write most of my code and learn the STM32 from scratch.

By Abdul Rehman

My name is Abdul Rehman and I love to do Reasearch in Embedded Systems, Artificial Intelligence, Computer Vision and Engineering related fields. With 10+ years of experience in Research and Development field in Embedded systems I touched lot of technologies including Web development, and Mobile Application development. Now with the help of Social Presence, I like to share my knowledge and to document everything I learned and still learning.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.