Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions plugins/spi/SPIOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,13 @@ const uint16_t SPIOutput::LPD8806_SLOTS_PER_PIXEL = 3;
const uint16_t SPIOutput::P9813_SLOTS_PER_PIXEL = 3;
const uint16_t SPIOutput::APA102_SLOTS_PER_PIXEL = 3;
const uint16_t SPIOutput::APA102_PB_SLOTS_PER_PIXEL = 4;
const uint16_t SPIOutput::WS2812B_SLOTS_PER_PIXEL = 3;

// Number of bytes that each pixel uses on the SPI wires
// (if it differs from 1:1 with colors)
const uint16_t SPIOutput::P9813_SPI_BYTES_PER_PIXEL = 4;
const uint16_t SPIOutput::APA102_SPI_BYTES_PER_PIXEL = 4;
const uint16_t SPIOutput::WS2812B_SPI_BYTES_PER_PIXEL = 9;

const uint16_t SPIOutput::APA102_START_FRAME_BYTES = 4;
const uint8_t SPIOutput::APA102_LEDFRAME_START_MARK = 0xE0;
Expand Down Expand Up @@ -260,6 +262,18 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend,
"APA102 Pixel Brightness Combined",
sdc_irgb_combined));

personalities.insert(
personalities.begin() + PERS_WS2812B_INDIVIDUAL - 1,
Personality(m_pixel_count * WS2812B_SLOTS_PER_PIXEL,
"WS2812b Individual Control"));

personalities.insert(
personalities.begin() + PERS_WS2812B_COMBINED - 1,
Personality(WS2812B_SLOTS_PER_PIXEL,
"WS2812b Combined Control",
sdc_rgb_combined));


m_personality_collection.reset(new PersonalityCollection(personalities));
m_personality_manager.reset(new PersonalityManager(
m_personality_collection.get()));
Expand Down Expand Up @@ -871,6 +885,125 @@ uint8_t SPIOutput::CalculateAPA102PixelBrightness(uint8_t brightness) {
return (brightness >> 3);
}

void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) {
const unsigned int first_slot = m_start_address - 1; // 0 offset
if (buffer.Size() - first_slot < WS2812B_SLOTS_PER_PIXEL) {
OLA_INFO << "Insufficient DMX data, required " << WS2812B_SLOTS_PER_PIXEL
<< ", got " << buffer.Size() - first_slot;
return;
}

// We always check out the entire string length, even if we only have data
// for part of it
const unsigned int output_length = m_pixel_count
* WS2812B_SPI_BYTES_PER_PIXEL;
uint8_t *output = m_backend->Checkout(m_output_number, output_length);
if (!output) {
OLA_INFO << "Unable to create output buffer of required length: "
<< output_length;
return;
}

const unsigned int length = std::min(m_pixel_count * WS2812B_SLOTS_PER_PIXEL,
buffer.Size() - first_slot);

for (unsigned int i = 0; i < length / WS2812B_SLOTS_PER_PIXEL; i++) {
// Convert RGB to GRB
unsigned int offset = first_slot + i * WS2812B_SLOTS_PER_PIXEL;
uint8_t r = buffer.Get(offset);
uint8_t g = buffer.Get(offset + 1);
uint8_t b = buffer.Get(offset + 2);
uint8_t low = 0, mid = 0, high = 0;

WS2812bByteMapper(g, &low, &mid, &high);
output[i * WS2812B_SPI_BYTES_PER_PIXEL] = low;
output[i * WS2812B_SPI_BYTES_PER_PIXEL + 1] = mid;
output[i * WS2812B_SPI_BYTES_PER_PIXEL + 2] = high;

WS2812bByteMapper(r, &low, &mid, &high);
output[i * WS2812B_SPI_BYTES_PER_PIXEL + 3] = low;
output[i * WS2812B_SPI_BYTES_PER_PIXEL + 4] = mid;
output[i * WS2812B_SPI_BYTES_PER_PIXEL + 5] = high;

WS2812bByteMapper(b, &low, &mid, &high);
output[i * WS2812B_SPI_BYTES_PER_PIXEL + 6] = low;
output[i * WS2812B_SPI_BYTES_PER_PIXEL + 7] = mid;
output[i * WS2812B_SPI_BYTES_PER_PIXEL + 8] = high;
}

// write output back...
m_backend->Commit(m_output_number);
}

