-
Notifications
You must be signed in to change notification settings - Fork 221
SPI Plugin - TI TLC5971 support #1399
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
base: master
Are you sure you want to change the base?
Changes from 12 commits
243b150
6f9d7c1
017fce5
394b1ef
3ead26f
888807a
d8ab05e
38b651e
04528fc
5360d10
3607990
04da39e
7fe9ea4
1e0eddd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -86,7 +86,10 @@ const uint16_t SPIOutput::WS2801_SLOTS_PER_PIXEL = 3; | |
| 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; | ||
| // 3 ch color + 1 pixel brightness | ||
| const uint16_t SPIOutput::APA102_PB_SLOTS_PER_PIXEL = 4; | ||
| // 12 channels @ 16bit = 24 dmx channels | ||
| const uint16_t SPIOutput::TLC5971_SLOTS_PER_DEVICE = 24; | ||
|
|
||
| // Number of bytes that each pixel uses on the SPI wires | ||
| // (if it differs from 1:1 with colors) | ||
|
|
@@ -96,6 +99,39 @@ const uint16_t SPIOutput::APA102_SPI_BYTES_PER_PIXEL = 4; | |
| const uint16_t SPIOutput::APA102_START_FRAME_BYTES = 4; | ||
| const uint8_t SPIOutput::APA102_LEDFRAME_START_MARK = 0xE0; | ||
|
|
||
|
|
||
| const uint16_t SPIOutput::TLC5971_SPI_BYTES_PER_DEVICE = 28; | ||
| // struct TLC5971_PACKET_CONFIG_MASKS { | ||
| // // Write Command (6Bit) | ||
| // const uint8_t WRCMD = 0b00111111; | ||
| // // Function Control Data (5 x 1Bit = 5Bit) | ||
| // const uint8_t OUTTMG = 0b00000001; | ||
| // const uint8_t EXTGCK = 0b00000001; | ||
| // const uint8_t TMGRST = 0b00000001; | ||
| // const uint8_t DSPRPT = 0b00000001; | ||
| // const uint8_t BLANK = 0b00000001; | ||
| // // BC-Data (3 x 7Bits = 21Bit) | ||
| // const uint8_t BCB = 0b01111111; | ||
| // const uint8_t BCG = 0b01111111; | ||
| // const uint8_t BCR = 0b01111111; | ||
| // }; | ||
| // | ||
| // const struct TLC5971_PACKET_CONFIG_LSHIFT { | ||
| // // Write Command (6Bit) | ||
| // const uint8_t WRCMD = 0 + 7 + 7 + 7 + 1 + 1 + 1 + 1 + 6; | ||
| // // Function Control Data (5 x 1Bit = 5Bit) | ||
| // const uint8_t OUTTMG = 0 + 7 + 7 + 7 + 1 + 1 + 1 + 1; | ||
| // const uint8_t EXTGCK = 0 + 7 + 7 + 7 + 1 + 1 + 1; | ||
| // const uint8_t TMGRST = 0 + 7 + 7 + 7 + 1 + 1; | ||
| // const uint8_t DSPRPT = 0 + 7 + 7 + 7 + 1; | ||
| // const uint8_t BLANK = 0 + 7 + 7 + 7; | ||
| // // BC-Data (3 x 7Bits = 21Bit) | ||
| // const uint8_t BCB = 0 + 7 + 7; | ||
| // const uint8_t BCG = 0 + 7; | ||
| // const uint8_t BCR = 0; | ||
| // }; | ||
|
|
||
|
|
||
| SPIOutput::RDMOps *SPIOutput::RDMOps::instance = NULL; | ||
|
|
||
| const ola::rdm::ResponderOps<SPIOutput>::ParamHandler | ||
|
|
@@ -261,6 +297,10 @@ SPIOutput::SPIOutput(const UID &uid, SPIBackendInterface *backend, | |
| "APA102 Pixel Brightness Combined", | ||
| sdc_irgb_combined)); | ||
|
|
||
| personalities.insert(personalities.begin() + PERS_TLC5971_INDIVIDUAL - 1, | ||
| Personality(m_pixel_count * TLC5971_SLOTS_PER_DEVICE, | ||
| "TLC5971 Individual Control (16bit per channel)")); | ||
|
|
||
| m_personality_collection.reset(new PersonalityCollection(personalities)); | ||
| m_personality_manager.reset(new PersonalityManager( | ||
| m_personality_collection.get())); | ||
|
|
@@ -387,6 +427,9 @@ bool SPIOutput::InternalWriteDMX(const DmxBuffer &buffer) { | |
| case PERS_APA102_PB_COMBINED: | ||
| CombinedAPA102ControlPixelBrightness(buffer); | ||
| break; | ||
| case PERS_TLC5971_INDIVIDUAL: | ||
| IndividualTLC5971Control(buffer); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
|
|
@@ -874,6 +917,239 @@ uint8_t SPIOutput::CalculateAPA102PixelBrightness(uint8_t brightness) { | |
|
|
||
|
|
||
|
|
||
| void SPIOutput::IndividualTLC5971Control(const DmxBuffer &buffer) { | ||
| // some detailed information on the protocol: | ||
| // http://www.ti.com/lit/ds/symlink/tlc5971.pdf | ||
| // 8.5.4 Register and Data Latch Configuration (page23) | ||
| // 9.2.2.3 How to Control the TLC5971 (page27) | ||
| // How to send: | ||
| // the first data we send are received by the last device in chain. | ||
| // Device Nth (244Bit = 28Byte) | ||
| // Write Command (6Bit) | ||
| // WRCMD (fixed: 25h) | ||
| // Function Control Data (5 x 1Bit = 5Bit) | ||
| // OUTTMG 1bit; GS clock edge select | ||
| // 1=rising edge, 0= falling edge | ||
| // EXTGCK 1bit; GS reference clock select | ||
| // 1=SCKI clock, 0=internal oscillator | ||
| // TMGRST 1bit; display timing reset mode | ||
| // 1=OUT forced of on latchpulse, 0=no forced reset | ||
| // DSPRPT 1bit; display repeat mode | ||
| // 1=auto repeate | ||
| // 0=Out only turned on after Blank or internal latchpulse | ||
| // BLANK 1bit; | ||
| // 1=blank (outputs off) | ||
| // 0=Out on - controlled by GS-Data | ||
| // ic power on sets this to 1 | ||
| // BC-Data (3 x 7Bits = 21Bit) | ||
| // BCB 7bit; | ||
| // BCG 7bit; | ||
| // BCR 7bit; | ||
| // GS-Data (12 x 16Bits = 192Bit) | ||
| // GSB3 16bit; | ||
| // GSG3 16bit; | ||
| // GSR3 16bit; | ||
| // GSB2 16bit; | ||
| // GSG2 16bit; | ||
| // GSR2 16bit; | ||
| // GSB1 16bit; | ||
| // GSG1 16bit; | ||
| // GSR1 16bit; | ||
| // GSB0 16bit; | ||
| // GSG0 16bit; | ||
| // GSR0 16bit; | ||
| // Device Nth-1 (244Bit = 28Byte) | ||
| // Device .. | ||
| // Device 2 | ||
| // Device 1 | ||
| // short break of 8x period of clock (666ns .. 2.74ms) to generate latchpulse | ||
| // + 1.34uS | ||
| // than next update. | ||
|
|
||
| // OLA_WARN << "******************************************"; | ||
|
|
||
| // calculate DMX-start-address | ||
| const unsigned int first_slot = m_start_address - 1; // 0 offset | ||
|
|
||
| // calculate how many channels for full devices are available in dmx_buffer | ||
| uint16_t devices_in_buffer = | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be a uint8_t, or at least the value will always be < 255, or does it need to be a uint16_t as that's what buffer.Size() returns? |
||
| (buffer.Size() - first_slot) / TLC5971_SLOTS_PER_DEVICE; | ||
| // OLA_WARN << " devices_in_buffer:" | ||
| // << static_cast<uint16_t>(devices_in_buffer); | ||
|
|
||
| // only do something if at least 1 device can be updated.. | ||
| if (devices_in_buffer == 0) { | ||
| OLA_INFO << "Insufficient DMX data, required " << TLC5971_SLOTS_PER_DEVICE | ||
| << ", got " << buffer.Size() - first_slot; | ||
| return; | ||
| } | ||
|
|
||
| // rename m_pixel_count for easier understanding. | ||
| const unsigned int device_count = m_pixel_count; | ||
|
|
||
| // We always check out the entire string length, even if we only have data | ||
| // for part of it | ||
| uint16_t output_length = (device_count * TLC5971_SPI_BYTES_PER_DEVICE); | ||
|
|
||
| uint8_t *output = m_backend->Checkout( | ||
| m_output_number, | ||
| output_length); | ||
|
|
||
| // only update SPI data if possible | ||
| if (!output) { | ||
| return; | ||
| } | ||
|
|
||
| for ( | ||
| uint16_t device_index = 0; | ||
| device_index < devices_in_buffer; | ||
| device_index++ | ||
| ) { | ||
| // OLA_WARN << " ~~~~~"; | ||
| uint16_t dmx_offset = | ||
| first_slot + (device_index * TLC5971_SLOTS_PER_DEVICE); | ||
|
|
||
| uint16_t spi_offset = (device_index * TLC5971_SPI_BYTES_PER_DEVICE); | ||
|
|
||
| // OLA_WARN << " device_index:" | ||
| // << static_cast<uint16_t>(device_index); | ||
| // OLA_WARN << " dmx_offset:" | ||
| // << static_cast<uint16_t>(dmx_offset); | ||
| // OLA_WARN << " spi_offset:" | ||
| // << static_cast<uint16_t>(spi_offset); | ||
|
|
||
| // setup configuration for this device. | ||
| TLC5971_packet_t device_data; | ||
| // this configuration values are currently hard coded.. | ||
| // following values are equal for all devices. | ||
| // device_data.fields.config.fields.WRCMD = 0x25; | ||
| // device_data.fields.config.fields.OUTTMG = 0; // falling edge | ||
| // device_data.fields.config.fields.EXTGCK = 0; // internal | ||
| // device_data.fields.config.fields.TMGRST = 0; // no forced reset | ||
| // device_data.fields.config.fields.DSPRPT = 1; // auto repeate | ||
| // device_data.fields.config.fields.BLANK = 0; // output enabled | ||
| // device_data.fields.config.fields.BCB = 0x7F; // full | ||
| // // device_data.fields.config.fields.BCG = 0x7F; // full | ||
| // device_data.fields.config.fields.BCG = 0x00; // 0 for test | ||
| // device_data.fields.config.fields.BCR = 0x7F; // full | ||
|
|
||
| // OLA_WARN << "TLC5971_packet_config_t size:" | ||
| // << sizeof(TLC5971_packet_config_t); | ||
| // should return 4 | ||
|
|
||
| // OLA_WARN << "FC + BC data:"; | ||
| // for (uint16_t i = 0; i < 4; i++) { | ||
| // OLA_WARN << "[" << static_cast<int>(i) << "] " | ||
| // << std::bitset<8>(device_data.fields.config.bytes[i]); | ||
| // } | ||
|
|
||
| // // reset | ||
| // device_data.fields.config[0] = 0; | ||
| // device_data.fields.config[0] = 0; | ||
| // device_data.fields.config[0] = 0; | ||
| // device_data.fields.config[0] = 0; | ||
| // // fill byte 0 | ||
| // device_data.fields.config[0] |= | ||
| // static_cast<uint32_t>(0x25) << TLC5971_PACKET_CONFIG_LSHIFT_WRCMD; | ||
| // device_data.fields.config[0] |= | ||
| // (0 & TLC5971_PACKET_CONFIG_MASKS_OUTTMG) // falling edge | ||
| // << TLC5971_PACKET_CONFIG_LSHIFT_OUTTMG; | ||
| // device_data.fields.config[0] |= | ||
| // (0 & TLC5971_PACKET_CONFIG_MASKS_EXTGCK) // internal | ||
| // << TLC5971_PACKET_CONFIG_LSHIFT_EXTGCK; | ||
| // // byte border ------------------------------------------ | ||
| // // fill byte 1 | ||
| // device_data.fields.config[1] |= | ||
| // (0 & TLC5971_PACKET_CONFIG_MASKS_TMGRST) // no forced reset | ||
| // << TLC5971_PACKET_CONFIG_LSHIFT_TMGRST; | ||
| // device_data.fields.config[1] |= | ||
| // (1 & TLC5971_PACKET_CONFIG_MASKS_DSPRPT) // auto repeate | ||
| // << TLC5971_PACKET_CONFIG_LSHIFT_DSPRPT; | ||
| // device_data.fields.config[1] |= | ||
| // (0 & TLC5971_PACKET_CONFIG_MASKS_BLANK) // output enabled | ||
| // << TLC5971_PACKET_CONFIG_LSHIFT_BLANK; | ||
| // // BC data could be device dependent to calibrate led colors | ||
| // uint8_t temp_BCB = 0x7F; // full | ||
| // uint8_t temp_BCG = 0x7F; // full | ||
| // uint8_t temp_BCR = 0x7F; // full | ||
| // device_data.fields.config[1] |= | ||
| // (temp_BCB & TLC5971_PACKET_CONFIG_MASKS_BCB) | ||
| // >> TLC5971_PACKET_CONFIG_LSHIFT_BCB_RS; | ||
| // // byte border ------------------------------------------ | ||
| // device_data.fields.config[2] |= | ||
| // (temp_BCB & TLC5971_PACKET_CONFIG_MASKS_BCB) | ||
| // << TLC5971_PACKET_CONFIG_LSHIFT_BCB_LS; | ||
| // device_data.fields.config[2] |= | ||
| // (temp_BCG & TLC5971_PACKET_CONFIG_MASKS_BCG) | ||
| // >> TLC5971_PACKET_CONFIG_LSHIFT_BCG_RS; | ||
| // // byte border ------------------------------------------ | ||
| // device_data.fields.config[3] |= | ||
| // (temp_BCG & TLC5971_PACKET_CONFIG_MASKS_BCG) | ||
| // << TLC5971_PACKET_CONFIG_LSHIFT_BCG_LS; | ||
| // device_data.fields.config[3] |= | ||
| // (temp_BCR & TLC5971_PACKET_CONFIG_MASKS_BCR) | ||
| // << TLC5971_PACKET_CONFIG_LSHIFT_BCR; | ||
|
|
||
| // // fixed values for testing other things: | ||
| device_data.fields.config.bytes[0] = 0b10010100; // 0x94 | ||
| device_data.fields.config.bytes[1] = 0b01011111; // 0x5F | ||
| device_data.fields.config.bytes[2] = 0b11111111; // 0xFF | ||
| device_data.fields.config.bytes[3] = 0b11111111; // 0xFF | ||
|
|
||
|
|
||
| // OLA_WARN << "FC + BC data:"; | ||
| // for (uint16_t i = 0; i < 4; i++) { | ||
| // OLA_WARN << "[" << static_cast<int>(i) << "] " | ||
| // << std::bitset<8>(device_data.fields.config.bytes[i]); | ||
| // } | ||
|
|
||
| // fill gs data | ||
| // possible with | ||
| // device_data.gsdata.gs_fields.GSB3 = 65000 | ||
| // or | ||
| for ( | ||
| uint8_t gs_index = 0; | ||
| gs_index < TLC5971_SLOTS_PER_DEVICE; | ||
| gs_index++ | ||
| ) { | ||
| // OLA_WARN << " gs_index:" | ||
| // << static_cast<uint16_t>(gs_index); | ||
| // OLA_WARN << " dmx_offset + gs_index:" | ||
| // << static_cast<uint16_t>(dmx_offset + gs_index); | ||
| device_data.fields.gsdata.bytes[gs_index] = | ||
| buffer.Get(dmx_offset + gs_index); | ||
| } | ||
|
|
||
| // OLA_WARN << "GS data:"; | ||
| // for (uint16_t i = 0; i < 24; i++) { | ||
| // OLA_WARN << "[" << static_cast<int>(i) << "] " | ||
| // << std::bitset<8>(device_data.fields.gsdata.bytes[i]); | ||
| // } | ||
|
|
||
| // OLA_WARN << " TLC5971_packet_t size:" | ||
| // << sizeof(TLC5971_packet_t); | ||
| // OLA_WARN << " device_data size:" | ||
| // << sizeof(device_data); | ||
| // should return 28byte = 224bit | ||
|
|
||
| // copy data to output buffer | ||
| // memcpy(output + spi_offset, device_data.bytes, sizeof(TLC5971_packet_t)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the memcpy not work? Why not, that ought to be quicker and safer. |
||
| for ( | ||
| uint8_t data_index = 0; | ||
| data_index < sizeof(TLC5971_packet_t); | ||
| data_index++ | ||
| ) { | ||
| output[spi_offset + data_index] = device_data.bytes[data_index]; | ||
| } | ||
| } // for devices_in_buffer end | ||
|
|
||
| // write output back | ||
| m_backend->Commit(m_output_number); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low tech debug, add a log line before this, is it this causing the issue, or the code above. |
||
| } | ||
|
|
||
|
|
||
|
|
||
|
|
||
| RDMResponse *SPIOutput::GetDeviceInfo(const RDMRequest *request) { | ||
| return ResponderHelper::GetDeviceInfo( | ||
| request, ola::rdm::OLA_SPI_DEVICE_MODEL, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably ideally want both 8 and 16bit individual and combined options eventually, but feel free to fix the underlying issues first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - that is the plan when the rest works :-)
for the combined options there are more then one way to solve it...
what do you think does make more sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it's a 4 channel RGB driver right, with global dimmer or similar over each RGB group? You could also use it as 3 channel RGBA drivers (although the global dimmer wouldn't align).
I suspect the latter option is likely to be a better fit for most people, but it's kind of hard to tell.
The main solution would be to implement http://rdm.openlighting.org/pid/display?manufacturer=31344&pid=32773 and then personalities can be independent of driver type and just offer a range of sensible ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - its meant as 4xRGB - i think its not really
global dimmer- is more a correction value per color group... (but it is a long time since i actually read the datasheet / wrote this code... - eventually there is both.. a correction and a color-group dimming)as fare as i know all libraries (i have found) does not let you control any of the 'advanced' features..
for the PIXEL_TYPE thing i think that is handled in #871
do you mean it makes more sens i try and do this first? (iam currently unaware of how much work / where to start for this - but i can read on this...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think #871 should be masses of work @s-light , it should essentially be just tracking another variable and then using both to work out what function to run, rather than just personality. We probably also need to double check the RDM spec, but I think in theory every fixture should offer all personality sets, but perhaps just NAck the irrelevant ones (like a 24 channel clone on a normal RGB WS2801 or whatever). I suspect it broadly makes more sense to do it first, although I guess the bulk of the code to write is in the functions that actually process the DMX, so perhaps it doesn't make that much difference overall.
We should probably try and add the PIXEL_TYPE PIDs to the web UI, which will be interesting as the first manufacturer specific ones, but I can probably handle that bit. As well as that stuff needing to go in the config file.