OpenHeartLib

From Noisebridge
Revision as of 02:24, 18 February 2009 by BillyBuggy (talk | contribs)
Jump to navigation Jump to search

At the Noisebridge soldering party on Valentine's Day, February 14 2009, thanks to the gracious assistance of Mitch, Christie, Andy, and many others, I built a Jimmie Rodgers' Open Heart.

While I simply adore the Open Heart Animation Programmer, I decided to take a look at the code it generates and write a small C++ class for the Arduino Development Environment, that lets you control the Open Heart LED's by calling simple functions.

The class library consists of only two files (besides your "main" sketch file):

 * .../hardware/libraries/OpenHeartLib/OpenHeartLib.h
 * .../hardware/libraries/OpenHeartLib/OpenHeartLib.cpp
File OpenHeartLib.h
#include <WConstants.h>

class OpenHeart {
public:
  OpenHeart();
  void setPinMap( byte pin1, byte pin2, byte pin3,
                  byte pin4, byte pin5, byte pin6 );
  void turnOnLed( byte led );
  void alloff();
private:
  static byte pin1, pin2, pin3, pin4, pin5, pin6;
  static byte heartpins[27][2];
  static byte allpins[];
};
File OpenHeartLib.cpp
#include <OpenHeartLib.h>

byte OpenHeart::pin1=1, OpenHeart::pin2=2, OpenHeart::pin3=3,
     OpenHeart::pin4=4, OpenHeart::pin5=5, OpenHeart::pin6=6;

byte OpenHeart::heartpins[27][2] = {
  {pin3, pin1}, {pin1, pin3}, {pin2, pin1}, {pin1, pin2}, {pin3, pin4},
  {pin4, pin1}, {pin1, pin4}, {pin1, pin5}, {pin6, pin1}, {pin1, pin6},
  {pin6, pin2}, {pin4, pin3}, {pin3, pin5}, {pin5, pin3}, {pin5, pin1},
  {pin2, pin5}, {pin5, pin2}, {pin2, pin6}, {pin4, pin5}, {pin5, pin4},
  {pin3, pin2}, {pin6, pin5}, {pin5, pin6}, {pin4, pin6}, {pin2, pin3},
  {pin6, pin4}, {pin4, pin2}
};

byte OpenHeart::allpins[7];   // Gets indexed 1~6, 0 is unused.

OpenHeart::OpenHeart() {};

void OpenHeart::setPinMap( byte pin1, byte pin2, byte pin3,
                           byte pin4, byte pin5, byte pin6 ) {
  allpins[1] = pin1;
  allpins[2] = pin2;
  allpins[3] = pin3;
  allpins[4] = pin4;
  allpins[5] = pin5;
  allpins[6] = pin6;
  for ( byte k=0 ; k<27 ; k++ ) {
    heartpins[k][0] = allpins[ heartpins[k][0] ];
    heartpins[k][1] = allpins[ heartpins[k][1] ];
  }
  OpenHeart::pin1 = pin1;
  OpenHeart::pin2 = pin2;
  OpenHeart::pin3 = pin3;
  OpenHeart::pin4 = pin4;
  OpenHeart::pin5 = pin5;
  OpenHeart::pin6 = pin6;
}

void OpenHeart::turnOnLed (byte led) {
  byte pospin = heartpins[led][0];
  byte negpin = heartpins[led][1];
  pinMode      (pospin, OUTPUT);
  pinMode      (negpin, OUTPUT);
  digitalWrite (pospin, HIGH  );
  digitalWrite (negpin, LOW   );
}

void OpenHeart::alloff() {
  for( byte i=1 ; i<7 ; i++ )
    pinMode (allpins[i], INPUT);
}

Note that the two actual source files contains the Open Heart Animation Programmer-generated comment lines:

//**************************************************************//
//  Name    : Charlieplexed Heart control                       //
//  Author  : Jimmie P Rodgers   www.jimmieprodgers.com         //
//  Date    : 08 Feb, 2008  Last update on 02/13/08             //
//  Version : 1.3                                               //
//  Notes   : Uses Charlieplexing techniques to light up        //
//          : a matrix of 27 LEDs in the shape of a heart       //
//          : project website: www.jimmieprodgers.com/openheart //
//**************************************************************//

The following minimal "main" sketch shows a simple usage of the OpenHearLib class. It simply flashes the 27 LEDs one at a time:

(In all of the sample sketches here, you likely have to modify the pin arguments to setPinMap(). These are the same pin mappings that you fill in after clicking on "Generate" in the Open Heart Animation Programmer.)

File Bill_OpenHeartMain.pde (section NN=1, simplified)

