Well it's a big step for me, and you'll see why pretty soon. But let's start with a gratuitous arty-looking shot of where we're headed ...
So, as a bit of an introduction, I've been messing around with a number of projects. Having got the hang of designing my own PCBs and getting them made and sent from China for very little money and just a lengthy wait (ok, about a 10 day turnaround if you pay extra for DHL), what I found I've started doing is pipelining my hobby.
So what I mean is, I have an idea for a project, then I design a circuit, research components, breadboard it, write some code, and then when I'm confident enough that it's going to get me somewhere I order some PCBs and wait.
Then, while I'm waiting, I have an idea for a project, I design a circuit, research components, breadboard it, write some code, and when I'm confident enough that it's going to get me somewhere I order some PCBs ... and so on.
This way, by the time the boards arrive for project X, I've already submitted a PCB order for project Y and am busy breadboarding project Z.
This is magic. It not only stops me getting bored, but it allows me to fail (and therefore learn) at three times the rate that I was achieving previously!
Accelerated failure! Perfect. I should patent this idea or something ...
So, following my terminology above, I've managed to successfully fail with project Y before even receiving the PCBs. How cool is that?
I'll get to the "small step" thing soon, honest, but in case you're wondering what I'm blathering on about, here's the current state of projects X, Y and Z.
Project X - I've got hold of an ArduCAM Mini module which I've ported to work with a PIC and then combined this with an ESP8266 so I can automate the process of capturing a full colour HD resolution image and then email it to myself for posterity using WiFi. The ultimate aim is to build some kind of time-lapse photography system. The basics work fine, I have some working boards but things need tweaking to allow low-power modes etc. and I still haven't tidied up the software ... so, a work in progress which I'll bore you with in a later blog ...
Project Y - If you remember back in December last year I was messing about with some 8x8 LED matrices. Well, the eBay seller that I bought these LEDs from was having a bit of a sale. Not being able to resist a bargain, I bought a load more of these LED arrays and thought it would be good to use PWM (remember the fan?) to drive each of the 64 LEDs at a different brightness level. So, a quick design with a 74HC138 multiplexer and a PIC16F648A, which features "high current" sink/source for direct LED driving, and I was quickly on my way to waiting for some more PCBs to arrive .... but ....
... too keen .... far too keen ...
Thinking that I could get a useful range of brightness levels while multiplexing all 64 LEDs on an individual basis was wrong. Well, not wrong, more like stupid.
So .... before I tell you about Project Z, I need to tell you why Project Y failed.
Project Y ... or Y not!
This was the basic idea. A single 8x8 LED matrix with the cathodes driven low via a 74HC138 inverting multiplexer and the anodes connected directly to the PIC via current limiting resistors.
The plan was to run the PIC at 20MHz from an external oscillator (saves me a pin) and cycle through each LED by setting one (and only one) anode high - by very careful programming - and one (and only one) cathode low - courtesy of the 74HC138 because that's what it does - and while I'm delaying for a bit on that one LED, to set the PWM duty cycle in proportion to the required brightness and use the PIC's built-in PWM output to drive the enable line on the multiplexer. So effectively toggling the LED on and off to vary its brightness.
By running the PIC at its maximum allowable speed and configuring the PWM for ~78KHz,, this gave me about 12.5 microseconds dwell time per LED, which was enough for about ten cycles of PWM to get the required brightness.
So, even though this worked, and worked quite well, it looked a bit dim. I probably should have done the testing in daytime rather than evening because I thought it was just a matter of tweaking the current limiting resistors a bit to get as much current as possible through each LED without exceeding the 25mA peak limits of the PIC source pin. So I designed some PCBs that matched the physical size of the LED matrix and which held the '138 and the resistors, thinking I'd have a nice module that I could piggy-back multiple copies of onto another board where a collection of PICs would orchestrate a larger display.
As soon as the order for the boards had gone in, I set about maximising the brightness by measuring the current consumption and reducing the resistor values to get a happy medium. This is where I failed. Even removing the resistor completely (normally not a wise idea!) only gave me an average current draw of just over a milliamp. Pathetic.
The reason eventually dawned on me, that by multiplexing all 64 LEDs individually, the brightest that any of then could be was only going to be one sixty-fourth of its maximum. Effectively, my duty cycle had an upper limit of ~1.5%, which I was then reducing further by applying PWM on top! Doomed to fail.
Thus, Project Z was born ...
Wondering how this LED display thing is really done, I stumbled upon Application Note 1216 from Avago Technlogies, entitled "Introduction to Driving LED Matrices".
Yes, I should have read that first, but hey ... impatience is something else I need to work on ...
Anyway, if you read that PDF, you'll see that multiplexing LED matrices isn't done one pixel (LED) at a time, it's done one row at a time!
The whole thing starts with the required frame rate - normally no less than 60Hz - which is required for persistence of vision to fool the eye into thinking that the display isn't flickering. So, for the sake of argument, we'll aim for 100Hz, which means a single frame will last for 10 milliseconds. As we have eight rows and we're multiplexing them, then it follows that each row will be active for one eighth of that 10ms, so each row gets 1.25ms (or 1250 microseconds).
Now the 1250us time slice for that row is further divided into a number of time slots that relate to the number of desired brightness levels. Still with me? Ok, then each LED in that row is held on for the number of time slots equivalent to its required brightness.
For example, keeping the mental arithmetic simple, suppose we want 125 different brightness levels (aka grey levels). Then the row time of 1250us is divided into 125 slots, each of 10us and this 10us period represents our "inner loop" at each step of which we decide if each LED in the active row needs to be on or off. We can do that simply by decrementing its grey level within that loop and turning the LED off when we reach zero.
Sounds like a good plan, my multiplexing overhead goes from one-sixty-fourth to one-eighth, my LED brightness increases accordingly and I only need minor changes to my code. Hmmm..... really?
Suppose that all 8 LEDs in the active row have the maximum grey level of 125. They will all be on for all of that 1250us time slice. During that time they will each be drawing 25mA, which means 25 x 8 = 200mA needs to be sourced and sunk (sinked?) ... that's well outside the maximum limits of both the PIC and the multiplexer. If we don't want a nasty accident, something beefier is required....
Skinning a cat
As in "there's many ways..." - don't worry if you don't understand, no animals were harmed ...
Anyway, there are a number of solutions for this LED driving problem. You can buy dedicated LED driver chips, I've used some myself with the previous foray into LED matrices. You can get built-in current limiting, PWM, dot correction, yada yada ... for example, things like Texas Instruments' TLC5941 and many others. But it all boils down to the same things:
- You need a way of sourcing current
- You need a way of sinking current
- You need a method of multiplexing
- You need a way of controlling grey levels
Well, I've already got all of these, just that the first two are not man enough for the job. This was because the PIC handled both the sourcing and the grey level control, and the '138 handled both the sinking and the multiplexing. So, let's delegate those sourcing and sinking jobs to something else ...
More source sir?
I found a few possible components for sourcing eight channels at suitable current levels, but these were not as common as the sinking counterparts. I eventually settled on another Texas Instruments chip, the TLC59213, which is an 8-bit parallel Darlington source driver with an input latch. Its can be driven directly from TTL or CMOS levels and it can source up to 500mA per channel, which is easily enough for what I need.
Here's the "Typical Application Diagram" from the datasheet.
In my application, I'll be using this to drive the 8 LEDs in a single column - so, like I explained above but with rows and columns swapped. Therefore, at any one time there may be more than one output channel active, so - like it's shown in the diagram - this is where I need to put my current limiting resistors (so that the 8 LEDs in the active column are effectively driven in parallel, each with its own resistor).
There was also an older, similar chip from Allegro called UDN2981, which although discontinued some time ago is still available for some eBay sellers. However, apart from being old and difficult to obtain (I did get some to try), it also doesn't have the input latch. That latch is very useful for me, because it means I can take my time - in the code - setting up my high/low outputs on the PIC without worrying that it will cause glitches on the display as I switch columns. Once I've set up the new configuration, I just pulse the clock input to latch my new outputs to the LED. Nice.
One other alternative I considered was the TPIC6B595, which is a high-current 8-bit shift register. It can also handle up to 500mA per channel and it otherwise behaves just like a normal '595 shift register and is in fact pin-compatible. However, having to serially shift in the 8-bit LED pattern for the active column takes longer than setting up the parallel outputs of the TLC59213 and I was a bit wary of introducing this delay into the innermost loop of my scanning code. I didn't try it, so I don't know if it would be a problem ... just felt a lesser option for me.
Ok, so that's the beefy source (beef sauce?) sorted - at the cost of one more component - so what about the sink?
Sinking, sinking ....
Easy choice here and really only one way to go. Yet another TI component (no, I don't get commission!), the ULN2803A high-voltage, high-current, 8-channel NPN Darlington array. The "A" on the end means that this part has built-in 2.7K base resistors for each Darlington pair, so it's an easy one to use directly from a CMOS driven output from the PIC.
Eight channels in, eight 500mA sinks out. Again, more than enough for what I need, but at the cost of yet another component (see where I'm going here?).
Only one complication here is that the sinks - which relate to my LED columns - are the ones driven by the multiplexer (because I don't have enough pins on the PIC). However, the 74HC138 multiplexer I used in Project Y is inverting - so all but one output is HIGH. Whereas, the ULN2803A expects a HIGH input to activate each sink channel. Simple solution though is to swap out the 74HC138 for its non-inverting companion, the 74HC238, which is otherwise identical.
Ok, we have the technology .... we can rebuild him ...
8x8 Greyscale LED Matrix Driver
Remember that the point I'm trying to reach here is to have a small PCB, small enough to just fit a single 8x8 LED matrix, that contains as much of the driver circuitry as possible, so that I can use it in conjunction with another board - or by flying ribbon cables - so I can hook up a larger display.
Bearing that in mind, here is the full schematic for such a board. It contains the source and sink drivers, the multiplexer, the eight current limiting resistors and the 8x8 LED matrix. It has some pin headers so I can hook it up to power and to the PIC (just click on it to see it larger).
Notice that the row and column pin ordering is now not so logical. You'll see why in a moment.
So what's with the "small step"?Ok, finally, I get to the point! You probably got there before me though, and I'd be surprised if you didn't. But ... if you're still wondering .... just think ....
... one 16-pin IC, one 18-pin IC, one 20-pin IC, eight resistors, one capacitor, 16 connectors for the LED matrix and 14 pin headers for connection to the PIC. All on a PCB that's a maximum of 33 millimetres square....
Not a snowball's chance in hell.
It doesn't fit. It will never fit. You knew it wouldn't. I knew it wouldn't. You always knew what I'd have to do. You told me. I didn't listen. I put my fingers in my ears and went "la la la".
I got there eventually.
I'd always said "I'd have to get boards made for everything" .... crap ... "It's not easy to breadboard with SMD" ... crap ... "It's difficult to solder" .... crap ... actually all crap. All my excuses, that is.
If you've been paying attention to my accelerated failure technique, you'll realise (although I didn't) that I've developed the habit of getting boards made for everything anyway. So I may as well design SMD boards right? You get more space to play with and you can use both sides of the board. Components are very often cheaper and you have a wider selection of parts to choose from. No brainer. No brain at all, more like.
As for the other "objections", look at this:
What is it? I'll tell you what it is. It's a TSSOP-20 to DIP adapter. You take a tiny 20-pin device, like the TLC59213 in the picture (ring any bells?), solder it on, add some 0.1" pin headers and you stick it ... in your breadboard .... and it works. Easy.
Who made it? I did. How many attempts? One.Was it easy? Well, a bit shaky to be honest, but it was much easier than my large sausage-like fingers and rubbish eyesight thought it would be.
Yes, I have arrived. I shall celebrate with a cold can later .... but for now, we have a board to design!
Squeezing it in ...
Easy to fit everything but routing is more tricky. Pin pitch is smaller, so more difficult to route tracks between pins. Even if you do, you have to make them narrow enough to get the right clearance and then make sure they can still take the required current and don't add too much impedance ... yes, a whole new world of problems, but now I'm here I don't think I'll be going back.
The fact is that I did it. You can now see that the non-sequential numbering of the headers is purely to aid in routing. These bloody LED matrices don't nicely have eight anodes on one side and eight cathodes on the other, but they're spread around in some unknowable but seemingly consistent configuration. Anyway, like I said, I did it.
Here's the top of the board:
And here's the bottom (mirrored, so as viewed from the top):
Maybe you can see lots wrong with it, but it's my first, so it's special. I will always love it. Here's the real thing.
So, now, I can try to get this mess off my breadboard and crack on with the real aim of the project, which is to build an arbitrary-sized display by combining the modules - drive each one with its own PIC to distribute the computing load (and hence the need for an FPGA) and see if I can do anything useful or interesting with it.
Before and after
So, before I took that one small step, this is what my breadboard looked like with just one of these LED matrices wired up:
Now, this is what it's like with two of them:
Neat eh? Now I can start to progress with trying to drive more than one of these modules somehow, but that's another project ... project ...errrm .... whatever comes after Z, I guess ....oh .... bugger .... you see what I did by not thinking ahead .... again?
Accelerated failure is a success ..... !
Just in case you're still reading ..... ;-)
Ok, I'm off to start or finish another project now .... more later ....