[hal, wpilib] Update Addressable LED support (#8100)

This commit is contained in:
Peter Johnson
2025-07-21 21:52:10 -07:00
committed by GitHub
parent 8aa312fb6f
commit f3af50fc8e
40 changed files with 857 additions and 1104 deletions

View File

@@ -6,19 +6,18 @@ package edu.wpi.first.wpilibj;
import edu.wpi.first.hal.AddressableLEDJNI;
import edu.wpi.first.hal.HAL;
import edu.wpi.first.hal.PWMJNI;
/**
* A class for driving addressable LEDs, such as WS2812B, WS2815, and NeoPixels.
*
* <p>By default, the timing supports WS2812B and WS2815 LEDs, but is configurable using {@link
* #setBitTiming(int, int, int, int)}
*
* <p>Some LEDs use a different color order than the default GRB. The color order is configurable
* using {@link #setColorOrder(ColorOrder)}.
*
* <p>Only 1 LED driver is currently supported by the roboRIO. However, multiple LED strips can be
* connected in series and controlled from the single driver.
* <p>Up to 1024 LEDs may be controlled in total across all AddressableLED instances. A single
* global buffer is used for all instances. The start position used for LED data for the output is
* set via SetStart() and the length of the strip is set via SetLength(). Both of these default to
* zero, so multiple instances will access the same pixel data unless SetStart() is called to adjust
* the starting point.
*/
public class AddressableLED implements AutoCloseable {
/** Order that color data is sent over the wire. */
@@ -62,18 +61,21 @@ public class AddressableLED implements AutoCloseable {
}
}
private final int m_pwmHandle;
private final int m_channel;
private final int m_handle;
private int m_start;
private int m_length;
private ColorOrder m_colorOrder = ColorOrder.kGRB;
/**
* Constructs a new driver for a specific port.
* Constructs a new driver for a specific channel.
*
* @param port the output port to use (Must be a PWM header, not on MXP)
* @param channel the output channel to use
*/
public AddressableLED(int port) {
m_pwmHandle = PWMJNI.initializePWMPort(port);
m_handle = AddressableLEDJNI.initialize(m_pwmHandle);
HAL.reportUsage("IO", port, "AddressableLED");
public AddressableLED(int channel) {
m_channel = channel;
m_handle = AddressableLEDJNI.initialize(channel);
HAL.reportUsage("IO", channel, "AddressableLED");
}
@Override
@@ -81,9 +83,15 @@ public class AddressableLED implements AutoCloseable {
if (m_handle != 0) {
AddressableLEDJNI.free(m_handle);
}
if (m_pwmHandle != 0) {
PWMJNI.freePWMPort(m_pwmHandle);
}
}
/**
* Gets the output channel.
*
* @return the output channel
*/
public int getChannel() {
return m_channel;
}
/**
@@ -94,71 +102,63 @@ public class AddressableLED implements AutoCloseable {
* @param order the color order
*/
public void setColorOrder(ColorOrder order) {
AddressableLEDJNI.setColorOrder(m_handle, order.value);
m_colorOrder = order;
}
/**
* Sets the display start of the LED strip in the global buffer.
*
* @param start the strip start, in LEDs
*/
public void setStart(int start) {
m_start = start;
AddressableLEDJNI.setStart(m_handle, start);
}
/**
* Gets the display start of the LED strip in the global buffer.
*
* @return the strip start, in LEDs
*/
public int getStart() {
return m_start;
}
/**
* Sets the length of the LED strip.
*
* <p>Calling this is an expensive call, so it's best to call it once, then just update data.
*
* <p>The max length is 5460 LEDs.
*
* @param length the strip length
* @param length the strip length, in LEDs
*/
public void setLength(int length) {
m_length = length;
AddressableLEDJNI.setLength(m_handle, length);
}
/**
* Sets the LED output data.
*
* <p>If the output is enabled, this will start writing the next data cycle. It is safe to call,
* even while output is enabled.
* <p>This will write to the global buffer starting at the location set by setStart() and up to
* the length set by setLength().
*
* @param buffer the buffer to write
*/
public void setData(AddressableLEDBuffer buffer) {
AddressableLEDJNI.setData(m_handle, buffer.m_buffer);
AddressableLEDJNI.setData(
m_start,
m_colorOrder.value,
buffer.m_buffer,
0,
3 * Math.min(m_length, buffer.getLength()));
}
/**
* Sets the bit timing.
* Sets the LED output data at an arbitrary location in the global buffer.
*
* <p>By default, the driver is set up to drive WS2812B and WS2815, so nothing needs to be set for
* those.
*
* @param highTime0 high time for 0 bit in nanoseconds (default 400 ns)
* @param lowTime0 low time for 0 bit in nanoseconds (default 900 ns)
* @param highTime1 high time for 1 bit in nanoseconds (default 900 ns)
* @param lowTime1 low time for 1 bit in nanoseconds (default 600 ns)
* @param start the start location, in LEDs
* @param colorOrder the color order
* @param buffer the buffer to write
*/
public void setBitTiming(int highTime0, int lowTime0, int highTime1, int lowTime1) {
AddressableLEDJNI.setBitTiming(m_handle, highTime0, lowTime0, highTime1, lowTime1);
}
/**
* Sets the sync time.
*
* <p>The sync time is the time to hold output so LEDs enable. Default set for WS2812B and WS2815.
*
* @param syncTime the sync time in microseconds (default 280 μs)
*/
public void setSyncTime(int syncTime) {
AddressableLEDJNI.setSyncTime(m_handle, syncTime);
}
/**
* Starts the output.
*
* <p>The output writes continuously.
*/
public void start() {
AddressableLEDJNI.start(m_handle);
}
/** Stops the output. */
public void stop() {
AddressableLEDJNI.stop(m_handle);
public static void setGlobalData(int start, ColorOrder colorOrder, AddressableLEDBuffer buffer) {
AddressableLEDJNI.setData(start, colorOrder.value, buffer.m_buffer);
}
}

View File

@@ -14,7 +14,7 @@ public class AddressableLEDBuffer implements LEDReader, LEDWriter {
* @param length The length of the buffer in pixels
*/
public AddressableLEDBuffer(int length) {
m_buffer = new byte[length * 4];
m_buffer = new byte[length * 3];
}
/**
@@ -27,10 +27,9 @@ public class AddressableLEDBuffer implements LEDReader, LEDWriter {
*/
@Override
public void setRGB(int index, int r, int g, int b) {
m_buffer[index * 4] = (byte) b;
m_buffer[(index * 4) + 1] = (byte) g;
m_buffer[(index * 4) + 2] = (byte) r;
m_buffer[(index * 4) + 3] = 0;
m_buffer[index * 3] = (byte) b;
m_buffer[(index * 3) + 1] = (byte) g;
m_buffer[(index * 3) + 2] = (byte) r;
}
/**
@@ -40,7 +39,7 @@ public class AddressableLEDBuffer implements LEDReader, LEDWriter {
*/
@Override
public int getLength() {
return m_buffer.length / 4;
return m_buffer.length / 3;
}
/**
@@ -51,7 +50,7 @@ public class AddressableLEDBuffer implements LEDReader, LEDWriter {
*/
@Override
public int getRed(int index) {
return m_buffer[index * 4 + 2] & 0xFF;
return m_buffer[index * 3 + 2] & 0xFF;
}
/**
@@ -62,7 +61,7 @@ public class AddressableLEDBuffer implements LEDReader, LEDWriter {
*/
@Override
public int getGreen(int index) {
return m_buffer[index * 4 + 1] & 0xFF;
return m_buffer[index * 3 + 1] & 0xFF;
}
/**
@@ -73,7 +72,7 @@ public class AddressableLEDBuffer implements LEDReader, LEDWriter {
*/
@Override
public int getBlue(int index) {
return m_buffer[index * 4] & 0xFF;
return m_buffer[index * 3] & 0xFF;
}
/**

View File

@@ -8,15 +8,18 @@ import edu.wpi.first.hal.simulation.AddressableLEDDataJNI;
import edu.wpi.first.hal.simulation.ConstBufferCallback;
import edu.wpi.first.hal.simulation.NotifyCallback;
import edu.wpi.first.wpilibj.AddressableLED;
import java.util.NoSuchElementException;
/** Class to control a simulated addressable LED. */
public class AddressableLEDSim {
private final int m_index;
private final int m_channel;
/** Constructs for the first addressable LED. */
public AddressableLEDSim() {
m_index = 0;
/**
* Constructs an addressable LED for a specific channel.
*
* @param channel output channel
*/
public AddressableLEDSim(int channel) {
m_channel = channel;
}
/**
@@ -26,38 +29,7 @@ public class AddressableLEDSim {
*/
@SuppressWarnings("PMD.UnusedFormalParameter")
public AddressableLEDSim(AddressableLED addressableLED) {
// there is only support for a single AddressableLED, so no lookup
m_index = 0;
}
private AddressableLEDSim(int index) {
m_index = index;
}
/**
* Creates an AddressableLEDSim for a PWM channel.
*
* @param pwmChannel PWM channel
* @return Simulated object
* @throws NoSuchElementException if no AddressableLED is configured for that channel
*/
public static AddressableLEDSim createForChannel(int pwmChannel) {
int index = AddressableLEDDataJNI.findForChannel(pwmChannel);
if (index < 0) {
throw new NoSuchElementException("no addressable LED found for PWM channel " + pwmChannel);
}
return new AddressableLEDSim(index);
}
/**
* Creates an AddressableLEDSim for a simulated index. The index is incremented for each simulated
* AddressableLED.
*
* @param index simulator index
* @return Simulated object
*/
public static AddressableLEDSim createForIndex(int index) {
return new AddressableLEDSim(index);
m_channel = addressableLED.getChannel();
}
/**
@@ -68,8 +40,8 @@ public class AddressableLEDSim {
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerInitializedCallback(NotifyCallback callback, boolean initialNotify) {
int uid = AddressableLEDDataJNI.registerInitializedCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, AddressableLEDDataJNI::cancelInitializedCallback);
int uid = AddressableLEDDataJNI.registerInitializedCallback(m_channel, callback, initialNotify);
return new CallbackStore(m_channel, uid, AddressableLEDDataJNI::cancelInitializedCallback);
}
/**
@@ -78,7 +50,7 @@ public class AddressableLEDSim {
* @return true if initialized
*/
public boolean getInitialized() {
return AddressableLEDDataJNI.getInitialized(m_index);
return AddressableLEDDataJNI.getInitialized(m_channel);
}
/**
@@ -87,37 +59,37 @@ public class AddressableLEDSim {
* @param initialized the new value
*/
public void setInitialized(boolean initialized) {
AddressableLEDDataJNI.setInitialized(m_index, initialized);
AddressableLEDDataJNI.setInitialized(m_channel, initialized);
}
/**
* Register a callback on the output port.
* Register a callback on the start.
*
* @param callback the callback that will be called whenever the output port is changed
* @param callback the callback that will be called whenever the start is changed
* @param initialNotify if true, the callback will be run on the initial value
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerOutputPortCallback(NotifyCallback callback, boolean initialNotify) {
int uid = AddressableLEDDataJNI.registerOutputPortCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, AddressableLEDDataJNI::cancelOutputPortCallback);
public CallbackStore registerStartCallback(NotifyCallback callback, boolean initialNotify) {
int uid = AddressableLEDDataJNI.registerStartCallback(m_channel, callback, initialNotify);
return new CallbackStore(m_channel, uid, AddressableLEDDataJNI::cancelStartCallback);
}
/**
* Get the output port.
* Get the start.
*
* @return the output port
* @return the start
*/
public int getOutputPort() {
return AddressableLEDDataJNI.getOutputPort(m_index);
public int getStart() {
return AddressableLEDDataJNI.getStart(m_channel);
}
/**
* Change the output port.
* Change the start.
*
* @param outputPort the new output port
* @param start the new start
*/
public void setOutputPort(int outputPort) {
AddressableLEDDataJNI.setOutputPort(m_index, outputPort);
public void setStart(int start) {
AddressableLEDDataJNI.setStart(m_channel, start);
}
/**
@@ -128,8 +100,8 @@ public class AddressableLEDSim {
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerLengthCallback(NotifyCallback callback, boolean initialNotify) {
int uid = AddressableLEDDataJNI.registerLengthCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, AddressableLEDDataJNI::cancelLengthCallback);
int uid = AddressableLEDDataJNI.registerLengthCallback(m_channel, callback, initialNotify);
return new CallbackStore(m_channel, uid, AddressableLEDDataJNI::cancelLengthCallback);
}
/**
@@ -138,7 +110,7 @@ public class AddressableLEDSim {
* @return the length
*/
public int getLength() {
return AddressableLEDDataJNI.getLength(m_index);
return AddressableLEDDataJNI.getLength(m_channel);
}
/**
@@ -147,37 +119,7 @@ public class AddressableLEDSim {
* @param length the new value
*/
public void setLength(int length) {
AddressableLEDDataJNI.setLength(m_index, length);
}
/**
* Register a callback on whether the LEDs are running.
*
* @param callback the callback that will be called whenever the LED state is changed
* @param initialNotify if true, the callback will be run on the initial value
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerRunningCallback(NotifyCallback callback, boolean initialNotify) {
int uid = AddressableLEDDataJNI.registerRunningCallback(m_index, callback, initialNotify);
return new CallbackStore(m_index, uid, AddressableLEDDataJNI::cancelRunningCallback);
}
/**
* Check if the LEDs are running.
*
* @return true if they are
*/
public boolean getRunning() {
return AddressableLEDDataJNI.getRunning(m_index);
}
/**
* Change whether the LEDs are active.
*
* @param running the new value
*/
public void setRunning(boolean running) {
AddressableLEDDataJNI.setRunning(m_index, running);
AddressableLEDDataJNI.setLength(m_channel, length);
}
/**
@@ -186,9 +128,9 @@ public class AddressableLEDSim {
* @param callback the callback that will be called whenever the LED data is changed
* @return the {@link CallbackStore} object associated with this callback.
*/
public CallbackStore registerDataCallback(ConstBufferCallback callback) {
int uid = AddressableLEDDataJNI.registerDataCallback(m_index, callback);
return new CallbackStore(m_index, uid, AddressableLEDDataJNI::cancelDataCallback);
public static CallbackStore registerDataCallback(ConstBufferCallback callback) {
int uid = AddressableLEDDataJNI.registerDataCallback(callback);
return new CallbackStore(uid, AddressableLEDDataJNI::cancelDataCallback);
}
/**
@@ -197,7 +139,7 @@ public class AddressableLEDSim {
* @return the LED data
*/
public byte[] getData() {
return AddressableLEDDataJNI.getData(m_index);
return getGlobalData(getStart(), getLength());
}
/**
@@ -206,11 +148,32 @@ public class AddressableLEDSim {
* @param data the new data
*/
public void setData(byte[] data) {
AddressableLEDDataJNI.setData(m_index, data);
setGlobalData(getStart(), data);
}
/**
* Get the global LED data.
*
* @param start start, in LEDs
* @param length length, in LEDs
* @return the LED data
*/
public static byte[] getGlobalData(int start, int length) {
return AddressableLEDDataJNI.getData(start, length);
}
/**
* Change the global LED data.
*
* @param start start, in LEDs
* @param data the new data
*/
public static void setGlobalData(int start, byte[] data) {
AddressableLEDDataJNI.setData(start, data);
}
/** Reset all simulation data for this LED object. */
public void resetData() {
AddressableLEDDataJNI.resetData(m_index);
AddressableLEDDataJNI.resetData(m_channel);
}
}