Blink with ATTiny45

In a world of ever increasing distractions from education, it is growing more and more difficult to provide learning opportunities to students that have a large value. In an attempt to help students see that there is more to education than memorizing facts for standardized tests, I worked to develop a project for students to complete and show them that advanced education is fun!

This project was based around the Atmel ATTiny45 microcontroller. This little chip is neat because it has 4Kbytes of memory, and 5 digital outputs (two of which are PWM). It has many more features than this, but for this simple project, only the outputs were used. It also operates at a voltage of 1.8V to 5.5V, meaning it can be powered by a coin cell battery.

To bring everything together, a PCB was created that was as simple as possible, that students will solder the ATTiny, ISP headers, 5 LEDs, 5 resistors, and a battery clip.

electronics-007

 

You can probably guess that the first part of this project is to teach students how to solder.

electronics-001

 

Once the students complete soldering, they are able to upload code to the ATTiny using a SparkFun Pocket AVR Programmer. When they get to this point they are able to choose what they want to do, set up a simple blink pattern, write code themselves to do different things with the LEDs, or take the next step and learn binary with their new widget.

Letter 128 64 32 16 8 4 2 1 Result
A 0 1 0 0 0 0 0 1 65
B 0 1 0 0 0 0 1 0 66
C 0 1 0 0 0 0 1 1 67
a 0 1 1 0 0 0 0 1 97
b 0 1 1 0 0 0 1 0 98
c 0 1 1 0 0 0 1 1 99

See a pattern? Each ASCII character is the sum of the binary values (2^n, where n is the position) added together. Pretty neat. Uppercase ‘A’ would be

0x128 + 1x64 + 0x32 + 0x16 + 0x8 + 0x4 + 0x2 + 1x1 = 65

while lowercase ‘b’ is

0x128 + 1x64 + 1x32 + 0x16 + 0x8 + 0x4 + 1x2 + 1x1 = 99

As simple as that.

The neat thing about binary is that you can send messages with it with a single on/off or long/short (you might have seen this in Interstellar recently). Lucky for this project, there are 5 LEDs, so you can be a little more clear with your message. As it turns out there is a pretty simple conversion between ASCII and 8bit Binary. Unfortunately it would be more convenient to have 8 LEDs instead of 5 to this, but that does not mean it is not possible.

Code was written, and then modified by a former student (code-ape) to allow the ATTiny to use 3 LEDs to blink the 8bit binary. The three lower LEDs were used to indicate different things. LED4 indicated the end of the letter, LED3 was a 1 second blink, and LED2 acted as on/off in concert with LED3. This allows people to read the binary sentence and then convert it to ASCII to read. LEDs 0 and 1 were used to indicate the end of a sentence, and would fade on and off based on a sin function the rest of the time (everything needs fading LEDs).

So, a little soldering, a little programming, and a little math lets students learn new skills and hopefully gain an appreciation of how education and creativity work together!

 


//led 0 - > Fade/End of sentane
//led 1 - > Fade/End of sentance
//led 2 - > Binary Data
//led 3 - > 1 second time
//led 4 - > End of Letter

//####//####//####//####//####//####//
// Edit the text between the quotes //
// below to add your secret message //
// to your device.                  //
//####//####//####//####//####//####//

char secret_string[ ] = "Can you read binary?";     //initialize

//####//####//####//####//####//####//

int led[5] = {0, 1, 2, 3, 4};

// LED role assignments
const int LED_FADE = led[0];
const int LED_FADE2 = led[1];
const int LED_MESSAGE = led[2];
const int LED_BLINK = led[3];
const int LED_END_LETTER = led[4];
const int LED_END_MESSAGE = led[1];

// LED times to stay on
const int DELAY_BLINK = 2000;
const int LETTER_TIME = 2000;
const int DELAY_END_LETTER = 4000;
const int DELAY_END_MESSAGE = 6000;

// string variables
int stringLength = sizeof(secret_string);
int stringPosition = 0;
int binaryPosition = 7;

// state variable
int state = 0;

unsigned long led_time_register[5];

//state definitions
const int STARTING_MESSAGE = 1;
const int STARTING_LETTER = 2;
const int PRINTING_LETTER = 3;
const int END_OF_LETTER = 4;
const int INBETWEEN_LETTERS = 5;
const int END_OF_MESSAGE = 6;
const int INBETWEEN_MESSAGE = 7;

// time variables
int cycle_delay = 5; //milliseconds
unsigned long prior_loop_time = 0; //milliseconds
unsigned long TIME;
unsigned long loop_start;
unsigned long loop_end;
unsigned long letter_end_time;
unsigned long message_end_time;
unsigned long last_blink;

// blink variables
boolean blink_state = true; // true = on, false = off
const boolean ON = true;
const boolean OFF = false;


//setup
void setup()  {
  TIME = millis();
  //  Serial.begin(9600);
  //  Serial.println("Beginning setup");

  delay(500);
  for (int i = 0; i < 5; i++ ) {
    pinMode(led[i], OUTPUT);
    led_time_register[i] = 0;
    digitalWrite(led[i], HIGH);
  }
  delay(300);
  for (int i = 4; i >= 0; i--) {
    digitalWrite(led[i], LOW);
    delay(100);
  }
  delay(1000);

  state = STARTING_MESSAGE;

  //  Serial.println("Exiting setup");
}


char letter;
boolean bit;

