Skip to content

Instantly share code, notes, and snippets.

@eitch
Created November 24, 2022 13:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eitch/c4d1cb20b1d9f66e76b8dd765bff440e to your computer and use it in GitHub Desktop.
Save eitch/c4d1cb20b1d9f66e76b8dd765bff440e to your computer and use it in GitHub Desktop.
LED Strip using pi4j
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.slf4j:slf4j-api:2.0.3
//DEPS org.slf4j:slf4j-simple:2.0.3
//DEPS com.github.lalyos:jfiglet:0.0.8
//DEPS com.pi4j:pi4j-core:2.2.1
//DEPS com.pi4j:pi4j-plugin-raspberrypi:2.2.1
//DEPS com.pi4j:pi4j-plugin-pigpio:2.2.1
//DEPS com.pi4j:pi4j-plugin-linuxfs:2.2.1
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import com.pi4j.Pi4J;
import com.pi4j.context.Context;
import com.pi4j.io.spi.Spi;
import com.pi4j.io.spi.SpiConfig;
import com.pi4j.io.spi.SpiMode;
import com.pi4j.library.pigpio.PiGpio;
import com.pi4j.plugin.linuxfs.provider.i2c.LinuxFsI2CProvider;
import com.pi4j.plugin.pigpio.provider.gpio.digital.PiGpioDigitalInputProvider;
import com.pi4j.plugin.pigpio.provider.gpio.digital.PiGpioDigitalOutputProvider;
import com.pi4j.plugin.pigpio.provider.pwm.PiGpioPwmProvider;
import com.pi4j.plugin.pigpio.provider.serial.PiGpioSerialProvider;
import com.pi4j.plugin.pigpio.provider.spi.PiGpioSpiProvider;
import com.pi4j.plugin.raspberrypi.platform.RaspberryPiPlatform;
class LedStripSimple {
public static void main(String[] args) {
final var piGpio = PiGpio.newNativeInstance();
var pi4j = Pi4J.newContextBuilder()
.noAutoDetect()
.add(new RaspberryPiPlatform() {
@Override
protected String[] getProviders() {
return new String[] {};
}
})
.add(PiGpioSpiProvider.newInstance(piGpio))
.build();
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
int pixels = 30;
var ledStrip = new LedStripSimple(pi4j, pixels, 0.2);
waitForKey("Initialized");
waitForKey("AllOff");
ledStrip.allOff();
while(true) {
//waitForKey("Set led strip to ORANGE");
ledStrip.setStripColor(PixelColor.ORANGE);
ledStrip.render();
delay(200);
//waitForKey("Set led strip to PINK");
ledStrip.setStripColor(PixelColor.PINK);
ledStrip.render();
delay(200);
}
}
public static void waitForKey(String context) {
System.out.println(context + ": Waiting...");
try {
new BufferedReader(new InputStreamReader(System.in)).readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static void delay(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* Default Channel of the SPI Pins
*/
protected static final int DEFAULT_SPI_CHANNEL = 0;
/**
* Minimum time to wait for reset to occur in nanoseconds.
*/
private static final long LED_RESET_WAIT_TIME = 300_000;
/**
* The PI4J SPI
*/
protected final Spi spi;
/**
* The PI4J context
*/
protected final Context context;
/**
* The amount of all LEDs
*/
private final int numLeds;
/**
* Default frequency of a WS2812 Neopixel Strip
*/
private static final int FREQUENCY = 800_000;
/**
* between each rendering of the strip, there has to be a reset-time where nothing is written to the SPI
*/
private final long renderWaitTime;
/**
* The array of all pixels
*/
private final int[] leds;
/**
* The raw-data of all pixels, each int of LEDs is split into bits and converted to bytes to write
*/
private final byte[] pixelRaw;
/**
* the conversion from bit's of an integer to a byte we can write on the SPI
*/
private static final byte Bit_0 = (byte) 0b11000000;// 192 in Decimal
private static final byte Bit_1 = (byte) 0b11111000;// 248 in Decimal
private static final byte Bit_Reset = (byte) 0b00000000;// 0 in Decimal
/**
* Brightness value between 0 and 1
*/
private double brightness;
/**
* The time, when the last rendering happened
*/
private long lastRenderTime;
/**
* Creates a new simpleLed component with a custom BCM pin.
*
* @param pi4j
* Pi4J context
* @param numLeds
* How many LEDs are on this Strand
* @param brightness
* How bright the leds can be at max, Range 0 - 255
*/
public LedStripSimple(Context pi4j, int numLeds, double brightness) {
this(pi4j, numLeds, brightness, DEFAULT_SPI_CHANNEL);
}
/**
* Creates a new simpleLed component with a custom BCM pin.
*
* @param pi4j
* Pi4J context
* @param numLeds
* How many LEDs are on this Strand
* @param brightness
* How bright the leds can be at max, range 0 - 1
* @param channel
* which channel to use
*/
public LedStripSimple(Context pi4j, int numLeds, double brightness, int channel) {
if (numLeds < 1 || brightness < 0 || brightness > 1 || channel < 0 || channel > 1) {
throw new IllegalArgumentException("Illegal Constructor");
}
System.out.println("initialising a ledStrip with " + numLeds + " leds");
this.numLeds = numLeds;
this.leds = new int[numLeds];
this.brightness = brightness;
this.context = pi4j;
this.spi = pi4j.create(buildSpiConfig(pi4j, channel, FREQUENCY));
// The raw bytes that get sent to the ledStrip
// 3 Color channels per led, at 8 bytes each, with 2 reset bytes
pixelRaw = new byte[(3 * numLeds * 8) + 2];
// 1.25us per bit (1250ns)
renderWaitTime = numLeds * 3L * 8L * 1250L + LED_RESET_WAIT_TIME;
}
/**
* Builds a new SPI instance for the LED matrix
*
* @param pi4j
* Pi4J context
*
* @return SPI instance
*/
private SpiConfig buildSpiConfig(Context pi4j, int channel, int frequency) {
return Spi.newConfigBuilder(pi4j)
.id("SPI" + 1)
.name("LED Matrix")
.address(channel)
.mode(SpiMode.MODE_0)
.baud(8 * frequency) //bitbanging from Bit to SPI-Byte
.build();
}
/**
* Setting all LEDS off and closing the strip
*/
public void close() {
System.out.println("Turning all leds off before close");
allOff();
}
/**
* function to get the amount of the leds on the strip
*
* @return int with the amount of pixels
*/
public int getNumPixels() {
return numLeds;
}
/**
* function to get the color (as an int) of a specified led
*
* @param pixel
* which position on the ledStrip, range 0 - numLEDS-1
*
* @return the color of the specified led on the strip
*/
public int getPixelColor(int pixel) {
return leds[pixel];
}
/**
* setting the color of a specified led on the strip
*
* @param pixel
* which position on the strip, range 0 - numLEDS-1
* @param color
* the color that is set
*/
public void setPixelColor(int pixel, int color) {
leds[pixel] = color;
}
/**
* Setting all leds to the same color
*
* @param color
* the color that is set
*/
public void setStripColor(int color) {
Arrays.fill(leds, color);
}
/**
* Pixels are sent as follows: - The first transmitted pixel is the pixel closest to the transmitter. - The most
* significant bit is always sent first.
* <p>
* g7,g6,g5,g4,g3,g2,g1,g0,r7,r6,r5,r4,r3,r2,r1,r0,b7,b6,b5,b4,b3,b2,b1,b0
* \_____________________________________________________________________/ | _________________... | /
* __________________... | / / ___________________... | / / / GRB,GRB,GRB,GRB,...
*/
public void render() {
//beginning at 1, because the first byte is a reset
int counter = 1;
for (int i = 0; i < numLeds; i++) {
//Scaling the color to the max brightness
leds[i] = PixelColor.setRedComponent(leds[i], (int) (PixelColor.getRedComponent(leds[i]) * brightness));
leds[i] = PixelColor.setGreenComponent(leds[i], (int) (PixelColor.getGreenComponent(leds[i]) * brightness));
leds[i] = PixelColor.setBlueComponent(leds[i], (int) (PixelColor.getBlueComponent(leds[i]) * brightness));
// Calculating GRB from RGB
for (int j = 15; j >= 8; j--) {
if (((leds[i] >> j) & 1) == 1) {
pixelRaw[counter++] = Bit_1;
} else {
pixelRaw[counter++] = Bit_0;
}
}
for (int j = 23; j >= 16; j--) {
if (((leds[i] >> j) & 1) == 1) {
pixelRaw[counter++] = Bit_1;
} else {
pixelRaw[counter++] = Bit_0;
}
}
for (int j = 7; j >= 0; j--) {
if (((leds[i] >> j) & 1) == 1) {
pixelRaw[counter++] = Bit_1;
} else {
pixelRaw[counter++] = Bit_0;
}
}
}
// While bitbanging, the first and last byte have to be a reset
pixelRaw[0] = Bit_Reset;
pixelRaw[pixelRaw.length - 1] = Bit_Reset;
// waiting since last render time
long diff = Math.abs(System.nanoTime()) - lastRenderTime;
if (renderWaitTime - diff > 0) {
long wait = renderWaitTime - diff;
long millis = wait / 1_000_000L;
int nanos = (int) (wait % 1_000_000);
System.out.println("Waiting " + (millis) + "ms and " + nanos + "ns");
sleep(millis, nanos);
}
//writing on the PIN
spi.write(pixelRaw);
System.out.println("finished rendering");
lastRenderTime = Math.abs(System.nanoTime());
}
/**
* setting all LEDs off
*/
public void allOff() {
Arrays.fill(leds, 0);
render();
}
/**
* Utility function to sleep for the specified amount of milliseconds. An {@link InterruptedException} will be
* caught and ignored while setting the interrupt flag again.
*/
protected void sleep(long millis, int nanos) {
try {
Thread.sleep(millis, nanos);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* @return the current brightness
*/
public double getBrightness() {
return this.brightness;
}
/**
* Set the brightness of all LEDs
*
* @param brightness
* new max. brightness, range 0 - 1
*/
public void setBrightness(double brightness) {
if (brightness < 0 || brightness > 1) {
throw new IllegalArgumentException("Illegal Brightness Value. Must be between 0 and 1");
}
this.brightness = brightness;
}
public class PixelColor {
public static final int WHITE = 0xFFFFFF;
public static final int RED = 0xFF0000;
public static final int ORANGE = 0xFFA500;
public static final int YELLOW = 0xFFFF00;
public static final int GREEN = 0x00FF00;
public static final int LIGHT_BLUE = 0xadd8e6;
public static final int BLUE = 0x0000FF;
public static final int PURPLE = 0x800080;
public static final int PINK = 0xFFC0CB;
public static final int Color_COMPONENT_MAX = 0xff;
private static final int WHITE_MASK = 0xffffff;
private static final int RED_MASK = 0xff0000;
private static final int GREEN_MASK = 0x00ff00;
private static final int BLUE_MASK = 0x0000ff;
private static final int RED_OFF_MASK = 0x00ffff;
private static final int GREEN_OFF_MASK = 0xff00ff;
private static final int BLUE_OFF_MASK = 0xffff00;
/**
* validate if the color channel is in a valid range
*
* @param color
* the color which is to check
* @param value
* the color channel value
*/
public static void validateColorComponent(String color, int value) {
if (value < 0 || value >= 256) {
throw new IllegalArgumentException(
"Illegal Color value (" + value + ") for '" + color + "' - must be 0.." + Color_COMPONENT_MAX);
}
}
/**
* Get the red value of a color
*
* @param color
* provide the color
*
* @return the red value
*/
public static int getRedComponent(int color) {
return (color & RED_MASK) >> 16;
}
/**
* Set the red value of a color
*
* @param color
* provide the color
* @param red
* provide the desired red value
*
* @return the new color
*/
public static int setRedComponent(final int color, int red) {
validateColorComponent("Red", red);
int new_Color = color & RED_OFF_MASK;
new_Color |= red << 16;
return new_Color;
}
/**
* Get the green value of a color
*
* @param color
* provide the color
*
* @return the green value
*/
public static int getGreenComponent(int color) {
return (color & GREEN_MASK) >> 8;
}
/**
* Set the green value of a color
*
* @param color
* provide the color
* @param green
* provide the desired red value
*
* @return the new color
*/
public static int setGreenComponent(final int color, int green) {
validateColorComponent("Green", green);
int new_Color = color & GREEN_OFF_MASK;
new_Color |= green << 8;
return new_Color;
}
/**
* Get the blue value of a color
*
* @param color
* provide the color
*
* @return the blue value
*/
public static int getBlueComponent(int color) {
return color & BLUE_MASK;
}
/**
* Set the blue value of a color
*
* @param color
* provide the color
* @param blue
* provide the desired red value
*
* @return the new color
*/
public static int setBlueComponent(final int color, int blue) {
validateColorComponent("Blue", blue);
int new_Color = color & BLUE_OFF_MASK;
new_Color |= blue;
return new_Color;
}
}
}
@MGDSStudio
Copy link

Why counter starts from 1 (line 273) ?
int counter = 1;

Why set you the first byte of the pixelRaw array on BitReset (line 306)?
pixelRaw[0] = Bit_Reset;

@eitch
Copy link
Author

eitch commented Apr 21, 2023

Because the communication is being done using bitbanging, we need to seen reset before and after the data so that the device knows the communication is over.

@MGDSStudio
Copy link

I read the protocol of the ws2811. 50 ms is enough for the end of the transmission. I have deleted this code and my led strip works as before. Nothing was changed. But in the both cases the first LED doesn't flash. It doesn't take first 24 data bits and passes throught all the bits. Did you have the same trouble?

@eitch
Copy link
Author

eitch commented Apr 21, 2023

Hmm, we do have sometimes have an issue where a LED does not flash. Do you have the updated gist? Then i can try it out.

@MGDSStudio
Copy link

Yes, it is here. I wanted to know about your experience. My two LED-strips can not switch on the first LED. When I try to switch on the LEDs from 0 to 2, the LED-strip switches on the LEDS 1,2,3. My code uses your class without changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment