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