Arduino Timer0 in Assembly Explained Like I’m Teaching My Grandma

by BoboacaC in Teachers > Pre-K

58 Views, 0 Favorites, 0 Comments

Arduino Timer0 in Assembly Explained Like I’m Teaching My Grandma

instructables.jpg
Arduino Timer0 Explained, CTC Mode, OC0A Toggle, PD6, Beginner Friendly, ATmega328, AVR Assembly

Many beginners — and even some experienced makers — get confused or stuck when trying to understand or use Timer0 on the ATmega328, the microcontroller inside the classic Arduino Nano and Uno. Abstract definitions in datasheets can be intimidating, so in this guide we’ll break things down into clear, practical steps you can actually use in your own projects.

Supplies

arduino-nano.jpg

In this tutorial will use Arduino nano (with atmega328p) an usb cable and an pc or phone tat can comunicate over serial and https://costycnc.github.io/avr-compiler-js/ compiler and uploader based on webserial

Timer0: Opening the Drawer

registri.jpg

When you pull out the Timer0 drawer, you’ll see several compartments inside. Each one is a register — a small storage space inside the microcontroller — and each has a job to do.

See this Atmega328p datasheet at page 279

Activating Timer0

registri1.jpg

The first step is opening the right drawer.

For Timer0, that drawer is called 0x25 — the TCCR0B register.

Inside, you’ll find 8 little switches (bits). At the beginning, they’re all in the off position (0).

So at the start, the register (drawer) 0x25 has the value 0b00000000 — all switches are off.

Each switch controls something different:

  1. Some set the speed (prescaler)
  2. Others decide how the timer counts
  3. One can even stop everything completely

To “activate” Timer0, we need to flip the right switches so it knows:

“Okay, Timer0, start counting — and use this speed!”

In the next step, we’ll see which switches to flip to get it running.

See this Atmega328p datasheet at page 279

Step 3 — Waking Up Timer0 and Counting

registri2.jpg


Step 3 — Waking Up Timer0 and Counting

Now that we know the switches in drawer 0x25 (TCCR0B), let’s see how Timer0 actually starts counting.

  1. If the last three switches (bits 0–2) are all 0, the timer is off.
  2. If the value is greater than 0, the timer wakes up and starts incrementing the counter in register 0x26 (TCNT0).

Here’s how the last three bits affect the timer speed:


Bits Prescaler Clock Frequency (Hz) Tick Frequency (increments/sec)
001 1 16,000,000 16,000,000
010 8 2,000,000 2,000,000
011 64 250,000 250,000
100 256 62,500 62,500
101 1024 15,625 15,625

For example, if we set the last three bits to 101, the clock is divided by 1024:

16,000,000/1024=15,625 ticks per second

Each tick increments TCNT0 (0x26). Since TCNT0 is an 8-bit register, it counts from 0 to 255, then overflows back to 0 and starts again.

  1. With a prescaler of 1024, this overflow happens roughly 60 times per second, giving us a timer tick frequency that can be used for blinking LEDs, generating PWM, or timing events.

See this Atmega328p datasheet at page 279

Writing the ASM to Activate Timer0

asm.jpg

Now that we know which switches to flip, it’s time to write some ASM code to turn them on.


We want to set the last three switches in drawer 0x25 (TCCR0B) to 0b101.

Unfortunately, we cannot write directly to the drawer in one instruction. Instead, we use a temporary register (r16) as a helper:

  1. Load the value into r16:

ldi r16, 0b101 ; load temporary register r16 with 0b101
  1. Write r16 into drawer 0x25:

out 0x25, r16 ; set the switches in TCCR0B

Once this is done, Timer0 is awake.

  1. Every tick of the timer increments register 0x26 (TCNT0).
  2. When TCNT0 reaches 255, it overflows back to 0 and starts counting again.

TCNT0 Counter (0x26)
+------------------------------------------------+
| 0 -> 1 -> 2 -> ... -> 253 -> 254 -> 255 -> 0 |
+------------------------------------------------+
^ ^
| |
starts here overflows

Puoi anche aggiungere un piccolo commento esplicativo sotto:


Each tick increments TCNT0 by 1.
When it reaches 255, the next tick resets it back to 0.
With a prescaler of 1024, this happens ~60 times per second.


This simple routine makes Timer0 start counting at the speed defined by the last three bits we set (in this case, 101 → prescaler 1024 → ~60 overflows per second).

Generating a Square Wave on PD6 Using Timer0 Compare Match

pins.jpg

The timer has two dedicated pins for Timer0 operations: PD6 and PD5.

We will use PD6.

We will also use register 0x27 (OCR0A) to compare with the counter 0x26 (TCNT0).

For example, if we write the value 128 in register 0x27:

  1. The counter 0x26 counts up
  2. Every time the counter equals the value in 0x27, PD6 changes its state (toggles)
  3. Then the counter continues counting and repeats the process

This way, PD6 automatically toggles each time the counter reaches the compare value.

This is the easy mode to work with Timer0.

Timer0 also offers many other modes, but they are a bit more complex.

Configuring TCCR0A for CTC and OC0A Toggle

24.jpg

