IoT RGB LED Matrix Controller (ESP32)

This project is about building and programming an ESP32 based controller for a 32x32 RGB LED matrix, controlled from Node-RED over MQTT

Similar projects worth following

Back in 2016 one of my projects on Hackaday won me a 32x32 RGB LED matrix from Adafruit. At the time I didn't quite know what to do with it. It came with a Raspberry Pi shield to control it, however I thought it would be overkill to dedicate a whole Raspberry Pi for such a simple task.

32x32 RGB LED Matrix Panel - 6mm pitch

Fast forward to now, the ESP32 Arduino support has come a long way. I was looking to make a project using Arduino IDE, Node-RED and MQTT (to add to my ever growing home-made IoT devices) and learn to do some more "complex" things on the ESP32. For the hardware this project technically only needs a LED matrix, ESP32 breakout, prototyping cables and a power supply.

I wanted to use my Raspberry Pi-based Node-RED server and MQTT to generate and stream the display, the reason being that it's much easier and faster to edit and add new animations on the server side, also more memory is available and processing is faster.

One of my other previous projects, already integrated to Node-RED, was relaying the household electricity usage data, this seemed like the perfect platform to display the live data.

KiCAD source files for the PCB

Zip Archive - 170.79 kB - 03/30/2018 at 14:04


Gerber production files for the breakout PCB

Zip Archive - 14.63 kB - 03/30/2018 at 12:59



Electronic schematic of the breakout

Adobe Portable Document Format - 60.76 kB - 03/30/2018 at 12:57



PCB design of the breakout

Adobe Portable Document Format - 88.22 kB - 03/30/2018 at 12:57



3D printed corner piece to center the matrix in the IKEA frame.

Standard Tesselated Geometry - 8.82 kB - 03/18/2018 at 21:15


  • 1 × RGB LED matrix 32x32 LEDs, 6mm pitch
  • 1 × ESP-WROOM-32 breakout 15 pins on each side
  • 1 × Power supply 5V, 2A minimum
  • 13 × Prototyping cables (signals) Female to female
  • 2 × Prototyping cables (power) Male to female

