Baremetal Arduino: Timers

0 - Introduction

While using an arduino there are a lot of things that require timings, communications like SPI and I2C or PWM but, besides communications, maybe you want to make an animation with LEDs, or measure time between events, either way, having access to a timer is very important. The ATMega328p inside the arduino has 3 timers and, in this article, we will be using Timer0. Timer0 and Timer2 have 8bit counters and Timer1 has a 16bit one. 

Before starting, add this to the top of your ‘main.c’ file, ‘F_CPU’ is the cpu frequency for the Arduinos Uno and Nano and the includes import all the necessary register addresses and functions we will be using:

#include <avr/io.h>
#include <avr/interrupt.h>

#ifndef F_CPU
// CPU Frequency (Arduino is usually 16MHz but can also be 8MHz)
#define F_CPU 16000000UL
#endif

Every register and formula in this article can be found in this datasheet. and, if you do not own an Arduino, you can buy one here.

Let’s start by learning what is the OCRnA. The OCRnA is the Output Compare Register n (can be 0, 1 or 2), and it’s function is to store the value at which the timer interrupts (and in the mode we will be using, CTC, it makes the counter go back to zero).

To calculate this number we can use the following formula:

On this formula, ‘t_secs’ is the time in seconds that you want the timer to take in between each interrupt, ‘p’ is the prescaler value and ‘f_cpu’ is the CPU frequency (defined as ‘F_CPU’).
Keep in mind though that if the result is more than 255 that means that the 8bit timer and prescaler chosen can’t be used, either increase the prescaler or use Timer1.

2 - Using Timer0

In this article we will write a small example that will use Timer0 to turn the onboard LED on or off every second. To achieve this, we will make the timer generate an interruption every millisecond, incrementing a variable every time and, at 1000ms, the led state is inverted.

To do that we start by initializing the Timer0. We clear the ‘TCCR0A’ register and enable CTC by setting bit ‘WGM01’ to ‘1’, then on register ‘TCCR0B’ we set the prescaler to ’64’ by turning on bits ‘CS01’ and ‘CS00’, after that, enable interrupts for output compare A by turning on bit ‘OCIE0A’ on ‘TIMSK0’ and, finally, you use the formula from before to calculate the ‘OCR0A’. The result code is the following:

// Initialize and start the timer 0
void init_timer0()
{
    TCCR0A = 0; // normal operating mode
    // enable CTC
    TCCR0A |= (1 << WGM01);
    // set prescaler to 64 (page 117)
    TCCR0B |= (1 << CS01) | (1 << CS00);
    // enable output compare A
    TIMSK0 |= (1 << OCIE0A);
    // 1 interrupt per ms
    // used the formula given at 
    // https://www.tmvtech.com/baremetal-arduino-timers#calc
    OCR0A = 250;
}

After creating the function that initializes the timer, you will need an ‘ISR’ (Interrupt service routine), that is the function that will be called every millisecond by the interrupt. You will also need a volatile variable (on interrupt, volatile is used to prevent the compiler from optimizing the variable) to keep track of how much time has passed (‘uint8_t’ does not have enough ‘resolution’, so ‘uint16_t’ will be used). In the ‘ISR’ you only need to increment the value of your variable:

// Stores how much ms since the arduino is on
volatile uint16_t t0_millis = 0;
// Interrupt service routine (ISR) for Timer0
ISR(TIMER0_COMPA_vect)
{
    t0_millis++;
}

Finally, on ‘main’, it is needed to set the onboard led’s pin to ouput, initialize the timer, enable global interrupts and create the infinite loop. In the loop, you check if one second or more has elapsed and, if true, flip the led state and reset the counter:

int main()
{
    // onboard led pin as output
    DDRB |= (1 << PB5);

    // initialize timer 0
    init_timer0();

    // Enable global interrupts
    sei();

    // loop forever
    while (1)
    {
        // when one second has elapsed
        if (t0_millis >= 1000)
        {
            // flip led state
            PORTB ^= (1 << PB5);
            // reset count
            t0_millis = 0; 
        }
    }
    return 0;
}

If you have an Arduino to test this code in, you can use this makefile we made in a previous article that will be getting updated as needed.

If you do not have an Arduino, or want to test the code before flashing it to one, you can test it on Wokwi:

And that’s all for today’s article. In a future article we will also touch the subject of PWM, using the timers you learned to use in this one.

As always, thanks for reading and stay tuned for more tech insights and tutorials. Until next time and keep exploring the world of tech!