An Awesome Interactive LED Table

If you want to create a large display with a matrix of LEDs, it’s a relatively straightforward process. Thanks to addressable LED tape and microcontrollers it becomes more of a software issue than one of hardware. [Vincent Deconinck] had some inexpensive WS2812 strips, so he sliced into an inexpensive IKEA coffee table  and mounted them in a grid beneath an acrylic sheet. Some work with Arduino Nanos and a Raspberry Pi later, and he had a very acceptable LED matrix table.

An attractive hack, you might say, and leave it at that. But he wasn’t satisfied enough to leave it there, and so to make something rather special he decided to add interactivity. With an infra-red emitter and receiver as part of each pixel, he was able to turn an LED table into an LED touchscreen, though to be slightly pedantic it’s not sensing touch as such.

The design of the IR sensors was not entirely straightforward though, because to ensure reliable detection and avoid illumination from the LED they had to be carefully mounted and enclosed in a tube. He also goes into some detail on the multiplexing circuitry he used to drive the whole array from more Arduinos and a GPIO expander.

The write-up for this project is a long one, but it’s well worth the read as the result is very impressive. There are several videos but we’ll show you the final one, the table playing touch screen Tetris.

We’ve featured another Lack table with LEDs, but you might comment that it lacked the touch element of this one. And of course, there is York Hackspace’s Tetris Table.

