Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing WS2812b signal transmission in SPI plugin. #1382

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
50 changes: 33 additions & 17 deletions plugins/spi/SPIOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend,
personalities.begin() + PERS_WS2812B_COMBINED - 1,
Personality(WS2812B_SLOTS_PER_PIXEL,
"WS2812b Combined Control",
sdc_irgb_combined));
sdc_rgb_combined));

peternewman marked this conversation as resolved.
Show resolved Hide resolved

m_personality_collection.reset(new PersonalityCollection(personalities));
m_personality_manager.reset(new PersonalityManager(
Expand Down Expand Up @@ -894,10 +895,12 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) {

// 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;
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;
OLA_INFO << "Unable to create output buffer of required length: "
<< output_length;
peternewman marked this conversation as resolved.
Show resolved Hide resolved
return;
}

Expand All @@ -912,24 +915,24 @@ void SPIOutput::IndividualWS2812bControl(const DmxBuffer &buffer) {
uint8_t b = buffer.Get(offset + 2);
uint8_t low = 0, mid = 0, high = 0;

WS2812bByteMapper(g, low, mid, high);
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);
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);
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);
m_backend->Commit(m_output_number);
}

void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) {
Expand All @@ -942,10 +945,12 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) {

// 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;
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;
OLA_INFO << "Unable to create output buffer of required length: "
<< output_length;
peternewman marked this conversation as resolved.
Show resolved Hide resolved
return;
}

Expand All @@ -958,17 +963,17 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) {
// create Pixel Data
uint8_t pixel_data[WS2812B_SPI_BYTES_PER_PIXEL];

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

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

WS2812bByteMapper(b, low, mid, high);
WS2812bByteMapper(b, &low, &mid, &high);
pixel_data[6] = low;
pixel_data[7] = mid;
pixel_data[8] = high;
Expand All @@ -983,13 +988,24 @@ void SPIOutput::CombinedWS2812bControl(const DmxBuffer &buffer) {
m_backend->Commit(m_output_number);
}

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);
/*
* 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) {
return ResponderHelper::GetDeviceInfo(
request, ola::rdm::OLA_SPI_DEVICE_MODEL,
Expand Down
3 changes: 2 additions & 1 deletion plugins/spi/SPIOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +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);
void WS2812bByteMapper(uint8_t input,
uint8_t *low, uint8_t *mid, uint8_t *high);
peternewman marked this conversation as resolved.
Show resolved Hide resolved

static const uint8_t SPI_MODE;
static const uint8_t SPI_BITS_PER_WORD;
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));
}