Arduino IR Pet

From HeatSync Labs Wiki
Revision as of 20:03, 19 November 2010 by en>Will
Jump to navigation Jump to search

This is code used in teaching an Arduino workshop. It combines a 16x2 character LCD, an IR LED, an IR photodetector, and a piezo speaker to form an electronic "pet" similar to a Tamogachi.

For assistance with this code, please post in the HeatSync Google Group in this thread: HSL Thread: Arduino IR Pet (workshop code)

Assembly

(Assembly instructions/photos here)

Libraries

Download and extract this ZIP into your arduino app's libraries folder-- resulting file structure should look like arduino\libraries\tone\tone.h

File:Tone.zip

Code


/*
  HeatSync Labs IR Pet
  
 Demonstrates the use of a 16x2 LCD display, IR emitters and sensors, Piezo 
 
  The circuit:
 * LCD PWR- pin to GND
 * LCD PWR+ pin to +5V
 * LCD RS pin to digital pin 7
 * LCD Enable pin to digital pin 8
 * LCD D4 pin to digital pin 9
 * LCD D5 pin to digital pin 10
 * LCD D6 pin to digital pin 11
 * LCD D7 pin to digital pin 12
 * Potentiometer:
 *  ends to +5V and ground
 *  center ("wiper") to LCD VO pin (LCD pin 3)
 * Photodetector:
 *  connect to digital pin 3 with a resistor in between, and GND 
 * LED: 
 *  connect to digital pin 2 with a resistor in between, and GND
 * Piezo Speaker:
 *  connect to digital pin 5 and GND
 
  Operation: 
 * Once powered on and this code uploaded, the LCD should show a HeatSync Labs splash screen followed by health and happiness gauges.
 * If you can't see anything on the LCD, try adjusting the potentiometer. If you see garbled stuff, press the reset button on the arduino or double-check your wiring.
 * To increase the pet's happiness, "feed" its IR photodetector with infrared light (possibly from its own infrared LED, or a remote control, or another pet's LED.)
 * To increase the pet's health, "clean up" after it by making a loud noise into the piezo (try blowing on the piezo, it can act like a really weak microphone!)
 * If you get continuous heart or smiley animations and noises, some of your sensors may be stuck on (i.e. the IR photodetector) or they might be wired incorrectly (my photodetector produces a 0 when active and 1 when inactive, for example.)
 
 */

// include the LCD library code:
#include <LiquidCrystal.h>
// include the RTTTL Tone (music) library:
#include <Tone.h>

/*
 * This section for music settings
 */
Tone tone1;

#define OCTAVE_OFFSET 0

int notes[] = { 0,
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7
};

  char *songA = "MissionImp:d=16,o=6,b=95:32d,32d#,32d,32d#,32d,32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g,g,8p";
  char *songB = "shorter_r2d2:d=4,o=5,b=450:16c#7,16g#7,16f6,16a#7,16e7,16g#8,16e8,16g#6,16g7,16c#7,16g7,16f#8,16c#7,16f#6,16f8,16g#7,16g6,16a8,16c#8,16g6,16f#8,16g7,16f8,16a#6,16f#8,16d8,16g7,16g8,16c8,16g#6,16a8";

#define isdigit(n) (n >= '0' && n <= '9')

/*
 * This section for custom characters on the LCD
 */
 
// blank 
byte blank[8] = {	B00000,	B00000,	B00000,	B00000,	B00000,	B00000,	B00000,	B00000};
// smiley
byte smiley[8] = {	B00000,	B01010,	B01010,	B00000,	B10001,	B10001,	B01110,	B00000};
// heart
byte heart[8] = {	B00000,	B01010,	B10101,	B10001,	B10001,	B01010,	B00100,	B00000};
// chip
byte chip[8] = {	B01110,	B11111,	B01110,	B11111,	B01110,	B11111,	B01110,	B11111};
// heatsync logo
byte hsl_toplft[8] = {	B00000,	B00000,	B00000,	B00011,	B00100,	B00100,	B01001,	B01011};
byte hsl_toprt[8] = {	B00000,	B00000,	B01010,	B11010,	B00101,	B00111,	B00010,	B00011};
byte hsl_btmrt[8] = {	B11010,	B10110,	B01111,	B11001,	B01100,	B01110,	B00000,	B00000};
byte hsl_btmlft[8] = {	B01000,	B00100,	B01100,	B01011,	B00010,	B00110,	B00000,	B00000};

