Intro

I’m including my attiny85_util.h for completeness, although it is very simple and standard. I’m also including my Makefile so you can see my compiler options, etc. I use an arduino pro mini running the ArduinoISP sketch with avrdude to upload my code to the ATTiny85. There are many tutorials on getting this set up all over the web so I won’t repeat that procedure here.

attiny_85_util.h
#ifndef __ATTINY85_UTIL_H
#define __ATTINY85_UTIL_H

// === pin functions ===
#define pin_set_input(portdir,pin) portdir &= ~(1<<pin)
#define pin_set_output(portdir,pin) portdir |= (1<<pin)
#define pin_low(port,pin) port &= ~(1<<pin)
#define pin_high(port,pin) port |= (1<<pin)
#define pin_toggle(port,pin) port ^= (1<<pin)
#define pin_get(pindir,pin) (pindir & (1<<pin))

#endif /* __ATTINY85_UTIL_H */
Makefile
CC = avr-gcc
OBJCOPY = avr-objcopy
DUDE = avrdude

CFLAGS = -Wall -Os -mmcu=attiny85 -DF_CPU=8000000
OBJFLAGS = -j .text -j .data -O ihex
DUDEFLAGS = -c arduino -p attiny85 -P /dev/ttyUSB0 -b 19200 -v

OBJECTS = main.o

all: main.hex

upload: main.hex
	$(DUDE) $(DUDEFLAGS) -U flash:w:$<

eeprom: main.eep
	$(DUDE) $(DUDEFLAGS) -U eeprom:w:$<

clean:
	$(RM) *.o *.hex *.elf

%.hex: %.elf
	$(OBJCOPY) $(OBJFLAGS) $< $@

main.elf: $(OBJECTS)
	$(CC) $(CFLAGS) $(OBJECTS) -o $@

$(OBJECTS):

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

%.o: %.S
	$(CC) $(CFLAGS) -x assembler-with-cpp -c $< -o $@

This is just your standard blink example. Set the pin high and wait for a short time (100ms) then set the pin low and wait for a short time (100ms).

main.c
#include <avr/io.h>
#include <util/delay.h>
#include "attiny85_util.h"


int main (void)
{
    pin_set_output(DDRB,4);       // set PB4 to output

    while (1)
    {
        pin_high(PORTB,4);        // set PB4 high
        _delay_ms(100);           // wait 1/10 of a second
        pin_low(PORTB,4);         // set PB4 low
        _delay_ms(100);           // wait 1/10 of a second
    }

    return 1;
}

This example uses a timer to blink the led instead of a delay. This basically allows your code to do other things during the time that was wasted during the _delay_ms() calls in the previous example. I left the extra lines that set the CPU clock to various speeds commented out for reference. The native clock rate of the CPU is 8MHz, so the clock_div_X parameter just divides the native clock rate to acheive the new one. You can save some power by clocking the CPU down, but for this example I’m leaving the clock rate high to better introduce the math involved. I used the Timer/Counter0 for this which is described in the ATTiny85 datasheet starting on page 65. This is an 8 bit timer which means that every time the CPU clock ticks (cycles), the register TCNT0 (Timer Counter 0) gets incremented by 1. This means that every 256 cycles (the maximum number that an 8 bit register will hold) the TCNT0 register will overflow from 255 back to 0. A cycle takes 1/8,000,000 0.000000125 seconds. This means that the TCNT0 register will reach it’s maximum of 255 every 0.000000125*256 0.000032 seconds or 0.032ms. This is way too fast to duplicate our 100ms delay that we used in the example above, so how can we change this? The lower 3 bits in the TCCR0B register (Timer Counter Control Register 0 B) (page 79 in the datasheet) allow you to set a timer prescaler. This means that if the prescaler is set to 8 for example, the TCNT0 register is only incremented every 8 cycles instead of every cycle. This means that now the TCNT0 register will be incremented every 1/(8,000,000/8) 0.000001 seconds. Now TCNT0 will reach 255 every 0.000001*256 0.000256 seconds or 0.256ms. This is still too low for our 100ms delay so let’s try the largest prescaler available, 1024. Now TCNT0 will be incremented every 1/(8,000,000/1024) 0.000128 seconds which means that it will reach 255 every 0.000128*256 0.032768 seconds or 32.768ms. This still doesn’t get us to our 100ms goal, but if we waited for every 100ms/32.768ms 3.051757813 times it reaches 255, we will have waited 100ms. We would need to wait for it to reach 255 3 full times and then 0.0517 times. Doing the math, we would wait for it to reach 0.051757813*256 13.25 in the 4th time and viola, we have waited 100ms. Obviously TCNT0 can only contain integers so we will just wait for it to reach 13 instead of 13.25 which will give us an actual wait time of (256+256+256+13)*0.000128 0.099.968 seconds or 99.968ms which we can just call close enough. As you see you can always get perfect wait times, but with some effort you can get close enough for almost any application.