View all 9 components

  • Sourcing the parts

    Solenoid03/31/2018 at 09:58 0 comments

    In line of making this project as complete as possible I list in this log the components, where I got them, how much they cost and possible alternatives.

    To build this project, while buying all the components, the cost comes down to $40 for the version with the prototyping cables, with the PCB and components it's about $50, this does not include the Node-RED/MQTT server.

    RGB LED Matrix

    I won my RGB LED matrix and technically got it for free, it was from Adafruit and is sold for $40. That was the one on all the pictures here. I bought a second matrix to build another one, this time from Aliexpress and it works just as well, but is half the price (including shipping).

    The matrix comes with the power supply cable. It has screw-terminal connectors on the input and two JST-VH 4-pole female connectors on the output.

    ESP32 WROOM devkit

    If one is inclined to use the adapter PCB then only the 15-pin version is suited. Otherwise, with prototyping cables, any ESP32 breakout should be compatible, as long as 11 GPIO pins are available (careful: not all GPIO are usable on the ESP32).

    Power supply

    These can be bought from eBay/Aliexpress. I cannot provide a source for this one as I salvaged mine from an old appliance. As long as it can output 5V at 2A minimum it should be fine. I would avoid using a phone charger as they can often supply only up to 1A and may overheat and be unreliable. I estimate the cost of this part to about $5.

    Note that you need some kind of adapter, crimps, or just plain solder to connect the power supply cable to the matrix power cable.

    Prototyping cables

    If you want to use prototyping cables then those can also be bought from eBay/Aliexpress for very cheap, the result might be messy, but it'll work.

    Printed circuit board

    You can order the PCB from a one of the many board houses, the Gerber files are in the project files section. Here are some PCB houses:

    • JLCPCB: $2 for 10 boards ($0.2/board), shipping free on first order (otherwise $21 via DHL), total: $2 or $23
    • SeeedStudio: $5 for 10 boards ($0.5/board), shipping $21 via DHL or $12 via Singapore Post, total: $26 or $17 (but slow and untracked shipping)
    • DirtyPCBs: $17 for 10 boards ($1.7/board), $8 shipping, total: $25
    • OSHPark: $15 for 3 boards ($5/board), free shipping, total: $15

    PCB components

    I ordered these parts from Digikey.

    However, Digikey has a minimum order price of about $50 before the shipping becomes free (shipping is about about $20). It might be cheaper to buy from eBay or JLCPCB sister company LCSC (I have no experience with them though).


    The frame comes with a white cardboard inside that needs to be cut to size for the LED matrix.


    I rounded the cost of the four 3D printed corners to about $1. This is about 21g of PLA plastic (0.021kg * $30/1kg = $0.63) and 2h or printing time on my slow 3D printer (0.05kW * 2h * $0.2/kWh=$0.02). Of course, this part can be substituted with anything that can position the matrix inside the frame, like wood pieces or some sticky stuff.

  • Free MQTT sample!

    Solenoid03/30/2018 at 19:25 0 comments

    Since this project requires an MQTT server and knowledge on how to set it up it makes the building/coding curve a bit steep at first.

    I wanted to make this project more accessible and set up my own Node-RED server to publish, once a minute, the english word clock display to a public MQTT broker (HiveMQ) where one can point the matrix to test it:

    I updated the dummy configuration file in GitHub repository so this will be the default setting. The time is UTC+1 (Swiss time), so unless one is in the same timezone the clock will be wrong… but good enough for a test.

    The other issue with unprotected public MQTT brokers is that anybody can send data to the broker with the same topic name and then the matrix will behave strangely. I simply expect fair usage.

    Read more »

  • No more wires!

    Solenoid03/30/2018 at 09:44 0 comments

    The wires inside the frame were bothering me, it was messy and unreliable. Any movement of the frame might have made the device inoperable and it was easy to make connection mistakes. On the other hand the ESP32 module needed so many passives and soldering without an oven was impossible.

    I decided to make a breakout for a breakout. I designed a board around the ESP32 devkit board that had the footprint for the RGB matrix connector, the ESP32 devkit module and also for the 5V power input connector. The devkit breakout of the ESP32 module already had a voltage regulator from 5V to 3.3V and all the bits for programming (USB-UART, Micro-USB connector…).

    I rolled my PCB:

    Looks good right? But...

    This was a pretty hard fail, the board was so easy to make that I didn’t even double check the ESP32 devkit footprint. I did however succeed in testing it before I reordered a corrected batch:

    My second try worked out well (traces are on the other side):

    The parts are the following:

    • 2x, 15 pin female, 1 row, female connector, 2.54mm pitch, part number: PPPC151LFBN-RC
    • 1x, 16 position, 2 row, female connector, 2.54mm pitch, part number: PPTC082LFBN-RC
    • 1x, 4 pin, JST-VH, male connector, 3.96mm pitch, part number: B4PS-VH(LF)(SN)

    The 15 pin connectors are not really needed, the ESP32 devkit module can be soldered directly to the board. The female connectors are handy so, but they make the ESP32 module touch the backside of the frame...

    This was the insides before:

    And now:

    The power cable came with the matrix. It has two outputs, presumably to power a second screen.

  • Conveniently framed

    Solenoid03/13/2018 at 22:36 2 comments

    Recently, while moving and getting some furniture from IKEA, I found the perfect frame to host the RGB matrix, the RIBBA frame:

    The matrix is 190.5mm x 190.5mm x 14mm, the frame is 230mm x 230mm x 45mm, plenty of space for the matrix and the electronics. The frame has a white cardboard insert that can be cut to the matrix size:

    However the matrix will not be centred...

    So I 3D printed some corners to center the matrix in the frame and push it against the front face:

    The electronics are not looking very professional though, I need to rethink that, wires are pretty unreliable...

  • The hidden (server) side of the IoT Matrix

    Solenoid03/12/2018 at 20:21 0 comments

    Writing the ESP32 firmware, with all its timing critical constraints, was only half of the job, the actual image generation happens on an MQTT server (hosted on a Raspberry Pi). I've made one of my most complex flows:

    It does the following things:

    • Fetch the data from different sources (weather data, public transport, Bitcoin value)
    • Parse the data according to selected display
    • Evaluate the screen update rate (the ESP32 sends back a "heartbeat" message every time it is updated)
    • Ping the matrix to make sure it is present
    • Send the data to MQTT only if the pixels have changed and the matrix has been successfully pinged
    • Apply a "brightness filter" to set the pixel intensity level according to the user settings
    • Compress the data to a binary stream for efficient transport (as efficient as it can be made with this framework...)
    • Update the Node-RED user interface

    In the user interface one can:

    • See the preview of what is currently displayed
    • Select the matrix display
    • Set the LED brightness
    • Change the display every minute (selects the next one in the list)
    • Turn off the LED matrix (send a black screen and then stop sending updates)
    • Show the connection state (ESP32 IP ping status)

  • Pixel challenge!

    Solenoid01/06/2018 at 18:22 0 comments

    Having highly limiting restrictions is a fun way to practice problem solving and creativity. In the case of the 32x32 pixel matrix I wanted to have lots of information on the display, but were challenged by the resolution. Despite this limitation it was possible to achieve very interesting things.

    I programmed the animations using JavaScript and tried out different designs in a browser.

    This project being an Internet-of-Things device I wanted to fetch interesting and relevant information off the internet and local sensors and display them.

    Super tiny font

    I searched for a small font to display human-readable letters to convey information. I found the TomThumb font which squeezed latin characters in a 5x3 area, if you think about it it's pretty amazing how our brain can distinguish them at such low resolution (context helps a lot though):

    I had to adapt some letters, as not all of them perfectly fitted the 5x3 area (g, j, p, q, y). This amazing font allowed 32/4=8 characters per line and 32/6=5 lines of text:


    The first thing that I wanted to display was time. My ultimate goal was to replace my dad's wall clock:

    Here are the animations I came up with:

    Analog clock

    This one was pretty easy, simply evaluate the angle from the hours, minutes and seconds and draw lines from the center to the edges. I liked the way the hands extend up to the very edges of the matrix. One can tell which hand is which by the intensity of the hands.

    I also tried to use red, green and blue colours to distinguish the hands, but it was atrocious.

    Digital clock

    The digital clock only took the topmost line, so I added some more information on the next 4 lines: date, inside temperature, inside humidity and outside temperature which I got from openweathermap and update every 10 minutes.

    The temperature colour is blue under 10C, cyan between 10-20C, green between 20-25C, yellow between 25-30C and red above 30C. The humidity is green between 40-60%, yellow between 20-40% and 60-80% and red otherwise.

    Binary clock

    This one was for the sake of the exercise, completely useless for its function as it's not easily human-readable. The numbers are defined by 6 bits, so maximum value of 2^6-1=63. The most significant bit was on the left side, first line is hours, then minutes and then seconds.

    This example shows a time of 16+2:16+8+4+1:32+16+4+1: 18:29:53.

    Reading the time and getting your head around the powers of two usually meant that by the time you understood the time it had already changed.

    English word clock

    Since there was enough space I wanted a clock that spells out the time in plain English. This is called a "word clock", there are many similar projects on Very human-friendly.


    I'm very fond of Japanese, I wanted to make a "Japanese word clock" display where the time would be spelled in Japanese characters. Amazingly there was a full Japanese alphabet (Hiragana, Katakana and Kanji) that fit a 7x7 area per symbol called Misaki font (Geocities is still alive?). It has even been ported to Arduino so it was just a matter of reformatting it for JavaScript.

    I tried to use only Katakana to tell the time, but there wasn't enough space as there was a hard limit of 4 characters per line and 4 lines with this font (16 symbols total). Also using only Katakana characters didn't look very nice.

    Using Kanji fixed that and I must say I like it a lot:


    今は午後六時二十六分です means "it is now 6:26 in the afternoon", although I doubt my dad can read it.

    Bitcoin ticker

    This animation was just a "why not" idea: a Bitcoin ticker that updates itself every minute. The data is taken from Blockchain ticker...

    Read more »

  • Matrix driver development process

    Solenoid01/06/2018 at 15:13 0 comments

    I had some ESP8266-12E's, but there weren't enough GPIO pins (11) to drive the LED matrix (13 needed). I got one of these standard ESP32 development boards off of eBay.

    This board offered 25 GPIO pins, plenty enough and then some. At this point I didn't want to develop my own breakout for the ESP32 chip, that was not the goal. This board had everything I needed: the required passives (resistances and capacitors), LEDs, buttons, USB to UART converter and the 5V to 3.3V converter.

    The driver

    The first step was to look around if somebody had already made a driver for this particular chip and peripheral, I found that VGottselig had made exactly that. It was really great to start with something so advanced. However, this library did miss out on some features I wanted: it was hardcoded for 64x32 matrix, it used the Adafruit GFX library which I didn't need as I intended to generate the graphics remotely, it was not prepared for MQTT... so I used it as an inspiration for my own.

    The things I mostly took from there were the way the GPIO were defined (much faster than digitalWrite() function), the way the display was updated with a timer/interrupt and the pixel light intensity control.

    Performance challenges

    I went for the PubSubClient MQTT library for the MQTT part, this library limited a MQTT message to 128 bytes, including the overhead (topic name and whatnot). The LED screen had 32x32=1024 pixels, every pixel needed 3 colours and I wanted 4-bit colour depth (4 bits per pixel colour), that meat 32*32*3*4/8=1536 bytes/message, when rounded up to 2 bytes per pixel: 32*32*2=2048 bytes/message. This wouldn't work, even when I increased the hardcoded limit in the PubSubClient library (the ESP32 crashed).

    Fortunately PubSubClient also allowed data streaming. It took some time to figure out how to use this feature, it felt like it was made exactly for this type of use case. Data was simply read into a buffer byte by byte as it came. This technically allowed for unlimited message size (within the ESP32 memory space of course).

    I also got into trying to understand how things were organised on the ESP32 execution-wise, as the display update rate had to be really high to avoid visible flicker, but I also needed to dedicate some CPU time for MQTT message reception. The ESP32 uses FreeRTOS, which allows for concurrent task execution. It also has two identical CPUs inside, which share all the memory, this was a really nice feature: I could dedicate one CPU for display and the other for data reception and other tasks (WiFi stuff). Having shared memory meant I could simply write to the display buffer and the screen would update itself as data came in.

    Latency challenges

    Having done the above I saw that when the screen was updating it didn't do it in one go, I could see the screen updating itself as the bytes came over (slow transfer).

    This was no good, it didn't look right to have "scan lines" coming in, this was an issue with data transfer latency, but there was not much that could have been done about it. I measured about 4Hz maximum update rate. So I implemented double buffering: data was coming to a "back buffer" while the display was using a "display buffer" or "front buffer", once all data had been transferred the buffers would switch and the screen would be updated all at once. No more flicker!

    The video is in slow motion and in two parts: with single buffer and double buffer. The camera picked up on the refresh rate of the screen, but that's not the important part here as it's invisible to the human eye. The difference is that in the first part, when switching colour, the pixels change gradually from top to bottom, as the incoming data is slower than the screen update rate. In the second part a "background buffer" is filled instead and then the back and front buffers are switched, giving the effect of instant screen update.

    ... Read more »

  • Software to drive the matrix

    Solenoid01/01/2018 at 17:55 0 comments

    I looked if there was anything done on the software side for something less powerful than a Raspberry Pi. Some hardware is unfortunately very finicky and won't even work and lower clock speeds (WS2812 LED's, OV7670 camera...). Fortunately Adafruit had libraries and examples for Arduinos, unfortunately though the only Arduinos I had on hand were Leonardos (ATmega32U4 based), which were explicitly unsupported for various reasons.

    Operation theory

    The 32x32 RGB LED matrix is updated row-by-row, over 16 rows, upper and lower part at the same time. There are 6 pins for the color, so 3 for the upper half and 3 for the lower. 4 pins select one of the 16 rows (both for upper and lower matrices) and a clock signal that clocks in the pixel values. Then there are some other pins whose function I haven't quite understood...

    Pixel glory!

    As my ESP32 hadn't arrived yet I started with the Adafruit's library as an example and wrote a program to drive the matrix from an Arduino Leonardo. The reason they did not support this particular model was because they wanted to remain compatible on a certain number of other models, however the way the Leonardo pins are mapped to the Arduino pinout made it awkward to accommodate the library in a way that would remain maintainable. They did some really clever tricks to speed-up draw time to avoid visible flickering, such as using automatic pointer address increase in assembly (this is cycle counting business right there!).

    I really liked the way Adafruit "parallel-banged" the GPIO directly from memory, basically the upper and lower pixel RGB values (6 bits) were stored on a single byte which was directly applied on an entire GPIO port array. This meant a simple loop could just sequentially bang the contents of a 32*32/2=512 byte array to a port without any conditionals or checks, considerably speeding up the screen update rate compared to, say, an RGB object that would define 3 bits per pixel to which the compiler would add all sorts of operations and jumps...

    Working example

    Finally I got some sort of animation going on:

View all 8 project logs

Enjoy this project?



mat wrote 07/16/2018 at 07:58 point

Any chance you can share your implementation for displaying Japanese characters? Is there a way to have it handle a full font of Katakana, Hiragana and a good chunk of the Kanji?

  Are you sure? yes | no

Solenoid wrote 07/16/2018 at 16:30 point

The display is generated on the Node-RED server, then sent to the ESP32 that just receives it, transfers the data to a buffer and displays it. I run my Node-RED server on a Raspberry Pi which can handle that without problems. It can does exactly what you say: all Katakana, Hiragana and a select number of Kanji. I used this font: and converted it to JavaScript arrays so they could be blitted to the display.

I did run into a problem however: I wanted to have the full Kanji alphabet available, but the way Node-RED saves the "flows" is into one huge single file, crashing the system if I store every Kanji character in there. I simply selected the characters needed for a word clock, I didn't bother finding a solution for storing all characters in Node-RED.

  Are you sure? yes | no

mat wrote 07/16/2018 at 17:52 point

Yeah, I got a test setup running that way but didn't convert every character and struggled getting the few in that I did. I love the server-side approach and 'dumb display' and would like to use this with three 32x64 displays arranged horizontally, and I'm going to want to lay out many characters, probably in different sizes. So, I think I need to find a way to get the node-red setup to live process a TTF or something.

I want to set it up as a learning aid, showing a clock of course, and the day in hiragana and kanji (or maybe just kanji with small furigana if I can get fancy with it!!) and then a sort of 'word of the day' (or hour, more likely).

I've been looking for a solution to this, and your framework is so far the closest to a fix, offloading the processing is an ingenious solution.

  Are you sure? yes | no

Joshua Snyder wrote 03/31/2018 at 00:30 point

Can the devkit adapter boards be soirced?   I have a dev kit board, that I would love to use for this project.   And I am a big fan of the tidy look of the board you are using over one of my hand done boards.

  Are you sure? yes | no

Solenoid wrote 03/31/2018 at 08:25 point

You can order the PCB's from your favourite PCB house yes. I added the KiCAD source files as well as the Gerber files I used to order the them. Check the project files section.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates