Navigation

5x7 LED Matrix Display driven by a PIC16F88 (Updated 28/10/12 with video)

I came across an article on Hackaday recently for a rather inspirational little build from TigerUp. A small necklace charm which is a mini 5x7 LED display pre-programmed with a large collection of rather adorable 5x7 pixel art, and animations. This immediately sparked my interest, especially seeing as he'd coded in some very cool Space Invader characters, amongst others. I am a massive retro computing/gaming fan!

What's more, I thought it might make a perfect birthday present for my niece, who is turning 18 (I think) in about a month at time of writing - especially coded with a lot of girly oriented imagery, flowers, hearts, that sort of thing </sexist>.

His is wired into a simple Atmel AtTiny 4313 and is literally a whole lot of direct connections from the uC to the LED matrix and little else, in fact I'm not even sure what he needs the resistor for either, could be left out.

I thought it would be cool to include some animations in my version, as well as 'still frames', as TigerUp did, but also include a graphics scroller, something which could be used to scroll imagery from right to left, graphics wider than the width of the display. This functionality would allow text to scroll along the display too, because I wanted to make it show her name. I toyed with the idea of having it say 'Happy Birthday' but that wouldn't be very accurate after her birthday, and I'm not building in a full date time clock!

I'm not an Atmel AVR person, I generally use PIC uC's. Don't ask me why, I'm still not really sure, as the AtTiny 4313 can be powered from as little as only 1.8v, and I'm not sure if this is a specifically low powered version of AVR gear. A normal PIC chip, non-low voltage version dies (locks up) below 4v, not great for battery powered apps. What's more, Atmel AVRs can be programmed from simple serial connections without the need to shell out for a relatively expensive programmer.

Part of the appeal of TigerUps work was the fact that it is a pendant, a necklace charm, which I'm sure my niece would appreciate, but the difficulties involved in powering a full whack PIC chip from a battery might make it a little unwieldy, and short lived.

So regardless, I set to work. The uC I planned on using for this job is the rather awesome PIC16F88. A massive improvement over the unfortunately ubiquitous 16F84a allowing you to use (nearly?) all 16 pins as GPIOs and a ton of other peripherals. And I have the option of purchasing the PIC 16LF88 low powered version, which apparently works down to 2v, still a smidgeon more demanding than the AVR but from my Google travels I am not entirely sure whether my programmer will work with the 16LF88 - another difficulty with PICs.

But Farnell and RS which stock the low powered chip have difficult purchasing limitations, either a £20 minimum, or a preposterous £5 delivery charge (for chips weighing a few grams a piece!) so I'm not so sure about doing that.

I was amazed at quite how it could be done on a single uC, especially given the relevant power sourcing and draining, being ultimately limited to around 25mA per pin, and only around 100mA per port (collection of 8 pins) at least on the 16F88, no so sure about the AtTiny 4313 - I haven't looked at it's datasheet.

Here's the idea. The LED matrices in both mine and TigerUp's designs are column/row oriented, so you power up a column, then 'drain' each LED to make it light, and impede it to keep it dark. Switch source current to another column and do the same with a slightly different light pattern. Do this quickly for each row, and you have a collection of 35 perceived individually addressable LEDs. I'm using a common Anode arranged set, so I source a column (x5) with power from PORTA, and drain rows (x7) with PORTB. Every relevant pin is configured as an output, and is sourced and drained appropriately in that mode. This means you put logic state 1 into the PORTA pin for each column. And put a logic state 0 into the PORTB pin (to light that LED, enabling the current drain) for each row.

Much like the display on your TV and computer monitor, this is a 'strobing' operation, because you strobe the illumination across the display, done quickly enough, the persistence of vision that humans perceive means you don't even see a flicker, but complete imagery.

The aforementioned power issues kick in because each LED in the matrix is quite happy to accept a full 25mA for decent illumination, and that's just not possible when performing the lighting of every LED in a column from a PIC16F88. In fact the chip can't source 7 x 25mA LEDs from PORTA, furthermore it can't drain, or sink 7 x 25mA on portB (max 100mA, so four LEDs). So what does happen? Well after designing the test circuit, it's clear that a reasonable compromise is reached by the chip - the LEDs simply dim the more that are lit. It can be quite an apparent change of brightness, but for this simple application, I don't think it matters too much.

It seems TigerUp has disguised this issue for his article - he doesn't mention it at all, and the pictures he posted show a reasonably well lit set of displays, I also noticed that he carefully chose a set of imagery that doesn't dim the display too much in one spot, nice trick! It's a shame he doesn't discuss the issues and I perhaps feel he was more interested in getting the article put out to as many people as possible, actively avoiding any negativities in his design which might turn people away. I on the other hand feel that if you're going to convey a design, tell people about it's full capabilities and compromises - better to teach, than to withold.

Fig 1. Full circuit. This has a button wired to PORTB.7 but was since removed as I want it to be really simple.

I put the test circuit together on a normal breadboard. Wired up the first column and a few rows only, initially as a test to see what would happen. Lo and behold, setting the PORTA source pin to 1, and setting the relevant PORTB drain pin to 0 lights that LED! That's Proof of Concept (PoC) completed!

From here I had a basis to continue, and expanded it to fill a whole column, eased in because I was concerned about the power drains. It was from here that I noticed the dimming effect, and had a mini-struggle with my more professional inner self to settle on this method - basically my inner self told me that I should be using a whole collection of transistors and resistors to allow it to power the LEDs uniformly and fully. But then that would compromise the very tight small low component count nature of the whole design.

I even considered using a self contained LED driver chip which I had in stock, but its too big, and again requires a few external components, what's more it didn't fit into the whole column/row thing too well.

So after success with that I moved on to wiring it up completely. I feel it's important to undergo those earlier tests because the deeper you go into the design from the get go, without proving that parts of it actually work, then you're increasing the amount of things that could go wrong later on and making your debug process a whole lot more difficult - you may even need to backtrack.

I wrote a small program which lit a simple test pattern, a pattern complicated enough to tell me it was addressing correctly. I had no idea about the sort of 'refresh rate' I should be dealing with for each column, especially seeing as I'm not working on the Assembly Langage level, I'm using PicBasic Pro. With this in mind I made a byte variable for the column refresh rate PAUSE command and set the PAUSE operation to this variable, I also had it changeable from a button press, basically it started at 10ms and with each button press it would reduce by one. This allowed me to slowly reduce the millisecond pause between column refreshes and find a pause value which eliminated flicker.

The results surprised me, as flicker (to my eyes) only vanished at a 2ms column refresh rate. That's a full display update of 10ms or thereabouts, well if I'm doing the maths correctly, that's 100hz. A surprising value given I'm writing this article on a 60hz display and can't see a flicker.

Regardless, it worked, I saw the test pattern and it looked really steady.

From then on I gave the program a 'display buffer', an array of 5 byte values. The display update routine was nothing more than an unrolled loop of 5 column updates from this array, the 5 values you put into the array match the required PORTB value needed to light your arrays - Seeing as we're using 7 rows, a byte is a natural storage size for each column. Each row LED is lit sequentially from PORTB, so simply put -

For columns

PORTA.0 controls (sources) column 1

PORTA.1 controls (sources) column 2

etc 5 times total.

AND

PORTB.0 controls (sinks) row 1

PORTB.1 controls (sinks) row 2

etc 7 times total.

a zero means it's lit, a 1 means it's unlit.

All this saves the display driver routine from having to perform any extra work to decode the 'signal' to keep the display update succinct and fast.

Doing it this way allowed me to perform straight forward array fills to replace the current still image, but also to perform simple wipe left and right transitions by slowly sliding in each new column data one by one, in both directions, so I alternate between a left slide and a right slide for still images.

I don't use interrupts, I feel that the PICBasic Pro interrupts are too primitive and slow, so instead it's all still possible just by keeping the display loop running at all times, and performing a number of secondary and tertiary tracking and changing routines, all on count timers in one thread - a timer and check for the still image display time, a timer and check for the wipe left/right transition time, and a value for the current transition type.

