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)
{
}