Introduction: Watch Futurama on an 8x8 Pixel Screen

tired of hi-def? (booo!!!)

here's how to convert otherwise reasonable quality video into pixelated garbage and play it on a 2 color 8x8 led matrix, with no sound and only moderate sync.

ingredients:
- (1) 8x8 2 color led matrix
- (1) atmel avr atmega168
- (2) 74hc595 shift register
- (1) 3.3V regulator
- (1) a linux system

this is a mid level avr project, in that it assumes ( does not explain ) how to get a program onto a chip. it's pretty easy once you've done it though, so don't worry. to see how to actually load up a program, The Real Elliot has a nice introduction.

onto the show!

Step 1: Have Linux, Avr-gcc, Python, Mplayer...


you'll need linux to use this method, because i used common linuxy things in it. these things are, in no particular order:

1. avr-gcc: needed to make c code into avr code --> wiki stuff about it

2. python: a surprisingly nice programming language --> official site

3. python image library: used here to turn nice video into tiny specs of light without nearly as much hassle as that sounds like. --> pil

4. mplayer: used to turn video into stills --> mplayer

5. mencoder: (optional) change the frame rate of video --> same place as mplayer

i think that is all of the dependancies.

Step 2: Circuit Overview

shift registers:

we use 2 of them, one for green and one for red.

the 74hc595 shift register is a simple latched device that converts serial 1's and 0's to parallel 1's and 0's. the 'don't clear' pin is held high while the serial data is clocked in, then the latch pin is set high triggering the output of the parallel data. dropping the 'don't clear' pin empties the output register and gets ready for fresh data.

all that means is the chip acts as a friendly robot that patiently waits until you have said 8 things while touching it on the shoulder. and then when you punch it in the stomach the robot says them all at once out of its 8 mouths. just slap it upside the head and it forgets, ready to go again.

Step 3: Circuit Overview: Led Matrix

this particular led matrix has a green and a red led in each of the 64 positions. if you light them both at once you get a sort of yellowyorange color.

the only thing special about this matrix is that it is the one i had when i did this. a HUGE improvement in the final result would be to use an RGB led matrix and an additional shift register, but i don't have 64 RGB leds lying around.


moving on:


say the green shift register outputs some fancy pattern like 10110011, that would light up the matrix like this:

g x g g x x g g
g x g g x x g g
g x g g x x g g
g x g g x x g g
g x g g x x g g
g x g g x x g g
g x g g x x g g
g x g g x x g g

(x means off)

as long as all of the ground pins were held low. same deal with the red register.

so to achieve animation, just hold all of the ground pins high except for the one that is on the line you want to draw to.

x x x x x x x x
x x x x x x x x
g x g g x x g g
x x x x x x x x
etc.






Step 4: Atmega168 == Brains

the atmega168 has a bunch of storage space and 28 pins, which is more than we need. by default it runs at 1MHz on an internal RC oscillator, which is not very stable. but that's ok.

PORTD:
to control the ground pins on the matrix, they are wired to PORTD of the avr. in code this will use the &= ~ (see step 6) type of method to keep all pins HI except the one for the row we want to display.

PORTB:
the latch will be on pin 0. the shift register clock is pin 3, green serial is pin2, red serial is pin4, and the no_clear is on pin1.

Step 5: Put It Together

oh my, there are some wires involved.

Step 6: Code for Avr

the avr's job is pretty easy. the overall procedure is as follows:

1. read a byte from memory and write it to the red shift register
2. read a byte from memory and write it to the green shift register
3. set the appropriate pin low to ground the row we want to display
4. throw the latch to trigger the shift register outputs
5. wait.
6. clear the shift registers and repeat.

easy.

this is all done in code using a lot of bit shifts and bit masks. a quick refresher:

the << operator means shift the thing before, over by the amount after.
in an 8bit world,
(1<<3) means: 0000 1000
(1<<0) means: 0000 0001
(1<<7) means: 1000 0000

a |= means to apply an OR to the thing before and after, saving it in the before's spot. this is how you make sure that a bit is set high, or that a corresponding pin is on.
so if
a = 0100, and b = 0001,
then
a |= b changes a to 0101 and leaves b the same. b is usually called a mask

a tilde ~ in front of a value negates it:
a = 0110 1111
a = ~a changes a to 1001 0000

finally &= is just like |= except it's an AND instead of an OR:
a = 0110
b = 0100 and ~b = 1011
so
a &= ~b changes a to 0010. this is how you make sure that a pin is off.

Step 7: Draw a Frame

the data is stored in arrays of unsigned chars, which are 8 bit numbers. PROGMEM forces the data to be stored in program memory, rather than allowing the avr to copy all of the data to ram. the purpose here is to reduce the number of steps involved and to maximize the amount of info that can be stored and quickly retrieved.

unsigned char green_vals[][8] PROGMEM = { { 0x01, 0xff, 0x80, 0xff, etc...unsigned char red_vals[][8] PROGMEM = { { 0x01, 0x02, 0x03, 0x04, etc...

remember that the matrix is only 8 leds across, meaning that each entry in those arrays corresponds to a complete description of an entire row. so looping through 8 of them in succession is a frame...

for (i=0; i<8; i++){  set_frame( red_vals[ 0 ][ i ], green_vals[ 0 ][ i ], (1<<i) );  show_frame( TIME_TO_SHOW_LINE );}

except that won't work, because of PROGMEM up there... just call pgm_read_byte() on red_vals and green_vals to fix it.

set_frame( pgm_read_byte( &red_vals[ 0 ][ i ] ), pgm_read_byte( &green_vals[ 0 ][ i ] ), (1<<i) );

the set_frame() function is pretty simple:
/* send frame line data to shift registers and set appropriate grounder pin */void set_frame ( unsigned char red, unsigned char green, unsigned char gnd ){	/* just a loop variable */	unsigned char i = 0;		/* set up red, loop through all 8 bits */	for (i=0; i<8; i++)	{		if ( red & (1<<i) ) PORTB |= red_ser;	// if it's a one then serial pin hi		else PORTB &= ~red_ser;			// otherwise make sure it's low		if ( green & (1<<i) )  PORTB |= green_ser;   // if it's a one then pin hi		else  PORTB &= ~green_ser; 	// otherwise make sure it's low		PORTB |= ser_sck;	// clock up		PORTB &= ~ser_sck;	// clock down         }        PORTB &= ~red_ser;		// be sure to leave red's ser line low	PORTB &= ~green_ser;		// leave green serial pin low	/* set the grounder pin for this frame line to low */	PORTD = ~gnd;		// upsidedown logic... }

Step 8: Great, But Where's the Data?

the led_matrix_formatted.py script is very simple thanks to the python imaging library.

since we want to change the palette of images the first thing to do is generate a palette array, into which we place the palette that will replace the palette of the soon to be paletted image. palette palette palette, palette palette image.
using some loops that array gets filled up with black, red, yellow, and green.

then we cycle through the command line args, which hopefully contain a bunch of images. each image is resized, converted to 'indexed' (palette) mode, and given the new palette that was generated above.

(btw, you can mess with that palette a great deal to achieve a variety of final looks)

then using more of the bit masking method seen in step 6, two long strings are formed and written to a file for copy/pasting.

Step 9: Python Makes It Easy...

calculating and entering in a bunch of binary data is for people who are living in the sixties. this is the oughts, so we let the computer do it for us!

first dig up a video file that you want to use ( i used Futurama s05e10 the Farnsworth Paradox )

second generate a bunch of jpegs. to do so type this is an empty directory:
mplayer -ss 00:17:20 -vo jpeg:quality=50 ~/tv/futurama.mpg
the '-ss 00:17:20' seeks to 17 minutes 20 seconds in the file (funny part) and the '-vo jpeg:quality=50' tells mplayer that you want to output mid quality jpegs instead of say, video on your screen. i never tested that quality setting, but given what comes next i don't think it matters.

third run this handy python script: `python ./led_matrix_formatted_data.py ./*.jpg` to create the "RGout.t" file, which is full of data to paste into the c code file.

fourth copy and paste the contents of RGout.t into led_matrix_template.c, and save it as matrix.c.

fifth compile and load the program onto your avr with a programmer. be sure to check the makefile to set the type of programmer you have! (btw i didn't write that makefile, i found it on google and modified it.)

sixth watch pixelly cartoons!

Step 10: Post Scripts

for people who build this i have included a couple of RGout.t 's for their viewing pleasure:

the intro to Futurama, the intro to Harvey Birdaman (including car chase!), and of course 00:17:20 from Farnsworth's Paradox.

as they are 64 pixel descriptions of tv, i assume that they fall under fair use at this point. the shows certainly aren't recoverable in any way from the data provided.


also, for people who don't build the device but would like to watch funny pixelly cartoons (or the tv news, whatever) anyhow, the python script contains commented out lines to make small bitmaps of the converted images.

typing `convert -delay 5 ./*.bmp Animated.gif` in the directory after running the uncommented script will produce an animated gif of the images which can then be played however you like, but i recommend `mplayer -fs Animated.gif` to get a fullscreen experience. ( you'll notice that the commented lines expand the images up to 256x256 pixels. this is not nearly as cool as watching the 8x8 versions blown up to fullscreen. check it out)