Hey, I have a LED cube with an on-board Arduino controlling (4 x 4 x 4 = 64) RGB LEDs through a constant current driver. i.e. no neopixels or dotstars.
TLDR: I would like to have slower Data Speed options available for the Output Expander to allow custom expanders to utialise the PixelBlaze’s rendering engine.
I have also attached my Arduino sketch below.
The cube’s MCU is a AtMega32u4, running at 16MHz. It can technically receive at 2mbs baud. A lot of the received data gets dropped (I’m guessing from noise causing parity issues and the MCU not being able to keep up with the amount of data coming in).
With the PixelBlaze v3 configured with:
LED type: | PixelBlaze Output Extender |
Pixels: | 64 |
Data Speed: | 2mbs |
Color Order: | RGB |
Output Expander Configuration:
Channel | Type | Start Index | count | Options |
---|---|---|---|---|
0 | WS2812 | 0 | 64 | RGB |
The other channels have default settings
After some trial and error, I realised that the buffered data I was receiving looked like the following.
You can see the magic bytes(0x5550584C - “UPXL”), followed by the channel (0x00) and type (0x01 - ws2812). Then the number of elements (0x03), colour order (0x24 - RGB) and finally the pixel count (0x4000 - 64 in little endian).
annoyingly, there is never enough pixel data to fill out the expected (3 * 64 = 192) pixels and often the CRC is missing before the next magic bytes are sent through.
5550584C0001032440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009FC56CB5550584CDF5550584C000103244000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055385550584C000103244000000000000000000000000000000000000000000000000000000000000000000000000000000000
5550584C0300000000000000000000000000000000000000000058DF555000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000505550584C00010324400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000CB025550584C00010324400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
5550584C00010324400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056FF555001032430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005550584C000103244000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FCFF5550584C000103244000000000000000000000000000000000000000000000
5550584C0001032440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000559B5550584C0001032440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005550584C000103000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050DF
5550584C000103244000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050815550584C00010324400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005550584C000103000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050385550584C000103244000
5550584C00010324400000000000000000000000000000000000000000000000000000000000000050385550584C00010324400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005550584C000103244000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056FF5550584C0001032440000000000000000000000000000000000000000000000000000000000000000000
5550584C00010324400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005550584C00010324400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056FF5550584C00010324400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055500000000000000000000000000000000000000000000000000000000000
Here is my Arduino sketch
[!NOTE]
The sketch is compiled with the build flag-D SERIAL_RX_BUFFER_SIZE=256
// PixelBlase to Cube4 adapter using PixelBlase Output Extender protocol.
//
// Cube4 - https://www.freetronics.com.au/products/cube4-4x4x4-rgb-led-cube
//
#include "Cube.h"
// ==============================================================================
// The following snippets were copied from
// https://github.com/simap/PBDriverAdapter/blob/master/src/PBDriverAdapter.cpp
enum ChannelType {
CHANNEL_WS2812 = 1, CHANNEL_DRAW_ALL, CHANNEL_APA102_DATA, CHANNEL_APA102_CLOCK
};
typedef struct {
int8_t magic[4];
uint8_t channel;
uint8_t recordType; //set channel ws2812 opts+data, draw all
} PBFrameHeader;
typedef struct {
uint8_t numElements; //0 to disable channel, usually 3 (RGB) or 4 (RGBW)
union {
struct {
uint8_t redi :2, greeni :2, bluei :2, whitei :2; //color orders, data on the line assumed to be RGB or RGBW
};
uint8_t colorOrders;
};
uint16_t pixels;
} PBWS2812Channel;
static const uint32_t crc_table[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};
uint32_t crc_update(uint32_t crc, const void *data, size_t data_len) {
const unsigned char *d = (const unsigned char *) data;
unsigned int tbl_idx;
while (data_len--) {
tbl_idx = crc ^ *d;
crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4);
tbl_idx = crc ^ (*d >> 4);
crc = crc_table[tbl_idx & 0x0f] ^ (crc >> 4);
d++;
}
return crc & 0xffffffff;
}
// ==============================================================================
Cube cube;
union {
struct {
PBFrameHeader header;
PBWS2812Channel channel;
rgb_t pixels[64];
uint32_t rx_crc;
};
uint8_t data[sizeof(PBFrameHeader) + sizeof(PBWS2812Channel) + sizeof(pixels) + sizeof(rx_crc)];
} frame;
void setup() {
memcpy(frame.header.magic, "UPXL", 4);
Serial.begin(115200);
Serial1.begin(2000000);
cube.begin(-1);
}
void loop() {
uint32_t crc = 0xffffffff;
// Discard all bytes received until the magic bytes are found.
if (!Serial1.find((uint8_t*)frame.header.magic, sizeof(frame.header.magic))){
// Timed out waiting for the magic bytes.
Serial.println("No data received from PixelBlaze.");
return;
}
// Read in the rest of the frame header.
Serial1.readBytes(&frame.data[4], sizeof(frame.header) - 4);
if(frame.header.channel == 0xff && frame.header.recordType == CHANNEL_DRAW_ALL) {
Serial1.readBytes((uint8_t *) &frame.rx_crc, sizeof(frame.rx_crc));
// Only calculate teh checksum on the frame's header.
crc = 0xFFFFFFFF;
crc = crc_update(crc, &frame.header, sizeof(frame.header));
crc = crc ^0xffffffff;
if (crc != frame.rx_crc){
return;
}
cube.set(0, 0, 0, frame.pixels[0]);
for(uint16_t i = 1; i < 64; i++){
cube.next(frame.pixels[i]);
}
return;
} else if(frame.header.channel != 0 || frame.header.recordType != CHANNEL_WS2812){
return;
}
memset(frame.pixels, 0, sizeof(frame.pixels));
// Raed in the rest of the frame.
size_t offset = sizeof(frame.header);
while (offset < sizeof(frame.data)) {
offset += Serial1.readBytes((uint8_t *) &frame.data[offset], sizeof(frame) - offset);
}
if (frame.channel.numElements != 3 || frame.channel.pixels != 64){
return;
}
crc = 0xFFFFFFFF;
crc = crc_update(crc, &frame.data, sizeof(frame) - 4);
crc = crc ^ 0xFFFFFFFF;
// Clear out the received pixels if the checksum is invalid.
if (crc != frame.rx_crc) {
// Reset the pixels in case a DRAW ALL frame is sent.
memset(frame.pixels, 0, sizeof(frame.pixels));
return;
}
}