#include <OpenHeartLib.h>
OpenHeart ht = OpenHeart();
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  for ( byte k=0 ; k<27 ; k++ ) {
    ht.turnOnLed (k);
    delay (100);     // (msec)
    ht.alloff ();
  }
}

Sometimes it is better to use variables instead of sprinkling constants throughout your code, as shown in the above code rewritten:

File Bill_OpenHeartMain.pde (section NN=1)

#include <OpenHeartLib.h>
OpenHeart ht = OpenHeart();
int  gDelayTime = 100;  // (msec) 'g' stands for "global".
byte gkmax      = 27;   // Number of LEDs
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  for ( byte k=0 ; k<gkmax ; k++ ) {
    ht.turnOnLed (k);
    delay (gDelayTime);
    ht.alloff ();
  }
}

If all you wanted to do with your Arduino was flash the LEDs, the above code is fine. But if you need to do some other computations, such as at the end of your function loop(), you would not want to put your cpu in massive "wait" states with those delay() calls. Instead, you can "unroll" the for loop and use something like:

File Bill_OpenHeartMain.pde (section NN=2)
int  gDelayTime = 100;
byte gkmax      = 27;
byte gk         = 0;
unsigned long gLastTime = millis();
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  if ( gk < gkmax ) {
    if  ( millis() < gLastTime + gDelayTime )
      ht.turnOnLed (gk);
    else {
      ht.alloff();
      gk++;
      gLastTime = millis();
    }
  } else
    gk = 0;
}

Because of how charlieplexing works, you can only turn on one LED at a time with method turnOnLed(). To make it appear that more than one LED is on at the same time, you have to multiplex at high speed:

File Bill_OpenHeartMain.pde (section NN=5)
int  gDelayTime = 300;
unsigned long gLastTime = millis();
int kstart[] = { 23,  4,  0 };   // Three groups of LED's
int kend  [] = { 27, 23,  4 };
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  for ( byte j=0 ; j<3 ; j++ ) {
    while ( millis() < gLastTime + gDelayTime ) {
      for ( byte k=kstart[j] ; k<kend[j] ; k++ ) {
        ht.turnOnLed( k );
        if ( j == 1 )     // Twinkle the middle LED group (ie, flash slower).
          delay( 5 );
        ht.alloff();
      }
    }
    gLastTime = millis();
  }
}

Here is the complete "main" test sketch, where you must select of the eight sections by setting the #define NN value:

File Bill_OpenHeartMain.pde (complete)
//# NN=1 -- With delay().
//# NN=2 -- Looping over frames w/o delay().
//# NN=3 -- DOUBLE Looping with delay().
//# NN=4 -- Program DOUBLE looping over frames w/o delay().
//# NN=5 -- Simultaneous with tiny delay().
//# NN=6 -- Two flickering groups.
//# NN=7 -- Purely random, one LED at a time.
//# NN=8 -- Sequential concentric ring groups -- BEATING HEART!

#define NN 8    /* Pick which section below to compile and execute. */

#include <OpenHeartLib.h>

OpenHeart ht = OpenHeart();   // OpenHeart is a class.

#if (NN == 1)
//--- With delay(). 'g' is for "global"
int  gDelayTime = 100;
byte gkmax      = 27;   // Number of Open Heart LED's
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  for ( byte k=0 ; k<gkmax ; k++ ) {
    ht.turnOnLed (k);
    delay        (gDelayTime);
    ht.alloff    ();
  }
}

#elif (NN == 2)
//--- Looping over frames w/o delay().
int  gDelayTime = 100;
byte gkmax      = 27;
byte gk         = 0;
unsigned long gLastTime = millis();
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  if ( gk < gkmax ) {
    if  ( millis() < gLastTime + gDelayTime )
      ht.turnOnLed (gk);
    else {
      ht.alloff();
      gk++;
      gLastTime = millis();
    }
  } else
    gk = 0;
}

#elif (NN == 3)
//--- DOUBLE Looping with delay()
int  gDelayTime = 100;
byte gjmax      = 3;
byte gkmax      = 9;
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  for   ( byte j=0 ; j<gjmax ; j++ ) {
    for ( byte k=0 ; k<gkmax ; k++ ) {
      ht.turnOnLed( j*gkmax + k );
      delay       ( gDelayTime );
      ht.alloff   ();
    }
  }
}

