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==' '){
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
}
