Animating WALL-E on a LED dot-matrix display with AVR

I was asked to an­i­mate a dot-ma­trix dis­play for the ro­bot­ics club re­cently. They wanted some­thing that said Robotics Club” to hang over their door. We had some old P10(1r) DMDs which I had worked on in the past to make a lit­tle score­board for a ro­bot foot­ball match. And, even though I am not very good at it, I re­ally love an­i­mat­ing things. So I de­cided to give it a shot. I ended up writ­ing an an­i­ma­tion soft­ware for the DMD in JavaScript which is un­for­tu­nately only as func­tional as a flip­book. But I had fun an­i­mat­ing lit­tle per­son car­ry­ing a bal­loon, ugly gear trains and a lit­tle pixel Wall-E blink­ing.

Yeah, that’s a lit­tle Wall-E look­ing at you. After think­ing long and hard about it, I de­cided that Wall-E is the best ro­bot to look over us all while we do our ro­bot­ics stuff. Wall-E was also, thanks to his an­gu­lar de­sign and dis­tinctly rec­og­niz­able shape, much bet­ter suited to a low res­o­lu­tion dis­play than some other things I wanted to do.

Another thing I an­i­mated was a lit­tle man who car­ries a bal­loon and strolls into the DMD where Robotics Club” is writ­ten and just stands there idly for a while, then goes away. I thought it was a cool thing to do, but no one else agreed. So I re­luc­tantly scrapped that idea. I also wanted to an­i­mate some other things, like a gear train dri­ven by a ham­ster, and GIR from Invader Zim (remember that show?). But of course, a mono­chrome 32x32 ma­trix does­n’t give you much space to play with. I was also lim­ited by time. I’m work­ing al­most full time on our mi­nor pro­ject, and I also have to at­tend my classes. For the next ver­sion of this screen, I’m planning a much cooler an­i­ma­tion, that uses the whole 32x32 screen and the whole 32KB of the AVR pro­gram space, and maybe also some form of com­pres­sion. In this post, I doc­u­ment all the ma­jor por­tions of the pro­ject, while also try­ing to be help­ful to peo­ple de­sign­ing DMDs.

Electronics

I made all the elec­tron­ics for the pro­ject last se­mes­ter with Bhuwan. We made the cir­cuit board by hand, do­ing every­thing from hack-saw­ing the PCBs, etch­ing the cop­per traces with a fu­mi­gat­ing so­lu­tion of FeCl3, drilling the boards and sol­der­ing the com­po­nents. That process, while man­ual, was very en­joy­able. The re­sult­ing cir­cuit looks nice and is so ro­bust that from the time it was built, there has never been any hard­ware bug in the sys­tem. I mean, sure, be­ing a MCU based dig­i­tal cir­cuit does­n’t leave much room for bugs. But still, con­sid­er­ing the many many many hard­ware bugs we en­coun­tered while do­ing the ro­bot MIMO (we ended up mak­ing 6 it­er­a­tions for the cir­cuit board), this one was a walk in the park.

The board is straight­for­ward. It’s an Atmega32A with it’s sup­port cir­cuitry (os­cil­la­tors, de­cou­pling ca­pac­i­tors and such) and a bunch of head­ers: one for con­nect­ing the In-System Programmer, one for con­nect­ing to a con­trol re­mote, and one for the Dot-Matrix dis­play.

The dis­play it­self (P10(1r)-V70) is a ma­trix of LEDs all con­nected to a bunch of 74HC595 shift reg­is­ters in se­ries. The DMD takes the fol­low­ing in­puts:

  1. Output Enable (OE)
  2. Row Selectors (A and B)
  3. A data line (D)
  4. A clock line (CLK)
  5. A Latch line (SCLK)
  6. A ground

The data line D in­puts the data se­ri­ally, clocked by the CLK line. On the ris­ing edge of the SCLK pin, the data shifted in is dis­played on the LEDs. So far, it is ex­actly as if we were us­ing the 74HC595 di­rectly. But the row se­lec­tors com­pli­cate things slightly. The peo­ple who de­signed this board have done some­thing re­ally cool. Perhaps to re­duce part count, or to keep the board lay­out sim­ple, or maybe be­cause of fanout is­sues, they have con­nected 4 con­sec­u­tive LEDs to the same set of shift reg­is­ters. Which rows of the ma­trix you are ad­dress­ing de­pends on the in­puts to the se­lec­tor pins. That is, for A = LOW, B = LOW, the first of the four rows is se­lected; for A = LOW, B = HIGH, the sec­ond; and so on. I will talk more about this fur­ther be­low.

The Animation Software

For var­i­ous rea­sons, I had to write the an­i­ma­tion soft­ware my­self. The last time, I had writ­ten an an­i­ma­tion pro­gram on Python us­ing Tkinter. Actually, ‘written’ is a bad verb to de­scribe that process. I had jer­ry­built the whole sys­tem in an hour. And over the course of the next week, I had hap­haz­ardly stuck var­i­ous ap­pendages and func­tions which made the code very dif­fi­cult to nav­i­gate. On top of that, the pro­gram it­self was very ugly.

This time, I de­cided to go with a HTML can­vas based ap­proach be­cause I did­n’t want to down­load a GUI de­signer for python, and I sure as heck was­n’t go­ing to sit around and waste my pre­cious time learn­ing Tkinter’s packs and grids and what not. So any­way, I sur­prised my­self by jer­ry­build­ing the en­tire sys­tem in a few hours, com­plete with an­i­ma­tion frames, onion skin­ning and the undo fea­ture. The can­vas API was so sim­ple, and JavaScript so for­giv­ing, that I did­n’t even run into any time con­sum­ing bug. And the most in­ter­est­ing part? The com­plete HTML file (with JS and CSS in the same doc­u­ment) is 500 lines of code! It is still bad code, and is very in­ef­fi­cient in many re­spects. But be­cause it is a means to an end, I allowed my­self to take short­cuts. The re­sult is the screen­shot be­low.

