Introduction
STM32F103C6T6A provides robust solutions for tasks ranging from simple time delays to sophisticated motor control using timers. The timers have the capabilities including up, down, or center-aligned counting, and their capacity to handle interrupts and DMA requests efficiently. Whether you are developing a system that requires precise PWM signal generation, event counting, or time measurement, the STM32F103C6T6’s timers are designed to meet the challenge.
In STM32F103C6T6A, the Timer module comes along with Capture and Compare module. In this post I will explain the Timer module and in the upcoming post I will explain the Capture and compare module.
STM32F103C6T6A microcontroller has total three timers, an advanced control timer (TIM1) and two general puspose timers (TIM2 and TIM3) – (See the datasheet – page 16). In this post I will explain, how to create a delay using the timer (TIM1). The same can also be done with other timers also.
A timer will have a counter register that counts from 0 to its maximum value based on the number of bits it supports. All the timers in the STM32F103C6T6A are 16-bit wide, meaning the counter register will increment or decrement the data from 0 to 65,535 or 65,535 to 0 based on the direction. Whenever the counter increments from 65,535 during upcounting, an overflow will occur, and when it decrements from 0, an underflow will occur. A flag will be set, and an interrupt may be generated if configured when the counter value becomes 0. By default, the counter will increment for each clock pulse. If we configure a prescaler between 0 to 65535, say n, the counter will increment when the n CPU clock cycles complete.
Exercise
Create a program to blink built-in LED in 1 min delay
Registers Used
- APB2 Peripheral clock enable (RCC_APB2ENR) – Enable the clock to the timer TIM1 and GPIOC
- Timer 1 Prescaler (TIM1_PSC) – Set the prescaler of the counter
- Timer 1 Auto Reload Register (TIM1_ARR) – The value loaded here will be copied to a shadow register. This shadow register will compare the counter value and generate UEV signal when the values match. This copy can happen immediately when we write to TIM1_ARR or when the update event (UEV, ie during counter overflow or generated by software) is raised base on the ARPE bit of the TIM1_CR1 register.
- Timer 1 Counter register (TIM1_CNT) – The counter register will count from 0 to predefined value in TIM1_ARR.
- Timer 1 Control register 1 (TIM1_CR1) – To enable the timer.
- Timer 1 Status register (TIM1_SR) – To check the overflow happened. This can be checked by using UIF flag.
- GPIOC Port Configuration Register (GPIOC_CRH) – Set the built-in LED pin as output
- GPIOC Port Output Data Register (GPIOC_ODR) – The register to set the LED on/off
Solution
The solution is fairly straightforward. By default, the microcontroller uses the internal RC oscillator for generating the clock. However, this oscillator is unstable and can vary with respect to voltage and temperature, making the timer generated using the internal clock potentially inaccurate. Nevertheless, for learning purposes, this is perfectly fine. In my solution, I have added code to use an external crystal oscillator, which provides an accurate time delay. I will not delve into the details of selecting the clock source in this post, but if needed, I will create another post about it. My solution is available on my GitHub page, and I have provided the link at the end of this post.
No matter whether we are using the internal RC oscillator or an external crystal oscillator as the clock source, both will provide an 8MHz clock frequency. Therefore, we do not need to change the values configured in the registers.
The maximum prescaler value is 65,535. The maximum counter value is 65,535. The clock frequency is 8,000,000 Hz.
The maximum delay that can be achieved in a single rollover is 65,535 * 65,535 / 8,000,000 ~= 536.85 seconds.
So, for our exercise, we can achieve this within a single rollover. I am setting the prescaler value to 8,000 so that each count will result in a 1ms delay.
By default the prescaler value will be 0, in this case the counter will count for every clock pulse. So for getting the prescaler value of 8000, the TIM1_PSC would be like
TIM1->PSC = 7999;
Additionally, I will configure 60,000 counts to achieve a delay of 60 seconds. The count start from 0, so the TIM1_ARR would be
TIM1->ARR = 59999;
Now clear the counter register, UIF flag and then enable the timer.
TIM1->CNT = 0;
TIM1->SR &= ~(TIM_SR_UIF);
TIM1->CR1 |= TIM_CR1_CEN;
So the timer setup would be like this
void setup_timer_1() {
RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;
TIM1->PSC = 7999;
TIM1->ARR = 59999;
TIM1->CNT = 0;
TIM1->SR &= ~(TIM_SR_UIF);
TIM1->CR1 |= TIM_CR1_CEN;
}
The UIF flag will set when the TIM1_CNT register value matches the TIM1_ARR register value. So in the main function, we will wait until the UIF flag is set and whenever the flag is set, toggle the LED.
while(1) {
while( (TIM1->SR & TIM_SR_UIF) == 0) {}
TIM1->SR &= ~(TIM_SR_UIF);
TOGGLE_LED();
}
Here is the link for my GitHub repository: Click Here