Custom 16x16 matrix display PCB
By: Erik Van Zijst
Tags: Programming, Makers, Gaming, Electronics, Arduino

For a previous project I explored what it would take to create a text marquee on an 8x8 LED matrix display without microcontroller, using only 7400 chips, an old EEPROM and breadboard components.

That worked, but 8x8 is very small to do anything interesting and so I wanted to give it another go, create a larger 16x16 panel, design a custom PCB and ultimately hook it up to a microcontroller this time to write some games for it.

LED matrix panels

Matrix displays typically don’t have individual pins for each LED. Instead, internally the LEDs are connected in a grid pattern, exposing only the row and column terminals. Either the row pins are all anode and the column pins cathode, or vice versa.

For reasons unknown to me, the pin-out tends to be rather randomized and unintuitive.

You can uniquely address only one entire row or column at a time and so to display a steady image covering the entire display, you need to quickly scan over the rows/columns, refreshing them faster than the eye can see.

For this project I was going to connect 4 panels, creating a 16x16 screen with 256 single-color pixels.

KiCad schematic of four displays wired together

Row shift registers

While it would be possible to pick a microcontroller with enough gpio pins to directly connect all 32 row and column pins, we can only draw one row or column at a time and so it makes sense to use a shift register to select the active row.

Like a running light, we set one bit to high and continuously shift that in a circle, connecting the register’s output back to its input. The register’s output pins then connect to the matrix’s anode rows, lighting up one row at a time.

Shift registers like these are made up internally of a string of d-type flip flops, the input of each connected to the output of the previous. All clock lines are connected together.

serial-in, parallel-out shift register (www.electronics-tutorials.ws)

To reach all 16 rows, I daisy-chained two 8-bit 74HC595’s and instead of creating a circle, I expose the data line of the first as an input of the display.

This way, when we start at the top of the screen, we pull ROWSDI high, toggle ROWCLK and write a 1 to the first bit (QA) of the first register. For the next 15 clock ticks we keep ROWSDI low.

As these shift registers also contain a storage register parallel to the shift register, we actually need to pulse LATCH after performing a shift to make the result visible on the outputs.

Column shift registers

Now that we can supply power to a single row at a time, we do a similar thing for the columns, where we can sink individual row LEDs to ground, closing their circuit and lighting them up.

For this we can also use shift registers. With a row selected, we then load up a binary pattern into the column shift registers where a 0 or low connects the output to ground. Again, we need 16 bits wide.

We expose the data and clock lines as COLSDI and COLCLK respectively, bringing the running total to 4 wires to control the matrix. We also expose a single, shared output enable ( ~OE ) pin and latch ( LE / LATCH ) pin. Adding a dedicated ground and power lines this gives a total of 8 wires.

External interface to the display

Because of the shared LATCH we can stage the selection of the next row, as well as the column bit patterns sequentially and then apply it to the displays with a single, atomic operation.

Constant current and the TLC59026

LEDs being non-ohmic need a current limiting component to prevent them from burning out.

If we run the circuit at 3.3 volts and the LEDs forward voltage drop is 2V, we could use 16 identical 130Ω resistors to limit the current through each LED to 10mA. However, not all individual resistors and LEDs are the same and so slight differences in tolerance could lead to subtly different brightness levels.

It would also make it harder to support different input voltages for the board. At 5V we would need 300Ω resistors which, at a fully lit screen, would dissipate 480mW in heat, 1.7 times as much as the LEDs themselves.

A better approach in this case is to use a constant current driver, a component that fixes the total amount of current in a circuit, regardless of the resistance of the load.

The TLC59026 is a chip that combines a 16 bit shift register with adjustable constant current driver, aimed specifically at circuits like ours.

The current is set through the value of R-ext which is independent of the supply voltage:

I = (1.21V / R) × 15.5

I’m using 2.2K for about 9mA per LED. Since all components can operate on 3.3V as well as 5V, we’ll be able to use the board both on 3.3V Raspberry Pi’s, as well as 5V Arduinos.

MOSFETs

We’re nearly there. If we’d build the circuit as described, we’d likely run into problems with the 595 shift registers not being powerful enough to supply up to 144mA to a row of 16 LEDs. In fact, the 74HC595 is rated to supply up to 35mA maximum and 144mA will likely cause damage.

Instead, we can use the 595’s output to switch a transistor which in turn supplies power to the row.

I have a stack of general purpose PN2222 NPN bipolar junction transistors (BJT) which are effectively current amplifiers. The input current through the base gets amplified on the output side. This is fine for low current applications, but at higher currents a Field Effect Transistor (FET) is generally a better choice.

Bipolar Junction vs Field Effect Transistors (quora)

Both BJTs and FETs apply junctions of P and N semiconductor material. A BJT allows current to flow from collector to emitter by sending a current between base and emitter. Higher current through the base translates to higher current from the collector.

A FET works differently and does not require any actual current flow on its input. Instead, applying a voltage to the input (gate) causes an electric field to extend across the semiconductor channel between source and drain which alters the conductivity as a function of the gate voltage.

Since a MOSFET’s gate is not electrically connected to either source or drain, but acts more like the plate of a capacitor, no current is drawn and so we also won’t need a current limiting resistor for each of our shift register outputs.

High-side P-channel switch

Our matrix panels are common anode, which is where we’ll need to apply the transistor. Since this is between VCC and the load (LEDs), we connect the MOSFETs source to VCC and drain to the matrix’s row anode. This configuration is known as a high-side switch. This opposed to a low-side switch where a transistor sits between a load and ground.

Lots of MOSFETs!

Like BJTs, FETs come in either N-channel or P-channel and for our high-side configuration we use a DMP3085LSD dual P-channel MOSFET in SO-8 package with a gate threshold voltage of -1V to -3V. This is the voltage differential between source and gate that drives the transistor to saturation (fully open).

This is a negative voltage because the source is connected to VCC and so to achieve a voltage differential, we need to apply a lower voltage to the gate to activate the transistor. With VCC at 3.3V and the gate at ground, the -3.3V will fully open it. When we have a high signal on the gate, there is zero voltage differential and the transistor will be closed.

This means that the signals from our 595's are now inverted and so instead of a single high bit, we need a single low bit to activate a screen row. We’ll do this in the firmware.

KiCad PCB layout

As a novice not knowing where to source symbols and footprints, I created my own custom schematic symbol for the dual MOSFET package. A decision that would come back to bite me later.