Led-matrix controller driven via SPI

Introduction.


Time ago, Stanislav -a Netduino community user- posted a problem on how to drive a 6-by-4 led-matrix using its Netduino. After some experiment, he got stuck with the circuit, because a matrix must be multiplexed, and that’s not easy to solve.
Here is the link to the forum thread.

 

If you read the message exchange on the thread, then you’ll collect easily a list of constraints. Here they are:

  • the leds have been already assembled (i.e. only the multiplex driver is needed)
  • the overall price should fall within 10 Euro
  • must be handcrafted, thus no use of small parts (e.g. SMDs)
  • the multiplex should not stop its cycling as the Netduino stops (avoid leds burnout)
  • the circuit should avoid complicate wiring, so that the PCB can get pretty easy
  • reliable enough
  • finally, Stanislav asked to learn how to design such a circuit

It was clear that Netduino only wasn’t enough to drive a 6×4 led-matrix. First off, for the inability to give enough current for the leds, and secondly for the relative slowness of the managed code running into.

 

The problem in depth.

Light up a led is very simple. Starting from the power supply, as parameter you have the current flowing through the led, then calculating the resistor to put in series. A led needs from few mA (SMDs), to several hundreds of mA (or even more) for the high-power class.
Let’s face the multiplex problem thinking to a normal discrete-led which needs 10 mA for a normal brightness.

So, what is a multiplex?
The multiplexing is a technique for driving many loads (e.g. leds), using a relatively low number of wires. Thinking to a 6×4 led-matrix, instead having 24 wires (one for each led), the multiplex-way needs only 6+4 = 10 wires at all. The trick is enabling one column at once, and issuing the related row pattern. If this process is fast enough, our eyes can’t perceive the scanning.
Now, let’s focus on a single column of four leds: the scan process cycles over, but each column is enabled only at 25% (i.e. 1/4) of the total cycle-time. It means that to yield the same brightness as the led was lit with 10 mA, we should raise it of a factor of 4, thus 40 mA. This current is off the upper limit achievable by a normal logic chip.
By the way 40 mA is probably above the led’s limit. However, the current is flowing only for a quarter of the cycle, so there’s no warm up in the *average*. We only should take care to *avoid* any cycle break, otherwise the 40 mA will flow for a too long time, and the led blows.
That’s not all. When a column is enabled, there are 6 leds composing it, and they might be all on (worst case). So, the total current flowing is 40 mA x 6 = 240 mA.
How much is the current of each row, instead? A row drives only the led at where the column is enabled, but at 25% duty, of course. It means the 40 mA seen above.

 

My solution.

To solve this problem, I see three ways:

  1. using any small micro-controller (e.g. AVR, STM8, etc), then creating a program for both multiplexing the matrix, and for communicating with the Netduino. However, this solution still has the current limitation problem, and easily could take the overall cost over 10 Euro.
  2. using an ASIC, such as the AS1108: this way probably keep the cost within the limit, but can’t get the current higher than the chip’s max-rating.
  3. creating the circuit in the “classic-way”, using simple logic gates that you can buy everywhere for fews Euro. This solution seems having only the fault on the low compactness. However, there are tricks to minimize the hardware, and the compactness didn’t seem a constraint.

My choice was for the third option, for several reasons: it’s easy to create, it teaches how to design a led-matrix driver, it’s also cheap. I’d add it’s also pretty flexible, because you could change some component upon the leds current. It’s even modular: can create (theoretically) as many rows and columns as you wish, by simply adding a stage.
I had an old 7×5 led-matrix display: not so good, IMHO. It requires about 10 mA to light a led, but the brightness isn’t so high. Even raising the current to 20-25 mA, there’s no significantly better shining. I have several modern leds, and they should fit much better a similar project, because with as little as 5 mA they shine a lot more than my matrix. However, I used it for a faster prototyping.
In my circuit I also reversed the rows/columns role, but that does not change anything about the concept. It’s only for my convenience.

 

How it works.

The circuit is based on the famous shift-register 74HC595. A single register holds one-column pattern, that is 7 leds. Since we can chain several shift-register, I chained 5 of them: one for each row. The software for loading via SPI a bit stream into the chained registers is trivial, but there are several libraries such as the Stefan’s “Bit-shift shizzle”.
The Netduino has only one task: shift the whole stream of bits into the five registers, by using the SPI.
Afterward, the trick is playing with the /OE input of each register: when this line is high, all the register’s outputs are completely “detached” from the circuit. That is, we can parallel all the outputs, and enable one register a time leveraging the /OE behavior.

NOTE: the circuit shows only three registers for clarity.

