The Biological Clock dress

The Rainbowduino board was selected to drive a matrix of high intensity red LEDs assembled to represent a wearable clock face for an “art” dress. The theme of the dress is that the “biological clock” is ticking quite rapidly.

While the Rainbowduino board was primarily designed to interface with a conventional RGB dot matrix display, it is very capable of driving other matrix-connected arrays of LEDs.

This display was constructed of a plastic disk with holes to locate the (really bright) LEDs and the wiring hand soldered at the back. The Rainbowduino board and a battery holder with 4 AA cells hide elsewhere in the dress.

The Rainbowduino makes construction of a wearable device like this simpler because it eliminates the need for resistors in series with each of the LEDs - it controls the current on the board.

The still picture:

IMG_2036.jpg
The video:

[size=200]http://www.vimeo.com/6928154[/size]

The sketch (program which runs on the Rainbowduino board) :

[code]// first whack at driving the “biological clock” with a Rainboduino board from SeeedStudio sept0609
// derived from Rainbowduino manual and beta example “Rainbow_CMD_V2_0”
// using the board to drive a matrix of 192 single LEDs on or off, no intensity modulation (going for “bright”)
// Rainbowduino was designed to mate with a specific full color LED matrix and pinout

#include “Rainbow.h”
#include <Wire.h>

// using the Wire library to make access to the Rainbowduino board slightly more straightforward…
// some sleuthing to determine Arduino pin numbers for the Wire library calls from the Rainbow.h file…
// Arduino pin number conventions - PORTD[0…7] are 0…7, PORTB[0…5] are 8…13 PORTC[0…5] are 14…19

// datapin is PORTC, bit 0 and clockpin is PORTC, bit 1
const int datapin = 14;
const int clockpin = 15;
// without having a datasheet with pinout for the MBI5168 (or Rainbowduino schematic), the following are guesses at pin functions
// PORTC, bit 2
const int latchEnable = 16; // active low
// PORTC, bit 3
const int outputEnable = 17; // active low…
// similarly for the 8 current drive lines
int driveLine[] = { 10, 9, 8, 7, 6, 5, 4, 3 }; // permute the order for different wiring hookups

// bioclock has 18 radial rows (like spokes of a wheel) of 6 LEDs each organized as 3 sets of 6x8 matrices.
// each spoke is driven by a source line and one byte of shift register switches.
// organized as 8 drive lines and “G” byte, then same 8 drive lines and “B” byte, then same 8 drive lines and “R” byte.
// long hand drives all the LEDs in a spoke, short hand drives only the inner 4
// running around the clock consists of indexing the drive lines in sequence for each of the three byte groups

unsigned long tickTimer; // overflows in 50 days?

void setup(void) {
pinMode(datapin, OUTPUT);
pinMode(clockpin, OUTPUT);
pinMode(latchEnable, OUTPUT);
pinMode(outputEnable, OUTPUT);
for (int i=0; i<8; i++) {
pinMode(driveLine[i], OUTPUT);
}
// misc variables for timing
tickTimer = millis(); // initialize timer to “now”
}

void loop(void) {

// // most basic, see if it will do anything. Yup, works.
// digitalWrite(clockpin, LOW);
// delay(250);
// digitalWrite(clockpin,HIGH);
// delay(250);
// // OK, try a LED anode drive line. Yup, works.
// digitalWrite(driveLine[0], LOW);
// delay(250);
// digitalWrite(driveLine[0],HIGH);
// delay(250);
// // test another part of it… Yup.
// for (int j=0; j<8; j++) {
// loadSource(j);
// delay(250); }

 bioClock(); // biological clock

}

void loadSource(int rowIndex) // this selects one of the eight “row” drivers // in this case, “spoke” = “row”
{
static int currentRow = 0; // only want one row at a time turned on - this remembers which one is on
digitalWrite(driveLine[currentRow], LOW); // only want one at a time on, so turn off the old
digitalWrite(driveLine[rowIndex], HIGH); // turn on the new
currentRow = rowIndex; // remember what’s on to turn off next call
}
void loadswitches( unsigned char sw[3] ) // this loads 24 bits into the “column” switches
{
digitalWrite(latchEnable, HIGH);
shiftOut(datapin, clockpin, MSBFIRST, sw[0]);
shiftOut(datapin, clockpin, MSBFIRST, sw[1]);
shiftOut(datapin, clockpin, MSBFIRST, sw[2]);
digitalWrite(latchEnable, LOW);
}

void lamptest(void) { // light entire row in column order to check for opens
unsigned char fullColumn[][3] = {{0xFF,0,0}, {0, 0xFF, 0}, {0, 0, 0xFF}};
for (int i=0; i<3; i++) {
for (int j=0; j<8; j++) {
digitalWrite(outputEnable, HIGH);
loadSource(j);
loadswitches(&fullColumn[i][0]);
digitalWrite(outputEnable, LOW);
delay(250); // run all spokes in 6 seconds… should be visible
}
}
}

void lamptest2(void) { // light one LED at a time, in row then column order to see if any bits stuck together
static unsigned char bitSelector[][3] = { {1,0,0},{2,0,0},{4,0,0},{8,0,0},{16,0,0},{32,0,0},{64,0,0},{128,0,0},
{0,1,0},{0,2,0},{0,4,0},{0,8,0},{0,16,0},{0,32,0},{0,64,0},{0,128,0},
{0,0,1},{0,0,2},{0,0,4},{0,0,8},{0,0,16},{0,0,32},{0,0,64},{0,0,128}};
for (int i=0; i<6; i++) {
for (int j=0; j<24; j++) {
digitalWrite(outputEnable, HIGH);
loadSource(i);
loadswitches(&bitSelector[j][0]);
digitalWrite(outputEnable, LOW);
delay(250);
}
}
}

boolean past(unsigned long timer) {
return (millis() > timer) ; // timer is in the past if current time is greater
}

int handToggle = 0;
int longHandSpoke = 0;
int shortHandSpoke = 0; // spoke which is currently illuminated
int columns = 18; // logically 3 6x8 arrays
unsigned char longHand[][3] = {{0xFC,0,0},{0,0xFC,0},{0,0,0xFC}};
unsigned char shortHand[][3] = {{0x3C,0,0},{0,0x3C,0},{0,0,0x3C}};
unsigned char centerDot[3] = {0,0,0x1} ; //enable the center dot

void bioClock(void) // sequence the stepping of both the short and long hand
{
if (past(tickTimer)) // update the pointers when the bioclock ticks
{ // wrap row pointers
if ( ++longHandSpoke >= columns )
{ longHandSpoke = 0;
if (++shortHandSpoke >= columns )
{ shortHandSpoke = 0; }
}
tickTimer = millis() +64ul; // set time for the next clocktick
}

// draw hands on alternate passes
switch (handToggle) {
case 0:
// longHand
digitalWrite(outputEnable, HIGH);
loadSource(longHandSpoke % 6);
loadswitches(&longHand[longHandSpoke/6][0]);
digitalWrite(outputEnable, LOW);
break;
case 1:
digitalWrite(outputEnable, HIGH);
loadSource(shortHandSpoke % 6);
loadswitches(&shortHand[shortHandSpoke/6][0]);
digitalWrite(outputEnable, LOW);
break;
case 2: // dot in the center
digitalWrite(outputEnable, HIGH);
loadSource(0);
loadswitches(&centerDot[0]);
digitalWrite(outputEnable, LOW);
break;
}
handToggle = ++handToggle % 3 ; // switch display refresh task
delay(6);
}
[/code]