Things started off badly this week when I pulled my FabISP out of my backpack and found it like this:

It seemed like a waste to make a new board, so I threw a few jumper wires and some hot glue on it to get it up and running again.

I decided to drill through my hello world board and install through hole headers to make sure the connector stayed on for good. Our drill press has a vice mounted in it that you can move in x and y accurately, this was really helpful for getting exactly 0.1" spacing for the header.

Since the FTDI cables were in short supply this week, I also added a switch to my FabISP to optionally use it as a power supply for my hello world board. This made it so that I could program and run my board using only the FabISP connection. This made my workflow much nicer.

Last week I noticed that the delay() function in the Arduino code was running extremely slowly on my ATtiny. I thought this was a timer issue, but now realize it was because the fuses weren't set properly to run the ATtiny at 20mHz. I followed the steps outlined here to set those using the Arduino IDE.

I re-uploaded the basic blink sketch from the examples section of Arduino and the timing is fixed!

Then I followed this tutorial to run Neil's echo.c code on my ATtiny (with one small edit).

First I downloaded hello.ftdi.44.echo.c and hello.ftdi.44.echo.c.make and navigated to my Downloads folder (or whatever folder you saved the files to) in the Terminal:

cd ~/Downloads/

When I ran:

sudo make -f hello.ftdi.44.echo.c.make program-usbtiny

I got the following error:

I tried googling for the solution, but couldn't find much. I'd already installed Crosspack (and avrdude) and successfully programmed the FabISP so I know that avr-gcc was installed correctly on my computer. Finally, as I was about to email the class, I tried deleting the problem line entirely to see what would happen. hello.ftdi.44.echo.c.make now reads:

PROJECT=hello.ftdi.44.echo
SOURCES=$(PROJECT).c
MMCU=attiny44
F_CPU = 20000000

CFLAGS=-mmcu=$(MMCU) -Wall -Os -DF_CPU=$(F_CPU)

$(PROJECT).hex: $(PROJECT).out
	avr-objcopy -O ihex $(PROJECT).out $(PROJECT).c.hex;\

$(PROJECT).out: $(SOURCES)
	avr-gcc $(CFLAGS) -I./ -o $(PROJECT).out $(SOURCES)

program-bsd: $(PROJECT).hex
	avrdude -p t44 -c bsd -U flash:w:$(PROJECT).c.hex

program-dasa: $(PROJECT).hex
	avrdude -p t44 -P /dev/ttyUSB0 -c dasa -U flash:w:$(PROJECT).c.hex

program-avrisp2: $(PROJECT).hex
	avrdude -p t44 -P usb -c avrisp2 -U flash:w:$(PROJECT).c.hex

program-avrisp2-fuses: $(PROJECT).hex
	avrdude -p t44 -P usb -c avrisp2 -U lfuse:w:0x5E:m

program-usbtiny: $(PROJECT).hex
	avrdude -p t44 -P usb -c usbtiny -U flash:w:$(PROJECT).c.hex

program-usbtiny-fuses: $(PROJECT).hex
	avrdude -p t44 -P usb -c usbtiny -U lfuse:w:0x5E:m

program-dragon: $(PROJECT).hex
	avrdude -p t44 -P usb -c dragon_isp -U flash:w:$(PROJECT).c.hex

(I deleted the line avr-size --mcu=$(MMCU) --format=avr $(PROJECT).out )

To my astonishment, this actually fixed the problem and I was able to successfully connect to my board using the Arduino IDE's Serial Monitor.

I read through the ATtiny44's datasheet to understand the layout of the ports/pins on the chip and figure out how to do timer interrupts. It turns out timer interrupts on the ATtiny are almost identical to the 328P (the chip on an Arduino) except that the ATtiny only has two timers. I borrowed code from an old Instructables post of mine about structuring timer interrupts on Arduino.

Section 9 of the ATtiny introduces all the types of interrupts that are possible on the chip.

I'm using Timer/Counter Compare Match (CTC) interrupts in my code (sections 11 and 12 of the datasheet go into more detail about implementation). In this type of interrupt, the ATiny increments an internal counter (TCNT0 for timer 0 and TCNT1 for timer 1) at a frequency given by:

clock speed of ATtiny / prescaler

The prescalers for each timer can be set by setting bits on TCCR0B (for timer 1) and TCCR1B (for timer 2).

prescaler table for timer 0:

prescaler table for timer 1:

so setting

TCCR0B |= (1 << CS01);

will give me a prescaler of 8 for timer 0.

TCCR1B |= (1 << CS10) |(1 << CS11);

will give me a prescaler of 64 for timer 1.