The /OE signals should be cycled. To do that, I used a simple clock generator (NE555), and a 4017 (or 74HC4017), which is a Johnson counter.
The NE555 generates a square-wave of about 500 Hz, which feeds the counter. The 4017 simply puts high one of its outputs at once: every clock edge the next output is pulled high, as a natural 10-sequence. This sequence is also used for the column’s cycle, because the registers enabling must be synchronized with the proper led-column activation. Since the matrix is composed by 5 columns, the 4017 sequence must be shorten to that quantity. To achieve this, simply wire the 6th output to the counter reset: as soon the sequence hits the 6th output, its logic high also resets the counter taking the first output high immediately.


Both the shift-registers circuit, and the sequencer requires no particular difficulty.
The complex section is the real leds driver, which has to amplify the current (possibly wasting almost no power).
There are two cases of led-matrix pattern: common-cathode or common-anode. To clarify, let’s take the columns as reference: either the column lines represent the leds’ cathode, or the leds’ anode instead.


My display is a common-cathode, and the Stanislav case is about a common-anode, instead.

 

The led driver for columns sharing the cathodes.

The following circuits targets the current amplification for both columns- and rows-lines.

NOTE: the circuit shows only few rows/cols for clarity.

The row signals are coming directly from the 74HC595 outputs, which aren’t powerful enough to drive the leds. Thus a PNP-transistor (I used BC640) for each row is used for amplifying the signal. The PNP is wired as common-emitter, so that the registers have to set the output low to activate the led-row.
The ultimate goal for these transistors is taking them to the saturation: that’s for minimizing the voltage drop across the emitter-collector. More voltage available for the leds, and lesser power waste on the transistor themselves.
Notice that I didn’t used any resistor in series to the transistors base. That’s because I wanted to maximize the current through the base, so that the saturation will be guaranteed. The register stress is relative, because -yes- the current is above the chip’s rating, but also that is for a short period in a cycle. We should always bear in mind the *average* behavior.

For the columns the amplification circuit is more complex.
That’s because every column transistor should saturate flowing a current of 350 mA (or more). It worth noting that my matrix is 7×5, so that the column current is 7 x 50 mA = 350 mA (see the above calculation).
The BC639 NPN-transistor is the dual of the BC640, and it’s rated up to 1 A (continuous). Its hFE (current amplification ratio) is rated at about 25 when the collector current is about 500 mA. That means a base current greater than 500 / 25 = 20 mA, to ensure the saturation. This value is very close to the upper limit achievable by the 74HC4017, and furthermore the drop C-E looks still pretty high. The BC639 specs indicate a VCE = 0.5V @ Ic=500 mA and Ib=50 mA. All that imply another stage for pre-amplification.
The pre-amplification stage is a common-collector pattern: simple, yet good for taking the current higher. Please, also notice that the transistor pair are NOT connected as Darlington. The Darlington fault is that you cannot take it to the saturation, and we want that instead.
I used a 1k Ohms resistor for the final-stage-base, which is fine for a column current up to 500 mA. However, you could take this value lower (e.g. 330 Ohms) whereas the required column current should be greater.

You know, this driver is designed for taking the columns to the ground in order to light the leds. So, when the 74HC4017 output is high (just one at once), the related transistor-pair stage shorts the column line to the ground.
But…remember? We also need to enable the related shift-register, so that the bits pattern will be issued against the rows. The same column signal is also used for activating the /OE of the 74HC595. Since when the column is not active there’s also nothing taking the /OE high, there’s an additional pullup (2.2k Ohms) to achieve that. However, the presence of this pullup doesn’t involve the matrix behavior in any way.

 

The led driver for columns sharing the anodes.

This is the case of Stanislav, and the circuit looks a little simpler than the above.


The considerations about the current flowing through the transistors are the same as before. The only difference is in the polarity, which has to be reversed.

 

Power supply.

NOTE: this section is very important. An improper wiring may lead to an unexpected or quirky behavior of the circuit.

There are three sections involved in the whole project: the Netduino, the logic (shift-registers and the sequencer), and the drivers (rows’ and columns’). All the grounding must be carefully shared: the logic can be powered from the Netduino +5V supply, but the driver can’t.
The drivers (i.e. the leds) need a lot of power, which must be supplied separately.
You should observe this pattern for supplying the various power sources:


The circuit should be created along two sections: the logic and the drivers.
The two grounds (Netduino’s and leds’ power) should be joined in the middle point between the two sections. So, the two current loops are kept separated.
As stated, the positive leads of the two supplies must be not connected together.

 

Modularity.

As stated in the beginning, the circuit is clearly modular.
The 74HC595 offers up to 8 outputs (i.e. rows), so you can add a transistor and a led strip with ease. Since the registers are already chained, it’s also not hard to double-chain them, and reach 16 rows or even more.
Pretty the same consideration for the columns: the 74HC4017 yields up to 10 outputs (i.e. columns). Just add a transistor-pair stage, and the leds.
In this case is a bit more difficult to expand the column outputs over ten, but it’s not impossible at all. I’ll avoid any description over here, unless explicitly requested.
Modularity yields a relatively easy wiring of the PCB, or any other concrete solution.

 

