Atmega328p timer

Hello there. It been a while for me not updating my blog. This is because last week I was quite busy with the school exam. Anyways, let’s learn how to control atmega328p timer by controlling register directly. The aim of this blog is using the internal timer to achieve timestamp since start and delay function.

How a timer works

To explain this, I have drawn a simplified diagram which show its mechanism.

DISCLAIMER: This diagram is not the complete block diagram of the timer but it is enough for learning how timer works.

Basically, only two register need to handle. They are TCNTn (Timer/Counter) and TCCRX (Timer/Counter control register).

Register Description

There are three timer in atmega328p. Although their specification are slightly different, their registers are still similar. So, I will only show the first timer register in this blog. For the others, you can refer to the official datasheet from page 74.

Timer 0 (8 bits)

TCCR0A (Timer/Counter 0 control register

Bit 7 6 5 4 3 2 1 0
Name COM0A1 COM0A0 COM0B1 COM0B0 - - WGM01 WGM00
IO R/W R/W R/W R/W R R R/W R/W
Init 0 0 0 0 0 0 0 0

TCCR0B (Timer/Counter 0 control register B)

Bit 7 6 5 4 3 2 1 0
Name FOC0A FOC0B - - WGM02 CS02 CS01 CS00
IO W W R R R/W R/W R/W R/W
Init 0 0 0 0 0 0 0 0

Compare mode bit (COM0A1, COM0A0, COM0B1, COM0B0) are for PWM and not important in this blog. Meanwhile, wavefront generation mode (WGM01, WGM 00) are important for configuring TOV0 interrupt (Timer 0 Overflow) trigger value. This can be done by refering to the table below.

Mode WGM02 WGM01 WGM00 TOP TOV Flag Set on
0 0 0 0 0xFF MAX
1 0 0 1 0xFF BOTTOM
2 0 1 0 OCRA MAX
3 0 1 1 BOTTOM MAX
4 1 0 0 - -
5 1 0 1 OCRA BOTTOM
6 1 1 0 - -
7 1 1 1 OCRA TOP

Force Output Compare (FOC0A, FOC0B) is not in today’s topic so we are not going to discuss about it. However, clock selection bit (CS00, CS01, CS02) is necessary for setting clock prescaler. Here’s the configuration that can be done.

CS02 CS01 CS00 Description
0 0 0 No clock source (Timer/Counter stopped)
0 0 1 clk I/O /(no prescaling)
0 1 0 clk I/O /8 (from prescaler)
0 1 1 clk I/O /64 (from prescaler)
1 0 0 clk I/O /256 (from prescaler)
1 0 1 clk I/O /1024 (from prescaler)
1 1 0 External clock source on T0 pin. Clock on falling edge.
1 1 1 External clock source on T0 pin. Clock on rising edge.

Now, we already know the registers and their usages. But, there’s still a thing we need to learn. It is the TOV, timer overflow interrupt. However, it is initially disabled by the control unit. To enable it, we need to write a logical one to TOIE (Timer/Counter0 Overflow Interrupt Enable) bit to TIMSK (Timer/Counter Interrupt Mask Register).

How to achieve millis() and micros() function

millis()

At first, TOV interrupt ISR is needed.

When interrupt is triggered:

  1. Increase timer0_overflow_count by 1
  2. Add the timer0_millis with millisecond per overflow
  3. Add the fraction value of millisecond per overflow to timer0_fract
  4. If timer0_fract overflow, timer0_fract clear and timer0_millis increase by 1

When millis() function is called:

  1. store state register and disable interrupt by cli()
  2. store timer0_millis to m
  3. restore state register
  4. return m

micros()

When micros() function is called:

  1. store state register and disable interrupt by cli()
  2. store timer0_overflow_count to m
  3. fetch TCNT0 value to t
  4. if TOV0 bit in TIFR0 is one, increase m by 1
  5. shift m to left by 8 bit and add with t
  6. multiple m by 4
  7. restore state register
  8. return m

For further understanding, you can refer to my code