// main loop
void loop() {

  prior_loop_time = loop_end - loop_start;
  loop_start = millis();

  if (prior_loop_time < 5) {
    delay(5 - prior_loop_time);
  }

  TIME += prior_loop_time;


  // turns blinking light on and off
  if (time_has_passed_since(DELAY_BLINK, last_blink)) {
    turn_led_to_for_dur(LED_BLINK, DELAY_BLINK / 2, ON);
    last_blink = TIME;
  }

  int fade_val = 127.5 + 127.5 * sin(2 * PI / 2500 * TIME);
  analogWrite(LED_FADE, fade_val);    //tell LED what its PWM fade value should be.

  if (state != INBETWEEN_MESSAGE) {
    int fade_val = 127.5 + 127.5 * sin(2 * PI / 2500 * TIME);
    analogWrite(LED_FADE, fade_val);
    analogWrite(LED_FADE2, 255 - fade_val);  //tell LED what its PWM fade value should be.
  } else {
    digitalWrite(LED_FADE2, HIGH);
    digitalWrite(LED_FADE, HIGH);
  }

  turn_off_expired_leds();

  switch (state) {
    case STARTING_MESSAGE:
      {
        letter_end_time = 0;
        message_end_time = 0;
        stringPosition = 0;

        // Serial.println("Changing state to 'STARTING_LETTER'");
        state = STARTING_LETTER;
      }
      break;


    case STARTING_LETTER:
      {
        binaryPosition = 7;

        // Serial.println("Changing state to 'PRINTING_LETTER'");
        state = PRINTING_LETTER;
      }
      break;

    case PRINTING_LETTER:
      {
        if (!time_has_passed_since(LETTER_TIME, letter_end_time)) {
          break;
        }

        letter = get_letter(secret_string, stringPosition);
        bit = get_bit(letter, binaryPosition);
        print_bit(bit);

        binaryPosition--;
        letter_end_time = last_blink;

        if (binaryPosition < 0) {
          // Serial.println("Changing state to 'END_OF_LETTER'");
          state = END_OF_LETTER;
        }
      }
      break;

    case END_OF_LETTER:
      {
        if (!time_has_passed_since(LETTER_TIME, letter_end_time)) {
          break;
        }
        print_bit(OFF);
        turn_led_on_for_dur(LED_END_LETTER, DELAY_END_LETTER);

        stringPosition += 1;

        if (stringPosition >= stringLength - 1) {
          //          Serial.println("Changing state to 'END_OF_MESSAGE'");
          state = END_OF_MESSAGE;
          message_end_time = TIME;
        } else {
          //          Serial.println("Changing state to 'INBETWEEN_LETTERS'");
          state = INBETWEEN_LETTERS;
          letter_end_time = TIME;
        }
      }
      break;


    case INBETWEEN_LETTERS:
      {
        if (time_has_passed_since(DELAY_END_LETTER, letter_end_time)) {
          //          Serial.println("Changing state to 'STARTING_LETTER'");
          state = STARTING_LETTER;
        }
      }
      break;

    case END_OF_MESSAGE:
      {
        turn_led_on_for_dur(LED_END_MESSAGE, DELAY_END_MESSAGE);

        //        Serial.println("Changing state to 'INBETWEEN_MESSAGE'");

        state = INBETWEEN_MESSAGE;

      }
      break;


    case INBETWEEN_MESSAGE:
      {
        if (time_has_passed_since(DELAY_END_MESSAGE, message_end_time)) {
          //          Serial.println("Changing state to 'STARTING_MESSAGE'");
          state = STARTING_MESSAGE;
        }
      }
      break;
  }

  loop_end = millis();
}



char get_letter(char *char_array, int position) {
  return char_array[position];
}

boolean get_bit(char letter, int position) {
  if (letter & 1 << position) {
    return true;
  } else {
    return false;
  }
}

void print_bit(boolean bit) {
  if (bit == ON) {
    //    Serial.println("Printing '1' bit");
  } else {
    //    Serial.println("Printing '0' bit");
  }

  if (bit == ON) {
    //digitalWrite(LED_MESSAGE, HIGH);
    turn_led_to_for_dur(LED_MESSAGE, LETTER_TIME / 2, ON);
  } else {
    //digitalWrite(LED_MESSAGE, LOW);
    turn_led_to_for_dur(LED_MESSAGE, LETTER_TIME / 2, OFF);
  }
}


void turn_led_on_for_dur(int led_number, int duration) {
  //  Serial.print("Turning on led '");
  //  Serial.print(led_number);
  //  Serial.print("' for '");
  //  Serial.print(duration);
  //  Serial.print("' milliseconds\n");
  turn_led_to_for_dur(led_number, duration, ON);
}

void turn_led_to_for_dur(int led_number, int duration, boolean state) {
  turn_led_to_for_dur_since(led_number, duration, state, TIME);
}


void turn_led_to_for_dur_since(int led_number, int duration, boolean state, unsigned long initial_time) {

  if (state == ON) {
    digitalWrite(led_number, HIGH);
  } else {
    digitalWrite(led_number, LOW);
  }

  led_time_register[led_number] = initial_time + duration;
}


boolean time_has_passed_since(int interval_length, unsigned long start_time) {
  if (start_time + ((unsigned long) interval_length) < TIME) {
    return true;
  } else {
    return false;
  }
}


void turn_off_expired_leds() {
  for (int pin = 0; pin < 5; pin++) {

    if ( led_time_register[pin] > 0 ) {
      if ( TIME > led_time_register[pin] ) {
        //        Serial.print("Turning off led '");
        //        Serial.print(pin);
        //        Serial.print("'\n");

        digitalWrite(pin, LOW);

        led_time_register[pin] = 0;
      }
    }
  }
  
}


http://highlowtech.org/?p=1695
https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json