40 thoughts on “An Awesome Interactive LED Table

    1. The arduinos are used as serial buffers because the raspberry pi that is producing the animations isn’t an RTOS so it can produce timing glitches.

      One nano is driving the LED strips, another reading the IR sensors and pushing data upstream to the Pi, but because there’s a limited number of A/D inputs to the nano he used two.

          1. There’s only one A/D converter on an arduino nano afaik and it’s already handling 8 inputs in a mux. Making it do all the 14 inputs is just going to slow it down.

          2. On the output side to the WS2812, the serial output was a constraint built in Glediator. I could have overridden Glediator’s output with new code but I doubt doing DMA in Java is a good idea :-), so that would have meant adding a C layer and interfacing. Plus, that would have made the project RPi-only while this should also work with an OrangePi or similar.
            On the input side, one A/D of some sort was required to return serial data, so one Nano was mandatory. I had first thought using a mux indeed (I even bought a CD74HC4067 breakout board) but as Dax guessed, I preferred to double the scan rate using 2 Nanos.
            But yeah, I don’t claim I made the best choices, and it’s nice to get feedback on what could have been done differently. Really appreciated :-)

          3. its slow because its arduino. What is it, 10KHz max for adc?

            Vincent no matter what you put in there, the end effect is phenomenal, quality stuff!

            What is the Gladiator output? Its not like its bit banging in java? :) Using DMA is simply filling a buffer in memory with data (that you probably send over serial now) and triggering a transfer. You could write a python script emulating serial client that does this invisibly in the background while pretending to be a serial device on the same Pee you run gladiator.

          4. Thanks for the compliment, rasz_pl.

            Well, in fact, the Arduino can perform 10000 conversions per second – – so with 200 leds x 2 samples (emitter on vs off), I could theoretically achieve 50fps.
            The reason I slowed down the rate (with a 2ms delay between measurements) is that the receiving leds are extremely slow to settle. That can be seen in the scope tests at (scale is 2ms/div). With a single A/D, it would have required 400ms to perform a full scan, that is a scan rate of 2.5fps.
            Performing the measure on the two halves of the table at the same time leads to 5fps. That speed is acceptable, although the lag is noticeable, e.g. in
            The best solution would be to use better quality components for sure, but well, I was on a budget :-).

            I didn’t really study the Glediator output code but indeed, I could probably modify it, but sharing data between Java and other processes probably involves JNI which is hardware dependent and I really don’t like that.
            Emulating a serial port with a software performing the DMA stuff is an attractive idea though. I didn’t know you could emulate a serial port in Python. Cool !

            Thanks :-)

          5. You could try “precharging” cells in a 1×3 or 1×4 grid(depending how much distance you need to eliminate neighbor interference). Activate

            wait for settle, read as fast as you can and precharge next batch
            and so on, reading whole diagonal lines at a time = you only wait that settle time 7 times per whole matrix instead of every single cell.

          6. Hmm, I agree that grouping measurement of several cells would be a good idea, but I admit I don’t get how to make it the way you propose with a multiplexed power supply.

            e.g. to power the following cells:
            0×0 => I need Vcc on R0 and Gnd on C0
            1×2 => I need Vcc on R1 and Gnd on C2 => So 0×2 and 1×0 will light up too
            2×4 => I need Vcc on R2 and Gnd on C4 => So 0×4 and 1×4 and 2×0 and 2×2 will light up too
            3×6 => I need Vcc on R3 and Gnd on C6 => So 0×6 and 1×6 and 2×6 and 3×0 and 3×2 and 3×4 will light up too

            So in the end half of the table will be lit…

            However, one easy optimization would be to perform all “noise” measurements of the same column without any delay, because “noise” measurement is done with all emitting diodes off. So that would replace 7x14x2 = 196 delays by 7×14 + 1×14 = 105 delays, or almost double the rate…

          7. I spend exactly 0 seconds thinking about the matrix and what that implies :-) duh
            ok, so light up every second diode in same row

            then move to
            and so on all the way down, then move one left(0x1 0x3..) and repeat all rows (interleave), bonus points for updating the display state between the readings for lower latency – no need to wait whole frame if you already captured some data.

          8. I see your point.
            Indeed I could light up several rows in the same column at the same time and, provided they are far apart enough to avoid cross illumination, I could probably double or triple the rate. And with Arduino outputs able to provide 40mA max, powering several leds in parallel shouldn’t be an issue.
            Honestly, I don’t think I’m going to revamp this project because I moved on to other things now :-), but it was nice to discuss improvements, and hopefully others (or me in a future project) will benefit from it.
            KR, Vincent.

      1. There are already libraries to drive WS2812 with the Raspberry Pi. The use some hardware modules of the Pi, AFAIK including DMA, to generate the timing. You do not have to do everything bitbanging in software, although much arduino stuff is designed that way.

    2. I don’t know. The price works out about the same, and the “wasted transistors” leave options for adding new features later.

      More importantly, you could implement this in a few hours as opposed to a few days designing, testing, and debugging a more efficient circuit. Sometimes time efficiency is more important.

      1. STM32F103C8T6 minimal board is $2.5 free shipping
        offers dual 1MHz sampling ADCs with buildin muxes, 16 channels out of the box

        but the fact is I didint build anything, Vincent did :)
        Arduino thing was just my observation, its easy to spot a coder doing electronics this way ;-)

    1. Exactly, they are also called BHD (Black Hole Diodes), and they suck up all the light that’s around :-)
      More seriously, the data available on their page – – is a joke. That’s even the exact same data as on the emitter counterpart –
      The level of current these “receiving diodes” let through and their slow response make me think they are really phototransistors and not photodiodes, but any information is welcome…

      1. Really awesome project! The moment I saw it just thought… MUST HAVE :)
        I’m not into tinkering but giving it a try, and am now going for the interactive part.
        But.. … run into a wall now with the Glediator input calibration java part.
        I’ve managed to run my own simple Test Generator –
        but with my (non-existent) Java skills uncapable of doing the calibration generator myself.
        Any help with this code would be really appreciated !

          1. My problem is that I cant light up the WS2812 LEDs.
            I’ve connected the Tx from the Raspberry Pi 3 to the Rx from the Arduino Nano. The Arduino Nano is connected to the WS2812.

            When I try to send a template over to the Arduino from the Raspberry the Rx LED on the Arduino is flashing. But then there is only the first LED on the WS2812 lighting up Green. It doesent matter what template i’m using. Every time the green LED is lighting up. When I press Serial Open in the Gladiator software the Green LED is flashing once. So there is somethink going on.

            I’m not sure that I free’ed the UART.
            I would bet that the Arduino Nano dont convert the data right so that the WS2812 is showing the right LED’s.

          2. Hmm… 2 thoughts:
            – is everything ok if you flash the Nano with a stand alone sketch to drive the leds (could rule out a wiring or power issue)
            – if so, did you make sure you have configured the Pi so that it can reach high bit rates required (see blog post)

  1. Thank you for fast answering.
    First we did. And with the sketch from Great Scott the WS2812 does perfectly what he has to do. Here is the sketch:

    “#include “FastLED.h”
    #define NUM_LEDS 150
    const int dataline = 4;
    CRGB leds[NUM_LEDS];

    void setup() {
    LEDS.addLeds(leds, NUM_LEDS);

    for (int p=0;p< NUM_LEDS;p++){
    leds[p] = CRGB::Black;;}

    int serialReadBlocking() {
    while (!Serial.available()) {}

    void loop() {
    while (serialReadBlocking() != 1) {}

    for (long i=0; 1 < NUM_LEDS; i++) {
    leds[i].r = serialReadBlocking();
    leds[i].g = serialReadBlocking();
    leds[i].b = serialReadBlocking();

    So i think it is something in the Arduino sketch from glediators side , what doesn't work. Can you explain me please what "Port DDR and Port D " do ? And how must i connect them ?

    1. Well, as you may (or may not) know, most of the microcontroller’s features are performed by writing to or reading from registers (such as DDRD).
      Although, the Arduino environment abstracts most of the access to these registers and exposes the feature as high level C/C++ functions or classes. This makes code easier to read and write, but more importantly, it sets a standard layer which allows the same code to be compiled for different hardware by replacing the implementation for a given function according to the target microcontroller.
      For example, the instructions pinMode() or digitalWrite() are implemented by writing certain values to certain registers. For the standard Arduino hardware, I advise you to take a look at the following file to see for yourself:

      Now coming back to the led sketch on , as performance and precise timings are required to control the WS2812, the authors chose not to use the high level arduino functions, but instead to directly access registers to control the microcontroller’s behaviour. That code should compile and run on all Arduino variants based on the ATmega328, such as the Uno or the Nano.

      To find the right registers, as indicated by the comment just above PORTD and DDRD, you need to check the Arduino Pin Mapping, e.g. here :
      As you see, digital pin 6 is called “PD6” or short for “Port D, pin 6”. Consequently, the sketch defined that the port to use is PORTD and that the data direction register is DDRD. For more information about these registers, please refer to the microcontroller datasheet at sections 14.2.1 and 14.4 of

      Finally, as I said, for the exchange to succeed between the Raspberry Pi and the Arduino, the Pi first has to be configured to free up the serial port. It should then work at 115200 baud if you modify lines 72/73 of the sketch to enable 115200 instead of 1M bps
      Finally, to increase the refresh rate, I advise you to increase the speed to 1Mbps by reversing the above change in the sketch and by configuring the Pi to go above 115.2 kbps, as explained here:

      Kind regards,


      1. Dear Vincent,
        now the WS2812 controlled by Glediator works as intendet. We build the IR Matrix and connected it right. But how do i read this Matrix with Glediator? In your Videos i’ve seen you’re use Glediator and you’re useing a type called “Input Echo”, but i cant find a type that’s called “Input Echo”. And how did you read the LED Matrix so it looks like in your video “first if rendering”?
        Greetings, Michael

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.