void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) {
const unsigned int first_slot = m_start_address - 1; // 0 offset
if (buffer.Size() - first_slot < WS2812B_SLOTS_PER_PIXEL) {
OLA_INFO << "Insufficient DMX data, required " << WS2812B_SLOTS_PER_PIXEL
<< ", got " << buffer.Size() - first_slot;
return;
}

// We always check out the entire string length, even if we only have data
// for part of it
const unsigned int output_length = m_pixel_count
* WS2812B_SPI_BYTES_PER_PIXEL;
uint8_t *output = m_backend->Checkout(m_output_number, output_length);
if (!output) {
OLA_INFO << "Unable to create output buffer of required length: "
<< output_length;
return;
}

// Grab RGB data for conversion to GBR
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a typo based on the other use and the code.

Suggested change
// Grab RGB data for conversion to GBR
// Grab RGB data for conversion to GRB

uint8_t r = buffer.Get(first_slot);
uint8_t g = buffer.Get(first_slot + 1);
uint8_t b = buffer.Get(first_slot + 2);
uint8_t low = 0, mid = 0, high = 0;

// create Pixel Data
uint8_t pixel_data[WS2812B_SPI_BYTES_PER_PIXEL];

WS2812bByteMapper(g, &low, &mid, &high);
pixel_data[0] = low;
pixel_data[1] = mid;
pixel_data[2] = high;

WS2812bByteMapper(r, &low, &mid, &high);
pixel_data[3] = low;
pixel_data[4] = mid;
pixel_data[5] = high;

WS2812bByteMapper(b, &low, &mid, &high);
pixel_data[6] = low;
pixel_data[7] = mid;
pixel_data[8] = high;

// set all pixel to same value
for (uint16_t i = 0; i < m_pixel_count; i++) {
memcpy(&output[i * WS2812B_SPI_BYTES_PER_PIXEL], pixel_data,
WS2812B_SPI_BYTES_PER_PIXEL);
}

// write output back...
m_backend->Commit(m_output_number);
}

/*
* Converting to WS2811/12b format.
*
* The format sends each bit with a leading 1 and a trailing 0.
* This function spaces out the bits of a byte and inserts them into a
* hexadecimal version (0x924924) of the octal 44444444, ending up with
* three bytes of information per byte input.
*/
void SPIOutput::WS2812bByteMapper(uint8_t input,
uint8_t *low, uint8_t *mid, uint8_t *high) {
*low = 0x24 | ((input & 0x1) << 1) | ((input & 0x2) << 3)
| ((input & 0x4) << 5);
*mid = 0x49 | ((input & 0x8) >> 1) | ((input & 0x10) << 1);
*high = 0x92 | ((input & 0x20) >> 5) | ((input & 0x40) >> 3)
| ((input & 0x80) >> 1);
}


RDMResponse *SPIOutput::GetDeviceInfo(const RDMRequest *request) {
Expand Down
8 changes: 8 additions & 0 deletions plugins/spi/SPIOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface {
PERS_APA102_COMBINED = 8,
PERS_APA102_PB_INDIVIDUAL,
PERS_APA102_PB_COMBINED,
PERS_WS2812B_INDIVIDUAL,
PERS_WS2812B_COMBINED,
};

struct Options {
Expand Down Expand Up @@ -136,6 +138,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface {
void CombinedAPA102Control(const DmxBuffer &buffer);
void IndividualAPA102ControlPixelBrightness(const DmxBuffer &buffer);
void CombinedAPA102ControlPixelBrightness(const DmxBuffer &buffer);
void IndividualWS2812bControl(const DmxBuffer &buffer);
void CombinedWS2812bControl(const DmxBuffer &buffer);

unsigned int LPD8806BufferSize() const;
void WriteSPIData(const uint8_t *data, unsigned int length);
Expand Down Expand Up @@ -200,6 +204,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface {
uint8_t P9813CreateFlag(uint8_t red, uint8_t green, uint8_t blue);
static uint8_t CalculateAPA102LatchBytes(uint16_t pixel_count);
static uint8_t CalculateAPA102PixelBrightness(uint8_t brightness);
void WS2812bByteMapper(uint8_t input,
uint8_t *low, uint8_t *mid, uint8_t *high);

static const uint8_t SPI_MODE;
static const uint8_t SPI_BITS_PER_WORD;
Expand All @@ -214,6 +220,8 @@ class SPIOutput: public ola::rdm::DiscoverableRDMControllerInterface {
static const uint16_t APA102_SPI_BYTES_PER_PIXEL;
static const uint16_t APA102_START_FRAME_BYTES;
static const uint8_t APA102_LEDFRAME_START_MARK;
static const uint16_t WS2812B_SLOTS_PER_PIXEL;
static const uint16_t WS2812B_SPI_BYTES_PER_PIXEL;

static const ola::rdm::ResponderOps<SPIOutput>::ParamHandler
PARAM_HANDLERS[];
Expand Down
107 changes: 107 additions & 0 deletions plugins/spi/SPIOutputTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1139,3 +1139,110 @@ void SPIOutputTest::testCombinedAPA102ControlPixelBrightness() {
OLA_ASSERT_DATA_EQUALS(EXPECTED8, arraysize(EXPECTED8), data, length);
OLA_ASSERT_EQ(5u, backend.Writes(0));
}

/*
* WS2812b unit tests
*/
void SPIOutputTest::testIndividualWS2812bControl() {
const uint16_t this_test_personality = SPIOutput::PERS_WS2812B_INDIVIDUAL;
// setup Backend
FakeSPIBackend backend(2);
SPIOutput::Options options(0, "Test SPI Device");
// setup pixel_count to 2 (enough to test all cases)
options.pixel_count = 2;
// setup SPIOutput
SPIOutput output(m_uid, &backend, options);
// set personality to Individual WS2812b
output.SetPersonality(this_test_personality);

// simulate incoming dmx data with this buffer
DmxBuffer buffer;
// setup an pointer to the returned data (the fake SPI data stream)
unsigned int length = 0;
const uint8_t *data = NULL;

// test1
// setup some 'DMX' data
buffer.SetFromString("1, 10, 100");
// simulate incoming data
output.WriteDMX(buffer);
// get fake SPI data stream
data = backend.GetData(0, &length);
// this is the expected spi data stream:
const uint8_t EXPECTED1[] = { 0x92, 0x4D, 0x34, //Pixel 1 Green
0x92, 0x49, 0x26, //Pixel 1 Red
0x9B, 0x49, 0xA4, //Pixel 1 Blue
0x92, 0x49, 0x24, //Pixel 2 Green
0x92, 0x49, 0x24, //Pixel 2 Red
0x92, 0x49, 0x24 //Pixel 2 Blue
};
// check for Equality
OLA_ASSERT_DATA_EQUALS(EXPECTED1, arraysize(EXPECTED1), data, length);
// check if the output writes are 1
OLA_ASSERT_EQ(1u, backend.Writes(0));

// test2
buffer.SetFromString("255,128,0,10,20,30");
output.WriteDMX(buffer);
data = backend.GetData(0, &length);
const uint8_t EXPECTED2[] = { 0xD2, 0x49, 0x24, //Pixel 1 Green (128)
0xDB, 0x6D, 0xB6, //Pixel 1 Red (255)
0x92, 0x49, 0x24, //Pixel 1 Blue (0)
0x92, 0x69, 0xA4, //Pixel 2 Green (20)
0x92, 0x4D, 0x34, //Pixel 2 Red (10)
0x92, 0x6D, 0xB4 //Pixel 2 Blue (30)
};
OLA_ASSERT_DATA_EQUALS(EXPECTED2, arraysize(EXPECTED2), data, length);
OLA_ASSERT_EQ(2u, backend.Writes(0));

// test3
// test what happens when only new data for the first leds is available.
// later data should be not modified so for pixel2 data set in test2 is valid
buffer.SetFromString("34,56,78");
output.WriteDMX(buffer);
data = backend.GetData(0, &length);
const uint8_t EXPECTED3[] = { 0x93, 0x6D, 0x24, //Pixel 1 Green (56)
0x93, 0x49, 0x34, //Pixel 1 Red (34)
0x9A, 0x4D, 0xB4, //Pixel 1 Blue (78)
0x92, 0x69, 0xA4, //Pixel 2 Green (20)
0x92, 0x4D, 0x34, //Pixel 2 Red (10)
0x92, 0x6D, 0xB4 //Pixel 2 Blue (30)
};
OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length);
OLA_ASSERT_EQ(3u, backend.Writes(0));

// test4
// tests what happens if fewer then needed color information are received
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPaG

Suggested change
// tests what happens if fewer then needed color information are received
// tests what happens if fewer than needed slots of color information are received

buffer.SetFromString("7, 9");
output.WriteDMX(buffer);
data = backend.GetData(0, &length);
// check that the returns are the same as test3 (nothing changed)
OLA_ASSERT_DATA_EQUALS(EXPECTED3, arraysize(EXPECTED3), data, length);
OLA_ASSERT_EQ(3u, backend.Writes(0));

// test5
// test with changed StartAddress
// set StartAddress
output.SetStartAddress(3);
// values 1 & 2 should not be visible in SPI data stream
buffer.SetFromString("1,2,3,4,5,6,7,8");
output.WriteDMX(buffer);
data = backend.GetData(0, &length);
const uint8_t EXPECTED5[] = { 0x92, 0x49, 0xA4, //Pixel 1 Green (4)
0x92, 0x49, 0x36, //Pixel 1 Red (3)
0x92, 0x49, 0xA6, //Pixel 1 Blue (5)
0x92, 0x49, 0xB6, //Pixel 2 Green (7)
0x92, 0x49, 0xB4, //Pixel 2 Red (6)
0x92, 0x4D, 0x24 //Pixel 2 Blue (8)
};
OLA_ASSERT_DATA_EQUALS(EXPECTED5, arraysize(EXPECTED5), data, length);
OLA_ASSERT_EQ(4u, backend.Writes(0));
// change StartAddress back to default
output.SetStartAddress(1);

// test6
// Check nothing changed on the other output.
OLA_ASSERT_EQ(reinterpret_cast<const uint8_t*>(NULL),
backend.GetData(1, &length));
OLA_ASSERT_EQ(0u, backend.Writes(1));
}