Fig 2. Skull still image. Notice how certain columns are darker because more LEDs are lit in it. Not as noticeable with the naked eye though.

Fig 4. The obligatory heart shape! Another nice image - goes to show how careful design can offset some of the circuit design shortcomings.

Further expanding upon this, I wrote simple animation support, and coded a number of animations including a moving cartwheel and a magic tunnel, kind of like TigerUp's example video demonstrates. Again, this works very similarly to the normal transitions, by keeping a normal display driver routine intermixed with secondary and tertiary tracking, with counters and value checks. The still image loop is considered the 'default' outer routine, and this animation routine a subroutine, GOSUBed to. As the animation ends, it RETURNs from the GOSUB.

Lastly I wrote the graphics (and text) scroller. This was simpler than the animation code, and just required the normal shift left display routine, and an arbitrary set of columns (above 5 if need be) to be shifted in one by one to the right edge. Again, as the routine finishes, it RETURNs from the GOSUB.

In future, to support more imagery, I'll expand this to allow the arbitrary selection of different animations and a graphics scroller visuals prior to GOSUBing in.

I was becoming tired of hard coding a set of graphics, especially for animations, and so I devised and wrote a small program in C# to allow me to graphically draw the patterns, I also coded in basic animation support to improve the preview of what it would eventually look like on the chip -

Fig 5. Software interface for designing imagery. Take the five numbers and pop them into your PIC source code.

As you can see, you have a drawable grid on the left, which you click in to toggle that LED, on the right are a number of utilities and animation support. The key outcome are the numbers along the bottom. These are the actual encoded values for PORTB for each of the five columns. Simply lift them from here and put them straight into the PICBasic code for each still pic or animation frame, obeying the requirements of that block of data (you also need to specify animation repeat count in the first byte, then for each frame, put a byte at the start for the display time value for that frame - you might want that frame to skip by really quickly, or to stay for a while).

Below is an example of the animation support data block -

datset:'GOSUB HERE - DATASET FOR ANIMATION, pass in a valid animadr, it passes out tmp1.

I noticed display anomalies when trying to load the data from EEPROM on the fly for animation, so instead I decided to use the copious program flash memory inside the chip, LOOKUP allows this - EEPROM is only 256 bytes in size.

Set animadr to the data offset address, then call this subroutine. Upon outcome, TMP1 will contain the result, so I treat this like a kind of memory READ operation. Looking at the data above, the 50 at the start indicates the amount of times this animation will run through (repeat count). The 10 following it shows the amount of times the display will refresh before moving to the next frame of animation (kind of a frame rate), then the next 5 bytes indicates the display data, exactly as represented in the software screenshot above. So each frame is 6 bytes, repeated as required. I put lots of zeros on the end just in case the routine hiccups and reads too much info and overflows. The zero indicates the routine should recycle back to the beginning, or exit out if the animation has played thru 50 times, or whatever specified.

Here are some frames of a basic spinning animation -

There's about five or six frames to this animation, looks a darned sight better running the animation than these might indicate!

The scroller works in a similar fashion to animation, but performs left shifts on four of the columns, and puts in straight picture data a column at a time on the right side of the display. The data for this looks like this -

The first byte is the 'frame rate', again the amount of display refreshes before performing a shift left on everything. The rest of it is just column image data in a long line, not five wide at a time. So the routine just reads data from left to right one at a time.