The prototype.

NOTE: the circuit scans the led-matrix automatically, also when the Netduino is halted or even detached. This should prevent over-current through the leds, and facilitates the debugging of the software application.

Here are some pictures about the prototype built over two (!) bread-boards.

The whole prototype seen from above.

 

Detail of the 5×7 led-matrix

 

Detail of the five shift-registers (74HC595) chained.

 

Detail of the clock generator, and the 74HC4017 (i.e. the column scanning)

 

Detail of the seven rows’ drivers (BC640)

 

Detail of the five columns’ drivers (2 x BC639)

 

The demo program.

Below is the source code used for the demo (see below). Nothing else is required.

    /// <summary>
    /// Sample application for testing the led-matrix driver
    /// </summary>
    /// <remarks>
    /// NOTE: it's important to bear in mind that the circuit
    /// uses a negative logic, thus a logic '1' means a led off.
    /// </remarks>
    public class Program
    {
        //define the bit-buffer as mirror to the 'HC595 chain
        private static byte[] _buffer = new byte[5];

        //define some bit-masks, just for improving speed
        private static int[] _mask0 = new int[8] { 0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F };
        private static int[] _mask1 = new int[8] { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };


        public static void Main()
        {
            //fill the whole buffer with logic '1' (turn all the leds off)
            for (int i = 0; i < 5; i++)
            {
                _buffer[i] = 0xFF;
            }

            //defines the first SPI slave device with pin #10 as SS
            var cfg595 = new SPI.Configuration(
                Pins.GPIO_PIN_D10, // SS-pin
                false,             // SS-pin active state
                0,                 // The setup time for the SS port
                0,                 // The hold time for the SS port
                false,             // The idle state of the clock
                true,              // The sampling clock edge (this must be "true" for the 74HC595)
                1000,              // The SPI clock rate in KHz
                SPI_Devices.SPI1   // The used SPI bus (refers to a MOSI MISO and SCLK pinset)
            );

            //open the SPI port
            using (var spi = new SPI(cfg595))
            {
                //set the initial ball's position
                var ball1 = new Point();
                ball1.X = 2;
                ball1.Y = 5;

                //set the initial ball's speed
                var speed1 = new Point();
                speed1.X = 1;
                speed1.Y = 1;

                //endless loop
                while (true)
                {
                    //clear the led where the ball now is
                    SetPixel(ref ball1, false);

                    //move the ball accordingly to its speed
                    ball1.X += speed1.X;
                    ball1.Y += speed1.Y;

                    //check for the display "walls"
                    //NOTE: it's a rect having width=5, and height=7
                    if (ball1.X > 4)
                    {
                        ball1.X = 4;
                        speed1.X = -1;
                    }
                    else if (ball1.X < 0)
                    {
                        ball1.X = 0;
                        speed1.X = 1;
                    }

                    if (ball1.Y > 6)
                    {
                        ball1.Y = 6;
                        speed1.Y = -1;
                    }
                    else if (ball1.Y < 0)
                    {
                        ball1.Y = 0;
                        speed1.Y = 1;
                    }

                    //light the led at the new ball's position
                    SetPixel(ref ball1, true);

                    //copy the bit-buffer to the 'HC595 chain
                    spi.Write(_buffer);

                    //wait a little
                    Thread.Sleep(120);
                }
            }

            //useless in this case, but better than missing it!
            Thread.Sleep(Timeout.Infinite);
        }


        /// <summary>
        /// Simple helper for setting the state of a pixel
        /// </summary>
        /// <param name="pt">The pixel coords</param>
        /// <param name="state">The desired led state (true=on)</param>
        /// <remarks>
        /// The "ref" yields better performance than
        /// passing two parameters (X,Y) separately
        /// </remarks>
        private static void SetPixel(
            ref Point pt, 
            bool state)
        {
            if (state)
            {
                //the led should be turned on,
                //then let's clear the related bit
                _buffer[pt.X] &= (byte)_mask0[pt.Y];
            }
            else
            {
                //the led should be turned off,
                //then let's set the related bit
                _buffer[pt.X] |= (byte)_mask1[pt.Y];
            }
        }

    }


    /// <summary>
    /// The basic point structure
    /// </summary>
    public struct Point
    {
        public int X;
        public int Y;
    }

Here is a short video demonstrating the circuit prototype.
The Netduino runs a small program for bouncing a ball.

Enjoy!

One thought on “Led-matrix controller driven via SPI

Leave a comment