#LedMatrix

256 LED SMD “Practice” Board

I can’t resist a ridiculously cheap LED matrix (as might be apparent by now), so when this popped up in the usual enticing “deals” section of a popular overseas electronics store, I must admit I was weak 🙂

It is described as some combination of the following:

  • Electronic LED Display Kit 64LED/256LED Red LED Dot Matrix Display Kit SMD Components Soldering Practice Board DIY Kit
  • DIY Electronic Kit SMD LED Advertising Screen 256 Display Units Soldering Project Practice Suite Component Welding Training

And variations thereof.

It is typically available for around £3-4 for the 256 LED version and £2-3 for the 64 LED version. One thing I thought was quite interesting is that the LEDs go right to two edges of the PCB so that does make me wonder if a couple of these could be tiled together.

It also includes a breakout header hinting at the possibility of customisation.

https://makertube.net/w/dRN6hyoHKTXez2PwyKs1A4

The Circuit Design

There is a bit of information online, but not as much as I’d like. Essentially it is a microcontroller, some 595 shift registers, driver transistors, LEDs and a few ancillary components in what would probably be a fairly standard arrangement. There is a low-res schematic I’ve found:

The bill of materials names the 8-pin microcontroller as a DX156, but I can find no information about it online. Interestingly though, all six pins that are used in the circuit are broken out to a header, so if the microcontroller is left off the board, I don’t see why it wouldn’t be possible to drive the shift registers directly via the header.

The full BOM is as follows:

  • 1x 220uF Electrolytic capacitor (listed as 220-1000uF in the BOM, shown as 220uF in the schematic)
  • 6x 0603 100nf (104) capacitors
  • 16x 0603 5K6 resistors (1K is shown in the schematic, 2K7 is listed in the official BOM!)
  • 16x 8550 transistors (PNP apparently)
  • 256x 0805 red LED
  • 4x 74HC595 shift registers (SOP-16)
  • 1x DX156 microcontroller (SOP-8)

There is a comment in the description of the kit about changing the display:

” If you want to change the display content, you can connect the control signal from interface JP1 without soldering the included microcontroller, and any content can be displayed. (You can refer to the relevant information of our store’s 16 * 16 dual color dot matrix, which is consistent with the interface of the microcontroller. This interface needs to be provided by oneself and is not included in the kit.)”

But I’ve not found who the “our” is in “our store” or which kit/module is being referred to, so I’ll come back to that in a bit. Again this confirms that for now, I’ll leave the MCU off until I’ve decided what to do.

Other things I note from the schematic:

  • Naturally the four 595s are chained together. I can just about make out the chain.
  • The last 595 OUT is connected to J4 so further chaining should be possible.
  • Other jumpers include J1, J2 for POWER and GND; J3, J5, J6 appear to be connected to the matrix.
  • As already mentioned, all used microcontroller pins are broken out to the 6-way header.

An LED will be on when the Hn signal is LOW and Rn signal is LOW. This is because Hn going LOW will allow the PNP transistor to conduct, thus making In HIGH. when In is HIGH and Rn is LOW the LED will light up.

Building the Kit

The approach to take for building should be relatively obvious. It will just take a fair bit of patience! One initial consideration – do I test each LED prior to soldering, or just go for it and rework anything that might not work…

Everything apart from the 100nF capacitors and resistors has a polarity to watch out for.

I’ve decided to fix the non-LED components first, thinking that I can then test each row of LEDs as I fix them to ensure they all work before moving on. So I built it in the following order:

  • Transistors
  • Resistors
  • Shift registers
  • Ceramic capacitors
  • Header

I’ll save the electrolytic until last, as it is presumably just for stability of the power supply, which can be external for now. And I think I’ll leave the MCU off for the time being too. I might use a SOP-8 to DIP breakout to allow me to use it to drive the board via the header.

Note: after starting on the LEDs, with hindsight, soldering on the header was a mistake. It allows me to test the board, but it means the board no longer sits perfectly flat on the desk whilst working on it. Something to consider for any similar activity in the future.

Determining the polarity of the LEDs is slightly challenging, but there is an arrow on the back, and if one looks carefully a dot on the front, that both indicate the cathode (line). When assembled in the orientation shown below, the dot is on the right-hand side (excuse my dodgy soldering – this is meant to be a soldering practice kit after all).

After one row I took a break to get some code running (see below). Then added three more rows and am now taking a longer break! Its slow, but steady progress 🙂

Initial Testing

The initial plan, once built and shown to be working, was to reprogram the microcontroller, but seeing as there is next to nothing published online about the DX156, it would be a lot easier to replace it with a SOP-8 footprint microcontroller that I already know how to use.

Unfortunately the pinout of the footprint doesn’t quite match with something like an ATtiny85… it’s close, so I don’t know yet if that would be an option.

Anyway, I’ve left off the provided microcontroller and for initial testing am driving the board via the 6-way header.

I anticipated just using the provided microcontroller on a SOP-8 to DIP-8 breakout but couldn’t find one to hand (I’ve definitely got one somewhere), but that will have to come later, as I now have one on order.

Instead, after soldering on one row of LEDs, I jumped into hooking it up to an Arduino which meant I had to figure out how to drive it myself.

Programming

The 74HC595 has the following pinout

The basic use in a circuit requires the following:

74HC595 pinFunctionHeader pinPCB or ArduinoVCCVCCVCC+5VSER (or DS)Serial / data inIND11/OEEnableOEGNDRCLKLatch (Storage register clock)STBD8SRCLKSerial clockCLKD10/SRCLR (or /MR)Clear / master re-clearN/C+5VQH’Data outN/CNext 595 in the chainGNDGNDGNDGNDQA-QHIndividual outputsN/CThe LEDs