The 127 is a blank column (PORTB mostly set to all on, indicates full impedance from sinking the current, i.e. all LED's off). Notice the collection of 127's at the end, this allows the display to finish empty by scrolling in all blank columns. The 255 indicates data termination - if it comes across 255, it knows to RETURN from the subroutine.

So as you can see, the project is still on the proto board. But I hope you can see quite how small the whole lot will become after all those wires are shortened, with the PIC chip mounted directly under the LED matrix.

Below is a very quick GIMP knockup of the intended wiring organisation, no PCBs needed here, doing so would in fact ruin the whole idea of making it really small. Knocking up this very rudimentary picture helped with planning - as you can see the PIC chip is upside-down in relation to the LED matrix because this helped the wire routing a little (it's still a bit of a mess though). I've marked dots for pin 1 on both components, the colour coding also helps with comprehension of the complex wiring.

Fig. 6 - Extremely rudimentary end result wiring diagram done in the GIMP.

For development I run this chip off of a bench PSU, but I attempted to run it from an old LiPo cell I had lying around, a 3.7v cell starts off with about 4.1 or 4.2 measured volts, but it only takes around half an hour before it drops below the dreaded 4v, where the chip freezes, leaving the display lit on one column only - of course the LEDs still light, but the PIC chip has browned out. I did enable brown out reset on the chip during programming, but it probably enters a continual brown-out, run, brown-out, run cycle forever as there's just not enough juice for it to continue. I'll probably get hold of the 16LF88 low powered version and try it out. But instead of a pendant, I'm thinking perhaps just a USB powered novelty??

You can find the PBP code and Visual C# matrix encoder code, along with a precompiled HEX file HERE. Please read the README.txt for further info.

Update - 28/10/12

Well I tried doing a full manual wire-up of all those wires, but turned out to be way too fiddly, and my eventual end result (second try) ended up not fully functional, with missing columns and rows depending upon how it's held.

So I started down a route which I originally didn't think I'd have to do, but ended up making it a much better end product, quite neat, relatively unbreakable.

I fired up the latest Eagle (6.3.0) and went about designing the schematic and board. Quite a simple job if you're familiar with the software, it's all mostly pin headers, with only the PIC chip and capacitor listed as real components. I have to say, the job was quite tough, because in Eagle PCB we're working with board front (to be reversed) but also a pin orientation for the LED module, of which I couldn't find a library item for online. I instead used two pin headers of 7 pins, but figuring out the correct orientation for them for a mirror image was quite a challenge! Figured it out in the end - because of the reversal, you have to first flip both the 7x1 pin headers horizontally across the board from each other, THEN flip them both vertically! Still not quite sure, but a full check of PIC pin to header pins confirmed it. See how the JP1 and JP2 objects below are switched from the schematic to the board.

Fig. 7 - Eagle 6.3.0 schematic of the display. Click to see bigger.

Fig. 8 - Eagle board layout, that must have been about the fiftieth autoroute, as manual routing wasn't really an option without jumpwires. Click to see bigger.

So the top schematic is literally only that, it doesn't take into account where things go, as can be seen it ended up a bit of a jumble - I blame this on the layout of the connections to the LED matrix but ultimately could have been 'fixed' with creative code writing.

From that board layout, I used the very cool drill-aid script to make the inner pad hole sizes smaller (For more eventual copper to work with). I then made a monochrome export. The below image is free for anybody to use for their own purposes, but pay attention to the LED Matrix size and pinouts, which differs (in size by 2x) from the one on the original TinyMatrix design - there are lots and lots of designs with different sizes and different pin-in arrangements out there.

This image was exported at 1200dpi for a very fine print. In the past I have even edited the PNG file to add details on the copper layer, such as polarity indicators, voltage levels, pad specifics, etc.

The process is quite simple but lengthy. Expose a small copper board with photoresist layer to UV light for about 10-15 mins, masked with the printed out transparency, so the blacked out areas won't get exposed.

Then take the board and put it in a cup of diluted caustic soda - and I mean really diluted. use plastic tweezers and gloves because it's really harmful, and pull it out when you see a nice circuit image appear. One cool thing about caustic soda is that it's traditionally used for drain cleaning, so once finished with, pour it down the sink, killing two birds...

Immediately put it in a cup of water to effectively cancel the 'burning' off of the photoresist - all this work with caustic soda is kind of a critical stage and must be done right, or there's no point in etching it because you'll have destroyed the photoresist mask.

Then submerge it in a container of your copper etchant. I can't remember the chemical name of my etchant, but the stuff works best when heated up to near 50° centigrade. I still keep the lights low because the board is still sensitive to light, and leave it in, agitating it regularly, for around 15-20 mins.

It takes experience to know how things are going, because firstly the copper loses it's sheen, relatively quickly, and turns a kind of a dull salmon pink. Over 15-20mins time you'll begin to see really dark patches appear in the unmasked traces, it's not immediately clear what's actually happening, but this is the copper being completely removed. The dark patches begin from the outer edges and kind of gradually work their way inwards in patches, as the copper is progressively removed, so you have to leave it in just a fraction longer than you might think to get rid of all unwanted copper.

Use the plastic tweezers to remove from the etchant bath, shake it off to remove excess etchant, and hold it up to the light to get a look at how the traces have come out. Put back in if you see un-exposed areas.

Once done, remove it and put it in a cup of water to stop the etching process - undercuts are apparently an issue if left with etchant liquid present for too long.

You're now ready to get rid of the rest of the photoresist, don't even try soldering it with it still on there! The simplest way of doing this is to put it in a stronger cup of diluted caustic soda, shake it around a bit and drop in a normal cup of water to clean it all off - this mix of the caustic soda will burn a hole in your skin so be ultra-careful!

Now comes the unenviable task of drilling and cutting the board to size. It's fibreglass, so do it with a dust mask, preferably outside the house. Clean off all your tools before bringing them back in the house, and wash your hands and clothes to stop the fibreglass dust from forming a thin mist in the air - powertools will literally turn the stuff into a dustcloud, and it's really harmful. For this process, of course, you'll need some micro sized drill bits - I used a set which includes lots of bits from 0.1mm to 1.5mm. I used the 0.7mm bit for all the holes.

Around when you're ready to start soldering it all together, give the copper layer a good flat sanding with a fine sandpaper. This deburrs the drill holes to make them nice and flat, but also cleans and scratches up the board to make it super compliant for soldering - making the job MUCH easier. Don't take off too much copper, only enough to make it shiny and scratched looking.

I won't go into the process of soldering it as that's another discussion. This is how mine ended up, with the video below showing the end result of two full builds.

Fig. 9 - End result, soldered board traces - notice the solder 'spillage' under the pic chip, this has been cleaned up but you wouldn't tell from this photo!

So as you can see, the LED matrix mounts on the top of the PCB, the PIC chip sits directly under it, the side pin headers connect to the pins on the LED matrix. I actually had to rise the pin headers up another level with some more socketed pin headers because the LED matrix wouldn't plug in nicely, what with the PIC chip in it's way.

This is a purposefully low resolution video because my upload speeds are abysmal, and shows some of the still pics and animations as found in the included source code (See above for a link). The animations are cycled through after every four or five still images are shown. I made a point of etching two boards at the same time to save having to backtrack if I cocked the process up at some point. Rather splendidly, both turned out nearly perfectly, so I soldered up and finished both.

LED Matrix end result

So lastly, as you can see in the vid, I am trailing an awful lot of wires around to supply them with power. All I need do now is shorten them, add battery connectors (male and female) wrap a Lithium Ion 3.7v battery around the back with some elastic bands, and I'm done!

I'm currently carrying out some 'endurance' testing using some old Lithium Ion batteries I had laying around - some nearly expired ones from an old RC heli, they no longer provide enough amps to lift the chopper, but they're rated around 1Ah and can still supply sufficient current for this tiny little thing for quite some time.

I'm using another one from a broken PS3 controller. They fit reasonably nicely, but are kind of bulky. So just today I disassembled an old useless Bluetooth headset. It amazes me quite how small headset batteries get, they are typically 3.7v and are only around 100mAh. This one lasted, maybe, two hours. This is probably because it has been left uncharged for quite some time too - Lithium Ion batteries really don't like being under-charged (over drained), and are effectively destroyed.

Still, with what I am seeing with the larger batteries, I'm estimating around a 1-2 week runtime. The estimation was based around the voltage drop levels I'm seeing after being left on for a couple of full days, and makes for really great battery lives!

Still not sure about the tiny 100mAh battery, but it's the most attractive option for when I eventually 'hand it over' to my niece because it's so small.

I'm thinking the other 'unit' you see in the video will find a home with my other niece when her birthday comes round. She's across the country, so instead of her having to send me back the Lithium Ion battery for recharging every so often, I'm going to have to make hers AAA powered, or maybe coin cell powered.