Driving 8x8 LED Displays with an Arduino
After playing around a bit, I moved on to connecting the 8x8 displays. I spent a bit of time thinking about how best to do it, and had come to the conclusion that using a 595 shift register to drive the anodes of each display was the way forward. I'd ordered some ULN8023A darlington transistor arrays which can sink up to 500ma of current. This is less than I was planning to draw through the 24 LEDs that make up a row, so the plan was to connect the cathodes of all the LED matrices to this chip. Again, a 595 shift register controls the 8023, so it means I can directly address each row and column in the same way.
Once I'd got one 8x8 display working, it seemed like checking it all worked properly was a plan.
It did, so I moved the current limiting resistors over to the 'y-axis' board, and started building the other 2 display boards.
Each board joins directly onto the next, so the wiring isn't ridiculous on any of them, but there's still an awful lot of extremely fiddly connections to make, and it's used pretty much all of my 150 bits of wire up. I wouldn't plan on doing this again in a hurry...
Once I'd connected the other 2 displays, I changed the display code to push out 2 additional sets of bytes on the X-axis with some slightly different display patterns and we were in business:
Here you can see (from the top) one of the 595 shift registers, the 8023 darlington array and 8 current limiting resistors. These collectively make up the Y-axis board, which controls the cathodes of all the displays. Each of these is operated in turn very quickly, lighting up an entire row. These are scanned quickly enough that the image on the display seems to be complete, thanks to the persistence-of-vision effect.
I had considered driving this 595 off separate pins, but decided not to. This is the first one that is connected, so it keeps the last byte of 4 that is sent out. This has the advantage that the latch of all 4 shift registers is operated at the same time, ensuring there's no lag between changing the column and row data. This would probably not be noticeable, but it would annoy me knowing there was a slight lag :)
Putting all this work together, I still had to make the software driving the display useful, rather than just pushing out hard-coded bitmaps.
I wrote some code to turn a 2d boolean array into a series of bytes for direct output. There's an intermediate stage which updates the cached bytes from the boolean array for performance, so the continuous display scanning/multiplexing isn't slowed down by excessive data shuffling. A quick demo later, and we have something that actually shows off the displays as one single screen:
Finally, I need to run some of the LCDs at a different brightness level. I modified my code to maintain 2 arrays and 2 byte caches. One contains the 'bright' LEDs, and one the 'dim', and these are lit alternatively for different periods to create this effect. Again, the refresh rate needs to be fast enough that it's not obvious to the human eye, and that's where I started to run into problems. Switching between the 2 display layers for the 24x8 display, multiplexing the rows, and varying the duty cycle of different LEDs seemed to be getting too much. I couldn't do all that fast enough to keep the refresh rate sufficiently high - the dim LEDs were showing horrible signs of flickering.
I turns out the shiftOut and digitalWrite functions provided by the Arduino software are pretty slow, and this becomes a problem when you're pushing a lot of data. My clever byte caching wasn't actually making a difference since it seems the shiftOut function turns that back into individual bits for output, which I could have done myself without the intermediate layer.
Fortunately it seems I'm not the only person who's had this problem, and thanks to the extremely clever MartinFick on this forum post, I replaced the shiftOut and digitalWrite calls with shiftRaw and fastWrite. The difference in performance is staggering - I have much more control over the duty cycle again, and there's no sign of flicker.
I think it's fair to say this has been a successful weekend. I've got a reasonably sane bit of code for driving the display with both dim and bright LEDs 'simultaneously', and it's run off a data structure that should be dead easy to implement the game of life on top of. All I'm missing now is an RTC to keep time, and actually porting the code over...