Given how everything is connected according to the schematic, the decoding works as follows:

  • There are four 8-bit shift registers, giving a total of 32 bits to drive the 16 ROWS and 16 COLUMNS.
  • Confusingly, the ROWS are labelled H1-H16, which become I1-I16 after the transistors; and the COLUMNS are labelled R1-R16.
  • QA to QH are bits 0 through 7 for each shift register, so are H1/I1/R1 to H8/I8/R8 or H9/I9/R9 to H16/I16/R16.
  • Bits are streamed to the shift registers most significant bit first.
  • For an LED to light up, the Hx and Rx must both be LOW.

This all means that a 32-bit value encodes the ROW/COLUMN information as follows:

      Bit: 31...24  23...16  15...08  07...00
Row/Col: C16..C9 C8...C1 R16..R9 R9...R1
Schematic: R16..R9 R8...R1 H16..H9 H8...H1

The algorithm to push data out to the shift registers is thus as follows:

void shiftWrite32 (uint32_t data) {
digitalWrite(SHIFT_LATCH, LOW);
digitalWrite(SHIFT_CLOCK, LOW);
digitalWrite(SHIFT_DATA, LOW);

// Shift data MSB first
for (int i=31; i>=0; i--) {
digitalWrite(SHIFT_CLOCK, LOW);
if ((data & (1UL<<i)) == 0) {
digitalWrite(SHIFT_DATA, LOW);
} else {
digitalWrite(SHIFT_DATA, HIGH);
}
digitalWrite(SHIFT_CLOCK, HIGH);
digitalWrite(SHIFT_DATA, LOW);
}

digitalWrite(SHIFT_CLOCK, LOW);
digitalWrite(SHIFT_LATCH, HIGH);
}

Note the use of “1UL”. Without the “UL” this does not appear to get extended to a full 32-bit value. This gave me quite a bit of grief, until I hooked up an oscilloscope to CLK and DATA and could only see half the data getting written out!

At some point this would be worth re-implementing using PORT IO, but the use of digitalWrite will do for now.

This means I can cycle through each column of LEDs with the following code:

  uint32_t dataval;
for (int i=0; i<16; i++) {
dataval = (~(1UL<<i))<<16UL;
shiftWrite32(dataval);
delay(50);
}

This will continually set one of the top 16 bits LOW in turn, whilst keeping all lower 16-bits LOW, thus illuminating each column in sequence. This will light up all completed rows as I’m doing nothing to select the row yet.

Note again the use of “UL” to force 32-bit arithmetic.

Here is a more complete version that now includes selecting the row too.

  uint32_t dataval;
for (int r=0; r<16; r++) {
for (int i=0; i<16; i++) {
dataval = (~(1UL<<i)) << 16UL; // Column
dataval |= (~(1UL<<r) & 0xFFFF); // Row
shiftWrite32(dataval);
delay(50);
}
}

In both cases the default “off” state is a bit high (so 0xFFFF for each of the 16-bit chunks). To select a row and column, the corresponding bit has to be set to 0, hence using ~(1<<bit) which is NOT (bit).

Scanning the Display

The above is all fine for some simple tests, but really I need a simple way to scan the display independently of any running code and for that, the best way is to use a timer interrupt to trigger the updating of the display.

#include <TimerOne.h>

uint16_t disp[16];
void shiftUpdate() {
uint32_t dataval;

for (int r=0; r<16; r++) {
dataval = (~((unsigned long)disp[r])) << 16UL; // Column
dataval |= (~(1UL<<r) & 0xFFFF); // Row
shiftWrite32(dataval);
}
shiftWrite32(-1);
}

void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(SHIFT_LATCH, OUTPUT);
pinMode(SHIFT_DATA, OUTPUT);
pinMode(SHIFT_CLOCK, OUTPUT);
shiftWrite32(-1);

Timer1.initialize(20000);
Timer1.attachInterrupt(shiftUpdate);
}

void loop (void) {
// update each row of the display using disp[row]
}

This uses the same shiftWrite32() function, but now it is called for all rows every 20 ms using the TimerOne library.

The use of shiftWrite32(-1) is a simple way of clearing the display as it will set all bits HIGH. I have to do this at the end of the shiftUpdate() function to clear the display after the last row update, otherwise the last row will remain lit until the next shiftUpdate scan. This makes the last row appear brighter than all the rest as it is on for slightly longer.

I’ve used 20,000 in the call to Timer1.Initialize() as 20mS appears to give a good balance between a flicker free scan of the display whilst allowing some spare CPU time to actually run the loop. I’m still using the relatively slow digitalWrite() function calls in the shiftWrite32() function, so this is one area for obvious performance improvements if I need to do something better.

There is no buffering between writing to the disp[] “screen” array and actually updating the display, so it would be quite possible to get a screen update partway through calculating a new display. If this becomes an issue then it would be possible, memory permitting, to use a double-buffering arrangement and ensure the screen buffer that is written to the display is never the one being written to by the main loop, but I’ve not bothered about that right now.

Conclusion

After realising that reprogramming the original MCU wouldn’t be an easy thing to do, I did wonder about the utility of this board, but actually driving it from an Arduino turned out to be relatively straight forward.

In the end, my total SMD parts count was:

  • SMD parts soldered: 256 + 16 + 16 + 6 + 4 + 1 = 299 (I think)
  • SMD parts lost = 1 (one LED pinged off into the great unknown)
  • SMD LEDs tested as FAIL after soldering = 6 (thankfully there were plenty spare!)
  • SMD parts unused = 1 (the original microcontroller)
  • Hours spent squinting at small components = 4 or 5 (approx, in shifts)
  • Completed and working 256 LED matrix board = 1

I’d say that was a success for me.

Kevin

#arduino #gameOfLife #ledMatrix
2026-02-16

Game of Life on a Cheap SMD Practice LED Matrix

makertube.net/w/dRN6hyoHKTXez2

2026-02-14

Testing a Cheap SMD Practice LED Matrix Kit

makertube.net/w/5QKV5H1W5FX4VR

Mini 8×8 LED Matrix

I have a few neat 20x20mm square 8×8 LED matrix modules that I want to drive.