#elif (NN == 4)
//--- Program DOUBLE looping over frames w/o delay()
int  gDelayTime = 100;
byte gjmax      = 3;
byte gkmax      = 9;
byte gj         = 0;
byte gk         = 0;
unsigned long gLastTime = millis();
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
  gLastTime = millis();  // Not needed unless above executes too early.
}
void loop() {
  if   ( gj < gjmax ) {
    if ( gk < gkmax ) {
      if ( millis() < gLastTime + gDelayTime )
        ht.turnOnLed(gj*9 + gk);
      else {
        ht.alloff();
        gk++;
        gLastTime = millis();
      }
    } else {
      gk = 0;  // Restart inner.
      gj++;    // Increment outer.
    }
  } else
    gj = 0;    // Restart outer.
}

#elif (NN == 5)
//--- Simultaneous with tiny delay()
int  gDelayTime = 300;
unsigned long gLastTime = millis();
//int kstart[] = {  0, 11, 23 };   // Top to bottom.
//int kend  [] = { 11, 23, 27 };
//int kstart[] = { 23, 11,  0 };   // Bottom to top.
//int kend  [] = { 27, 23, 11 };
  int kstart[] = { 23,  4,  0 };   // Bot-to-top many lights in top group
  int kend  [] = { 27, 23,  4 };
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  for ( byte j=0 ; j<3 ; j++ ) {
    while ( millis() < gLastTime + gDelayTime ) {
      for ( byte k=kstart[j] ; k<kend[j] ; k++ ) {
        ht.turnOnLed( k );
        if ( j == 1 )
          delay( 5 );
        ht.alloff();
      }
    }
    gLastTime = millis();
  }
}

#elif (NN == 6)
//--- Two flickering groups.
int  gDelayTime = 1000;
unsigned long gLastTime = millis();
  int kstart[] = { 11,  0 };          // 16/11 ~ 1.5
  int kend  [] = { 27, 11 };
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  for ( byte j=0 ; j<2 ; j++ ) {
    while ( millis() < gLastTime + gDelayTime ) {
      for ( byte k=kstart[j] ; k<kend[j] ; k++ ) {
        ht.turnOnLed( k );
        delay( j==0 ? 4 : 6 );
        ht.alloff();
      }
    }
    gLastTime = millis();
  }
}

#elif (NN == 7)
//--- Purely random, one LED at a time.
int  gDelayTime = 1;
void setup() {
  Serial.begin(9600);
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  int k = random(27);
  ht.turnOnLed( k );
  delay( gDelayTime );
  ht.alloff();
  delay( gDelayTime );
}

#elif (NN == 8)
//--- Sequential concentric ring groups -- BEATING HEART!
int gDelayTime = 250;
byte rings[][13] = { { 0,  1,  2,  3,  4, 10, 11, 17, 18, 22, 23, 25, 26},
                     { 5,  6,  7,  8,  9, 12, 16, 19, 21, 24, 99, 99, 99},
                     {13, 14, 15, 20, 99, 99, 99, 99, 99, 99, 99, 99, 99},
                     { 5,  6,  7,  8,  9, 12, 16, 19, 21, 24, 99, 99, 99}
                   };
byte ringsN[ ] = { 13, 10, 4, 10 };  // Number of non-99 in each row above.
byte Nrings = 4;
byte ringNo = 0;
unsigned long gLastTime = millis();
void setup() {
  ht.setPinMap( 4, 5, 3, 2, 7, 6 );
}
void loop() {
  byte k;
  if ( ringNo < Nrings ) {
    k = 0;
    while ( millis() < gLastTime + gDelayTime ) {
      byte led = rings [ringNo][k];
      if ( led != 99 ) {
        ht.turnOnLed( led );
        delay( 10 * (ringsN[0]+0.0)/ringsN[ringNo] );
        ht.alloff();
      }
      k = (k+1) % ringsN[ringNo];
    }
    ringNo++;
    gLastTime = millis();
  } else
    ringNo = 0;
}

#endif

//# Charlieplexing
//# ++++++++++++++
//#   From: http://www.instructables.com/id/Charlieplexing-LEDs--The-theory
//#
//#   0v/5v/open A -----[R]-----*-----*-----*-----*    LED ||  A  |  B  |  C
//#                             |     |     |     |    ======================
//#                           1 V   2 ^     |     |     1      1     0    XXX
//#                             |     |     |     |     2      0     1    XXX
//#   0v/5v/open B -----[R]-----*-----*   5 V   6 ^     3     XXX    1     0
//#                             |     |     |     |     4     XXX    0     1
//#                           3 V   4 ^     |     |     5      1    XXX    0
//#                             |     |     |     |     6      0    XXX    1
//#   0v/5v/open C -----[R]-----*-----*-----*-----*

//# Open Heart LED Numbers
//#   [     0  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          ]