This scenario uses the following TCCR0A register (0x24) configuration:

  1. WGM01 = 1 → CTC mode
  2. COM0A1:0 = 0b01 → toggle OC0A on compare match

As you can see, we set the bits to create this scenario.

Note: I used the bit values directly to make it easier to understand. If I had used abstract names with >> or <<, it would have been more difficult for beginners.

In ASM:

  1. Load R16 with this configuration:

LDI R16, 0b01000010
  1. Copy R16 into register 0x24:

OUT 0x24, R16

Now everything is ready for the timer to operate with OC0A toggling on compare match.

See this Atmega328p datasheet at page 279

Complete ASM Example With Prescaler 1024.

The avr asm code complete:



; Step 7 – Timer0 CTC mode with OC0A toggle on PD6
; ATmega328P, prescaler = 1024, beginner-friendly
.org 0
rjmp init

.org 0x60 ; Program start at address 0x60
init:
; ------------------------------
; 1. Configure Timer0 registers
; ------------------------------
SBI 0x0A, 6 ; Set PD6 as output
; TCCR0A: WGM01 = 1 (CTC), COM0A1:0 = 01 (toggle OC0A)
LDI R16, 0b01000010 ; Load configuration into R16
OUT 0x24, R16 ; Copy R16 to TCCR0A

; TCCR0B: Set prescaler = 1024 (CS02 = 1, CS01 = 0, CS00 = 1)
LDI R16, 0b00000101 ; CS02=1, CS01=0, CS00=1 → prescaler 1024
OUT 0x25, R16 ; Copy R16 to TCCR0B

; OCR0A: Set compare value
LDI R16, 128 ; Compare value = 128 → ~50% duty cycle
OUT 0x27, R16 ; Copy to OCR0A

; ------------------------------
; 2. Infinite loop (main program does nothing, timer handles toggle)
; ------------------------------

loop:
RJMP loop ; Infinite loop, all work done by timer

How it works

  1. TCCR0A → sets CTC mode and toggle OC0A.
  2. TCCR0B → sets prescaler 1024 → slows down the timer.
  3. OCR0A = 128 → OC0A (PD6) toggles every time TCNT0 reaches 128 → produces a square wave ~50% duty.
  4. Loop does nothing; hardware timer toggles PD6 automatically.

And uncomented version (only code) that will use to compile :


.org 0
rjmp init
.org 0x60
init:
SBI 0x0A, 6
LDI R16,0b01000010
OUT 0x24,R16
LDI R16,0b00000101
OUT 0x25,R16
LDI R16,128
OUT 0x27,R16
loop:
RJMP loop
Copy this code and paste in this page https://costycnc.github.io/avr-compiler-js/ compile and upload to arduino with atmega328p directly from browser

📜 the Tale of a Timer – Told Like a Tech Fairy Story 🕰️

Let’s imagine Arduino as a big cabinet full of little drawers 🗄️, and inside lives a tiny man 👷‍♂️ who opens them, checks what’s inside, and does whatever the notes inside tell him.

At the start of our program, the little man opens drawer 0x0A (DDRD) and sees that compartment 6 has a marble in it (bit = 1). This means: “Set pin D6 as output!”. He does it and closes the drawer. ✅

See this Atmega328p datasheet at page 279

Then he opens 0x24 (TCCR0A) and finds the number 01000010:


LDI R16,0b01000010
OUT 0x24,R16

He looks at the last two bits: 10. In his manual, it says:


WGM01 WGM00 → Meaning
0 0 → Normal mode
0 1 → Phase Correct PWM
1 0 → CTC mode (OCR0A is TOP)
1 1 → Fast PWM

So 1 0 = CTC mode: when the timer counter reaches the value in OCR0A (drawer 0x27), the timer resets to zero and starts counting again.

Then he checks the first two bits: 01. The manual says:


COM0A1 COM0A0 → Action on OC0A
0 0 → No change
0 1 → Toggle OC0A on compare match
1 0 → Clear OC0A on compare match
1 1 → Set OC0A on compare match

So 0 1 = Toggle OC0A: every time the timer matches OCR0A, pin D6 flips state (HIGH to LOW or vice versa).

He closes drawer 0x24 and moves on.

He opens 0x25 (TCCR0B) and sees 00000101:


LDI R16,0b00000101
OUT 0x25,R16

The last 3 bits are 101:


CS02 CS01 CS00 → Clock source
0 0 0 → Timer stopped
0 0 1 → No prescaler
0 1 0 → /8
0 1 1 → /64
1 0 0 → /256
1 0 1 → /1024 ✅
1 1 0 → External clock (falling edge)
1 1 1 → External clock (rising edge)

So the timer ticks once every 1024 CPU clock pulses. It’s like the little man keeps a separate counter and, when it reaches 1024, he drops a marble into 0x26 (TCNT0).

Finally, he opens drawer 0x27 (OCR0A) and puts in the value 128:


LDI R16,128
OUT 0x27,R16

Now the little man knows exactly what to do:

  1. Count ticks in TCNT0.
  2. When TCNT0 = 128, reset it to zero.
  3. Toggle pin D6.

The result? An automatic square wave, generated without the main program doing anything. 🎯

granma.jpg