Quite some time ago I tried to hook them up with a HT16K33 based 16×8 I2C LED matrix driver, but it was all on stripboard and not only was it quite messy, actually it really didn’t work very reliably either, so this is another attempt. Eventually this resulted in a small driver PCB.

The LED Matrix Module

The fundamental point of an LED matrix module is the matrix bit. The LEDs are arranged in a grid of some sort and consequently it isn’t possible to light up all LEDs at the same time. They have to be “scanned” and if they are scanned quickly enough then persistence of vision kicks in and it appears as if all LEDs are lit at the same time.

There are a whole range of 8×8 LED matrix modules to choose from, and often it isn’t obvious what the type or pinout for them is. At least it isn’t when they’ve been kicking around in your parts drawer for some time.

Options are:

  • Size of module. I’m using a square mini module which is 20x20mm (often described as 19x19mm). A larger 8×8 version is the 32x32mm module. There are also asymmetrical arrangements too, for example 5×7 LEDs.
  • Type of LED. I’m using single colour LED modules, but they are often available in different colours. There are also dual-colour versions (usually red and green, which also gives an orange option when both are lit) and even RGB versions. The RGB can be “programmable” LEDs or simply direct connections to each LED colour – so with RGB, that is 4 pins per LED…
  • Common Anode or Common Cathode. This refers to which leg of a row/column or LEDs is common to that entire row/column.
  • Number of pins and pinout. Different matrices are mapped onto pins differently. I’ve seen some with 14 pins and some with 16, both in DIP format.
  • Arrangement of the “grid”. There are two main variants here – a grid with the same number of LEDS per common connection, or a “Charlieplexed” arrangement which is a clever way of driving more LEDs from less IO pins. I have a square grid of 8 rows and 8 columns of LEDs.

Here is a schematic with LED arrangement and pinouts for a common 8×8 LED module found very cheaply online.

The left shows a common cathode, and the right a common anode. I’m pretty sure I have common anode LEDs from an old note I found lying around, so I’m going to start with that.

I’ve used the following simple circuit to verify which pin is which. The resistor is there to limit the current passing through each LED.

In the final circuit, I’d be looking at maybe using a 1K resistor so limiting the LED current to around 3-3.5mA (assuming 5V supply and forward voltage of 1.7-2.2V for a typical red LED). For a maximum of 8 LEDs per common connection, that would still only be less than 50mA so I don’t think current would be an issue in any of the approaches I’ll be looking at. But I also have to take into account that if there are 8 scan cycles for a complete display, then each LED will only be on for 1/8th of the time of the scan, so will appear 1/8th as bright anyway.

I do seem to have a module that follows the pinout as shown above (right). In the following diagram, the pins are numbered with pins 1 to 8 along the bottom (left to right) and pins 9 to 16 across the top (right to left).

In this orientation, the ROWs and COLUMNs as described above are mapped as shown below.

Just to bring all this together, the pin functions of my matrix are as follows (row = anode; column = cathode):

PinPinR5116C8R7215C7C2314R2C3413C1R8512R4C5611C6R6710C4R389R1

LED Matrix Driving

The fundamental principle for a common anode configuration is that if a positive voltage is patched onto a ROW and GND is connected to a COLumn then that LED at that position will light up. However of course, different ROWs and COLs will have different states so some kind of “scanning” will be required.

A microcontroller can do this without any additional support if it has 16 IO pins. For example, the following algorithm could be used to scan each ROW in turn, setting the appropriate COL values:

Initialise ROW and COL pins as OUTPUT
Set all ROWS and COLS to LOW
FOR EACH ROW:
FOR EACH COL:
Set HIGH or LOW according to required output pattern for that ROW.
Momentarily set ROW HIGH then return it LOW

Every COL/ROW LED will light up when the combination ROW=HIGH, COL=LOW is reached. If the ROWs are scanned quickly enough, then persistence of vision means that the LED matrix appears to be fully illuminated.

As I say, given suitable current limiting LEDs (one per ROW) and enough IO pins a microcontroller could do this directly, but it uses a lot of GPIO and is likely to reach the current limits of the microcontroller pretty quickly if lots of LEDs are illuminated at once.

The answer is to utilise an additional chip as an LED matrix driver, both freeing up the microcontroller’s GPIO and providing the option for a higher current draw.

LED Matrix Drivers

There are a number of options. Some are general IO expanders, some are dedicated to supporting LED matrices, some include higher current sinks or sources as required.

A key benefit of these types of devices is that in some the scanning is performed by the device itself, so the MCU only has to send it the data for the complete display and then let it just get on with things.

MAX2719 Segment Display Driver

This is a “Serially Interfaced, 8-Digit LED Display Driver” (more here). From the MAXIM datasheet:

“The MAX7219/MAX7221 are compact, serial input/output common-cathode display drivers that interface microprocessors (µPs) to 7-segment numeric LED displays of up to 8 digits, bar-graph displays, or 64 individual LEDs.”

They interface to a MCU using SPI and have additional features to directly support 7 or 8-segment displays. As they support up to 8 digits with up to 8 segments, they could also perfectly support my 8×8 LED matrix.

Key parameters:

  • 24 pin DIP.
  • MCU Interface: Serial IO (chainable).
  • Power: 4.0V – 5.5V.
  • DIG sink current: 500mA; SEG source current: 100mA. Max current supply (all segments on): 330mA.

These are often paired up with a 32mm 8×8 LED display on a cheap PCB kit. For my purposes though, there is an issue – these are designed to be used with common cathode displays.

I suspect this is due to the current ratings for the digit/segment pins. The datasheet expands:

“Eight-Digit Drive Lines that sink current from the display common cathode. The MAX7219 pulls the digit outputs to V+ when turned off.”

“Seven Segment Drives and Decimal Point Drive that source current to the display. On the MAX7219, when a segment driver is turned off it is pulled to GND.”

If the LEDs are in a matrix, in principle it ought to be possible to have an arrangement that could work with a common anode display if the current limits can be balanced.

