are these Neopixel-type LED strips you are talking about ?
Controlling an LED with variable colours using Bela and SuperCollider
- Edited
hi @giuliomoro, i was wondering if you could share the solutions you mentioned above. I couldn't find any good tips for driving a NeoPixel-like RGB LED yet. Thank you!
- Edited
Here's some example code. This assumes BelaMini is used and the neopixels are driven from pin P2.25. To enable that you'll need to run config-pin P2.25 spi
after each reboot ( this can be automated later if needed).
#include <Bela.h>
#include <stdio.h>
#include <libraries/Spi/Spi.h>
#include <cmath>
#include <string.h>
Spi spi;
const unsigned int kSpiClock = 12000000;
const unsigned int kSpiMinTransferSize = 160; // min number of bytes to trigger DMA transfer
const unsigned int kSpiMaxTransferSize = 4096; // max number of bytes to be sent at once
const unsigned int kSpiWordLength = 32;
const uint8_t kSpiMsbFirst = 1;
const float kSpiInterWordTime = 200;
const float kSpiPeriodNs = 1000000000.0 / kSpiClock;
const uint8_t kBitsPerByte = 8;
// The specs (WS2812B-2020 datasheet) say:
// T0H 0 code, high voltage time 220ns~380ns
// T1H 1 code, high voltage time 580ns~1µs
// T0L 0 code, low voltage time 580ns~1µs
// T1L 1 code, low voltage time 220ns~420ns
// RES Frame unit, low voltage time >280µs
// WS2812B v1.0 on the other hand says:
// TH+TL = 1.25µs ± 600ns
// T0H 0 code, high voltage time 0.4us ±150ns
// T1H 1 code, high voltage time 0.8us ±150ns
// T0L 0 code, low voltage time 0.85us ±150ns
// T1L 1 code, low voltage time 0.45us ±150ns
// More recent datasheets say
// We pick below some conservative values for WS2812B
typedef struct {
float min;
float max;
} T_t;
const T_t TH[2] = {
{.min = 200, .max = 400}, // T0H
{.min = 700, .max = 900}, // T1H
};
const T_t TL[2] = {
{.min = 750, .max = 950}, // T0L
{.min = 350, .max = 550}, // T1L
};
// Additionally, there is need for adding a long enough "0" output before we start clocking out data.
// The SPI peripheral in use seems to always set the MOSI high when not clocking out data, so we have
// to start clocking out some zeros first.
// Skipping this will result in 1-bit offset in the bit shifting. If you see random flashes
// in your LEDs strip and/or the first LED remaning on when set to off
// and/or the others have the colors "slightly" wrong, this could be the reason.
// This could be avoided if the MOSI was to be LOW when idle, but this would require
// an external transistor to invert it.
const double kSpiLeadingZerosNs = 10000; // determined empirically
const unsigned int kSpiLeadingZeros = std::ceil((kSpiLeadingZerosNs / kSpiPeriodNs));
bool readBitField(uint8_t* data, uint32_t offset) {
uint8_t position = offset % kBitsPerByte;
unsigned int i = offset / kBitsPerByte;
return data[i] & (1 << position);
}
void writeBitField(uint8_t* data, uint32_t offset, bool value) {
uint8_t position = offset % kBitsPerByte;
unsigned int i = offset / kBitsPerByte;
if(value)
data[i] |= 1 << position;
else
data[i] &= ~(1 << position);
}
ssize_t rgbToClk(uint8_t* rgb, size_t numRgb, uint8_t* out, size_t numOut)
{
memset(out, 0, numOut * sizeof(out[0]));
// writeBitField(out, 0, 1);
uint32_t clk = 0;
// emsure we have complete RGB sets
numRgb = numRgb - (numRgb % 3);
for(uint32_t inBit = 0; inBit < numRgb * kBitsPerByte; ++inBit) {
// data comes in as RGB but needs to be shuffled into GRB for the WS2812B
uint32_t actualInBit;
uint8_t remainder = inBit % (3 * kBitsPerByte);
if(remainder < kBitsPerByte)
actualInBit = inBit + kBitsPerByte;
else if(remainder < kBitsPerByte * 2)
actualInBit = inBit - kBitsPerByte;
else
actualInBit = inBit;
bool value = readBitField(rgb, actualInBit);
const T_t* Ts[2] = {TH, TL};
for(unsigned int t = 0; t < 2; ++t) {
const T_t* T = Ts[t];
bool highLow = (0 == t) ? true : false;
float time = 0;
while(time < T[value].min) {
bool wordEnd = (kSpiWordLength - 1 == clk);
writeBitField(out, clk, highLow);
time += kSpiPeriodNs;
if(wordEnd)
time += kSpiInterWordTime;
++clk;
if(clk / kBitsPerByte > numOut)
return -1;
}
if(time > T[value].max) {
printf("Error: expected %f but got %f\n", T[value].max, time);
return 0;
}
}
}
ssize_t numBytes = ((clk + kSpiWordLength - 1) / kSpiWordLength) * kSpiWordLength / kBitsPerByte; // round up to the next word
if(numBytes > numOut) // just in case numOut was not a multiple, we now no longer fit
return -1;
return numBytes;
}
void sendSpi(uint8_t* data, size_t length)
{
if(length < kSpiMinTransferSize)
length = kSpiMinTransferSize;
if (spi.transfer(data, NULL, length) == 0)
printf("SPI: Transaction Complete. Sent %d bytes\n", length);
else
printf("SPI: Transaction Failed\r\n");
}
#include <vector>
// rgb triplets, can be resized to match number of LEDs _before_ `task()` starts.
// after that, its content (but not size!) can be changed from anywhere
std::vector<uint8_t> colors = {{
0x00, 0xC0, 0x00,
0xC0, 0x00, 0x00,
0x00, 0x00, 0xC0,
0x00, 0x00, 0x00,
}};
const uint8_t kBytesPerRgb = 3;
void task(void*) {
std::vector<uint8_t> data(kSpiMaxTransferSize);
// uint8_t kNumColors = colors.size() / kBytesPerRgb;
uint8_t kNumLeds = 16;
std::vector<uint8_t> rgb(kNumLeds * kBytesPerRgb);
for (unsigned int n = 0; n < 100 && !Bela_stopRequested(); ++n) {
for(unsigned int l = 0; l < kNumLeds; ++l) {
printf("RGB[%d]: ", l);
for(unsigned int b = 0; b < kBytesPerRgb; ++b) {
uint8_t val = colors[(( n + l) * kBytesPerRgb + b) % colors.size()];;
rgb[l * kBytesPerRgb + b] = val;
printf("%#02x ", val);
}
printf("\n");
}
printf("data: ");
for(unsigned int n = 0; n < kNumLeds * kBytesPerRgb; ++n) {
if(0 == (n % 3))
printf("\n");
printf("%#02x ", rgb[n]);
}
printf("\n");
ssize_t len = kSpiLeadingZeros + rgbToClk(rgb.data(), rgb.size(), data.data() + kSpiLeadingZeros, data.size() - kSpiLeadingZeros);
if(len < 0) {
fprintf(stderr, "Error: message too long\n");
break;
} else {
printf("Data is %d\n", len);
}
// print data as it will be sent out
unsigned int k = 0;
for(unsigned int n = 0; n < len; ++n) {
for(unsigned int c = 0; c < kBitsPerByte; ++c) {
printf("%d", (bool)(data[n] & (1 << c)));
++k;
if(0 == (k % kBitsPerByte))
printf(" ");
if(0 == (k % kSpiWordLength))
printf("| ");
}
}
printf("\n");
// format data
// SPI transmits the most significant byte first ("right justified in each word")
// so if we have more than 1 byte per word we need to shuffle them around so that
// they are output in the correct order
unsigned int bytesPerWord = kSpiWordLength / kBitsPerByte;
for(unsigned int n = 0; n < len; n += bytesPerWord) {
for(unsigned int b = 0; b < bytesPerWord / 2; ++b) {
uint8_t tmp = data[n + b];
data[n + b] = data[n + bytesPerWord - 1 - b];
data[n + bytesPerWord - 1 - b] = tmp;
}
}
if(kSpiMsbFirst) {
for(unsigned int n = 0; n < len; ++n)
data[n] = __builtin_bitreverse8(data[n]);
}
sendSpi(data.data(), data.size());
usleep(100000);
}
}
bool setup(BelaContext *context, void *userData)
{
spi.setup({.device = "/dev/spidev2.1", // Device to open
.speed = kSpiClock, // Clock speed in Hz
.delay = 0, // Delay after last transfer before deselecting the device, won't matter
.numBits = kSpiWordLength, // No. of bits per transaction word,
.mode = Spi::MODE3 // SPI mode
});
size_t numLeds = 12; // TODO: adjust depending on num of leds in use
colors.resize(kBytesPerRgb * numLeds);
Bela_runAuxiliaryTask(task, 90);
return true;
}
void render(BelaContext *context, void *userData)
{
// TODO: set elements of `colors`
}
void cleanup(BelaContext *context, void *userData)
{}
wow, thanks a lot for the example code @giuliomoro!
I am using the BelaMini and also uploaded your SPI_Library to the project but I am still getting some errors.
First of all I had change #include <libraries/SPI/SPI.h> to #include <SPI.h> to use the library but I've still got the following error messages:
unknown type name 'SPI' column: 1, line: 6
use of undeclared identifier 'SPI' column: 3, line: 208
use of undeclared identifier 'SPI' column: 3, line: 211
I checked the example code in the SPI_Library and changed SPI::SS_LOW to SPI_SS_LOW and SPI::MODE3 to SPI_MODE3 so that they are recognized.
Now I get "use of undeclared identifier" for spi.transfer and spi.setup.
Any idea how to fix these?
Right, the code I posted was for an older version of the Spi
library. I now tested and amended it and it should work out of the box on your board.
now it runs perfectly, thank you!
hi, sorry for bothering again. I tried to "set elements of colors" but I don't have too much experience with C++. Is there any chance that you include an example in the code which shows how to do that? I would really appreciate it.
I would then try and set the color of the LED from PD. To do that I can follow the OLED example to receive OSC messages from PD.
the global variable colors
contains triplets of colors that are sent to each LED. When you start the program, the variable's content looks like this:
std::vector<uint8_t> colors = {{
0x00, 0xC0, 0x00,
0xC0, 0x00, 0x00,
0x00, 0x00, 0xC0,
0x00, 0x00, 0x00,
}};
So it drives four LED: green, red, blue, dark.
If you have more than 4 LEDs, edit this:
size_t numLeds = 12; // TODO: adjust depending on num of leds in use
Once you have adjusted that, from within setup()
or render()
or even task()
you can adjust the colors of the LEDs. This code will, for example, have all RED leds with increasing brightness
for(unsigned int n = 0; n < colors.size() / kBytesPerRgb; n++)
{
float relative = n / float(colors.size() / kBytesPerRgb);
colors[n * kBytesPerRgb] = 0xff * relative; // LED n, red
colors[n * kBytesPerRgb + 1] = 0; // LED n, green
colors[n * kBytesPerRgb + 2] = 0; // LED n, blue
}
wow, you have the best support. Thanks a lot!
Thanks for the code. After some adjustments for the leds i have, it work fine.
i have encapsulate them in a class for easy usage :
/***********************************************************
LedStripSpi : send Strip Led (like WS2812) data
with the SPI MOSI output
This assumes BelaMini is used and the neopixels are driven from pin P2.25.
To enable that you'll need to run 'config-pin P2.25 spi' after each reboot
************************************************************/
// The specs (WS2812B-2020 datasheet) say:
// T0H 0 code, high voltage time 220ns~380ns
// T1H 1 code, high voltage time 580ns~1µs
// T0L 0 code, low voltage time 580ns~1µs
// T1L 1 code, low voltage time 220ns~420ns
// RES Frame unit, low voltage time >280µs
// WS2812B v1.0 on the other hand says:
// TH+TL = 1.25µs ± 600ns
// T0H 0 code, high voltage time 0.4us ±150ns
// T1H 1 code, high voltage time 0.8us ±150ns
// T0L 0 code, low voltage time 0.85us ±150ns
// T1L 1 code, low voltage time 0.45us ±150ns
// More recent datasheets say
// For led YF923-F5-F8 (aliexpress) :
// T0H 0 code, high voltage time 0.3us ±150ns
// T1H 1 code, high voltage time 0.6us ±150ns
// T0L 0 code, low voltage time 0.6us ±150ns
// T1L 1 code, low voltage time 0.3us ±150ns
// RES Frame unit, low voltage time >50µs
#ifndef LEDSTRIPSPI_H
#define LEDSTRIPSPI_H
#include <Bela.h>
#include <stdio.h>
#include <libraries/Spi/Spi.h>
#include <cmath>
#include <string.h>
class LedStripSpi
{
public:
enum LedType{
WS2812B = 0,
YF923F5F8
};
protected:
const unsigned int kSpiClock = 12000000;
const unsigned int kSpiMinTransferSize = 160; // min number of bytes to trigger DMA transfer
const unsigned int kSpiMaxTransferSize = 4096; // max number of bytes to be sent at once
const uint8_t kSpiWordLength = 32;
const uint8_t kSpiMsbFirst = 1;
const float kSpiPeriodNs = 1000000000.0 / kSpiClock;
const uint8_t kBitsPerByte = 8;
// Additionally, there is need for adding a long enough "0" output before we start clocking out data.
// The SPI peripheral in use seems to always set the MOSI high when not clocking out data, so we have
// to start clocking out some zeros first.
// Skipping this will result in 1-bit offset in the bit shifting. If you see random flashes
// in your LEDs strip and/or the first LED remaning on when set to off
// and/or the others have the colors "slightly" wrong, this could be the reason.
// This could be avoided if the MOSI was to be LOW when idle, but this would require
// an external transistor to invert it.
const double kSpiLeadingZerosNs = 10000; // determined empirically
const unsigned int kSpiLeadingZeros = std::ceil((kSpiLeadingZerosNs / kSpiPeriodNs));
const int8_t kBytesPerRgb = 3;
typedef struct {
float min;
float max;
} T_t;
bool fReverseByte;
bool fGRB_Mode;
float fSpiInterWordTime;
T_t fTH[2];
T_t fTL[2];
Spi fSpi;
Spi::Mode fSpiMode;
unsigned int fNbrLeds;
uint8_t* fLedBuffers[2];
uint8_t* fDataBuffer;
uint8_t fCurrentSendingLedBuffer;
uint8_t fCurrentEditingLedBuffer;
bool fBufferSwitched;
void SetType(LedType type)
{
// We pick below some conservative values for Led Type
switch(type) {
case WS2812B:
fTH[0].min = 200;
fTH[0].max = 400; // T0H
fTH[1].min = 700;
fTH[1].max = 900; // T1H
fTL[0].min = 750;
fTL[0].max = 950; // T0L
fTL[1].min = 350;
fTL[1].max = 550; // T1L
fReverseByte = false;
fGRB_Mode = true;
fSpiInterWordTime = 200;
fSpiMode = Spi::MODE3;
break;
case YF923F5F8:
fTH[0].min = 200;
fTH[0].max = 450; // T0H
fTH[1].min = 500;
fTH[1].max = 750; // T1H
fTL[0].min = 500;
fTL[0].max = 750; // T0L
fTL[1].min = 200;
fTL[1].max = 450; // T1L
fReverseByte = true;
fGRB_Mode = false;
fSpiInterWordTime = 0;
fSpiMode = Spi::MODE2;
break;
default:
break;
};
}
bool readBitField(uint8_t* data, uint32_t offset) {
uint8_t position = offset % kBitsPerByte;
unsigned int i = offset / kBitsPerByte;
return data[i] & (1 << position);
}
void writeBitField(uint8_t* data, uint32_t offset, bool value) {
uint8_t position = offset % kBitsPerByte;
unsigned int i = offset / kBitsPerByte;
if(value)
data[i] |= 1 << position;
else
data[i] &= ~(1 << position);
}
ssize_t rgbToClk(uint8_t* rgb, size_t numRgb, uint8_t* out, size_t numOut)
{
memset(out, 0, numOut * sizeof(out[0]));
// writeBitField(out, 0, 1);
uint32_t clk = 0;
// emsure we have complete RGB sets
numRgb = numRgb - (numRgb % 3);
for(uint32_t inBit = 0; inBit < numRgb * kBitsPerByte; ++inBit) {
uint32_t actualInBit;
uint8_t remainder = inBit % (3 * kBitsPerByte);
if(fGRB_Mode) { // data comes in as RGB but needs to be shuffled into GRB for the WS2812B
if(remainder < kBitsPerByte)
actualInBit = inBit + kBitsPerByte;
else if(remainder < kBitsPerByte * 2)
actualInBit = inBit - kBitsPerByte;
else
actualInBit = inBit;
} else {
actualInBit = inBit;
}
bool value = readBitField(rgb, actualInBit);
const T_t* Ts[2] = {fTH, fTL};
for(unsigned int t = 0; t < 2; ++t) {
const T_t* T = Ts[t];
bool highLow = (0 == t) ? true : false;
float time = 0;
while(time < T[value].min) {
bool wordEnd = (kSpiWordLength - 1 == clk);
writeBitField(out, clk, highLow);
time += kSpiPeriodNs;
if(wordEnd)
time += fSpiInterWordTime;
++clk;
if(clk / kBitsPerByte > numOut)
return -1;
}
if(time > T[value].max) {
printf("Error: expected %f but got %f\n", T[value].max, time);
return 0;
}
}
}
ssize_t numBytes = ((clk + kSpiWordLength - 1) / kSpiWordLength) * kSpiWordLength / kBitsPerByte; // round up to the next word
if(numBytes > numOut) // just in case numOut was not a multiple, we now no longer fit
return -1;
return numBytes;
}
void SendSpi(uint8_t* data, size_t length)
{
if(length < kSpiMinTransferSize)
length = kSpiMinTransferSize;
if (fSpi.transfer(data, NULL, length) != 0)
printf("SPI: Transaction Failed\r\n");
}
void SendBufferToLeds(uint8_t* buffer, ssize_t buffersize, uint8_t* out, ssize_t outsize)
{
ssize_t len = kSpiLeadingZeros + rgbToClk(buffer, buffersize, out + kSpiLeadingZeros, outsize - kSpiLeadingZeros);
if(len < 0) {
fprintf(stderr, "Error: message too long\n");
return;
}
unsigned int bytesPerWord = kSpiWordLength / kBitsPerByte;
for(unsigned int n = 0; n < len; n += bytesPerWord) {
for(unsigned int b = 0; b < bytesPerWord / 2; ++b) {
uint8_t tmp = out[n + b];
out[n + b] = out[n + bytesPerWord - 1 - b];
out[n + bytesPerWord - 1 - b] = tmp;
}
}
if(kSpiMsbFirst) {
for(unsigned int n = 0; n < len; ++n)
out[n] = __builtin_bitreverse8(out[n]);
}
SendSpi(out, outsize);
}
public:
LedStripSpi()
{
SetType(WS2812B);
fNbrLeds = 0;
fDataBuffer = new uint8_t[kSpiMaxTransferSize];
fCurrentSendingLedBuffer = 1;
fCurrentEditingLedBuffer = 0;
fLedBuffers[0] = NULL;
fLedBuffers[1] = NULL;
fBufferSwitched = false;
}
bool Init(LedType ledtype, unsigned int nbrleds)
{
if(fNbrLeds > 0) // we don't call init sevelal times
return false;
SetType(ledtype);
if(fSpi.setup({ .device = "/dev/spidev2.1", // Device to open
.speed = kSpiClock, // Clock speed in Hz
.delay = 0, // Delay after last transfer before deselecting the device, won't matter
.numBits = kSpiWordLength, // No. of bits per transaction word,
.mode = fSpiMode // SPI mode
}) != 0) {
printf("SPI: Initialisation Failed\r\n");
return false;
}
// Led buffers initialisation
fNbrLeds = nbrleds;
ssize_t buffersize = fNbrLeds*kBytesPerRgb;
fLedBuffers[0] = new uint8_t[buffersize];
memset(fLedBuffers[0] ,0,buffersize);
fLedBuffers[1] = new uint8_t[buffersize];
memset(fLedBuffers[1] ,0,buffersize);
return true;
}
void Draw()
{
if(!fDataBuffer || !fLedBuffers[fCurrentSendingLedBuffer] || !fBufferSwitched) //draw only if new buffer
return;
fBufferSwitched = false;
uint8_t* buffer = fLedBuffers[fCurrentSendingLedBuffer];
SendBufferToLeds(buffer, fNbrLeds*kBytesPerRgb, fDataBuffer, kSpiMaxTransferSize);
}
void Begin()
{
memset(fLedBuffers[fCurrentEditingLedBuffer] ,0,fNbrLeds*kBytesPerRgb);
}
void SetLedColor(int idxled, uint8_t red, uint8_t green, uint8_t blue)
{
if(idxled >= fNbrLeds)
return;
uint8_t* buffer = fLedBuffers[fCurrentEditingLedBuffer];
buffer[idxled * kBytesPerRgb] = (fReverseByte == true) ? __builtin_bitreverse8(red) : red;
buffer[idxled * kBytesPerRgb + 1] = (fReverseByte == true) ? __builtin_bitreverse8(green) : green;
buffer[idxled * kBytesPerRgb + 2] = (fReverseByte == true) ? __builtin_bitreverse8(blue) : blue;
}
void SetAllLedsBlack()
{
memset(fLedBuffers[fCurrentEditingLedBuffer] ,0,fNbrLeds*kBytesPerRgb);
}
void End()
{
uint8_t tmp = fCurrentEditingLedBuffer;
fCurrentEditingLedBuffer = fCurrentSendingLedBuffer;
fCurrentSendingLedBuffer = tmp;
fBufferSwitched = true;
}
~LedStripSpi()
{
delete fLedBuffers[0];
fLedBuffers[0] = NULL;
delete fLedBuffers[1];
fLedBuffers[1] = NULL;
delete fDataBuffer;
fDataBuffer = NULL;
}
};
#endif
here a sample usage with a trill bar :
#include <Bela.h>
#include "ledstripspi.h"
#include <libraries/Trill/Trill.h>
Trill gTouchSensor;
LedStripSpi gLedStrip;
unsigned int gNbrLeds = 12;
unsigned int gRefreshRate = 50000;
unsigned int gTrillRefreshRate = 100000;
void LedStripTask(void*)
{
while(!Bela_stopRequested()) {
gLedStrip.Draw();
usleep(gRefreshRate);
}
//Back to black
gLedStrip.Begin();
gLedStrip.End();
gLedStrip.Draw();
}
// set the leds colors in funtion of the position (0 to 1)
void RenderBarGraph(float position)
{
position = position * gNbrLeds;
float luminosity = 1;
for(int i = 0; i < gNbrLeds; i++) {
if((position - i) < 1)
luminosity = position - i;
else if(position < i)
luminosity = 0 ;
else luminosity = 1;
gLedStrip.SetLedColor( i,
0x20*(float) (i/ (float) gNbrLeds)*luminosity,
0x14*(float) ((gNbrLeds-i)/ (float) gNbrLeds)*luminosity,
0*luminosity);
}
}
//Take the position of the trill bar and render the bar graph
void TrillBarTask(void*)
{
float Position;
while(!Bela_stopRequested()) {
gTouchSensor.readI2C();
int ActiveTouches = gTouchSensor.getNumTouches();
if( ActiveTouches > 0) {
Position = gTouchSensor.compoundTouchLocation();
gLedStrip.Begin();
RenderBarGraph(Position);
gLedStrip.End();
}
usleep(gTrillRefreshRate);
}
}
bool setup(BelaContext *context, void *userData)
{
if(!gLedStrip.Init(LedStripSpi::YF923F5F8,gNbrLeds)) {
fprintf(stderr, "Unable to initialise LedStrip\n");
return false;
}
//trill
if(gTouchSensor.setup(1, Trill::BAR) != 0) {
fprintf(stderr, "Unable to initialise Trill Bar\n");
return false;
}
Bela_runAuxiliaryTask(LedStripTask, 90);
Bela_runAuxiliaryTask(TrillBarTask, 90);
return true;
}
void render(BelaContext *context, void *userData)
{
}
void cleanup(BelaContext *context, void *userData)
{
}
Hi @PFaivre, thanks for your contribution, what part numbers does YF923F5F8
refer to?
giuliomoro This assumes BelaMini is used and the neopixels are driven from pin P2.25. To enable that you'll need to run config-pin P2.25 spi after each reboot ( this can be automated later if needed).
Hi, Could you maybe elaborate on automating this? I could not get this to work with the "user command line arguments"...
- Edited
you'd need to do something along the lines of what you see here.
is that clear enough?
noah automating this?
The solution linked above is probably the easiest if you are using supercollider or Pd or Csound. But if you are using C++, you can do it in a simpler way.
Put this line at the top of the file:
#include <stdlib.h>
and then in setup()
add this:
system("config-pin P2.25 spi");
Perfect, thanks so much!
Hijacking this thread again:
I've got some WS2812B 4x4 matrices and I've basically got them running quite well on a Bela mini, however, I still do get the occasional quirks (random single LEDs flashing in random colors every 20 or so seconds). I do think it's not HW related, as I have checked all connections, but it could still be some GND issue I assume.
I also don't know the exact manufacturer of the matrices, so I don't have a datasheet, but I think the standard WS2812B times you also used are working well.
I slowly don't know how to approach this issue anymore. @giuliomoro you wrote about the leading zeros and resulting random flashing if not done correctly. How did you determine those numbers (e.g. kSpiLeadingZerosNs)? I tried playing around a bit with those and I do think there are improvements, but as long as there are random flashes it's really hard to tell a difference between improvement and coincidence...
How many LEDs are you driving at once? Where are they powered from?
- Edited
I'm driving 64 LEDs (usually not more than 32 at once). They're powered from a 5V/2A supply, Bela is currently powered from my Laptop, but was also already running powered from the same power supply. While bela is powered from my laptop, I have connected the supply's GND to Belas GND, this was necessary to get the SPI working correctly.