#include <avr/io.h>
#include <avr/power.h>
#include "attiny85_util.h"


int main (void)
{
    clock_prescale_set(clock_div_1);      // clock cpu to 8Mhz
    //clock_prescale_set(clock_div_2);    // clock cpu to 4Mhz
    //clock_prescale_set(clock_div_4);    // clock cpu to 2Mhz
    //clock_prescale_set(clock_div_8);    // clock cpu to 1Mhz

    pin_set_output(DDRB,4);               // set PB4 to output
    pin_low(PORTB,4);                     // set PB4 low
    //TCCR0B |= (1<<CS00);                // set prescaler to 1
    //TCCR0B |= (1<<CS01);                // set prescaler to 8
    //TCCR0B |= (1<<CS01) | (1<<CS00);    // set prescaler to 64
    //TCCR0B |= (1<<CS02);                // set prescaler to 256
    TCCR0B |= (1<<CS02) | (1<<CS00);      // set prescaler to 1024

    int pass_counter = 0;
    int timer_counter_target = 255;

    while (1)
    {
        // simultaneous_task();

        if (TCNT0 == timer_counter_target)
        {
            TCNT0 = 0;                           // reset timer count to zero
            pass_counter++;

            if (pass_counter < 2)                // passes < 3 are full
            {
                timer_counter_target = 255;
            }
            else if (pass_counter == 3)          // pass 3 is partial
            {
                timer_counter_target = 13;
            }
            else if (pass_counter == 4)          // we have waited 100ms
            {
                pin_toggle(PORTB,4);             // toggle PB4
                pass_counter = 0;                // reset pass counter
                timer_counter_target = 255;      // and start over
            }
        }
    }

    return 1;
}

The previous example bought us the ability to do simultaneous tasks while we blinked the LED, but we can make the code much smaller and cleaner with a simple tweak. We can use one of the commented CPU clock lines to change the CPU clock down to 1MHz. Now TCNT0 will be incremented every 1/(1,000,000/1024) 0.001024 seconds which means that it will reach 255 every 0.001024*256 0.262144 seconds or 262.144ms. This is really what we wanted.. Now our 100ms passes within a single overflow of TCNT0. If we wait for 100ms/262.144ms 0.3814697265625 of a TCNT0 overflow, then our 100ms will have passed. If we let TCNT0 reach 0.3814697265625*256 97.65625 or 98 before toggling the pin and resetting TCNT0 back to zero, we will have acheived our 100ms goal and of course there is much less code invloved. Doing this "clocking down" of the CPU does mean that we lose some performance, which means we can get much less done in our simultaneous_task() function, but we also gain efficiency in power usage.. this can very important if you are running on a battery for instance. These are trade-offs that you are free to (will have to) make.

#include <avr/io.h>
#include <avr/power.h>
#include "attiny85_util.h"

int main (void)
{
    //clock_prescale_set(clock_div_1);  // clock cpu to 8Mhz
    //clock_prescale_set(clock_div_2);  // clock cpu to 4Mhz
    //clock_prescale_set(clock_div_4);  // clock cpu to 2Mhz
    clock_prescale_set(clock_div_8);    // clock cpu to 1Mhz

    pin_set_output(DDRB,4);             // set PB4 to output
    pin_low(PORTB,4);                   // set PB4 low
    //TCCR0B |= (1<<CS00);              // set prescaler to 1
    //TCCR0B |= (1<<CS01);              // set prescaler to 8
    //TCCR0B |= (1<<CS01) | (1<<CS00);  // set prescaler to 64
    //TCCR0B |= (1<<CS02);              // set prescaler to 256
    TCCR0B |= (1<<CS02) | (1<<CS00);    // set prescaler to 1024

    while (1)
    {
        // simultaneous_task();

        if (TCNT0 == 98)
        {
            TCNT0 = 0;                  // reset timer count to zero
            pin_toggle(PORTB,4);        // toggle PB4
        }
    }

    return 1;
}