The common cathode (DIG) will assume a worst case of all 8 LEDs illuminated, meaning a maximum sink of up to 500mA or up to around 60mA per LED. The individual anode (SEG) supports up to 100mA current source per LED. These are both pretty high, especially as I’m not anticipating more than a total of around 50mA for 8 LEDs.

But can a common cathode scanning chip be used with a common anode display? As it stands it will be assuming that it can set a single cathode line LOW and drive all 8 anode lines according to the required pattern. But as the LEDs are displayed in a matrix, looking back at the circuit diagrams, I think it just means that the cathodes are the columns and the anodes are the rows. So instead of scanning rows, it will be scanning columns, so I think it would be fine.

So to summarise, for my LED matrix, the ROWs will be the anode, so connected to SEG and the COLs will be the cathode so connected to DIG.

There is one other key advantage to using the MAX7219. It supports a single “current setting” resistor (Rset in the datasheet). A single resistor between the ISET pin (18) and VCC. Looking at the table in the datasheet I think using a ~60-80K resistor would keep he current down to <10mA for the LEDs, although I must admit I’m not quite following how the values are calculated.

IS31FL3731 LED Driver

The actual IS31FL3731 driver chip is a QFN-28 or SSOP-28 surface mount device, so I won’t be using these directly, but they are available in a breakout board from Adafruit (more here and here). From the datasheet:

“The IS31FL3731 is a compact LED driver for 144 single LEDs. The device can be programmed via an I2C compatible interface. The IS31FL3731 offers two blocks each driving 72 LEDs with 1/9 cycle rate. The required lines to drive all 144 LEDs are reduced to 18 by using the cross-plexing feature optimizing space on the PCB. Additionally each of the 144 LEDs can be dimmed individually with 8-bit allowing 256 steps of linear dimming.”

I believe by “cross-plexing” they are talking about “Charlie Plexing”. The breakouts have the following key parameters:

  • SMD but available as a breakout.
  • MCU Interface: I2C with 4 address options.
  • Power: 2.7V – 5.5V.
  • Pinout compatible with Adafruit 16×9 Charlieplexed LED modules.

These are available relatively cheaply (~£6 each) but they aren’t geared up for use with a common LED matrix like mine, so they won’t be considered further either. But I do have some of the modules and the corresponding LED boards, and I can confirm they are very neat and possibly the highest density LED matrix I have.

HT16K33 LED Controller

This is the driver chip Adafruit use on their own 20x20mm 8×8 LED matrix “backpacks” (more here). From the datasheet:

“The HT16K33 is a memory mapping and multi-function LED controller driver. The max. Display segment numbers in the device is 128 patterns (16 segments and 8 commons) with a 13*3 (MAX.) matrix key scan circuit. The software configuration features of the HT16K33 makes it suitable for multiple LED applications including LED modules and display subsystems. The HT16K33 is compatible with most microcontrollers and communicates via a two-line bidirectional I2C-bus.”

Key operational parameters:

  • SMD but available as a breakout.
  • MCU Interface: I2C with 8 address options (although only 4 are available on some of the backpacks themselves).
  • Power: 4.5V – 5.5V (although the Adafruit backpacks look like they can be powered from 3V3 too).
  • Available in 8×8 (20 pin), 8×12 (24 pin) and 8×16 (28 pin) variants, although Adafruit modules are only available in 8×16 format.
  • 16x ROW = anode (active HIGH to display); 8x COL = cathode (active LOW to display)
  • COL sink current: 200mA; ROW source current: <40mA

These are the modules I’d originally used with my first attempt (undocumented) at driving these LED matrix displays. If I was to use them again, then it would be a case of including the footprint on a PCB for a pair of LED matrices and then providing a connector for I2C and power.

Note: I can’t just use the Adafruit LED matrix breakouts directly as I want to cascade them up to each other and these breakouts have exposed PCBs top and bottom that stops the LED matrix from being positioned right next to each other.

MCP23017 IO Expander

This is a generic 16-way GPIO I2C expander providing access to an additional 16 GPIO pins over I2C (more here) – a “16-Bit I/O Expander with Serial Interface” according to the datasheet:

“The MCP23X17 consists of multiple 8-bit configuration registers for input, output and polarity selection. The system master can enable the I/Os as either inputs or outputs by writing the I/O configuration bits (IODIRA/B). The data for each input or output is kept in the corresponding input or output register. The polarity of the Input Port register can be inverted with the Polarity Inversion register. All registers can be read by the system master.”

Key parameters:

  • 28 pin DIP.
  • MCU Interface: I2C (23017) or SPI (23S17).
  • Power: 1.8V to 5.5V.
  • Max current sink/source for IO pins: 25mA; Max through VSS/VDD: 150mA/125mA.

These devices have two 8-bit GPIO ports, so it would seem a simple matter of hooking up one port to the rows and one to the columns and then it would work fine as a matrix driver. There are a couple of issues though:

  • Each common connector IO pin to be scanned should allow for up to the maximum of 8 LEDs to be illuminated. If there is a 25mA limit per IO pin, that isn’t much per LED. It could be done, e.g. by using a 2K resistor with my red LEDs, but they might not be very bright. But they still might be bright enough depending on what is required.
  • There is no built-in scanning, the microcontroller will have to constantly send the new IO values over I2C for each row or column scan. That will add up pretty quickly to become a limiting factor I suspect, especially as that has to be maintained at a suitable refresh rate.

Given the current limits and lack of automated scanning, whilst these could work, they offer very little advantage, and some disadvantage, compared to the other options.

74HC595 Shift Register

Whilst it is possible to drive LEDs directly from a microcontroller (assuming they can support the current), it is more usual to use a shift register to save IO pins.

“The SNx4HC595 devices contain an 8-bit, serial-in, parallel-out shift register that feeds an 8-bit D-type storage register.”

This means that 8-bits provided over a serial link get turned into 8 IO pin outputs and as these devices can be cascaded together, they can be expanded to 16-bits, 24-bits, etc.

