Programming the AVR Timers¶
Read time: 20 minutes (5239 words)
Let’s explore some “C” code and see if we can create a simple signal that can drive a Piezo buzzer.
The Piezo Buzzer¶
- Here is the buzzer we will use

- a crystal inside of this flexes when a voltage is applied across the leads
- when it flexes, it pushes air out of the way.
- Flex it fast enough and those air pulses hit your ear making noise
- You control the tone by the pulse rate
Buzzer Code¶
- Here is the start of our code
// Example Arduino code to sound a Piezo buzzer
#include <avr/io.h>
#include <util/delay.h>
#define CLOCK_PRESCALE(n) (CLKPR = 0x80, CLKPR = (n))
#define BUZZER_CONFIG (DDRB |= (1<<4))
#define BUZZ_ON (PORTB |= (1<<4))
#define BUZZ_OFF (PORTB &= ~(1<<4))
This code sets up the chip clock so it runs at a slower than full speed. There are defines that set up the output pin and toggle that pin on and off. We will use these macros in the main code.
Chip setup¶
Next we configure the chip
int main(void){
CLOCK_PRESCALE(0);
BUZZER_CONFIG;
We are going to connect the buzzer between pins 12 (PORTB bit 4) and the one of the ground lines on the opposite side of the board.
The buzzer loop¶
Finally, we enter the main loop
while(1) {
BUZZ_ON;
_delay_us(200);
BUZZ_OFF;
_delay_us(200);
}
Connecting the buzzer¶
Here is how to hook up a simple Piezo buzzer to output pin 12 on the Arduino
board (PB4
on the chip itself).

Note
OK, this image is for a different board. You will need to examine the buzzer, plug it into the right holes on the breadboard, then connect the right pins up with two jumpers. It should not be too hard!
Here is a picture of the chip, with all of the pins identified. This will help in hooking up other stuff to the board:

What is wrong with this?¶
Running this code makes noise (it did on my system, but not very loud!)
- The program works, but it consumes all the processor’s attention
- That wastes power we could use for other purposes.
- One way to solve this is to us an internal
Timer
- Really just a simple counter that you can set up and let run.
- The timer counts using the system clock
- Once the timer reaches a max value, it rolls over and we can detect that!
- One way to solve this is to us an internal
Warning
There is one more problem with this code: IT WILL NOT SHUT UP! The code runs forever, generating an annoying tone. At least we know it works!
AVR Timers¶
The chip on the Arduino has four internal timers that go by these names:
Timer0
- 8 bit counterTimer1
- 16 bit counterTimer3
- 16 bit counterTimer4
- 10 bit counter
Hmmm, wonder what happened to Timer2
?
Clock source¶
- The timer counts using the system clock
- we can use a clock prescaler to extend the time it takes reach the max
The possible prescaler settings available on these timers are:
- Clock / 8
- Clock / 64
- Clock / 256
- Clock / 1024
Timer counting rates¶
On the Teensy2 we can increment the timer counter every:
- 8/16000000 = 0.5us
- 64/16000000 = 4us
- 256/16000000 = 16us
- 1024/16000000 = 64us
Setting the timer count¶
- 8 bit timers run from 0 to 255
- rolling over generates the signal
- We can preload the counter to make it roll over faster
- The counter begins at the preload value and counts up
Maximum timer times¶
- The timer can generate a signal of some kind every:
- 256 * 0.5us = 128us = 7812Hz
- 256 * 4us = 1.024ms = 976Hz
- 256 * 16us = 4.096ms = 244Hz
- 256 * 64 = 16.384ms = 61Hz
- Of course, we can adjust these numbers
- slow the rate down by adjusting the clock
- Speed it up by adjusting the count
Detecting the roll over¶
- We can deal with the signal one of two ways
- write code that checks for roll over every so often (polling)
- configure the chip to generate an
interrupt
when it happens
Configuring the Timer¶
- In order to use a timer, we must set it up and start it running.
- set the prescaler value
- preload the counter
- for
Timer0
, the registers we use are: - Timer/Counter Control Register B -
TCCR0B
- Bit 0 =
CS0
- Bit 1 =
CS1
- BIT 2 =
CS2
- Bit 0 =
- Timer/Counter Control Register B -
- for
Setting the prescaler¶
CS2 | CS1 | CS0 | Function |
0 | 0 | 0 | Timer stopped |
0 | 0 | 1 | clock/1 |
0 | 1 | 0 | clock/8 |
0 | 1 | 1 | clock/64 |
1 | 0 | 0 | clock/256 |
1 | 0 | 1 | clock/1024 |
Setting the preload count¶
- Timer/Counter0 count register -
TCNT0
- This register is set with an initial counter value.
- Setting the count starts the timer (if enabled)
- Timer/Counter0 count register -
Detecting the roll over¶
- A timer register contains a bit that is set whenever the timer overflows
- Timer Interrupt Flag Register -
TIFR0
- For Timer0, the bit we will examine is
TOV0
.
- Timer Interrupt Flag Register -
Polling code¶
polling:
in r16, TIFR0 ; copy the flag register into r16
sbrs r16, TOV0 ; skip the next instruction if count overflow
rjmp polling ; loop back until bit it set
Skipping instructions¶
- Some instructions are a bit funny
- They test some condition
- If the condition is met, they skip the next instruction
- Otherwise, they execute the next instruction
- Usually this is a jump of some kind
- The result is like our conditional branch, but it takes two instructions
Is this better?¶
- The processor is stuck in a loop until the overflow bit gets set.
- However, we can add code inside this loop as we wish
- If the code is too long, we will have a delay before we see the flag
- Let’s write the code in assembly language
Building the Assembly Code¶
The Makefile
shown here can build any project involving C
or AVR Assembly
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | # source files
TARGET := $(shell basename $(PWD))
CSRCS := $(wildcard *.c)
COBJS := $(CSRCS:.c=.o)
SSRCS := $(wildcard *.S)
SOBJS := $(SSRCS:.S=.o)
OBJS := $(COBJS) $(SOBJS)
LST := $(TARGET).lst
# define the processor here
MCU := atmega328p
FREQ := 16000000L
# define the USB port on your system (this works on Linux)
PORT := /dev/ttyACM0
PGMR := arduino
# tools
GCC := avr-gcc
OBJDUMP := avr-objdump
OBJCOPY := avr-objcopy
DUDE := avrdude
UFLAGS := -v -D -p$(MCU) -c$(PGMR)
UFLAGS += -P$(PORT)
UFLAGS += -b115200
CFLAGS := -c -Os -mmcu=$(MCU)
CFLAGS += -DF_CPU=$(FREQ)
LFLAGS := -mmcu=$(MCU)
LFLAGS += -nostartfiles
.PHONY all:
all: $(TARGET).hex $(LST)
# implicit build rules
%.hex: %.elf
$(OBJCOPY) -O ihex -R .eeprom $< $@
%.elf: $(OBJS)
$(GCC) $(LFLAGS) -o $@ $^
%.o: %.c
$(GCC) -c $(CFLAGS) -o $@ $^
%.o: %.S
$(GCC) -c $(CFLAGS) -o $@ $<
%.lst: %.elf
$(OBJDUMP) -C -d $< > $@
# utility targets
.PHONY: load
load:
$(DUDE) $(DUDECONF) $(UFLAGS) -Uflash:w:$(TARGET).hex:i
.PHONY: clean
clean:
$(RM) *.hex *.lst *.o *.elf
|
Note
We will use this Makefilein all AVR based projects. All project source files are in
the same directory as this Makefile
.
main.S: Setting up¶
To rewrite this in assembly language, we start off by writing the configuration file we will use for this project:
1 2 3 4 5 6 7 8 9 | #include <avr/io.h>
#define BUZZ_PIN 4
#define BUZZ_DIR _(DDRB)
#define BUZZ_PORT _(PORTB)
// include this line to avoid SFR_REG issues
#define _(s) _SFR_IO_ADDR(s)
|
Next, we set up the processor with the same initial code we used in our Blink project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | ; arduinoBuzz.S
; Author: Roie R. Black
; Date: Apr 30, 2015
#include "config.inc"
.section .text
.org 0x00
.extern timer_init
.extern timer_delay
.global main
main:
; set stack to top of available ram
ldi r28, (RAMEND & 0x00ff)
ldi r29, (RAMEND >> 8)
out _(SPH), r29
out _(SPL), r28
;
|
main.S: Init Chip and Start polling¶
All additional chip initialization is done in a subroutine. We can call this safely, since we just set up the stack!
call Init ; complete initialization
And here is the polling loop:
1 2 3 | 1: call Toggle ; flip BUZZER on/off
call timer_delay ; wait a bit
rjmp 1b ; loop forever
|
main.S: Initialize the chip¶
Here is the initialization code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | Init:
; set up a register with zero for convenience
eor r1, r1 ; cheap zero
; clear flag register
out _(SREG), r1
; set up the system clock
ldi r24, 0x80 ; set up prescaler
sts CLKPR, r24
sts CLKPR, r1 ; run at full speed
; setup the buzzer pin
sbi BUZZ_DIR, BUZZ_PIN ; set pin 5 on port B for output
cbi BUZZ_PORT, BUZZ_PIN ; set pin 5 on port B off
call timer_init ; set up the timer
ret
|
Nothing new here. We are using the BUZZER definitions set up in the
configuration file in this code, but I moved all the logic related to the timer
into a second file named timer.S
.
Here is the code to toggle the buzzer pin:
1 2 3 4 5 6 | Toggle:
in r24, BUZZ_PORT ; get current port B values
ldi r25, (1 << BUZZ_PIN) ; LED pin number
eor r24, r25 ; toggle bit
out BUZZ_PORT, r24 ; write it back in place
ret
|
We have seen this pattern before.
timer.S: Setting up¶
The code needed to initialize the timer looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ; timer.S - routines for Piezo buzzer
#include "config.inc"
.equ PRESET ,1
.section .text
.global timer_delay
.global timer_init
timer_init:
ldi r16, (1 << CS01) | (1 << CS00) ; divide by 64
out _(TCCR0B), r16 ; set timer clock
ldi r16, 1 << TOV0 ; clear interrupt flag
out _(TIFR0), r16
ldi r16, PRESET ; initial value for timer
out _(TCNT0), r16
ret
|
timer.S: Polling delay code¶
Finally, the timer delay code, using our polling logic, looks like this:
1 2 3 4 5 6 7 8 9 | timer_delay:
in r16, _(TIFR0) ; check the flag
sbrs r16, 1 << TOV0 ; skip if overflow set
rjmp timer_delay ; wait for it
ldi r16, 1 << TOV0 ; clear the interrupt flag
out _(TIFR0), r16 ; clear the interrupt flag
ldi r16, PRESET ; reload the counter
out _(TCNT0), r16 ; set preset in
ret
|
How it works¶
- Main sets up the chip and timer system
- It then loops toggling buzzer pins and calling the timer delay routine
timer.S
has the timer init routine- It uses polling in the delay loop to wait for the timer overflow
- Overall logic is identical to the “C” example
The output signal¶

Making the delay shorter¶
- Loading an initial value into the timer counter will shorten the delay
- The timer counts from this value to its max before overflowing
- We modify the delay code by adding the preload value as we reset
The modified delay code¶
All I needed to do to shorten the delay, and make the tone higher, was this:
.equ PRESET , 128
Examining the new delay¶

That is all we need to make a little noise. We have improved things a little, next time we will improve things a lot. We will free up the processor to do other work while the timer runs!
Examining the code¶
Before we leave this lecture, let’s try to examine the code produced by reverse
engineering the .elf
file. The current Makefile
deletes the .elf
file after it builds the final .hex
file, but we can get it back by doing
this:
make asmBuzzer.elf
avr-objdump -d asmBuzzer.elf
Here is the output from this run
asmBuzzer.elf: file format elf32-avr
Disassembly of section .text:
00000000 <__ctors_end>:
0: 00 e8 ldi r16, 0x80 ; 128
2: 10 e0 ldi r17, 0x00 ; 0
4: 00 93 61 00 sts 0x0061, r16
8: 10 93 61 00 sts 0x0061, r17
c: 24 9a sbi 0x04, 4 ; 4
e: 2c 98 cbi 0x05, 4 ; 5
00000010 <main_loop>:
10: 05 d0 rcall .+10 ; 0x1c <timer_init>
12: 2c 9a sbi 0x05, 4 ; 5
14: 0a d0 rcall .+20 ; 0x2a <timer_delay>
16: 2c 98 cbi 0x05, 4 ; 5
18: 08 d0 rcall .+16 ; 0x2a <timer_delay>
1a: fb cf rjmp .-10 ; 0x12 <main_loop+0x2>
0000001c <timer_init>:
1c: 03 e0 ldi r16, 0x03 ; 3
1e: 05 bd out 0x25, r16 ; 37
20: 01 e0 ldi r16, 0x01 ; 1
22: 05 bb out 0x15, r16 ; 21
24: 01 e0 ldi r16, 0x01 ; 1
26: 06 bd out 0x26, r16 ; 38
28: 08 95 ret
0000002a <timer_delay>:
2a: 05 b3 in r16, 0x15 ; 21
2c: 00 ff sbrs r16, 0
2e: fd cf rjmp .-6 ; 0x2a <timer_delay>
30: 01 e0 ldi r16, 0x01 ; 1
32: 05 bb out 0x15, r16 ; 21
34: 01 e0 ldi r16, 0x01 ; 1
36: 06 bd out 0x26, r16 ; 38
38: 08 95 ret
(You can capture this output by redirecting into a file). We can see all of the
numbers from the included .def
file in this, and see where all of the other
routines ended up in the processor’s memory when loaded into the chip There
will be other output files you can examine to figure out why things might be
going wrong.