Running 65fps video on 500 ws2811 LEDs using Raspberry pi 4’s DPI output

WS2811 christmas lighting, 2019 reboot

In my previous post (4 years ago), WS2811 christmas lighting using VGA, I explained how I made my garden light up with 500 full-color LEDs, using a laptop and its VGA output to drive the digital signal needed for the pixels.

At the time, there were some comments that a raspberry pi has the capability to run a kind of ‘digital VGA’ called DPI, which would get rid of my analog-to-digital conversion circuit, and simplify things a lot.

This year I took the opportunity to implement this, and added a few features while I was at it. The new setup:

  • In the past 4 years, 10 to 20 pixels died (mostly during hanging/unhanging of the pixel strings), so I’m now down to about 480 pixels
  • Replaced the clunky laptop with a single Raspberry Pi 4, using a single GPIO output pin
  • Capability to drive up to 24 x 500 pixels without dropping the framerate (subject to GPU performance – this is untested as of yet)
  • Compensation for non-perfect LED matrix layout
  • Upped the framerate to 65 fps from the previous 33 fps. The main reason for the increase was that I thought my pixels were the 400kbit versions, while actually they were the 800kbit type. Doh!
  • Lots of software improvements

My back garden playing this video:

Let’s look at each of these features one-by-one:

Broken LEDs

I moved house since my last post, so I had to remove all the strings, and re-hang them in almost identical trees in my new house. Yes, I planted the same trees in my new house for the sole purpose of running the same LED setup. Anyway, some of the lamps failed, causing the rest of the string to be inoperable.

The easiest way to fix this is to cut out the lamp, and connect the wires through to the next. This involves cutting the 3 wires left and right of the lamp, removing the lamp, and then re-soldering the wires together.

Obviously it is not very handy to take your soldering iron up into a tree, but I found a beautiful product like this one that allows you to crimp-and-solder in one using a handheld torch. Mucho easier! The joins are waterproof and the oldest have already lasted a few years outside now.

Using raspberry pi4 instead of Ubuntu laptop

This was the primary goal of this year: get rid of the large box containing a laptop sitting my garden. (Also, the laptop had died recently due to me leaving the box open and rain destroying the laptop). The raspberry pi should be able to do the job in pretty much the same way, but without the ADC converter circuit needed for the original VGA-to-WS2811 setup.

Recap of ws2811-on-vga

The orginal idea was this: To generate the WS2811 data signal for the lamps, I carefully select a VGA resolution (840×1000), and draw white and black pixels on the screen in just the right place so that the resulting VGA “red” signal exactly represents the digital data signal I need for the pixels.

In the raspberry Pi, I’m using the same general approach: select a resolution, draw white and black pixels on the screen, and then use the Raspberry Pi’s VGA output function to drive the LEDs.

Using Raspberry Pi’s DPI mode

The Raspberry Pi’s VGA output method is known as ‘DPI’. It is normally used for various LCD screens that you can attach to the Raspberry Pi, or using a simple digital-to-analog converter and 15-pin D-sub connector (aka VGA port), can be connected to traditional VGA monitors. The way it works is that the VGA signal that normally comprises of a Red, Green and Blue (analog) signals and Hsync and Vsync (digital) signals, is provided by the GPIO pins on the raspberry pi.

However, GPIO pins are digital. They can go high (3.3v) or low (0v), and that’s it. So the way that DPI works is that it uses 6 pins for RED, 6 for GREEN and 6 for BLUE. Together, each 6-pin output represents a 6-bit digital value for that channel, giving 64 ‘levels’ of RED, GREEN and BLUE. The pinouts are documented. Note that you can also run in 8-bits-per-color, or 5, or a combination of 6.

This is great for me, because I don’t actually care about intermediate levels of red. I all want is HIGH or LOW. So in actually, I am using only the red0 (pin 38) channel for my entire setup. All I do is draw white pixels, which will make all of the DPI pins go HIGH and LOW at the same time.

My current raspberry pi config.txt looks like this (using 6 bits-per-color, but I’m only really using 1 channel at the moment):

dtoverlay=vga666
enable_dpi_lcd=1
display_default_lcd=1
dpi_group=2
dpi_mode=87
dpi_timings=839 0 0 1 0 500 0 0 10 1 0 0 0 33 0 28000000 1

The dpi_timings are the same as in my previous post, using a 28Mhz dot clock, 839 pixels per line, 1 pixel horizontal retrace and 10 lines of vertical blanking. The only difference is the 500 scanlines (versus 1000 in the previous setup).

I struggled to get this to work at first, because NOOBS force-enabled my HDMI, which I had to disable too:

hdmi_force_hotplug=0

Converting to 5V logic levels

So now I have pin 38 going HIGH and LOW at the right moment to drive the pixels, but it is going high at 3.3V instead of the required 5V. Fortunately this is a simple problem to solve, and I got myself two components to convert from the 3.3V logic to 5V:

Wiring this was rather obvious, driving one side of the level shifter from ther 3.3V and the other side from the 5V, and using only one of the 4 data channels available on the converter. I could run 4 channels off this level shifter, and would need to add more (extremely cheap) level converters if I want to go beyond 4 channels.

More channels, more better

In the previous VGA-based setup, I had 3 channels: red, green and blue. Now, I can set the Raspberry Pi to output 8-bits-per-color, which means I have 24 pins going high and low when I want them to, and I could theoretically drive 24 strings of 500 lamps, all running at 65 fps.

The main limitation would come from the GPU which is doing hard work to calculate which pins should go high or low at which point in time by drawing pixels in the right place. Currently, the GPU code is rather crude, but can easily do the required work for 65 fps. If we start writing different values for each channel, the GPU will take a hit. At the same time, a lot of work can be done to optimize the whole process, and my feeling is that the processing power in the relatively fast GPU of the Raspberry Pi4 should be sufficient to run the 12000 LEDs that could be driven from here.

Disappointing performance

When I first ran the LED’s, I was rather disappointed by the picture quality. Everything seemed wrong and blurry and horizontally obscured. This was until I realized what the problem was. All that cutting out of LEDs all over the place (as I said, I had to remove 10-20 defective lamps), meant that although I had put my strings as much as I could in a perfect matrix, each line of LEDs was being shifted by a little bit due to missing lamps, while the software assumed there was still a perfect 50-lamps-per-string.

That error builds up over the 10 lines of lamps, causing some major distortion. It was nowhere near as nice as I wanted it to look.

So I decided to do something else I had wanted to do for a while: map the exact location of each lamp, and render my video based on that. This makes sure that it doesn’t actually matter how/where I hang my lamps, as long as I map their location, video output will be perfect each time!

I though about automating the process, but in the end I went with some simple software that just lights up each LED one by one, takes a frame on a webcam, and saves each to disk. I then spent half an hour clicking on the illuminated LED on the webcam images to determine the exact location of each LED. I updated the software to take this into account, and you can now provide a JSON-based layout file to the renderer so that it will render each lamp based on its exact location, and all this still at 65 fps.

So now, picture quality is better than it was on the last setup, cheaper, smaller and more powerful. Yay!

What’s next?

I decided to use the same idea to drive 14-channel, 25Mhz HUB75 RGB panels. I have some working code. An article about that one will be here soon!

 

 

 

 

Leave a comment