Operating parameters:

  • 16 pin DIP.
  • MCU Interface: Serial/clock IO.
  • Power: 2V – 6V.
  • Max IO current 20mA; max total current via VCC/GND 70mA.

In principle these could work well with a matrix, using two devices, one for rows and one for columns, but the current could be an issue. When driving (say) a single row, the device driving the row will need to support up to 8 LEDs worth of current on a single IO pin, and the device driving the columns would have to allow for up to 8 LEDs illuminated on 8 IO pins. If the currents are limited as previous described, the latter would be ok (up to 50mA total, around 3mA per LED), but the combined total for a row would be too much for a single IO pin.

Typically such devices might be used with something like a ULN2803 current sink which can sink up to 500mA in total. This would have to be provided on the cathodes and the cathode would be the scanned column in the code.

Current limiting resistors should be put in the anode side of the circuit, meaning that the resistors serve only a single LED at a time. If they were placed in the cathode side, then if multiple LEDs are lit, the brightness would change if sharing a resistor.

Using a shift register only has a minor advantage over using direct MCU IO pins – it reduces the pin count required. But in all other respects is not so different. It will still have current limitations which really need an additional chip to overcome; and all scanning will have to be handled by the microcontroller and kept at a suitable refresh rate.

Summary of Options

  • MAX7219: unsurprisingly this is a device dedicated to driving segment and matrix displays. The fact it is designed for common cathode displays shouldn’t be an issue for my LED matrix. Driven via a serial IO link (SPI) and chainable.
    • Disadvantages: One device is required per matrix. They are not particularly cheap. They are quite large in DIP format.
  • IS31FL3731: designed for LED matrices.
    • Disadvantages: This is a surface mount device, so requires a breakout for me to use it. Designed for a Charlieplex matrix, so not really suitable.
  • HT16K33: again dedicated device for driving LED matrices. Can support two 8×8 matrices. Driven over I2C.
    • Disadvantages: Surface mount again, but breakouts are available, but again not particularly cheap.
  • MCP23017: general purpose I2C IO expander.
    • Disadvantages: unlikely to support current required. Needs MCU to perform scanning.
  • 74HC595: general purpose serial to parallel shift register. Can be chained.
    • Disadvantages: likely to require additional current sink device. Needs MCU to perform scanning.

The choice for me is going to have to be either the MAX7219 or HT16K33. Both are too large for my mini displays, but if I can design a board for several displays at the same time there might be options to overlay chips and displays in a useful manner.

The HT16K33 is perhaps the more useful device, with each supporting two displays but I would have to design a board to accept the footprint of the breakout or attempt a SMD PCB…

Mini LED Matrix MAX2719 PCB

In the end, rather than mess around with breakouts, I just dived right in and tried to design a PCB that would support a MAX7219 and one of my mini LEDs. I went with the MAX7219 over the HT16K33 as it seemed a simpler choice at this point in time. I had to create a custom symbol for my LED matrix following the pinout as shown previously.

In terms of connecting to the MAX7219, I have the following:

  • SEG A – G + DP -> ROWS 1 – 8
  • DIG 0 – 7 -> COLS 1 – 8

There was no way I would get a DIP version on – and I did try. One thought was if I had a PCB for several matrices at the same time, then I would have more layout options – but it just wouldn’t go.

So I decided to go with the surface mount version of the MAX7219. This comes in a SOP-24 package which looks just about hand solderable.

I have two board designs – one for two matrices and one for four. My first four-matrix design was a 2×2 square, but I just could not get traces between pins between the two sets of boards, so in the end I went with a 1×4 layout which gives plenty of room between the two long rows of LED matrix pins.

Unfortunately I managed to get the pitch wrong for my LED matrix and the rows are one pin-row-header too wide. But to be honest, I’m not sure I’d have been able to route it with them any narrower, so maybe this is just the way I’ll have to do it!

Each MAX7219 has a 0805 footprint 62K resistor and 100nF capacitor, although when it came to it, I only had 75K resistors in my parts box, so I used those.

There are IN and OUT headers so the idea is that the boards can be chained. I’ll have to see how well that works in practice.

I initially went with standard pin header sockets for the matrix as they can be bent fairly easily, but unfortunately they don’t make very good contact with the pins on the matrix, so I switched to round-hole pin headers instead.

There is another issue with the two-matrix board too. The DOUT signal from the second MAX7219 isn’t connected to the OUT header. This was an oversight on my part in creating the two-matrix board as a cut-down version of the four-matrix board.

To fix this a patch wire is required as follows:

That isn’t a trivial patch, but it’s not impossible either. The four-matrix board should all be connected ok.

Example Code

Driving the displays is relatively straight forward, it is just a case of hooking it up to an Arduino’s SPI and sending a number of addr-data pairs, one for each chained MAX7219, detailing either commands or DIGIT data to be displayed.

There is one problem however.

When it comes to programming the MAX7219, the register address corresponds to a DIGIT, which for me is a “column” value; and the data programmed corresponds to the SEGMENTS to illuminate, which for me is a “row”. Recall the MAX7219 is designed for common cathode 7-segment displays, but I’m using it to drive a common anode LED matrix.

So this means I am scanning columns and setting rows. But as the matrix is rotated through 90 degrees on the circuit board, it appears that I’m scanning rows and setting columns. It gets more confusing…

Mapping to segments, I’ve made a mistake. I thought DP was the last segment, but when it comes to setting the SEGMENT values in the registers for the MAX7219, they are encoded in an 8-bit value as follows:

I’ve hooked them up as follows in the schematic

Which means that to get R8, R7…R1 in the right “column” I need the 8-bit value written to be in the following format:

MAX7219 Register Bit:   7  6  5  4  3  2  1  0
MAX7219 Segment: DP A B C D E F G
LED Matrix "ROW": R8 R1 R2 R3 R4 R5 R6 R7
Physical Column: 1 8 7 6 5 4 3 2

This corresponds to the LED matrix being oriented with pin 1 top left. This makes the top left LED correspond to physical (row 1, column 1) and the bottom right LED is (row 8, column 8).

This means that when creating a bit pattern for the column values, they are swapped compared to bit numbers, and DP needs inserting into bit 7.

val = ((val & 0xff)>>1) | ((val & 0xff)<<7);

It does, ironically, mean that to scan columns in the order left->right, I can simply use:

for (int r=0; r<8; r++) {
for (int c=0; c<8; c++) {
// to scan columns 1 to 8
uint8_t val = (1<<c);
// adjust for DP
val = ((val & 0xff)>>1) | ((val & 0xff)<<7);
// Convert rows 0-7 to register address 1-8
sendToSPI(r+1, c);
}
}

It’s quirky, but it works!

But if I stick with this approach, then any bitmaps (or fonts!) stored in memory for display will have to be stored horizontally mirrored.

The final test code for an Arduino Uno or Nano, using the standard SPI pins (13=clock; 11=data out):

#include <SPI.h>
#define SS_PIN 10
#define NUM_MAX7219 2

#define MAX7219_TEST 0x0f
#define MAX7219_BRIGHTNESS 0x0a
#define MAX7219_SCAN_LIMIT 0x0b
#define MAX7219_DECODE_MODE 0x09
#define MAX7219_SHUTDOWN 0x0C

void maxCommand (uint8_t address, uint8_t value) {
digitalWrite(SS_PIN, LOW);
for (int i=0; i<NUM_MAX7219; i++) {
SPI.transfer(address); // Send address.
SPI.transfer(value); // Send the value.
}
digitalWrite(SS_PIN, HIGH); // Finish transfer.
}

void maxData (uint8_t row, uint8_t value) {
digitalWrite(SS_PIN, LOW);
for (int i=0; i<NUM_MAX7219; i++) {
SPI.transfer(row+1); // ROW registers address 1-8
uint8_t val = value;
val = ((val & 0xff)>>1) | ((val & 0xff)<<7);
SPI.transfer(val); // Send the value.
}
digitalWrite(SS_PIN, HIGH); // Finish transfer.
}

void setup() {
pinMode(SS_PIN, OUTPUT);

SPI.setBitOrder(MSBFIRST);
SPI.begin();
delay(500);

maxCommand(MAX7219_TEST, 0x01); // Test mode on
delay(1000);
maxCommand(MAX7219_TEST, 0x00); // Test mode off
maxCommand(MAX7219_DECODE_MODE, 0x00); // Disable BCD mode.
maxCommand(MAX7219_BRIGHTNESS, 0x00); // Turn brightness right down
maxCommand(MAX7219_SCAN_LIMIT, 0x0f); // Use all digits
maxCommand(MAX7219_SHUTDOWN, 0x01); // Turn on

// Start with everything off
for (int r=0; r<8; r++) {
maxData(r, 0);
}

for (int r=0; r<8; r++) {
maxData(r, 255);
delay(500);
maxData(r, 0);
}

for (int c=0; c<8; c++) {
for (int r=0; r<8; r++) {
maxData(r, (1<<c));
}
delay(500);
}

for (int r=0; r<8; r++) {
maxData(r, 0);
}
}

void loop() {
for (int r=0; r<8; r++) {
for (int c=0; c<8; c++) {
maxData (r, (1<<c));
delay(200);
}
maxData (r, 0);
}
}

Conclusion

I haven’t published the PCBs as I wasn’t sure many would accept the issues with them, and I didn’t want to spend ages attempting to explain them away in my GitHub repository, but if are of interest, given all the above, ping me a message somehow (via Mastodon is probably easiest) and I can send on Gerbers or KiCad files.

I’d like to get a whole block of 20 or so LED matrices all tied together. I’ll post back here if I manage it.

Software wise, I’ve proved the principle but can’t decide at the moment if it is more intuitive to think of the columns going left to right, or right to left. With hindsight it would have been a lot simpler to map them onto the SEGMENTS already bit-swapped. And with DP in the right place of course.

Summary of the known errors for the board:

  • LED matrix footprint too wide.
  • Missing DOUT connection on the two-matrix board.
  • DP is mapped incorrectly to the columns.
  • Ideally all segments would be mapped more usefully to columns.

Still, amazingly, given the above limitations, the PCBs do actually work.

Kevin

https://makertube.net/w/7uPTDhFuALk4SvHrpponXb

#include #ledMatrix

Ayke van Laethemayke@hachyderm.io
2026-01-14

Charlieplexing LEDs is actually really neat, if you understand how it works. It's basically just a LED matrix where you use the same pins on both sides of the matrix. I've written about it in my latest blog post:
aykevl.nl/2026/01/charlieplexi

Charlieplexing is what allows my 36-LED earrings to have so many LEDs - I would have needed a bigger microcontroller without Charlieplexing.

#Charlieplexing #led #LEDMatrix

2026-01-03

Die LED Matrix von Action macht Spaß :-) Und man kann mit python und pypixelcolor sehr einfach eigene Inhalte wiedergeben :-) #homeassistant #ledmatrix

Cheap Max7219 Chainable LED Matrix

I can’t resist a cheap LED matrix, so when I stumbled across these 8×8 LED matrix displays with a Max7219 driver LED in this chainable form-factor for, get this, less than £1.50 each from electrodragon.com … well, I had to give them a go.  It is a relatively simple circuit board to build, so there are very minimal instructions, but there are still a couple of gotchas that might catch out a beginner, so I’ve put together these notes whilst putting them together.  By the way, I ordered 9 so I could eventually form a 24×24 LED square (3×3 of the matrices).

I started with the headers, then the discrete components, finally the chip.  The only thing to note is the polarity of the electrolytic capacitor (of course – look for the + on the circuit board) and the orientation of the chip itself.  Also note that ‘pin 1’ of the LED matrix sockets are indicated by a square pad in the top right of the circuit board (as seen from the top, with the writing the right way up).  It is worth fiddling with the electrolytic prior to soldering to try to ensure it doesn’t poke out over the top edge of the circuit board – although if it does, if physically mounting boards next to each other, it will quite happily overlap into the next board.

The design suggests that all the header pins face upwards and that the jumpers are used on the top of the board to chain them together.  however, I didn’t really want to have to take off the LED matrix every time I wanted to change how it was linked, so I opted to solder the connecting header pins to the underside of the board as shown.  It also gets around the issue they describe on the product webpage about the LED matrix not really fitting snugly on the board.  Mine fits nice and tight.

So all that remains is to add the LED matrix.  As I said, pin 1 should be indicated on the matrix itself and is indicated on the circuit board by the square pad near the electrolytic capacitor.

In terms of making the thing work, it is relatively simple to connect up:

  • CLK – D2
  • LD – D3
  • DIN – D4
  • VCC – VCC
  • GND – GND

Of course when chaining with jumpers DOUT goes to the next LED DIN.  The other pins pair up.

There is a lot of arduino code for these types of driver chips – start here – http://playground.arduino.cc/Main/LEDMatrix.

I used the code from here to test my setup – http://playground.arduino.cc/LEDMatrix/Max7219 – as written this assumes the same pinouts as I describe above (i.e. CLK, LD, DIN on digital pins 2, 3 and 4).

You just need to set the number of chained displays at the top:

int maxInUse = 9;

(in my case) and get to work playing.  The routines in the library provide a simple interface to setting rows on a single or all of the chained displays.  maxSingle is meant for when there is just one display.  maxAll displays the same value on all displays in the chain.  maxOne will target a specific display (starting with display number 1 up to your last – 9 in my case).

As you can perhaps see, this is using an Ardunio nano.  With 9 boards cascaded, getting the PC to recognise the nano was plugged in was sometimes a problem – it often gave me a ‘there is a problem with your USB device’ error on Windows 7.  It was fine with lesser numbers of matrices, so I guess there is a power issue with the nano struggling with the USB setup and initialising all 9 LED matrices at the same time.  Temporarily disconnecting VCC from the LEDs when plugging in the USB seems to solve the issue for me.

As I eventually want to be setting an entire row of LEDs in a larger grid, the maxOne function is a little wasteful as it has to shunt null values to all of the LED displays you are not targeting – so calling it 9 times means writing 81 bytes out across the DIN pin just to actually set 9 bytes.  Consequently it is possible to optimise it a little if you want to write an entire row to all displays in the same transaction.

Of course, if you refer back to the LedMatrix page, there are many other libraries that will do most of this for you, including Marco’s very complete library for scrolling text displays – http://parola.codeplex.com/ – but I quite like being able to see what the low-level code is actually doing to make things work.

I’ve therefore added a maxRow function as follows:

// Note: Sloppy code warning!// There is no checking here that *col is an array of// the correct length - i.e. maxInUse//// It should be defined and used as follows://    byte row[maxInUse];//    // fill each byte of row with your data - row[0], row[1], row[2], etc.//    // using one byte for each matrix in sequence//    maxRow (1, &row[0]);//void maxRow (byte reg, byte *col) {  int c=0;  digitalWrite(load,LOW);  for (c=maxInUse; c>=1; c--) {    putByte(reg);    putByte(col[c-1]);  }  digitalWrite(load,LOW);  digitalWrite(load,HIGH);}

But I haven’t mentioned the absolutely best feature of these little boards yet.  And that is that they are almost exactly the same dimension as a 4-stud Lego brick.  This means it was trivial to make a simple enclosure to hold my 3×3 grid and the nano.

I now have a really cool game-of-life running on my 24×24 LED grid.  At this price, I have another 8 on order so I can take it to a 4×4 grid (with one spare).

Kevin

#arduino #ledMatrix #leds #lego #matrix #max7219

GurgleAppsGurgleApps
2025-09-21

Put the date in the diary, Episode 15 live tomorrow! grab a cup of tea sit back & relax
Circuit board builds, retro game & all the usual 3D-printed gadgets. Don’t miss the fun and creativity!⚡🎮
🛍️: gurgleapps.etsy.com/uk/listing

GurgleAppsGurgleApps
2025-09-19

🎉 Episode 15 drops this Monday!
We’re diving into the past with circuit board builds, sharing a new retro game recommendation, and showing off all the usual 3D-printed gadgets. Don’t miss the fun and creativity! ⚡🎮🖤
Shop 🛍️: gurgleappsshop.myshopify.com
Youtube 📺: youtube.com/channel/UCDeNZL4kd

Antoine - Software therapistavernois@piaille.fr
2025-07-01

Good thing, matrix has 60 columns so I can make some test.
And I like it :)
Definitely needs a 61 columns to get a symmetry.

#LedClock #LedMatrix #ws2812b #esp8266

A horizontal 60x11 leds matrix.
Starting on the first colum, centered on the fifth line, 3 leds are lit every 5 colums (so 12 groups of 3, separated by 4 colums).
The first two groups are cyan, the 3rd is yellow, the others are purple.
On the 5th line, from the 2nd colum, 30 leds are lit blue (overriding the middle led of the first 7 3leds groups).
2025-06-16

A short story about a #DIY project crunched out on a weekend

#hackerspace #makerspace #crunch #InteractiveArt #LED #LedMatrix

Messy makerspace table full of diy parts, electronic components and devices, screens, etc., and a half-eaten box of pizza. A LED matrix screen laying on its side is showing a green, scrolling text that says "testing". A TV in the background is filled with computer operating system boot messagesA bright LED matrix display showing colorful balloons and stick figures in party hatsBack panel of DIY LED matrix display cases, with various cables hanging out and devices tacked on with yellow tape that says "CYBER"
Philip Stellerphillo@fedifreu.de
2025-05-20

Wow! That small idea worked out way more flashy than expected :awesome: pimped up our artist's workshop a little last night... #theGoodHood #art #led #wled #ledmatrix

N-gated Hacker Newsngate
2025-04-25

🎨🤓 "Look, Ma! No EDA Tools!" proclaimed the brave hero as they reinvented the wheel with a code-fueled LED matrix 🤔💡. Because who needs practicality when you can write an entire novel in code to do what software already does effortlessly? ⚙️📚
docs.tscircuit.com/tutorials/b

Hacker Newsh4ckernews
2025-04-25

I Designed My LED Matrix PCB with Code Instead of Traditional EDA Tools

docs.tscircuit.com/tutorials/b

Paul-Vincent Roll (he/him)paul@whisper.tf
2025-03-21

Rebuild the frontend today looks a lot nicer now and is less horrible code wise. github.com/paviro/RPi-LED-Sign #ledmatrix #led #lights #rust #nextjs #node
whisper.tf/@paul/1141954605532

Main UI of the controller, shows all active playlist items (aka text elements) to display on the LED panel and allows adjusting the brightness.Text editor UI allows entering a text and colouring it. Also allows selecting a border for around the text with different effects like animated gradients. A color picker allows to pick the colours to be used for the border effect.
2025-03-19

What started off as trash in the street, then became disused trash in a friend's garden after they ran out of space for it, has become a shiny new 2D LED Matrix wall in Birkenhack after a good cleanup in the sink. There are five ingredients to this LED Matrix wall:

1. Plastic Bottles
2. Standard British Milk Crates
3. Tinfoil
4. LEDs
5. Esp32 running #WLED software

#weeknotes #ledmatrix #upcycling #hackspace #birkenhack #birkenheadisthenewberlin

An image of the disused 2D LED matrix display made from recycled plastic bottles and milk crates laying in disrepair in someone's garden, before refurbishment
Simple DIY Electronic Music Projectsdiyelectromusic.com@diyelectromusic.com
2025-02-22

Waveshare RP2040 Matrix MIDI Monitor

Following on from my ESP32C3 OLED Mini MIDI Montor and Waveshare Zero, Pimoroni Tiny, and Neopixels this project uses a Waveshare RP2040 mini Matrix display. This is largely the same pinout as the other Waveshare boards, but includes a 5×5 programmable LED (i.e. neopixel-like) matrix.

https://makertube.net/w/cdxAJJvsqsPKrzVg6xWodD

Warning! I strongly recommend using old or second hand equipment for your experiments.  I am not responsible for any damage to expensive instruments!

If you are new to microconrtollers, see the Getting Started pages.

Parts list

  • Waveshare RP2040 Matrix
  • Optional: MIDI interface
  • Optional: Breadboard and jumper wires

The Circuit

If used with USB MIDI, then all that is required is to plug the RP2040 Matrix into a computer and it will be able to come up as a USB MIDI device called “CircuitPython Audio”.

If serial MIDI is required, then a Ready-Made MIDI Module supporting 3V3 operation is needed and can be connected to TX/RX on GPIO 0 and GPIO 1 as well as 3V3 and GND.

The Code

I’m using Circuitpython for this one. It uses the same version of Circuitpython as the Waveshare RP2040 Zero – details here. It requires the following libraries to be installed:

  • /lib/adafruit_midi/*
  • /lib/neopixel
  • /lib/adafruit_pixelbuf

The string of Neopixels is hooked up to GPIO16, so can be initialised as follows:

pixel_pin = board.GP16
num_pixels = 25

pixels = neopixel.NeoPixel(pixel_pin, num_pixels, brightness=0.1, auto_write=False, pixel_order=neopixel.RGB)

I’m using the Adafruit MIDI library to pull out NoteOn and NoteOff events and using those to set certain LEDs.

The code can run off USB MIDI or serial MIDI:

# Serial MIDI
uart = busio.UART(tx=board.TX, rx=board.RX, baudrate=31250, timeout=0.001)
midi = adafruit_midi.MIDI(midi_in=uart)

--------

# USB MIDI
midi = adafruit_midi.MIDI(midi_in=usb_midi.ports[0])

As there are only 25 LEDs I set a range of MIDI notes to respond to at the start.

MIN_NOTE=48       # MIDI Note C3
MAX_NOTE=(48+25-1) # MIDI Note C#5

The main loop just cycles around checking for MIDI events and updating the LEDs.

while True:
msg = midi.receive()
if (msg is not None):
if (isinstance(msg, NoteOn)):
if (msg.note >= MIN_NOTE and msg.note <= MAX_NOTE):
pixels[midi2pix(msg.note)] = GREEN;

if (isinstance(msg, NoteOff)):
if (msg.note >= MIN_NOTE and msg.note <= MAX_NOTE):
pixels[midi2pix(msg.note)] = OFF;

pixels.show()

The Neopixel strip is scanned and MIDI is checked every time through the loop, but the display only changes if a NoteOn or NoteOff is received.

In terms of mapping notes to the LEDs, I’m keeping it simple and just using the LEDS in the string order they are already arranged.

This does however mean one compromise – I either go top-left to bottom-right; or bottom-right to top-left. That is because of the ordering of the matrix. There is more here: https://thepihut.com/blogs/raspberry-pi-tutorials/coding-the-waveshare-rp2040-matrix

Ideally, I’d go bottom-left to top-right, but that would mean some fancier mapping of position to note that I’m not bothered about doing at this stage.

Find it on GitHub here.

Closing Thoughts

At some point I’ll be able to resist a neat, small display, but today is not that day. And it is doubly hard when it is a small LED matrix.

This is a fun little board and I’m chewing over a few other ideas now I’ve got the basics out of the way.

Kevin

#circuitpython #ledMatrix #midi #rp2040 #waveshare

Paul-Vincent Roll (he/him)paul@whisper.tf
2025-02-07

Hellloooooooo 😍🥰 #ledmatrix

Close up of the LED matrix panel. The individual LEDs are visible. Some white foam is in the background around the panel.Three black LED Matrix panels in the cardboard boxes I received them in. They are protected by white foam. The boxes are laying on a sofa.

Client Info

Server: https://mastodon.social
Version: 2025.07
Repository: https://github.com/cyevgeniy/lmst