It was so fun to make an­i­ma­tions in my own an­i­ma­tion pro­gram. The only se­ri­ously de­sir­able fea­ture that this pro­gram lacks is the se­lect and the move fea­ture. The old python ver­sion ac­tu­ally had that fea­ture. But I was al­ready pressed for time so I did­n’t im­ple­ment it.

The soft­ware can be ac­cessed at ni­rav.com.np/​An­i­ma­tor-ina­tor. Feel free to check it out.

The Firmware

I used the same firmware I had writ­ten a se­mes­ter ago. The code it­self is straight­for­ward. But I re­mem­ber it took me a long time to write it, be­cause I didn’t have a lot of doc­u­men­ta­tion to con­sult. So I had to sit there, with the DMD board and it’s var­i­ous pins and wires, and I had to prod this and poke that to fig­ure out how it worked, rather like a puz­zle. Thankfully I had worked with the shift reg­is­ters be­fore, so it took a lot less work than it oth­er­wise would have. This piece of code be­low is pretty much the only part of the code that counts.

// Slightly altered from original
// for readablity
void clock_selected_lines(char selector) {
  for (int i = 0; i < (WIDTH / 8); i++)
    for (int j = 3; j >= 0; j--)
      clockbyte(display[j * 4 + selector][i]);
  setselector(selector);
  sendpulse(LATCH);
}

int main() {
  init();
  OCR0 = 0xff;
  TIMSK = 2;
  TCCR0 = 0X0b;
  sei();

  int frame = 0;
  while (1) {
    cli();
    disableoutput();
    assign_to_display(frame++);
    enableoutput();
    sei();

    if (frame >= framecount) frame = 0;
    _delay_ms(10);
  }
}

The clock_selected_lines() func­tion takes a value from 0 to 3, and based on that value, ex­tracts the rel­e­vant bits from the display bit ma­trix to send into the DMD. Keep in mind that, at one time, only 4 rows (every fourth row) of the 16 to­tal rows in the DMD can be ac­ti­vated. This is be­cause every group of 4 LEDs, row-wise, is con­nected to the same pin in the same shift reg­is­ter. Which of the 4 con­nected LED lights up is con­trolled by the 2 se­lec­tor pins A and B. To show a com­plete pic­ture, we have to cy­cle be­tween the four SEL pins fast enough that it is be­low the per­sis­tence of vi­sion (10 ms). And each time we switch rows, we also have to clock in the data in those rows. I know it sounds com­pli­cated, but if you think about it, this lit­tle de­sign in­ge­nu­ity re­duced the part count in the board by a fac­tor of four and min­i­mized the sig­nal prop­a­ga­tion de­lay in­her­ent in such shift reg­is­tor based sys­tem.

That process is called time-mul­ti­plex­ing, and is used in many kinds of dis­plays throught elec­tron­ics. The slow mo guys have an amaz­ing video on how dis­plays work. Definitely watch the video, even if you don’t see your­self work­ing with dis­plays any time soon. It gives you a new per­spec­tive on time.

The main() func­tion sim­ply sets up a timer which calls the clock_selected_lines() func­tion every cer­tain mi­crosec­onds and in­cre­ments the selector vari­able by one and makes sure it warps around at 4. Then it en­ters a while loop which swaps the dis­play frame every few mil­lisec­onds. Keep in mind that the 10ms value is not the real frame change time. The value in­creases some­what due to be­ing fre­quently in­ter­rupted. I should have set up an­other timer for chang­ing frames but I don’t think it was nec­es­sary for such a sim­ple pro­gram. The sei() and cli() func­tions turn in­ter­rupts on and off. We turn in­ter­rupts off be­fore en­ter­ing sen­si­tive por­tion of the code. For ex­am­ple, we don’t want to be in­ter­rupted when we copy the next frame from the PROGMEM (flash) into RAM be­cause we don’t want to dis­play half copied frames to the viewer.

Final thoughts

I en­joyed mak­ing this. This DMD gave me a much needed break from the ex­tremely for­mal and dry world of com­piler lit­er­a­ture which I have been por­ing over for weeks in or­der to make the साक्षर com­piler.

But there is one de­ci­sion that I re­gret im­mensely. In hind­sight, it would have been much bet­ter if I had writ­ten a pro­gram to con­vert any GIF file into a data for­mat suit­able for the DMD. It would have been much less glam­orous to work on, sure, but it would have given me the free li­cense to work with any pixel based an­i­ma­tion soft­ware that al­ready ex­ists, and al­ready sup­ports se­lect, move, multi-level undo and all that fancy stuff.

In the next ver­sion, I also think it would be awe­some if I could im­ple­ment some form of com­pres­sion in the an­i­ma­tion data. I’m in­ter­ested in per­form­ing some kind of Run-Length Encoding on the delta frames. What I mean is, un­less you are an­i­mat­ing a mov­ing checker­board, very lit­tle ac­tu­ally changes be­tween a frame and the next. So, if we cal­cu­late the delta ma­trix be­tween every frame and it’s suc­ces­sor, we should get a se­quence of sparse ma­tri­ces which we can en­code with RLE or some other com­pres­sion al­go­rithm to achieve good com­pres­sion on the frames while also be­ing very sim­ple to de­com­press se­quen­tially. The 2KB RAM on the AVR sys­tem should be more than enough.

The whole pro­ject is avail­able on my GitHub here