Controlling LED strips with Java

One of the most “fancy” electronic components is definitely a LED strip. It’s really cool to control a long strip of lights with only a few lines of code… But, there is a problem. The timing of the signals is crucial to reliably control these strips. Both Python and Java on a Raspberry Pi can struggle with these timings as they are running on Linux, a non-real-time operating system. So, for instance, pauses in the garbage collection of the Java virtual machine, or any glitch in the operating system can cause unexpected effects on the LED strips. That’s why in most projects, a microcontroller (Arduino, Raspberry Pi Pico, ESP32,…) is used to drive the LED strip. In my search for a good solution to use LED strips with Java, I stumbled on the Pixelblaze Output Expander. This small device is controlled through a serial interface, and handles the control of the LED strip. As it turns out, this is a perfect solution to offload the timing-critical operations from the Raspberry Pi and have reliable output on a LED strip.

Video Explanation

Based on the created example code and documentation on the Pi4J website, Robert von Burg and I did a live stream to explain the approach and walk through the code.

How LED Strips Work

The LED strips used in the examples of this post, contain LEDs of the WS2812B type, which means they have SMD 5050-LEDs with an integrated IC-driver, so they can be addressed separately. To control such a LED strip, you need to send it a byte array with RGB (red/green/blue) values. Let’s use an example for a strip with three LEDs, on which you want to show:

  1. Full red (RGB #FF0000)
  2. Not full white (RGB #A6A6A6)
  3. Full blue (RGB #0000FF)

Although you may be used to the color ordering RGB for e.g. CSS or in drawing applications, LED strips actually use GRB.

This means, we need a byte array with 9 values:

Array index 0 1 2 3 4 5 6 7 8
LED 1 2 3
G, R, B #00 #FF #00 #A6 #A6 #A6 #00 #00 #FF

The IC of the first led will take the first 3 values from the byte array and output the remaining part to the second LED:

Array index 0 1 2 3 4 5
LED 2 3
G, R, B #A6 #A6 #A6 #00 #00 #FF

Again, the second LED will take the first 3 values and output the remaining part:

Array index 0 1 2
LED 3
G, R, B #00 #00 #FF

For this system to work correctly, a strict timing of the data signal is needed. Otherwise the IC will handle parts of the data as being a new package, and you’ll get unexpected results.

About the Pixelblaze

The Pixelblaze is a product created by ElectroMage, with the aim to bring the creation of art, costumes, celebrations, and maker projects to a whole new level. Their two basic components, the “Pixelblaze V3 Standard - WiFi LED Controller” and “Pixelblaze V3 Pico - Tiny WiFi LED Controller” are Wi-Fi-enabled, live-codable LED controllers with a web-based development environment. Fifty light patterns are included, and over 200 more are shared in a community library.

But a third product by ElectroMage is the one that caught my attention to use with a Java application: the “Pixelblaze Output Expander” (PBOE). It is designed to expand output options for the other two Pixelblaze boards, but it can also be driven by just about any other microcontroller or computer over serial UART. The ideal solution for an experiment with Java, JBang and the Raspberry Pi!

Wiring

To control the PBOE, we actually only need one wire to be connected to the Raspberry Pi (RPi) for the serial data to be sent from RPi to PBOE. But we must not forget one important fact: a LED strip with a lot of LEDs will require more power than the RPi can supply. So we need an external power supply that is dimensioned correctly to provide all the power needed for the strip when all LEDs are at maximum level. As a guideline, 0,1W ~ 0,3W/LED is required per LED. For a strip with 60 LEDs/meter, that means 18W/meter, or a total of 90W for a 5-meter strip. That’s almost 20A at 5V! So, make sure to use an external power supply to power LED strips!!! The 5V of the Raspberry Pi is passed straight through from the USB and the current is therefore limited.

Connections between RPi, PBOE, and power supply:

Connections between PBOE and LED strip:

The following pictures show you the connections with one LED strip on channel 0 of the PBOE:

Sample Application

Although the sources used in this post are part of the Pi4J GitHub repository, no Pi4J library is used in this example! Remember, the Pi4J project is more than just a library, it wants to be the starting point for everyone who wants to experiment with Java and electronics on the Raspberry Pi.

JBang

To simplify the use of the examples, JBang is used, so no full Maven or Gradle project is needed, and the code can be executed with a single command. On the Pi4J website, a full description is provided with more information about JBang and how to install it on a Raspberry Pi.

PixelBlazeOutputExpanderHelper

The various examples share the same helper-class to send commands to the PBOE. This class takes care of the serial communication between the RPi and the PBOE, hiding most of the “complexity” and provides a few public methods to clear a LED strip or send a byte array with colors.

public PixelBlazeOutputExpanderHelper(String address) {
    System.out.println("Initializing serial");
    adapter = new ExpanderDataWriteAdapter(address);
}

public void sendAllOff(int channel, int numberOfLeds) {
    ...
}

public void sendColors(int channel, byte[] rgbPerPixel, boolean debug) {
    ...
}

public void closePort() {
    adapter.closePort();
}

PixelblazeOutputExpander Example Application

This is the main demo application that is configured to control three different LED strips:

JBang Specific Code

To configure our Java-file to be executable with JBang, we need these extra lines at the start of the file to define the execution with JBang, add the jSerialComm dependency, and include the helper class.

///usr/bin/env jbang "$0" "$@" ; exit $?

//DEPS com.fazecast:jSerialComm:2.10.2
//SOURCES helper/PixelBlazeOutputExpanderHelper.java

Highlight the LEDs One By One

When we look at one of the methods used in this example, we can see how the data is being created to generate a one-by-one effect for a given color mix.

The following approach is taken:

private static void sendOneByOne(int channel, int numberOfLeds, 
    byte red, byte green, byte blue) throws InterruptedException {

    System.out.println("One by one on channel " + channel);
    
    for (int i = 0; i < numberOfLeds; i++) {
        byte[] data = new byte[numberOfLeds * BYTES_PER_PIXEL];
        data[i * BYTES_PER_PIXEL] = red;
        data[(i * BYTES_PER_PIXEL) + 1] = green;
        data[(i * BYTES_PER_PIXEL) + 2] = blue;
        helper.sendColors(channel, data, false);
        Thread.sleep(50);
    }
}

Main Method

I invite you to take a look at the full source code, and will only highlight a few parts here, described in the code comments.

public class PixelblazeOutputExpander {

    // Each LED expects three values for RGB
    // When using RGBW LEDs, this value must be changed to 4
    private static final int BYTES_PER_PIXEL = 3;

    // Channels on which the strips are connected
    private static final int CHANNEL_STRIP_SHORT = 0;
    private static final int CHANNEL_STRIP_LONG = 1;
    private static final int CHANNEL_MATRIX = 2;

    // The length of each strip
    private static final int NUMBER_OF_LEDS_STRIP_SHORT = 11;
    private static final int NUMBER_OF_LEDS_STRIP_LONG = 300; // 60LEDs/m
    private static final int NUMBER_OF_LEDS_MATRIX = 256; // 8*32

    private static PixelBlazeOutputExpanderHelper helper;

    public static void main(String[] args) throws InterruptedException {
        // Initialize the helper by providing the serial address
        helper = new PixelBlazeOutputExpanderHelper("/dev/ttyS0");

        // All off, short LED strip
        helper.sendAllOff(CHANNEL_STRIP_SHORT, 
            NUMBER_OF_LEDS_STRIP_SHORT);
        helper.sendAllOff(CHANNEL_STRIP_LONG, 
            NUMBER_OF_LEDS_STRIP_LONG);
        helper.sendAllOff(CHANNEL_MATRIX, 
            NUMBER_OF_LEDS_MATRIX);
        Thread.sleep(500);

        // One by one red, short LED strip
        sendOneByOne(CHANNEL_STRIP_SHORT, NUMBER_OF_LEDS_STRIP_SHORT, 
            (byte) 0xff, (byte) 0x00, (byte) 0x00);

        // Fill strip with random colors, short LED strip
        Random rd = new Random();
        for (int i = 0; i < 5; i++) {
            byte[] random = new byte[NUMBER_OF_LEDS_STRIP_SHORT 
                * BYTES_PER_PIXEL];
            rd.nextBytes(random);
            helper.sendColors(CHANNEL_STRIP_SHORT, random, false);
            Thread.sleep(1000);
        }

        ...

        helper.closePort();
    }
}

Executing the Application

No sudo is needed for serial communication with the jSerialComm library, so the application can be started in the terminal with the following command and gives you the output of what is happening with the strips:

$ jbang PixelblazeOutputExpander.java 
Initializing serial
Opening /dev/ttyS0
All off on channel 0 with 11
All off on channel 1 with 300
All off on channel 2 with 256
One by one on channel 0, will take 2s
All red
All green
All blue
Random colors 1
..
All red
All off on channel 0 with 11
...
One by one on channel 1, will take 6s
...
All red on LED strip on channel 1
All off on channel 1 with 300
All red on LED matrix on channel 2
...
Closing /dev/ttyS0

Conclusion

This is only a short introduction, the Pi4J website offers more detailed information and extra examples, including the use of LED matrixes and a JavaFX user interface to define the colors being displayed on a LED strip.