Wireless Atmega8 LED Display

After spending some time understanding how the Medea vodka LED display was wired, I really wanted to use it in some kind of project. I had some LINX wireless modules lying around, so I made a wireless scrolling LED marquee. I had never worked with a scrolling display before, so I had to start from scratch. This is how I made it…

The first thing I had to do was figure out how to control the LEDs. The way this LED display is driven (like most) is with shift registers. There are 4 shift registers, each with 8 outputs for up to 32 pins. Only 30 are used; they’re connected to the 25 columns and 5 rows of the LED matrix. Because an entire row or column must be illuminated at once, the traditional way to display arbitrary ‘stuff’ is:

  1. Turn on/off all columns for row i.
  2. Illuminate row i.
  3. Let row i = i+1 and repeat the procedure.

By doing this very quickly, thousands of times per second, the eye is fooled into thinking that all rows / columns are on/off at once. Before messing with any wireless hardware or PCBs, I connected an Attiny2313 in a breadboard to my display, and wrote some code to do just this. Here’s the gist of it:

void blit(void)
{
    uint8_t j;
    uint8_t one = 0b00000111;
    uint8_t two = 0b11111111;
    uint8_t thr = 0b11111111;
    uint8_t fou = 0b11111111;
    one |= (1<<(7-(blitrow)));
 
    for (j=0; j<25; j++)
    {
        if (pixels[j] & (1<<(blitrow+2)))
        {
            uint8_t col = j;
 
            if (col <= 2)
                one &= ~(1<<(2-col));
            else if (col <= 10)
                two &= ~(1<<(7-(col-3)));
            else if (col <= 18 )
                thr &= ~(1<<(7-(col-11)));
            else
                fou &= ~(1<<(7-(col-19)));
        }
    }
    shift_byte(fou);
    shift_byte(thr);
    shift_byte(two);
    shift_byte(one);
    latch();
    blitrow++;
    if (blitrow >= 5)
        blitrow = 0;
}

NOTE: There’s some BS in there like shifting by 2 and subtracting by the byte offset to get the right bit position…it’s ugly, ignore it!

It didn’t work the first time, but after figuring out the right offsets for each bit I was able to turn on and off any individual LED. I was still a ways from scrolling text though. In fact, before I could even display static text, I needed to get a font.

Luckily, on the Arduino Forum, someone had already created a 5×5 pixel font! Each letter in the font is an array of 8, but really 5 bytes, each byte made of 8, but really 5 relevant bits. So to display a letter you would write each successive byte to a successive column. I dropped this into my code as a PROGMEM array (so it gets stored in flash instead of SRAM) and had a sideways letter displayed in no time.

uint8_t a[8] PROGMEM = {0x00,0x7c,0x44,0x44,0x7c,0x44,0x00,0x00};    
uint8_t b[8] PROGMEM = {0x00,0x7c,0x44,0x78,0x44,0x7c,0x00,0x00};
// ...
uint8_t z[8] PROGMEM = {0x00,0x7c,0x08,0x10,0x20,0x7c,0x00,0x00};

It was at this point that I realized I wanted a new, better font with more symbols, just like the Medea vodka bottle comes with. I found a font here in .ttf format that was free for personal use, but I couldn’t figure out a simple way to convert a .ttf file into a numeric description of pixels. After wasting an hour looking for some kind of font converter, I stumbled across a great set of tools for designing symbols for LED displays. I modified it slightly to dump byte arrays in my font format, and then recreated each glyph in the font by hand.

The new font took up so much space that it didn’t fit on my Attiny2313, and I had to switch to an Atmega8. I also stopped at this point to make a PCB with a wireless module on it (while it’s possible to prototype wireless circuits on a breadboard, the breadboard’s crappy connections make it really tough to track down problems). The wireless part of the circuit is basically taken straight from the LINX RXM-433-LR datasheet. This is what it looks like:

I usually have to go through a couple revisions of PCB to work out the kinks, and this board was no exception. On the first iteration I left out the crystal, on the second I made the ISP pads too small and they lifted off, on the third I changed to a different regulator and on the fourth, I finally got it right! After connecting the display and porting the tiny2313 code, I was ready to make the characters on the display scroll.

There are probably more efficient ways to do this, but I made a buffer representing the state of all pixels which the interrupt routine uses to continuously update the LED states. The main loop of my program fetches one column of the current character, shifts the entire buffer one pixel left, and appends this last column. After it’s appended all the columns for a single character, it fetches the next character in the message.

Getting the wireless to work was the most finicky part of the project. I left two debug points on the circuit to connect a serial device directly for debugging, and I designed the communication protocol assuming I would talk to the device with serial over a wireless link. The wireless modules will basically pipe an input on the TX side to an output on the RX side, so I assumed I could just connect TX and RX to serial ports that I wanted to talk, but I soon found out that only about 1 in 20 messages would go through the link. The reason is that the serial data coming from an avr’s UART is not balanced — it doesn’t have the same number of on and off states per time. This can lead to the transmitter or receiver becoming biased and not decoding everything. The right way to deal with this is to use something like Manchester encoding, which has at least one transition per bit, and therefore no DC component. But this was more work than I was ready to do. A cheap way of doing Manchester encoding is to send two bytes instead of one, with each byte the complement of the other. This helps enough to make the transmission work >90% of the time, and is really simple to implement, so I used it. Here’s an example of sending a ‘manchester serial’ byte from Python.

def manchester_send_byte(serial_port, data):
    a = b = 0
    i = j = 7
    # Loop through the bits of the data
    while (i >= 4):
        # Set the bits of the encoded data where needed
		if (bitSet(data, i)):
			a |= (1<<j);
		else:
			a |= (1<<(j-1));
 
        # Set the bits of the encoded data where needed
		if (bitSet(data, i-4) != 0):
			b |= (1<<j);
		else:
			b |= (1<<(j-1))
		j-=2
		i-=1
    # Send the data out the UART
    send_byte(serial_port, 'U')
    send_byte(serial_port, chr(a))
    send_byte(serial_port, chr(b))

The last step was to get a box to hold the circuit and display. I found a 1.5″ deep 3″x5″ wood frame at my local art store that was perfect. To mount the display, I replaced the coin cell batteries with magnets, and then I also attached magnets to both inner edges of the frame. The magnets hold everything together securely and the LED display can still be rotated and removed to work on it.

This project took me about 3 weekends to finish, even though I had basic display functionality working after only a few hours. The little things take the most time. The final product still has a few bugs, for example the display will stop scrolling if left alone for 24 hours or so, and it will glitch while receiving data, but overall this project was a success and I learned some of the basic mechanics of a scrolling LED display! I hope you can pull something useful out of it. Check out the video below for the final product.

[qt:/blog/wp-content/uploads/2011/02/display_video.mov /blog/wp-content/uploads/2011/02/display_video.jpg 480 270]

UPDATE: Code here.

2 comments.

  1. [...] out how to control the LED matrix, he uploaded his own fonts and added a LINX wireless module to remotely send messages to the board. He mounted it in a wooden frame and now uses it as a simple marquee [...]

  2. [...] out how to control the LED matrix, he uploaded his own fonts and added a LINX wireless module to remotely send messages to the board. He mounted it in a wooden frame and now uses it as a simple marquee [...]

Post a comment.

You must be logged in to post a comment.