Here I have a fully functional programmable led matrix array I made with a single microcontroller (an atmega48p) and some charlieplexing techniques.

(source code and more info available on github)

It acts as an "external device" that can be attached to a different main micro (even an Arduino-like board with the most common libraries) and also controlled in an easy way via I2C (sending commands to scroll text, control the buffer, the speed, etc.).

See it in action here:

Led Scroller Matrix Demo! (controlled by an external attiny85 acting as a primary device) pic.twitter.com/aCCRBzYH8c

— Albert Gonzalez, The Isolinear Chip (@isolinearchip) July 17, 2022

Features

Why charlieplexing?

It's a cool and interesting way to drive multiple leds using a small amount of pins and almost no aditional components / multiplexors rather than the main micro and a bunch of resistors.

By using 20 pins from an atmega48p (that's every "regular" I/O pin except the ones for the I2C communication) it can drive up to 380 different leds (n^2 - n, with n = number of pins). If the micro speed is fast enough (the atmega48p here is clocked at 8Mhz with works with no problem at all) and with some software tricks and delays it'll show a very nice and "stable" matrix that allows a pretty decent display.

Charlieplexing issues

Despite being functional, it's far from being perfect (specially if we compare it with non-charlieplexing solutions). There're some "problems" or, at least, minor inconveniences to be aware of:

So, in conclusion: it'll work nice for small projects, but if you're planning to build a super-giant-sized matrix you should probably consider a different option (nothing new here :D)

Why I2C?

Following the spirit of those small peripherals that are connected via I2C (OLED screens, real-time clock boards, etc.) and since I'm kinda comfortable with it (even wrote some I2C libraries for attiny85-like AVR micros) I've decided to use it in order to listen to commands and data. This way any main device can just send whatever buffer needs to be displayed, or even use the built-in text-scroller function!

The PCB

The PCB is a homemade design, currently on it's second iteration (the first one was pretty much the same but with more space between the leds, no SMD components and some critical design errors when mapping the resistors with the pins :D) and it's made with KiCad.

It's a big matrix of leds manually wired to a bunch of 20 ohms resistors that goes directly to the 20 atmega48p I/O pins. There're some breakouts for the I2C SDA and SCL lines, the VCC and the GND and some ISP programmer pins too (which cannot be used in this revision once all the leds are soldered due to some shorts between them and a couple of ISP signals :_ D).

The most difficult (or at least time consuming) part was to manually wire all the different leds to it's different paths on a 2-layer PCB. It's something that can be done, of course, but it takes a loooot of time and requires some organization skills in order to avoid ending up with a messy blob of unhandled vias.

(also solder 376 THT leds takes a looooot more time than I expected!)

How it works? The code

With all the leds properly wired and everything on it's place there's an internal led_status buffer that contains information about each column (since each column is made of 8 leds/pixels, a single byte is used to store it - higher bits for top, lower bits for bottom).

Once everything's set up accordingly there's an endless loop of iterations over this buffer and, for each led/pixel, the proper pins are turned on and off (only on those that are supposed to be "active", of course). Do this fast enough and the ilusion of having everything glowing at the same time will be real (at least for a human eye!).

There's also a timer set on a variable speed (controlled via I2C commands) that can trigger changes on the operation mode and also controls how each mode modifies the buffer once in a while (this modifications are the text scrolling, buffer changes, etc.).

By using the Two-Wire Interface capabilites on the atmega48 the device also listen to I2C commands and performs each action accordingly (turning the display on/off, changing the speed, receiving a string buffer, etc.).

More info on the github repository!

The modes

Issues while writing the software: Wire.h limitations

My first approach when editting the buffers (both the letters one and also the matrix-itself) was to send a fixed amount of stuff (47x8 pixels or a previously set string count) and reset everything each time a new "batch" of data was received.

It worked flawlessly, actually.

But when I switched from a single attiny85 (my test "host" device) to an Arduino (I also wanted to try the Wire.h libraries in order to make the device as "standard" as I could) I noticed there was a limitation on the internal I2C buffer size the Wire.h library uses!

So unless I wanted to have internal 32 bytes buffer too (this means a non-editable full buffer and strings around 30 bytes) I needed to change my approach...

Commands with index + length

Instead of sending everything, now you need to set an initial point on the destination buffer + the length of the data you're transmitting. This allows on-the-fly buffer edition and multiple updates on different parts on the matrix / string.

For instance, you can split a long string into different < 32 bytes chunks or even modify only a specific region on the matrix buffer instead of overriding the entire thing!

Future improvements and TODOs

The development board

Before this board (somehow a "final" or, at least, "usable" version) I made a first testing board to make sure this idea would work. It's a dev-board with more breakouts, no SMD components and leds packed in 10-columns groups.

(also notice the external wire chaos I added because I messed up the leds - resistors - pins connections and it needed a manual rewire - which I preferred rather than a software fix, since I wanted it to be closer to a hypothetical final version. There're also a pair of switches to disable the messy pins when programming the micro via ISP).