/*
 * This section for public variables used for the program runtime
 */

LiquidCrystal lcd(7,8,9,10,11,12); // initialize the library with the numbers of the interface pins
int sensorValue = 0;  // variable to store the value coming from the sensor
int ledPin = 2;    // select the output pin for the IR LED
int sensorPin = 3;    // select the input pin for the IR sensor
int speakerPin = 5;   // select the output pin for the speaker
int speakerValue = 0;  // variable to store whether the button is currently pushed
int buttonPin = 4;    // select the input pin for the button
int health = 50;      // starting "health"
int happy = 50;       // starting "happiness"
int loopCounter = 0;  // just a counter to space out events within the loop() below


void setup() {
  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);
  // set up the speaker's pin number
  tone1.begin(speakerPin);
  // set up serial communication (back to the PC via USB for debugging)
  Serial.begin(9600);
  // declare the sensorPin as an INPUT:
  pinMode(sensorPin, INPUT);  
  // write the sensorPin high
  digitalWrite(sensorPin, HIGH); 
  // declare the ledPin as an OUTPUT:
  pinMode(ledPin, OUTPUT); 

  lcd.createChar(0, blank);
  lcd.createChar(1, smiley);
  lcd.createChar(2, heart);
  lcd.createChar(3, chip);
  lcd.createChar(4, hsl_toplft);
  lcd.createChar(5, hsl_toprt);
  lcd.createChar(6, hsl_btmrt);
  lcd.createChar(7, hsl_btmlft);
  lcd.begin(16, 2);

  showBootScreen();
  delay(2000);
  lcd.clear();
}


void loop() {
  loopCounter++;
  if(loopCounter > 400)
  {
    adjustHealth(-1);
    loopCounter = 0;
    makeDirty();
  }
  
  if(loopCounter % 100 == 0)
  {
    adjustHappy(-1); 
  }
  
  
  showHealth();
  showHappy();
  
  digitalWrite(ledPin, HIGH); 
  
  // read the value from the sensor:
  sensorValue = digitalRead(sensorPin); 
  if(sensorValue == 0)  // for some reason IR transistors are 0 when light and 1 when dark?
  {
    Serial.println("Sensor");
    feedChips();
  }  
 
  // declare the speakerPin as an INPUT:
  pinMode(speakerPin, INPUT);  
  delay(100);    
  speakerValue = digitalRead(speakerPin);
  if(speakerValue == 1)
  {
    Serial.println("Speaker");
    cleanUp();
  }
  delay(100);    
  
}


void makeDirty() {
  lcd.setCursor(random(4, 11) ,random(0, 2));
  lcd.print(".");
  delay(100);
}

void cleanUp() {
  play_rtttl(songA);
  
  adjustHealth(6);
  writeAt(2,0,0);
  delay(100);
  writeAt(0,0,0);
  writeAt(2,1,1);
  delay(100);
  writeAt(0,1,1);
  writeAt(2,2,0);
  delay(100);
  writeAt(0,2,0);
  writeAt(2,3,1);
  delay(100);
  writeAt(0,3,1);
  writeAt(0,4,0);
  writeAt(0,5,0);
  writeAt(0,6,0);
  writeAt(0,7,0);
  writeAt(0,8,0);
  writeAt(0,9,0);
  writeAt(0,10,0);
  writeAt(0,11,0);
  writeAt(0,12,0);
  writeAt(0,4,1);
  writeAt(0,5,1);
  writeAt(0,6,1);
  writeAt(0,7,1);
  writeAt(0,8,1);
  writeAt(0,9,1);
  writeAt(0,10,1);
  writeAt(0,11,1);
  writeAt(0,12,1);
}

void feedChips() {
  
  play_rtttl(songB);

  adjustHappy(8);
  writeAt(3,0,0);
  delay(100);
  writeAt(0,0,0);
  writeAt(3,1,1);
  delay(100);
  writeAt(0,1,1);
  writeAt(3,2,0);
  delay(100);
  writeAt(0,2,0);
  writeAt(3,3,1);
  delay(100);
  writeAt(0,3,1);
}

void writeAt(int character, int x, int y) {
  lcd.setCursor(x, y);
  lcd.write(character); 
}


void adjustHealth(int diff){
  if(health > 0)
  {
    health = health + diff;
  }
  if(health > 99)
  {
    health = 99; 
  }
}

void adjustHappy(int diff){
  if(happy > 1 || diff > 0)
  {
    happy = happy + diff;
  }
  if(happy > 99)
  {
    happy = 99; 
  }
}

void showHappy() {
  lcd.setCursor(13, 1);
  lcd.write(1);
  lcd.print(happy);
  lcd.write(0);
}

void showHealth() {
  lcd.setCursor(13, 0);
  lcd.write(2);
  lcd.print(health);
  lcd.write(0);
}

void showBootScreen(){
  lcd.setCursor(0, 0);
  lcd.write(4);
  lcd.write(5);
  lcd.setCursor(0, 1);
  lcd.write(7);
  lcd.write(6);

  lcd.setCursor(3, 0);
  lcd.print("HeatSync Labs");
  
  lcd.setCursor(3, 1);
  lcd.print("BootROM v1337"); 
}




void play_rtttl(char *p)
{
  // Absolutely no error checking in here

  byte default_dur = 4;
  byte default_oct = 6;
  int bpm = 63;
  int num;
  long wholenote;
  long duration;
  byte note;
  byte scale;

  // format: d=N,o=N,b=NNN:
  // find the start (skip name, etc)

  while(*p != ':') p++;    // ignore name
  p++;                     // skip ':'

  // get default duration
  if(*p == 'd')
  {
    p++; p++;              // skip "d="
    num = 0;
    while(isdigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }
    if(num > 0) default_dur = num;
    p++;                   // skip comma
  }

  //Serial.print("ddur: "); Serial.println(default_dur, 10);

  // get default octave
  if(*p == 'o')
  {
    p++; p++;              // skip "o="
    num = *p++ - '0';
    if(num >= 3 && num <=7) default_oct = num;
    p++;                   // skip comma
  }

  //Serial.print("doct: "); Serial.println(default_oct, 10);

  // get BPM
  if(*p == 'b')
  {
    p++; p++;              // skip "b="
    num = 0;
    while(isdigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }
    bpm = num;
    p++;                   // skip colon
  }

  //Serial.print("bpm: "); Serial.println(bpm, 10);

  // BPM usually expresses the number of quarter notes per minute
  wholenote = (60 * 1000L / bpm) * 4;  // this is the time for whole note (in milliseconds)

  //Serial.print("wn: "); Serial.println(wholenote, 10);


  // now begin note loop
  while(*p)
  {
    // first, get note duration, if available
    num = 0;
    while(isdigit(*p))
    {
      num = (num * 10) + (*p++ - '0');
    }
    
    if(num) duration = wholenote / num;
    else duration = wholenote / default_dur;  // we will need to check if we are a dotted note after

    // now get the note
    note = 0;

    switch(*p)
    {
      case 'c':
        note = 1;
        break;
      case 'd':
        note = 3;
        break;
      case 'e':
        note = 5;
        break;
      case 'f':
        note = 6;
        break;
      case 'g':
        note = 8;
        break;
      case 'a':
        note = 10;
        break;
      case 'b':
        note = 12;
        break;
      case 'p':
      default:
        note = 0;
    }
    p++;

    // now, get optional '#' sharp
    if(*p == '#')
    {
      note++;
      p++;
    }

    // now, get optional '.' dotted note
    if(*p == '.')
    {
      duration += duration/2;
      p++;
    }
  
    // now, get scale
    if(isdigit(*p))
    {
      scale = *p - '0';
      p++;
    }
    else
    {
      scale = default_oct;
    }

    scale += OCTAVE_OFFSET;

    if(*p == ',')
      p++;       // skip comma for next note (or we may be at the end)

    // now play the note

    if(note)
    {
     // Serial.print("Playing: ");
      //Serial.print(scale, 10); Serial.print(' ');
      //Serial.print(note, 10); Serial.print(" (");
      //Serial.print(notes[(scale - 4) * 12 + note], 10);
      //Serial.print(") ");
      //Serial.println(duration, 10);
      tone1.play(notes[(scale - 4) * 12 + note]);
      delay(duration);
      tone1.stop();
    }
    else
    {
      //Serial.print("Pausing: ");
      //Serial.println(duration, 10);
      delay(duration);
    }
  }
}