War on Christmas Lights from Donald Delmar Davis on Vimeo.
Last year I cast a series of led arrays for thing-a-day but hadnt wired them up. With this in mind I ordered some hc595s in the last group order but the snow canceled the meeting.
I have been thinking about using the avr to drive the arrays directly and control them using i2c. I hacked 4 dorkboards by cutting the traces for pins 5-12 on the top of the board and soldering 220 ohm resistors on the other side. This gives a one chip solution which does not require a constant refresh or other attention.
/* Simple 4 node array of leds (8x8). */ #include <Wire.h> int node=0; /* CHANGE THIS BASED ON POSITION OF ARRAY */ volatile int twi_group_offset=128; volatile int colpins[]={//12,11,10,9,8,7,6,5}; 5,6,7,8,9,10,11,12}; volatile int rowpins[]={ 17,16,15,14,13,4,3,2}; //2,3,4,13,14,15,16,17}; volatile unsigned long int myTics; unsigned char pixels[]={0x3E, 0x51, 0x49, 0x45, 0x3E,0,0,node, 0x00, 0x42, 0x7F, 0x40, 0x00,0,1,1, 0x42, 0x61, 0x51, 0x49, 0x46,0,2,2, 0x21, 0x41, 0x45, 0x4B, 0x31,0,3,3 }; void setup() // run once, when the sketch starts { int i; myTics=millis(); for(i=0;i<8;i++){ pinMode(rowpins[i],OUTPUT); digitalWrite(rowpins[i],LOW); pinMode(colpins[i],OUTPUT); digitalWrite(colpins[i],HIGH); } // sets the digital pin as output if (node==0) { Wire.begin(); Serial.begin(19200); updateSlaveNodes(); } else { Wire.begin(node+twi_group_offset); Wire.onReceive(updatePixels); // register event } } void loop() //move most of this into a timer loop. { int r,c; int lastcolpin=colpins[0]; for (c=0;c<8;c++){ lastcolpin=colpins[c]; for(r=0;r<8;r++){ if ((pixels[c]>>r)&0x01){ //if ((!r)||((7-c)==r)||(c==r)){ digitalWrite(rowpins[r], HIGH); } else { digitalWrite(rowpins[r], LOW); } } digitalWrite(colpins[c], LOW); if (node==0) { updateSlaveNodes(); } else { //delayMicroseconds(1500); // delay(3); } digitalWrite(lastcolpin, HIGH); } } void updateSlaveNodes() { int slavenode, row, pixel8; pixel8=8; for (slavenode=1;slavenode<4;slavenode++){ Wire.beginTransmission(slavenode+twi_group_offset); for (row=0;row<8;row++) { Wire.send(pixels[pixel8++]); } Wire.endTransmission(); }; } // function that executes whenever data is received from master // this function is registered as an event, see setup() void updatePixels(int howMany) { int r=0; while(Wire.available()) // { pixels[r++] = Wire.receive(); // receive byte as a character } }
The next step is to move the actual refresh into a timer routine so that the body of code for the main node can focus on content.
/*------------------------------------------------------------ xmas timer array * * This is the program for a 4x8x8 led display using a processor for each segment * To program the different nodes set the node variable below. Node 0 is the * master node which keeps the pixel memory for the remaining nodes. * * The master node takes its memory and sends updates to each of the other segments * via the i2c buss using the Wire library. * * Each node takes its 8byte array of data and strobes it onto the display using * the 8 bit timer2 overflow interrupt. * * The first 7 characters are loaded into the display memory using the * loadCharacters function. The array is then shifted to the left in the main loop * with the next character being loaded a collumn at a time. * * The character data and the bitmap for the 5x7 array are stored in program * memory. * * Since interrupts are used all of the globals are marked as volatile. * * CopyLeft 2008 Donald Delmar Davis, Tempus Dictum, Inc. * This is free software (GPL) */ #include <Wire.h> #include <avr/interrupt.h> #include <avr/io.h> #include <avr/pgmspace.h> #define FREEK 180 int node=0; /* CHANGE THIS BASED ON POSITION OF ARRAY */ unsigned char PROGMEM Font5x7[] = { 0x00, 0x00, 0x00, 0x00, 0x00,// (space) 0x00, 0x00, 0x5F, 0x00, 0x00,// ! 0x00, 0x07, 0x00, 0x07, 0x00,// " 0x14, 0x7F, 0x14, 0x7F, 0x14,// # 0x24, 0x2A, 0x7F, 0x2A, 0x12,// $ 0x23, 0x13, 0x08, 0x64, 0x62,// % 0x36, 0x49, 0x55, 0x22, 0x50,// & 0x00, 0x05, 0x03, 0x00, 0x00,// ' 0x00, 0x1C, 0x22, 0x41, 0x00,// ( 0x00, 0x41, 0x22, 0x1C, 0x00,// ) 0x08, 0x2A, 0x1C, 0x2A, 0x08,// * 0x08, 0x08, 0x3E, 0x08, 0x08,// + 0x00, 0x50, 0x30, 0x00, 0x00,// , 0x08, 0x08, 0x08, 0x08, 0x08,// - 0x00, 0x60, 0x60, 0x00, 0x00,// . 0x20, 0x10, 0x08, 0x04, 0x02,// / 0x3E, 0x51, 0x49, 0x45, 0x3E,// 0 0x00, 0x42, 0x7F, 0x40, 0x00,// 1 0x42, 0x61, 0x51, 0x49, 0x46,// 2 0x21, 0x41, 0x45, 0x4B, 0x31,// 3 0x18, 0x14, 0x12, 0x7F, 0x10,// 4 0x27, 0x45, 0x45, 0x45, 0x39,// 5 0x3C, 0x4A, 0x49, 0x49, 0x30,// 6 0x01, 0x71, 0x09, 0x05, 0x03,// 7 0x36, 0x49, 0x49, 0x49, 0x36,// 8 0x06, 0x49, 0x49, 0x29, 0x1E,// 9 0x00, 0x36, 0x36, 0x00, 0x00,// : 0x00, 0x56, 0x36, 0x00, 0x00,// ; 0x00, 0x08, 0x14, 0x22, 0x41,// < 0x14, 0x14, 0x14, 0x14, 0x14,// = 0x41, 0x22, 0x14, 0x08, 0x00,// > 0x02, 0x01, 0x51, 0x09, 0x06,// 0x32, 0x49, 0x79, 0x41, 0x3E,// @ 0x7E, 0x11, 0x11, 0x11, 0x7E,// A 0x7F, 0x49, 0x49, 0x49, 0x36,// B 0x3E, 0x41, 0x41, 0x41, 0x22,// C 0x7F, 0x41, 0x41, 0x22, 0x1C,// D 0x7F, 0x49, 0x49, 0x49, 0x41,// E 0x7F, 0x09, 0x09, 0x01, 0x01,// F 0x3E, 0x41, 0x41, 0x51, 0x32,// G 0x7F, 0x08, 0x08, 0x08, 0x7F,// H 0x00, 0x41, 0x7F, 0x41, 0x00,// I 0x20, 0x40, 0x41, 0x3F, 0x01,// J 0x7F, 0x08, 0x14, 0x22, 0x41,// K 0x7F, 0x40, 0x40, 0x40, 0x40,// L 0x7F, 0x02, 0x04, 0x02, 0x7F,// M 0x7F, 0x04, 0x08, 0x10, 0x7F,// N 0x3E, 0x41, 0x41, 0x41, 0x3E,// O 0x7F, 0x09, 0x09, 0x09, 0x06,// P 0x3E, 0x41, 0x51, 0x21, 0x5E,// Q 0x7F, 0x09, 0x19, 0x29, 0x46,// R 0x46, 0x49, 0x49, 0x49, 0x31,// S 0x01, 0x01, 0x7F, 0x01, 0x01,// T 0x3F, 0x40, 0x40, 0x40, 0x3F,// U 0x1F, 0x20, 0x40, 0x20, 0x1F,// V 0x7F, 0x20, 0x18, 0x20, 0x7F,// W 0x63, 0x14, 0x08, 0x14, 0x63,// X 0x03, 0x04, 0x78, 0x04, 0x03,// Y 0x61, 0x51, 0x49, 0x45, 0x43,// Z 0x00, 0x00, 0x7F, 0x41, 0x41,// [ 0x02, 0x04, 0x08, 0x10, 0x20,// "\" 0x41, 0x41, 0x7F, 0x00, 0x00,// ] 0x04, 0x02, 0x01, 0x02, 0x04,// ^ 0x40, 0x40, 0x40, 0x40, 0x40,// _ 0x00, 0x01, 0x02, 0x04, 0x00,// ` 0x20, 0x54, 0x54, 0x54, 0x78,// a 0x7F, 0x48, 0x44, 0x44, 0x38,// b 0x38, 0x44, 0x44, 0x44, 0x20,// c 0x38, 0x44, 0x44, 0x48, 0x7F,// d 0x38, 0x54, 0x54, 0x54, 0x18,// e 0x08, 0x7E, 0x09, 0x01, 0x02,// f 0x08, 0x14, 0x54, 0x54, 0x3C,// g 0x7F, 0x08, 0x04, 0x04, 0x78,// h 0x00, 0x44, 0x7D, 0x40, 0x00,// i 0x20, 0x40, 0x44, 0x3D, 0x00,// j 0x00, 0x7F, 0x10, 0x28, 0x44,// k 0x00, 0x41, 0x7F, 0x40, 0x00,// l 0x7C, 0x04, 0x18, 0x04, 0x78,// m 0x7C, 0x08, 0x04, 0x04, 0x78,// n 0x38, 0x44, 0x44, 0x44, 0x38,// o 0x7C, 0x14, 0x14, 0x14, 0x08,// p 0x08, 0x14, 0x14, 0x18, 0x7C,// q 0x7C, 0x08, 0x04, 0x04, 0x08,// r 0x48, 0x54, 0x54, 0x54, 0x20,// s 0x04, 0x3F, 0x44, 0x40, 0x20,// t 0x3C, 0x40, 0x40, 0x20, 0x7C,// u 0x1C, 0x20, 0x40, 0x20, 0x1C,// v 0x3C, 0x40, 0x30, 0x40, 0x3C,// w 0x44, 0x28, 0x10, 0x28, 0x44,// x 0x0C, 0x50, 0x50, 0x50, 0x3C,// y 0x44, 0x64, 0x54, 0x4C, 0x44,// z 0x00, 0x08, 0x36, 0x41, 0x00,// { 0x00, 0x00, 0x7F, 0x00, 0x00,// | 0x00, 0x41, 0x36, 0x08, 0x00,// } 0x08, 0x08, 0x2A, 0x1C, 0x08,// -> 0x08, 0x1C, 0x2A, 0x08, 0x08 // <- }; unsigned char PROGMEM banner[] = "Happy Holidays!!! Merry Christmas!!! May every man with \"Merry Christmas\" on his lips be boiled in his own blood pudding with a steak of holly driven through his heart! Christmas! BAH! HUMBUG!!!"; volatile int twi_group_offset=128; volatile int colpins[]={5,6,7,8,9,10,11,12}; volatile int rowpins[]={17,16,15,14,13,4,3,2}; volatile unsigned long int myTics; //counter for timer interrupt. volatile unsigned char pixels[]={ 0x3E, 0x51, 0x49, 0x45, 0x3E,0,0,node, 0x00, 0x42, 0x7F, 0x40, 0x00,0,0,1, 0x42, 0x61, 0x51, 0x49, 0x46,0,0,2, 0x21, 0x41, 0x45, 0x4B, 0x31,0,0,3, 0x00, 0x00, 0x00, 0x00, 0,0,0,0}; //pixel buffer rounded out to another 8 bytes volatile unsigned int cursorpos=0; volatile unsigned int charcursor=0; volatile unsigned int currentcol; volatile unsigned int charmod; volatile unsigned char currentchar; volatile unsigned int currentcharoffset; /*---------------------------------------------------------------------setup() * initialize timer2 * setup the i/o pins (initialized with everything off); * Initialize the i2c buss; * if master node then load initial characters into display. */ void setup() // run once, when the sketch starts { int i; currentcol=0; /*------------ setting up timer two. ----------------*/ TCCR2A = 0; // normal mode TCCR2B = 1<<CS22 | 1<<CS21 | 0<<CS20; // clock selection TIMSK2 |= 1<<TOIE2; // enable overflow interupt TCNT2=FREEK; // adjustment of period ASSR=0; // paranoid myTics=0; // counter for delays sei(); // enable interrupts for(i=0;i<8;i++){ pinMode(rowpins[i],OUTPUT); digitalWrite(rowpins[i],LOW); pinMode(colpins[i],OUTPUT); digitalWrite(colpins[i],HIGH); } if (node==0) { // master node Wire.begin(); Serial.begin(19200); loadCharacters(); updateSlaveNodes(); } else { Wire.begin(node+twi_group_offset); Wire.onReceive(updatePixels); // register event } } /*------------------------------------------------------------ISR(TIMER2_OVF_vect) * loads the next collumn into the led array * */ ISR(TIMER2_OVF_vect) { int r; digitalWrite(colpins[currentcol], HIGH); //turn off the last column. if (++currentcol > 7) { currentcol=0; } for(r=0;r<8;r++){ if ((pixels[currentcol]>>r)&0x01){ digitalWrite(rowpins[r], HIGH); } else { digitalWrite(rowpins[r], LOW); } } digitalWrite(colpins[currentcol], LOW); myTics++; TCNT2 = FREEK; }; /*--------------------------------------------------------------loadCharacters() * load the initial characters into the pixel buffer */ void loadCharacters(){ int ch,i,j; cursorpos=0; for (charcursor=0;charcursor<7;charcursor++) { ch=pgm_read_byte_near(banner+charcursor); j= (ch-32) & 0x000000ff; j = j*5; for (charmod=0;charmod<5;charmod++){ pixels[cursorpos++]=pgm_read_byte_near(Font5x7+j+charmod); } } } /*------------------------------------------------------------updateSlaveNodes() * send the pixel data to the slave nodes. */ void updateSlaveNodes() { int slavenode, row, pixel8; if (node==0) { pixel8=8; for (slavenode=1;slavenode<4;slavenode++){ Wire.beginTransmission(slavenode+twi_group_offset); for (row=0;row<8;row++) { Wire.send(pixels[pixel8++]); } Wire.endTransmission(); }; } } /*------------------------------------------------------------------udatePixels() * recieve the pixel data from the master node. * executes whenever data is received from master * this function is registered as an event, see setup() */ void updatePixels(int ignored) { int r=0; while(Wire.available()) // { pixels[r++] = Wire.receive(); // receive byte as a character } } /*-------------------------------------------------------------------------loop() * */ void loop() {int c; //int head=pixels[0]; updateSlaveNodes(); for (c=1;c<35;c++) { pixels[c-1]=pixels[c]; } if (charmod==5) {//get next char charcursor++; charmod=0; currentchar=pgm_read_byte_near(banner+charcursor); if (currentchar=='\0'){ currentchar=pgm_read_byte_near(banner); charcursor=0; } currentcharoffset = (currentchar-32) & 0x000000ff; currentcharoffset = currentcharoffset*5; } pixels[34]=pgm_read_byte_near(Font5x7+currentcharoffset+charmod); charmod++; while (myTics<90) ; myTics=0L; //wait a few hundred millisecconds }