In our previous examples with timers we had to use code to periodically check for a the value in TCNT0 register to blink the led. Interrupts allow us to define a function (an Interrupt Service Routine) which will be called automatically when TCNT0 reaches a certain value. This really decreases the amount of code that we write. The first type of interrupt we will use is the Timer Overflow Interrupt. This interrupt occurs when TCNT0 overflows from 255 back to 0 like we talked about in the first timer example. One of the first things we have to do when using interrupts is to enable interrupts by calling sei() "Set Enable Interrupts". Next we need to look at the TIMSK (Timer Interrupt Mask) register describe on page 81 of the datasheet. If we set the TOIE0 (Timer Overflow Interrupt Enable 0) bit, then and interrupt service routine named TIM0_OVF_vect (as defined by the AVR Libc Documentation) will be called when TCNT0 overflows. To define our interrupt service routine we use the ISR() function. The example in the code below is very simple and straightforward. Now that we know how to setup our interrupt and it’s service routine we just need to use math to figure out our timing. We are going to aim for a 100ms blink like all of the other examples. We will set the CPU clock to 1MHz again and we’ll try to use a timer prescaler of 256 first. This tells us that TCNT0 will increment every 1/(1,000,000/256) 0.000256 seconds. This means that TCNT0 will overflow every 0.000256*256 0.065536 seconds or 65.536ms. This is faster than our 100ms blink rate so let’s try a timer prescaler of 1024. With this prescaler TCNT0 will increment every 1/(1,000,000/1024) 0.001024 seconds. This means that TCNT0 will overflow every 0.001024*256 0.262144 seconds or 262.144ms. This is slower than our 100ms blink rate so it’s no good either. With an overflow interrupt we don’t have the choice of triggering on a partial overflow so we are going to settle for the 256 timer prescaler since it’s the closest to the 100ms blink rate that we are targeting. In our next example we’ll do much better.

#include <avr/io.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include "attiny85_util.h"

int main (void)
{
    clock_prescale_set(clock_div_8);      // clock cpu to 1Mhz

    sei();

    pin_set_output(DDRB,4);               //set PB4 to output
    pin_low(PORTB,4);

    TCCR0B |= (1<<CS02);                  // set prescaler to 256
    TIMSK |= (1<<TOIE0);                  // enable the timer overflow interrupt

    while (1)
    {
        // simultaneous_task();
    }

    return 1;
}

ISR(TIM0_OVF_vect)
{
    pin_toggle(PORTB,4);
}

In our previous example we used the Timer Overflow Interrupt service routine to toggle our led which meant that we could only toggle on a full timer overflow, not a partial one like we did in the timer example. For this example we will use a Timer Compare interrupt service routine named TIM0_COMPA_vect (as defined by the AVR Libc Documentation) which will be called when TCNT0 matches the value in OCR0A (Output Compare Register 0). When this interrupt service routine is called the TCNT0 register will also be cleared back to 0. To setup this interrupt we use TCCR0A (Timer Counter Control Register 0) describe on page 77 of the datasheet. We use the table on page 79 of the datasheet to set the Waveform Generation Mode to CTC (Clear Timer on Compare Match). Next we need to use the TIMSK (Timer Interrupt Mask Register) to enable the Timer Compare interrupt service routine by setting the OCIE0A (Output Compare Interrupt Enable 0) bit as described on page 92 of the datasheet. Now that all of that has been set up, we just figure out our timing with the math we learned in the previous examples. If you remember from above, we know that using a 1MHz CPU clock and a timer prescaler of 1024 gives us a timer overflow that is as close to , yet still greater than our 100ms blink rate. A single timer increment takes 1/(1,000,000/1024 0.001024 seconds. This means that a full overflow of TCNT0 will take 0.001024*256 0.262144 seconds or 262.144ms. If we wait for 100ms/262.144ms 0.3814697265625 of a TCNT0 overflow, then our 100ms will have passed. If we let TCNT0 reach 0.3814697265625*256 97.65625 or 98 before toggling the pin, we will have acheived our 100ms goal once again. Now we have reached our blink rate goal with impressively clean code that gives us time to perform other task simultaneously and you should have the skills to use timers and interrupts in your own code.

#include <avr/io.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include "attiny85_util.h"

int main (void)
{
    clock_prescale_set(clock_div_8);    // clock cpu to 1Mhz

    sei();

    pin_set_output(DDRB,4);             //set PB4 to output
    pin_low(PORTB,4);

    TCCR0B |= (1<<CS02) | (1<<CS00);    // set prescaler to 1024
    TCCR0A |= (1<<WGM01);               // set CTC (Clear Timer on Compare Match) Mode
    TIMSK |= (1<<OCIE0A);               // enable an interrupt when TCNT0 matches OCR0A
    OCR0A = 98;                         // set the value to compare to TCNT0

    while (1)
    {
        // simultaneous_task();
    }

    return 1;
}

ISR(TIM0_COMPA_vect)
{
    pin_toggle(PORTB,4);
}