“Half our time is spent trying to find something to do with the time we have rushed through life trying to save.”
Will Rogers
In this, the second in the ARM tutorial series, we take a look at the STM32 Timer peripheral. The STM32 Timer peripheral is very versatile and "provides multiple operating modes to off-load the CPU from repetitive tasks while minimizing interfacing circuitry needs".
Because the Timer peripheral is such a complex subject, I am taking a somewhat different approach in this article as well as giving a detailed description of the Timer
peripheral, I have provided a variety of code examples that cover a wide range of functionality. Direct Memory Access (DMA) in association with the Timer
peripheral will be saved for another article where I'll tackle DMA in greater detail.
I have tried to use the on-board User Button and LED for most examples, but in some instances this was not possible. The STM32CubeIDE
debugger is necessary to view the STM32C031C6
registers; but to view the Timer
output other than the LED, an oscilloscope or logic analyzer is needed.
The code was written, programmed and debugged using the STM32CubeIDE
which is a IDE from STMicroelectonics
that may be downloaded here. You will need to jump through a few hoops to get it but it is a great tool, free with no restrictions on use. There are plenty of tutorials on setting up the STM32CubeIDe
environment, so I will not beat a dead horse.
Getting to Know the Microcontroller
I intend on using a different Microcontroller in each of these articles so that one can get a taste for the various devices, their capabilities, functionality and what peripherals they offer. For this article, I chose a NUCLEO-C031C6
with an STM32C031C6
Microcontroller as the main processor. It is a fairly new, inexpensive, entry level processor that is touted as "Your next generation 8-bit MCU is a 32-bit". It may be purchased direct from STMicroelectronics for $12.14.
A short list of features for the NUCLEO-C031C6
include:
- 32-bit Arm® Cortex®-M0+ core running at 48MHz, on reset configured to run at 12MHz
- 32 Kbytes of flash memory, and 12 Kbytes of RAM
- ARDUINO® Uno V3 connectivity support
- No requirement for any separate probe as it integrates the ST-LINK debugger/programmer
- STM32C0 MCUs are available in 8 to 48-pin packages
- 8 16-bit timers; 1 advanced, 4 general purpose, 2 watchdogs and 1 systick
Figure 1. NUCLEO-C031C6 Pinout
Because the MCU is so new, there is not much information available other than the manufacturer's literature. If you plan on working with this device, the literature that's available is critical.
STM32031C6 Reference Manual | st.com (rm0490) |
STM32031C6 Datasheet | st.com (ds13867) |
NUCLEO-C031C6 User Manual | st.com (um2953) |
Timer Peripheral Overview
STM32 Microcontrollers are all based on the same architecture with a resolution of 16- or 32-bits. They may be independently configured as an input or output and can each have from 1 to 9 channels. The Timer peripherals are linked internally for timer synchronization or chaining and may also interconnect with other peripherals for monitoring or triggering purposes.
Figure 2 shows the general purpose Timer
peripherals that the STM32C031C6
has on board. Although all STM32 Microcontrollers implement a different Timer
configuration, they all incorporate the advanced control TIM1 Timer peripheral.
Figure 2. Timer comparison chart
Timer 1 Peripheral, a Close Look (Datasheet Pg. 21)
Figure 3 shows the block diagram for the STM32
timer peripheral TIM1
which is made by the assembly of four units:
- Master/Slave controller unit (blue)
- The time-base unit (green)
- The timer channels unit (purple)
- The break feature unit (yellow)
Figure 3. NUCLEO-C031C6 Timer Block (Reference Manual Pg. 299)
In the following sections, delve into each section of the Master
/Slave
unit in more detail.
Master/Slave Controller Unit
The Master
/Slave
unit mainly provides control signals for the time-base unit. This includes providing the time-base unit with the counting clock signal, as well as the counting direction control signal. It decides the right counting configuration for the time-base unit based on master/slave configuration.
This unit handles inter-timer synchronization. It can be configured to output a synchronization signal to another peripheral by way of TRG0
as well as to control time-base counter events of external signals.
All STM32
devices incorporate timers, but not all devices feature the same master/slave capabilities. I would refer you to the reference manual of the device you are using to see what timers are implemented and their capabilities.
Time-base Unit
The time-base unit encompasses a counter, prescaler and the repetition counter. The incoming signal is first passed through the prescaler where the frequency may be adjusted before reaching the time-base unit. The incoming signal must be 3 times less than the clock frequency.
The timer counter is controlled by two registers:
TIMx_CNT
which is the timer register where the current count is stored.
TIMx_ARR
contains the reload value for the counter. When counting up, count is set to 0
. - when
TIMx_CNT = ARR
and set to ARR
when TIMx_CNT = 0
when counting down.
When the counters cycle is restarted, it fires an "update event" which can be found in the TIM_SR
register as the Update Interrupt Flag (UIF
). This value needs to be reset to continue receiving notifications.
Timer Channels Unit
The Timer Channels are the working elements in which the timer interacts with the external environment, with few exceptions mapped through the MCUs pins that can be configured as either input or output.
Timer Channel Configured as Output
When the timer channels is configured as output, it is used to generate a signal that is the result of comparing the content of the TIMx_CCRy
with the timer counter.
Referring to Figure 4 below, it can be seen that the result of the logical comparison is OCyREF
which is then fed into the Channel Output Stage which applies conditioning to the OCyREF
signal based on some configuration parameters and then output to the devices pins as alternate function. Notice that some channels may output a complementary signal as well.
Figure 4. Timer channel configured as input
Timer Channel Configured as Input
Figure 5 below shows the Input stage and we can immediately see that TIMx_CHy
can be configured as either input or output as alternate function. When configured as input, it can be used to time stamp the rising, falling or both edges of external signals. The incoming signal passes through a conditioning stage which includes a filter and an edge detection unit. The polarity of the edge detector may be configured using the TIMx-CCER
register. The conditioning circuitry outputs two signals; (source: General Purpose Timer Cookbook)
FIvFPy
: is the TIy
timer input signal which was filtered and on which an active edge is detected depending on the polarity of the timer channel "y
". TIyFPz
: is always the TIy
timer input signal which was filtered but on which an active edge is detected depending on the polarity of the timer channel "z
”.
Each timer channel may be configured on one of three modes that the prescaler mux connected to the timer channel prescaler. The passing through the prescaler, which scales the incoming signal and emits as ICyPS
which triggers the transfer of count into the capture/compare y
register.
Figure 5. Block diagram when timer configured as input
Break Unit
The Break unit is available only to channels that implement at least one channel with two complementary outputs. The Break unit works on output channels and pretty much does as its name implies, on the first edge after the break is executed, the output channel is either turned off or put in a safe state.
Timer Clock Source
The Timer
peripherals may be clocked either by an internal clock or an external clock.
Internal Clock
The internal Timer clock (TIMCLK
) is derived from SYSCLK
and may be scaled using the AHB Prescaler and the APB Prescaler (Refer to Figure 6). As shown in Figure 6, the APB Prescaler has an anomaly that if the APB Prescaler value is 1 TIMCLK
is equal to PCLK
times 1 and any other value TIMCLK
equals PCLK times 2.
Figure 6. NUCLEO-C031C6 Clock Tree (Reference Manual Pg. 108)
External Clock
There are two ways to synchronize (or externally clock) an STM Timer:
- External Clock Mode 1: External signal is input from
TIx
inputs - External Clock Mode 2: External signal is input from ETR
All incoming external signals must be 3 times less than the internal clock frequency. The timer synchronizes the signal first in all cases except on ETR thus allowing the input signal to be greater than the internal clock frequency. In all cases, the input signal must be three times less than the input frequency.
TIMCLK <= 3 <= Input Signal
Even though the Time
is clocked by an external source, the APB clock is still required to provide synchronization. The synchronization is provided by a D flip-flop, as shown in Figure 7 below. The external signal is fed into the first stage D flip-flop input where the synchronized signal is retrieved from the output of the second D flip-flop.
Figure 7. Synchronization Block
Timer inputs like TIx
and ETR feature a filter stage that may be activated to filter out unwanted signals of a duration that is less than a configured threshold. Both the Input Capture prescaler and the signal filter may be programmed using the Capture/Compare Control Register (CCMR); for the Prescaler ISxPSC
and for the filter ICxF. The filtering is also dependent on the CKD field in the CR1 register which controls the ratio between the sampling clock source and the minimal timer duration of a valid pulse. A very good reference for STM32 Timers is the STM32 General Purpose Cookbook.
In Figure 3, the TIMx_ETR
signal passes through the synchronization prescaler called ETRP, then it is routed through the resynchronization circuit (which is called the ETRF signal which has the same constraint as above), the frequency output from the prescaler should be three times less than the internal frequency.
Example Code
Code TOC
If you are only interested in the code or a particular implementation, click on the link you are interested in.
It is really hard to understand a new technology without getting your hands dirty, if your keyboard is anything like mine anyway. In this section, I will explain by example how the various timers work by the examples in the following sections. We will start simple with the Basic Count example and with each example get more complex and learn what the timer is capable of.
The Basic Count example is a very simple routine that sets a target frequency of 10Hz, the counter counts up to the configured value and when it reaches that value and the Update Interrupt Flag (UIF) is set. In the while
loop, we wait for the UIF flag to be set and when it is we reset it and toggle the User LED. This is not very efficient for two reasons: one it is a blocking routine and two because of the latency between the polling and the setting of the flag.
void BasicCount()
{
ConfigureUserLED();
RCC->APBENR1 |= (1 << 1);
TIM3->PSC = 12000 - 1;
TIM3->ARR = 100 - 1;
TIM3->CR1 |= 1;
while(1)
{
while(!(TIM3->SR & TIM_SR_UIF)){}
TIM3->SR &= ~TIM_SR_UIF;
GPIOA->ODR ^= (1 << 5);
}
}
Listing 1. Basic Counter
Compare With Output
There's a little more work involved configuring this routine but the polling has been taken out of the picture and the output directly controls the User LED. In order to do this, we need to configure the User LED, on PA5 to its alternate function, the code for ConfigureAFUserLED
is provided in the download. Additionally, we need to configure the time to output the 1Hz signal to that pin.
In Listing 2 line 17, the timer is configured to toggle the output instead of manually toggling it in the while loop as was done in the BasicCounter
example. Then the CCER register is set to enable the timer to output on pin TIM1_CH1
which we have configured to be PA5 and finally the Master Output Enable is set to begin the process.
void BasicCountWithOutput()
{
ConfigureAFUserLED();
RCC->APBENR2 |= (1 << 11);
TIM1->PSC = 1200 - 1;
TIM1->ARR = 10000 - 1;
TIM1->CCMR1 |= (3 << 4);
TIM1->CCER |= 1;
TIM1->BDTR |= (1 << 15);
TIM1->CR1 |= 1;
while(1)
{
}
}
Listing 2. Compare output
Basic Mode 1 Pulse Width Modulation (PWM)
PWM
is a signal that is generated by the channel where the frequency is determined by the value in the TIMx_ARR
register and the duty cycle from the TIMx_CCRx
register.
There are two PWN
modes that may be configured using the TIMx_CCMR1
register OCxM
field.
- PWM Mode 1: When upcounting the channel stays active as log as
TIMx_CNT
is less than TIMx_CCRx
else inactive and when down counting active long as TIMx_CNT
is greater than TIMx_CCRx
else active. - PWN Mode 2: When upcounting the channel stays inactive as log as
TIMx_CNT
is less than TIMx_CCRx
else active and when down counting inactive long as TIMx_CNT
is greater than TIMx_CCRx
else active.
void BasicPWMMode1()
{
ConfigureAFUserLED();
RCC->APBENR2 |= (1 << 11);
TIM1->PSC = 12000 - 1;
TIM1->ARR = 1000 - 1;
TIM1->CCR1 = 500;
TIM1->CCMR1 |= (6 << 4) | (1 << 3);
TIM1->CCER |= 1;
TIM1->BDTR |= (1 << 15);
TIM1->CR1 |= TIM_CR1_CEN;
}
Listing 3. Basic Mode 1 PWM
Linking Timers
The demo code links timers 1 and 3 together to make a 32-bit times that toggles the User LED every 4 seconds. Time 1 is configured as the Master with a frequency of 1Hz and Timer 3 as the slave that counts to 4.
void LinkTimer1And3()
{
RCC->IOPENR = 2;
GPIOB->MODER &= ~(3 << 16);
GPIOB->MODER |= (2 << 16);
GPIOB->AFR[1] &= ~0x0f;
GPIOB->AFR[1] |= 3;
RCC->APBENR2 |= (1 << 11);
RCC->APBENR1 |= (1 << 1);
TIM1->CR2 |= (2 << 4);
TIM1->PSC = 12000 - 1;
TIM1->ARR = 1000 - 1;
TIM3->SMCR |= 7;
TIM3->PSC = 0;
TIM3->ARR = 4 - 1;
TIM3->CCMR1 |= (3 << 4);
TIM3->CCER |= 1;
TIM3->BDTR |= (1 << 15);
TIM1->CR1 |= 1;
TIM3->CR1 |= 1;
while(1);
}
Listing 4. Linking Timer1 to Timer 3
One Shot Mode
One shot pulse or one shot, as I like to call it, allows the counter to be started in response to some stimulus and output a pulse of programmable length and delay.
Delay = value in CCRx register
- Pulse With =
TIMx_ARR - TIMx_CCRx
The code is set up that the pulse is triggered by pressing the User Button and the output may be viewed on PA8. Using the demo code requires an Oscilloscope or logic analyzer to view the results.
In the demo code, the Delay and Pulse Width calculations are:
MCU frequency = 12MHz with prescale of 100 = 1Mhz
Delay = 1MHz / 250 = 480 =? 1/480 = 2.08mS
Pulse Width = 1Mhz / (500 - 250) = 2.08mS
Since the Repetition Control Register (RCR) is set to 0
, the pulse will only occur one time.
void OneShotMode()
{
RCC->IOPENR = 5;
RCC->APBENR2 |= (1 << 11);
ConfigDelay();
GPIOA->MODER &= ~(3 << 16);
GPIOA->MODER |= (2 << 16);
GPIOA->AFR[1] |= 2;
GPIOC->MODER &= ~(3 << 26);
TIM1->PSC = 100;
TIM1->ARR = 500;
TIM1->CCR1 = 250;
TIM1->RCR = 0;
TIM1->CCMR1 |= (7 << 4);
TIM1->CR1 |= TIM_CR1_OPM;
TIM1->CCER |= 1;
TIM1->BDTR |= TIM_BDTR_MOE;
while(1)
{
if (!(GPIOC->IDR & (1 << 13)))
{
TIM1->CR1 |= TIM_CR1_CEN;
while(!(TIM1->SR & TIM_SR_UIF)){}
TIM1->SR &= ~1;
Delay(100);
}
}
}
Listing 5. One Shot Mode triggered by User Button
Input Capture External Source
The Input Capture demo code configures PA8 as an alternate function and then in SMCR register maps it to the ITR2 input. To view the results of the capture, install a jumper from User Button (PC13 CN7 Pin 23) to the User LED (PA5 CN10 Pin 24). When the User Button is pressed, the User LED will toggle. To view the time result from the CCR1 register, you will need to run the program in debug mode and put a break point at line 35.
When the User Button is pressed, a capture is detected on TI1
and the CC1IF
flag is set and the contents of CCR1
contain the time stamp value. If the CC1IF
flag is already set when the capture is detected, the over-capture flag (CCxOF
) will be set. The CC1IF
flag requires that the application reset the flag.
void InputCaptureExternalSource()
{
uint16_t result = 0;
RCC->APBENR2 |= (1 << 11);
RCC->IOPENR = 1;
GPIOA->MODER &= ~(3 << 16);
GPIOA->MODER |= (2 << 16);
GPIOA->AFR[1] &= ~(0x0f);
GPIOA->AFR[1] |= 2;
GPIOA->MODER &= ~(3 << 10);
GPIOA->MODER |= (1 << 10);
TIM1->CCMR1 |= 1;
TIM1->SMCR |= (5 << 4) + (4 << 0);
TIM1->CCER |= 1;
TIM1->CR1 |= 1;
while (1)
{
while(!(TIM1->SR & TIM_SR_CC1IF)){}
result = TIM1->CCR1;
GPIOA->ODR ^= (1 << 5);
TIM1->SR &= ~TIM_SR_CC1IF;
}
}
Listing 6. Input Capture demo code
Input Capture Internal Source
Time 1 is configured to output a signal every 250mS through TRG0
and since Timer 1 and Timer are linked though ITR2
Timer 3 is set up to receive the event on ITR2
. When this occurs, I toggle the User LED and reset the CC1IF
flag.
void InputCaptureInternalSource()
{
RCC->APBENR2 |= (1 << 11);
RCC->APBENR1 |= RCC_APBENR1_TIM3EN;
RCC->IOPENR = 1;
GPIOA->MODER &= ~(3 << 10);
GPIOA->MODER |= (1 << 10);
TIM3->PSC = 3000;
TIM3->ARR = 1000;
TIM3->CR2 |= (2 << 4);
TIM1->CCMR1 |= 3;
TIM1->SMCR |= (2 << 4) | (4 << 0);
TIM1->CCER |= 1;
TIM1->CR1 |= TIM_CR1_CEN;
TIM3->CR1 |= TIM_CR1_CEN;
while (1)
{
while(!(TIM1->SR & TIM_SR_CC1IF)){}
GPIOA->ODR ^= (1 << 5);
TIM1->SR &= ~TIM_SR_CC1IF;
}
}
Listing 7. Input capture via Internal Source
Blink via Interrupt
This is a very simple interrupt routine that blinks the User LED on each interrupt which occurs every 1S.
The routine basically sets up the PA5 pin as output, sets the interrupt enable flag and then sets the NVIC
registers.
I have not gone much into interrupts of DMA in this article because I intend to address both subjects in subsequent articles.
void BlinkyWithInterrupt()
{
ConfigureUserLED();
RCC->APBENR2 |= (1 << 11);
TIM1->PSC = 12000;
TIM1->ARR = 1000;
TIM1->EGR |= TIM_EGR_UG;
TIM1->DIER |= (1 << 0);
NVIC_SetPriority(TIM1_BRK_UP_TRG_COM_IRQn, 0);
NVIC_EnableIRQ(TIM1_BRK_UP_TRG_COM_IRQn);
TIM1->CR1 |= (1 << 0);
while(1);
}
Listing 8. Blink via Interrupt
Below is the interrupt handler for the demo code above:
void TIM1_BRK_UP_TRG_COM_IRQHandler()
{
GPIOA->ODR ^= (1 << 5);
TIM1->SR &= ~(1<<0);
}
Listing 9. Interrupt handler
Conclusion
There's so much more I wanted to add to this article but it got so long that I left it to the code to fill in some of the blanks. I really enjoyed writing this article but the research took a long time as I started pretty much from scratch. I had worked with Arduino timers but they don't have near the capabilities of the STM32
Timers. I hope that you will get as much out of this as I have.
References
- Understanding STM32 Naming Conventions, 5/20/2020, Maker.io Staff digikey.com (AN4013)
- Application Node, STM32 cross-series timer overview (AN4776) Application Node, General-purpose timer cookbook for STM32 microcontrollers
History
- 12th March, 2023: Initial version