Halloween Eyes is the first project using my LMDriver platform (which I've been working on, on and off, for about 4 years (real life can be very inconvenient)). The concept is simple; a pair of TERRIFYING disembodied eyes rendered on a 4 5x7 dot matrix displays which look about and blink every so often. Even though I was going for "terrifying", the most common reaction was "cute". Yeah, cute like a GHOST.
The whole thing is based around 2 complete LMDriver modules, and a third microcontroller which steps them through their sequence.
Illustration 1: Here you can see the 4 LED matrices and a partially populated board with the programming interface.
Illustration 2: The displays on either end are being controlled by the central micro which is simply sending out LMDriver commands via a bit-banged SPI bus
Each display module is driven by an Atmel ATMega169 microcontroller and nothing else. The uC accepts an extensive command set and handles the multiplexing of the displays. The displays are heavily multiplexed; each LED matrix has a maximum of 2 LEDs on simultaneously. The LED displays are dual color but I'm not using green for this project; the next one will.
Illustration 3: The programming is done via a tinyisp module from adafruit.org. Here you can see it attached to the eyes module, along with the module development jig next to it.
As an example of how easy the LMDriver is to use, here's the code that steps through the sequence
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>
unsigned char strLookStraightAhead[] PROGMEM = { "g001c91c12282224174134145d55d641c0c1a-A-" };
unsigned char C0C1[] PROGMEM = { "C0C1"};
unsigned char c0c1[] PROGMEM = { "c0c1"};
unsigned char strSlowBlink[] PROGMEM = {
"00890811481422272232262243e53e"
"10880821471431461441c51c"
"208308408508608708"
"10880821471431461441c51c"
"11481422272232262243e53e"
"01c91c12282224174134145d55d641"
};
unsigned char strGlanceRight1[] PROGMEM = { "35d541" };
unsigned char strGlanceRight2[] PROGMEM = { "25d441" };
unsigned char strGlanceRight3[] PROGMEM = { "45d241" };
unsigned char strGlanceRight4[] PROGMEM = { "55d341" };
unsigned char strGlanceLeft1[] PROGMEM = { "65d441" };
unsigned char strGlanceLeft2[] PROGMEM = { "75d541" };
unsigned char strGlanceLeft3[] PROGMEM = { "55d741" };
unsigned char strGlanceLeft4[] PROGMEM = { "45d641" };
// PORTC
#define BOTH_RESET _BV(6)
#define BOTH_SCK _BV(5)
#define BOTH_MISO _BV(4)
#define BOTH_MOSI _BV(3)
#define RIGHT_SS _BV(2)
#define LEFT_SS _BV(1)
// msb first, sck low on idle, sample on leading edge
void spiOut(unsigned char data)
{
while (SPSR & _BV(SPIF));
unsigned char toss = SPDR;
// set the SS lines
PORTC &= ~LEFT_SS;
PORTC &= ~RIGHT_SS;
_delay_us(5);
unsigned char ii;
for (ii = 0; ii < 8; ii++)
{
if (data & 0x80) { PORTC |= BOTH_MOSI; }
else { PORTC &= ~BOTH_MOSI; }
_delay_us(5);
PORTC |= BOTH_SCK;
_delay_us(5);
PORTC &= ~BOTH_SCK;
data <<= 1;
}
_delay_us(5);
// reset the SS lines
PORTC |= LEFT_SS;
PORTC |= RIGHT_SS;
}
void stringOut(unsigned char * stringPtr)
{
unsigned char data = pgm_read_byte_near(stringPtr++);
while (data)
{
spiOut (data);
data = pgm_read_byte_near(stringPtr++);
_delay_ms(1);
}
}
void fastBlink()
{
stringOut(C0C1);
_delay_ms(50);
stringOut(c0c1);
}
void slowBlink(void)
{
stringOut(strSlowBlink);
}
void glanceRight(void)
{
stringOut(strGlanceRight1);
_delay_ms(100);
stringOut(strGlanceRight2);
_delay_ms(3000);
stringOut(strGlanceRight3);
_delay_ms(200);
stringOut(strGlanceRight4);
_delay_ms(200);
}
void glanceLeft(void)
{
stringOut(strGlanceLeft1);
_delay_ms(100);
stringOut(strGlanceLeft2);
_delay_ms(3000);
stringOut(strGlanceLeft3);
_delay_ms(200);
stringOut(strGlanceLeft4);
_delay_ms(200);
}
int main(void)
{
// initialize
DDRC = 0x6e; // 0110 1110
PORTC = 0x46; // 0100 0110 reset, SS idle
// reset the displays
PORTC &= ~BOTH_RESET;
_delay_ms(30);
PORTC |= BOTH_RESET;
_delay_ms(100);
stringOut(strLookStraightAhead);
while (1)
{
_delay_ms(5000);
glanceLeft();
_delay_ms(5000);
glanceRight();
_delay_ms(5000);
slowBlink();
}
return 0;
}
Ok, so the control strings are complete gibberish, but they'll make more sense once I publish the spec; suffice to say for the moment that the code is manually programming each column in turn, so “055” means column 0 is value 0x55, where the LSB is the topmost pixel.
My email name is matt, and you can email me at this site, daughtrey.com. Each board is lovingly hand-assembled, programmed and tested by an artisan (me). Each board (with 2 LED matrices) is US$40 + postage.