When the ATtiny's internal counter increments above a value stored by OCR0A (for timer0) or OCR1A (for timer 1), the interrupt routine will execute. At this point the internal counter is reset to zero. You can calculate the value of OCR0A for a desired interrupt frequency with the following equation:

(clock speed) / (desired interrupt frequency*prescaler) - 1

An important thing to note is that timer 0 is an 8 bit timer, so OCR0A and TCNT0 cannot store values greater than 256. You should use a higher prescaler instead. Timer 1 is a 16 bit timer, so OCR1A and TCNT1 can go as high as 65536.

I used timer interrupts to do software PWM on my RGB led since I wasn't able to hook up all my pins to a hardware PWM. I programmed by board with the following code in Arduino:

//helloWorld

byte buttonPin = 7;
byte redLED = 2;
byte blueLED = 3;
byte greenLED = 2;

//green led has pwm - use it
byte currentGreenIntensity = 0;
boolean greenIntensityDirection = true;

byte appState = 0;

//button debouncing variables
boolean currentButtonState = false;
boolean lastButtonState = false;
boolean debouncedButtonState = false;
byte buttonDebounceMax = 10;
byte buttonDebounceTimer = buttonDebounceMax;

//softPWM variables
volatile byte redLEDIntensity = 0;
volatile byte greenLEDIntensity = 0;
volatile byte blueLEDIntensity = 0;
volatile byte softPWMCounter = 0;

//colorFade
volatile boolean shouldDoColorFade = false;
volatile int colorFadeCounter = 0;
volatile byte currentColor = 1;


void setup(){

  //set inputs/outputs
  DDRA = 0;
  DDRA = (1<<redLED)|(1<<blueLED);
  DDRB = 0;
  DDRB = (1<<greenLED);

  //turn LEDs off
  turnOffAllLEDS();

  cli();//disable interrupts

  TCCR0A = 0;// set entire TCCR0A register to 0
  TCCR0B = 0;// same for TCCR0B
  TCNT0  = 0;//initialize counter value to 0
  // set compare match register for 200khz increments
  OCR0A = 99;// = (20*10^6) / (200000*1) - 1 (must be <256 for 8 bit timer)
  // turn on CTC mode
  TCCR0A |= (1 << WGM01);
  // Set CS00 bit for 1 prescaler
  TCCR0B |= (1 << CS00);
  // enable timer compare interrupt
  TIMSK0 |= (1 << OCIE0A);

  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 1hz increments
  OCR1A = 194;// = (20*10^6) / (100*1024) - 1 (must be <65536 for 16 bit timer)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set CS10 and CS12 bits for 1024 prescaler
  TCCR1B |= (1 << CS12) | (1 << CS10);
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);

  sei();//enable interrupts
}

void loop(){

  appState = debounceButton(appState);

  switch(appState){
    case 0://red
      redLEDIntensity = 255;
      greenLEDIntensity = 0;
      blueLEDIntensity = 0;
      break;
    case 1://green
      redLEDIntensity = 0;
      greenLEDIntensity = 255;
      blueLEDIntensity = 0;
      break;
    case 2://blue
      redLEDIntensity = 0;
      greenLEDIntensity = 0;
      blueLEDIntensity = 255;
      break;
    case 3://fade colors
      shouldDoColorFade = true;
      break;
    case 4://pause at current color
      break;
    case 5:
      turnOffAllLEDS();
      break;
  }
}

byte debounceButton(byte _appState){

  currentButtonState = (PINA>>buttonPin)&1;

  //button debouncing
  if (currentButtonState != lastButtonState && currentButtonState != debouncedButtonState){//if current button state is different than last button state and current debounced state
    if (currentButtonState){//if the button is currently pressed
      debouncedButtonState = currentButtonState;
      _appState = getNextAppState(appState);
    } else {
      buttonDebounceTimer = buttonDebounceMax;//reset debounce counter
    }
  } else if (currentButtonState == lastButtonState && currentButtonState != debouncedButtonState){//if current state same as last but different that debounced
    buttonDebounceTimer--;
    if (buttonDebounceTimer == 0){
      debouncedButtonState = currentButtonState;
      if (currentButtonState){//if the button is currently pressed
        _appState = getNextAppState(appState);
      }
    }
  }

  lastButtonState = currentButtonState;

  return _appState;
}

byte getNextAppState(byte state){
  shouldDoColorFade = false;
  state++;
  if (state > 5) return 0;
  return state;
}

void turnOffAllLEDS(){
  //these are common anode leds, so we have to remove their connection to ground to turn them off

  redLEDIntensity = 0;
  greenLEDIntensity = 0;
  blueLEDIntensity = 0;

  PORTA |= (1 << redLED);
  PORTA |= (1 << blueLED);
  PORTB |= (1 << greenLED);
}

//softPWM
ISR(TIM0_COMPA_vect ){//timer0 interrupt 200kHz
  softPWMCounter++;//let this overflow to zero

  if (redLEDIntensity>softPWMCounter){
    PORTA &= ~(1 << redLED);
  } else {
    PORTA |= (1 << redLED);
  }
  if (greenLEDIntensity>softPWMCounter){
    PORTB &= ~(1 << greenLED);
  } else {
    PORTB |= (1 << greenLED);
  }
  if(blueLEDIntensity>softPWMCounter){
    PORTA &= ~(1 << blueLED);
  } else {
    PORTA |= (1 << blueLED);
  }

}

//set new intensity vals
ISR(TIM1_COMPA_vect ){//timer1 interrupt 100Hz
  if (shouldDoColorFade){
    switch(currentColor){
      case 1://red
      {
        if (redLEDIntensity == 0){
          currentColor = 2;
          break;
        }
        redLEDIntensity--;
        greenLEDIntensity++;
        break;
      }
      case 2://green
      {
        if (greenLEDIntensity == 0){
          currentColor = 3;
          break;
        }
        greenLEDIntensity--;
        blueLEDIntensity++;
        break;
      }
      case 3://blue
      {
        if (blueLEDIntensity == 0){
          currentColor = 1;
          break;
        }
        blueLEDIntensity--;
        redLEDIntensity++;
        break;
      }
    }
  }
}

Then I refactored my program in c and uploaded it via the terminal. Here is the code:

//softPWM

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

typedef int bool;
#define true 1
#define false 0

#define buttonPin 7
#define redLED 2
#define blueLED 3
#define greenLED 2

//green led has pwm - use it
int currentGreenIntensity = 0;
bool greenIntensityDirection = true;

int appState = 0;

//button debouncing variables
bool currentButtonState = false;
bool lastButtonState = false;
bool debouncedButtonState = false;
#define buttonDebounceMax 10;
int buttonDebounceTimer = buttonDebounceMax;

//softPWM variables
volatile int redLEDIntensity = 0;
volatile int greenLEDIntensity = 0;
volatile int blueLEDIntensity = 0;
volatile int softPWMCounter = 0;

//colorFade
volatile bool shouldDoColorFade = false;
volatile int colorFadeCounter = 0;
volatile int currentColor = 1;

int getNextAppState(int state){
    shouldDoColorFade = false;
    state++;
    if (state > 5) return 0;
    return state;
}

int debounceButton(int _appState){

    currentButtonState = (PINA>>buttonPin)&1;

    //button debouncing
    if (currentButtonState != lastButtonState && currentButtonState != debouncedButtonState){//if current button state is different than last button state and current debounced state
        if (currentButtonState == true){//if the button is currently pressed
            debouncedButtonState = currentButtonState;
            _appState = getNextAppState(appState);
        } else {
            buttonDebounceTimer = buttonDebounceMax;//reset debounce counter
        }
    } else if (currentButtonState == lastButtonState && currentButtonState != debouncedButtonState){//if current state same as last but different that debounced
        buttonDebounceTimer--;
        if (buttonDebounceTimer == 0){
            debouncedButtonState = currentButtonState;
            if (currentButtonState == true){//if the button is currently pressed
                _appState = getNextAppState(appState);
            }
        }
    }

    lastButtonState = currentButtonState;

    return _appState;
}



void turnOffAllLEDS(){
    //these are common anode leds, so we have to remove their connection to ground to turn them off

    redLEDIntensity = 0;
    greenLEDIntensity = 0;
    blueLEDIntensity = 0;

    PORTA |= (1 << redLED);
    PORTA |= (1 << blueLED);
    PORTB |= (1 << greenLED);
}

//softPWM
ISR(TIM0_COMPA_vect){//timer0 interrupt 200kHz
    softPWMCounter++;//let this overflow to zero
    if (softPWMCounter>255){
        softPWMCounter = 0;
    }

    if (redLEDIntensity>softPWMCounter){
        PORTA &= ~(1 << redLED);
    } else {
        PORTA |= (1 << redLED);
    }
    if (greenLEDIntensity>softPWMCounter){
        PORTB &= ~(1 << greenLED);
    } else {
        PORTB |= (1 << greenLED);
    }
    if(blueLEDIntensity>softPWMCounter){
        PORTA &= ~(1 << blueLED);
    } else {
        PORTA |= (1 << blueLED);
    }

}

//set new intensity vals
ISR(TIM1_COMPA_vect){//timer1 interrupt 100Hz
    if (shouldDoColorFade){
        switch(currentColor){
            case 1://red
            {
                if (redLEDIntensity == 0){
                    currentColor = 2;
                    break;
                }
                redLEDIntensity--;
                greenLEDIntensity++;
                break;
            }
            case 2://green
            {
                if (greenLEDIntensity == 0){
                    currentColor = 3;
                    break;
                }
                greenLEDIntensity--;
                blueLEDIntensity++;
                break;
            }
            case 3://blue
            {
                if (blueLEDIntensity == 0){
                    currentColor = 1;
                    break;
                }
                blueLEDIntensity--;
                redLEDIntensity++;
                break;
            }
        }
    }
}


int main(void) {

    //set inputs/outputs
    DDRA = 0;
    DDRA = (1<<redLED)|(1<<blueLED);
    DDRB = 0;
    DDRB = (1<<greenLED);

    //turn LEDs off
    turnOffAllLEDS();

    cli();//disable interrupts

    TCCR0A = 0;// set entire TCCR0A register to 0
    TCCR0B = 0;// same for TCCR0B
    TCNT0  = 0;//initialize counter value to 0
    // set compare match register for 200khz increments
    OCR0A = 99;// = (20*10^6) / (200000*1) - 1 (must be <256 for 8 bit timer)
    // turn on CTC mode
    TCCR0A |= (1 << WGM01);
    // Set CS00 bit for 1 prescaler
    TCCR0B |= (1 << CS00);
    // enable timer compare interrupt
    TIMSK0 |= (1 << OCIE0A);

    TCCR1A = 0;// set entire TCCR1A register to 0
    TCCR1B = 0;// same for TCCR1B
    TCNT1  = 0;//initialize counter value to 0
    // set compare match register for 1hz increments
    OCR1A = 194;// = (20*10^6) / (100*1024) - 1 (must be <65536 for 16 bit timer)
    // turn on CTC mode
    TCCR1B |= (1 << WGM12);
    // Set CS10 and CS12 bits for 1024 prescaler
    TCCR1B |= (1 << CS12) | (1 << CS10);
    // enable timer compare interrupt
    TIMSK1 |= (1 << OCIE1A);

    sei();//enable interrupts

    while (1) {
       appState = debounceButton(appState);
       switch(appState){
           case 0://red
               redLEDIntensity = 255;
               greenLEDIntensity = 0;
               blueLEDIntensity = 0;
               break;
           case 1://green
               redLEDIntensity = 0;
               greenLEDIntensity = 255;
               blueLEDIntensity = 0;
               break;
           case 2://blue
               redLEDIntensity = 0;
               greenLEDIntensity = 0;
               blueLEDIntensity = 255;
               break;
           case 3://fade colors
               shouldDoColorFade = true;
               break;
           case 4://pause at current color
               break;
           case 5:
               turnOffAllLEDS();
               break;
       }

   }
}

Here is the make file:

PROJECT=softPWM
SOURCES=$(PROJECT).c
MMCU=attiny44
F_CPU = 20000000

CFLAGS=-mmcu=$(MMCU) -Wall -Os -DF_CPU=$(F_CPU)

$(PROJECT).hex: $(PROJECT).out
	avr-objcopy -O ihex $(PROJECT).out $(PROJECT).c.hex

$(PROJECT).out: $(SOURCES)
	avr-gcc $(CFLAGS) -I./ -o $(PROJECT).out $(SOURCES)

program-bsd: $(PROJECT).hex
	avrdude -p t44 -c bsd -U flash:w:$(PROJECT).c.hex

program-dasa: $(PROJECT).hex
	avrdude -p t44 -P /dev/ttyUSB0 -c dasa -U flash:w:$(PROJECT).c.hex

program-avrisp2: $(PROJECT).hex
	avrdude -p t44 -P usb -c avrisp2 -U flash:w:$(PROJECT).c.hex

program-avrisp2-fuses: $(PROJECT).hex
	avrdude -p t44 -P usb -c avrisp2 -U lfuse:w:0x5E:m

program-usbtiny: $(PROJECT).hex
	avrdude -p t44 -P usb -c usbtiny -U flash:w:$(PROJECT).c.hex

program-usbtiny-fuses: $(PROJECT).hex
	avrdude -p t44 -P usb -c usbtiny -U lfuse:w:0x5E:m

program-dragon: $(PROJECT).hex
	avrdude -p t44 -P usb -c dragon_isp -U flash:w:$(PROJECT).c.hex

I named the files softPWM.make and softPWM.c and ran the following in the terminal:

sudo make -f softPWM.make program-usbtiny