diff --git a/doc/datasheets/WS2811.pdf b/doc/datasheets/WS2811.pdf new file mode 100644 index 00000000..b08b0171 Binary files /dev/null and b/doc/datasheets/WS2811.pdf differ diff --git a/doc/datasheets/WS2812.pdf b/doc/datasheets/WS2812.pdf new file mode 100644 index 00000000..8ceeec70 Binary files /dev/null and b/doc/datasheets/WS2812.pdf differ diff --git a/doc/datasheets/WS2812B_preliminary.pdf b/doc/datasheets/WS2812B_preliminary.pdf new file mode 100644 index 00000000..4925af68 Binary files /dev/null and b/doc/datasheets/WS2812B_preliminary.pdf differ diff --git a/libsrc/hyperion/CMakeLists.txt b/libsrc/hyperion/CMakeLists.txt index b1ccc93e..abc5f862 100644 --- a/libsrc/hyperion/CMakeLists.txt +++ b/libsrc/hyperion/CMakeLists.txt @@ -37,6 +37,7 @@ SET(Hyperion_HEADERS ${CURRENT_SOURCE_DIR}/device/LedDeviceTest.h ${CURRENT_SOURCE_DIR}/device/LedDeviceSedu.h ${CURRENT_SOURCE_DIR}/device/LedDeviceWs2801.h + ${CURRENT_SOURCE_DIR}/device/LedDeviceWs2811.h ${CURRENT_SOURCE_DIR}/device/LedDeviceLpd6803.h ${CURRENT_SOURCE_DIR}/device/LedDeviceLpd8806.h ${CURRENT_SOURCE_DIR}/device/LedDeviceLightpack.h @@ -62,6 +63,7 @@ SET(Hyperion_SOURCES ${CURRENT_SOURCE_DIR}/device/LedDeviceSedu.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceTest.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceWs2801.cpp + ${CURRENT_SOURCE_DIR}/device/LedDeviceWs2811.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceLpd6803.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceLpd8806.cpp ${CURRENT_SOURCE_DIR}/device/LedDeviceAdalight.cpp diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 1f245781..518c7631 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -22,6 +22,7 @@ #include "device/LedDeviceSedu.h" #include "device/LedDeviceTest.h" #include "device/LedDeviceWs2801.h" +#include "device/LedDeviceWs2811.h" #include "device/LedDeviceAdalight.h" #include "device/LedDevicePaintpack.h" #include "device/LedDeviceLightpack.h" @@ -51,6 +52,23 @@ LedDevice* Hyperion::createDevice(const Json::Value& deviceConfig) device = deviceWs2801; } + else if (type == "ws2811") + { + const std::string output = deviceConfig["output"].asString(); + const std::string outputSpeed = deviceConfig["output"].asString(); + const std::string timingOption = deviceConfig["timingOption"].asString(); + + ws2811::SpeedMode speedMode = (outputSpeed == "high")? ws2811::highspeed : ws2811::lowspeed; + if (outputSpeed != "high" && outputSpeed != "low") + { + std::cerr << "Incorrect speed-mode selected for WS2811: " << outputSpeed << " != {'high', 'low'}" << std::endl; + } + + LedDeviceWs2811 * deviceWs2811 = new LedDeviceWs2811(output, ws2811::fromString(timingOption, ws2811::option_2855), speedMode); + deviceWs2811->open(); + + device = deviceWs2811; + } else if (type == "lpd6803" || type == "ldp6803") { const std::string output = deviceConfig["output"].asString(); diff --git a/libsrc/hyperion/device/LedDeviceWs2811.cpp b/libsrc/hyperion/device/LedDeviceWs2811.cpp new file mode 100644 index 00000000..9fe0f8a3 --- /dev/null +++ b/libsrc/hyperion/device/LedDeviceWs2811.cpp @@ -0,0 +1,182 @@ + +// Local hyperion includes +#include "LedDeviceWs2811.h" + + +ws2811::SignalTiming ws2811::fromString(const std::string& signalTiming, const SignalTiming defaultValue) +{ + SignalTiming result = defaultValue; + if (signalTiming == "3755" || signalTiming == "option_3755") + { + result = option_3755; + } + else if (signalTiming == "3773" || signalTiming == "option_3773") + { + result = option_3773; + } + else if (signalTiming == "2855" || signalTiming == "option_2855") + { + result = option_2855; + } + else if (signalTiming == "2882" || signalTiming == "option_2882") + { + result = option_2882; + } + + return result; +} + +unsigned ws2811::getBaudrate(const SpeedMode speedMode) +{ + switch (speedMode) + { + case highspeed: + // Bit length: 125ns + return 8000000; + case lowspeed: + // Bit length: 250ns + return 4000000; + } + + return 0; +} +inline unsigned ws2811::getLength(const SignalTiming timing, const TimeOption option) +{ + switch (timing) + { + case option_3755: + // Reference: http://www.mikrocontroller.net/attachment/180459/WS2812B_preliminary.pdf + // Unit length: 125ns + switch (option) + { + case T0H: + return 3; // 400ns +-150ns + case T0L: + return 7; // 850ns +-150ns + case T1H: + return 7; // 800ns +-150ns + case T1L: + return 3; // 450ns +-150ns + } + case option_3773: + // Reference: www.adafruit.com/datasheets/WS2812.pdf‎ + // Unit length: 125ns + switch (option) + { + case T0H: + return 3; // 350ns +-150ns + case T0L: + return 7; // 800ns +-150ns + case T1H: + return 7; // 700ns +-150ns + case T1L: + return 3; // 600ns +-150ns + } + case option_2855: + // Reference: www.adafruit.com/datasheets/WS2811.pdf‎ + // Unit length: 250ns + switch (option) + { + case T0H: + return 2; // 500ns +-150ns + case T0L: + return 8; // 2000ns +-150ns + case T1H: + return 5; // 1200ns +-150ns + case T1L: + return 5; // 1300ns +-150ns + } + case option_2882: + // Reference: www.szparkson.net/download/WS2811.pdf‎ + // Unit length: 250ns + switch (option) + { + case T0H: + return 2; // 500ns +-150ns + case T0L: + return 8; // 2000ns +-150ns + case T1H: + return 8; // 2000ns +-150ns + case T1L: + return 2; // 500ns +-150ns + } + default: + std::cerr << "Unknown signal timing for ws2811: " << timing << std::endl; + } + return 0; +} + +uint8_t ws2811::bitToSignal(unsigned lenHigh) +{ + // Sanity check on the length of the 'high' signal + assert(0 < lenHigh && lenHigh < 10); + + uint8_t result = 0x00; + for (unsigned i=1; i & ledValues) +{ + if (_ledBuffer.size() != ledValues.size() * 3) + { + _ledBuffer.resize(ledValues.size() * 3); + } + + auto bufIt = _ledBuffer.begin(); + for (const ColorRgb & color : ledValues) + { + *bufIt = _byteToSignalTable[color.red ]; + ++bufIt; + *bufIt = _byteToSignalTable[color.green]; + ++bufIt; + *bufIt = _byteToSignalTable[color.blue ]; + ++bufIt; + } + + writeBytes(_ledBuffer.size() * 3, reinterpret_cast(_ledBuffer.data())); + + return 0; +} + +int LedDeviceWs2811::switchOff() +{ + write(std::vector(_ledBuffer.size()/3, ColorRgb::BLACK)); + return 0; +} + +void LedDeviceWs2811::fillEncodeTable(const ws2811::SignalTiming ledOption) +{ + _byteToSignalTable.resize(256); + for (unsigned byteValue=0; byteValue<256; ++byteValue) + { + const uint8_t byteVal = uint8_t(byteValue); + _byteToSignalTable[byteValue] = ws2811::translate(ledOption, byteVal); + } +} diff --git a/libsrc/hyperion/device/LedDeviceWs2811.h b/libsrc/hyperion/device/LedDeviceWs2811.h new file mode 100644 index 00000000..88edfceb --- /dev/null +++ b/libsrc/hyperion/device/LedDeviceWs2811.h @@ -0,0 +1,147 @@ +#pragma once + +// STL includes +#include + +// Local hyperion includes +#include "LedRs232Device.h" + +namespace ws2811 +{ + /// + /// Enumaration of known signal timings + /// + enum SignalTiming + { + option_3755, + option_3773, + option_2855, + option_2882, + not_a_signaltiming + }; + + /// + /// Enumaration of the possible speeds on which the ws2811 can operate. + /// + enum SpeedMode + { + lowspeed, + highspeed + }; + + /// + /// Enumeration of the signal 'parts' (T 0 high, T 1 high, T 0 low, T 1 low). + /// + enum TimeOption + { + T0H, + T1H, + T0L, + T1L + }; + + /// + /// Structure holding the signal for a signle byte + /// + struct ByteSignal + { + uint8_t bit_1; + uint8_t bit_2; + uint8_t bit_3; + uint8_t bit_4; + uint8_t bit_5; + uint8_t bit_6; + uint8_t bit_7; + uint8_t bit_8; + }; + // Make sure the structure is exatly the length we require + static_assert(sizeof(ByteSignal) == 8, "Incorrect sizeof ByteSignal (expected 8)"); + + /// + /// Translates a string to a signal timing + /// + /// @param signalTiming The string specifying the signal timing + /// @param defaultValue The default value (used if the string does not match any known timing) + /// + /// @return The SignalTiming (or not_a_signaltiming if it did not match) + /// + SignalTiming fromString(const std::string& signalTiming, const SignalTiming defaultValue); + + /// + /// Returns the required baudrate for a specific signal-timing + /// + /// @param SpeedMode The WS2811/WS2812 speed mode (WS2812b only has highspeed) + /// + /// @return The required baudrate for the signal timing + /// + unsigned getBaudrate(const SpeedMode speedMode); + + /// + /// The number of 'signal units' (bits) For the subpart of a specific timing scheme + /// + /// @param timing The controller option + /// @param option The signal part + /// + unsigned getLength(const SignalTiming timing, const TimeOption option); + + /// + /// Constructs a 'bit' based signal with defined 'high' length (and implicite defined 'low' + /// length. The signal is based on a 10bits bytes (incl. high startbit and low stopbit). The + /// total length of the high is given as parameter:
+ /// lenHigh=7 => |-------|___| => 1 1111 1100 0 => 252 (start and stop bit are implicite) + /// + /// @param lenHigh The total length of the 'high' length (incl start-bit) + /// @return The byte representing the high-low signal + /// + uint8_t bitToSignal(unsigned lenHigh); + + /// + /// Translate a byte into signal levels for a specific WS2811 option + /// + /// @param ledOption The WS2811 configuration + /// @param byte The byte to translate + /// + /// @return The signal for the given byte (one byte per bit) + /// + ByteSignal translate(SignalTiming ledOption, uint8_t byte); +} + +class LedDeviceWs2811 : public LedRs232Device +{ +public: + /// + /// Constructs the LedDevice with Ws2811 attached via a serial port + /// + /// @param outputDevice The name of the output device (eg '/dev/ttyS0') + /// @param signalTiming The timing scheme used by the Ws2811 chip + /// @param speedMode The speed modus of the Ws2811 chip + /// + LedDeviceWs2811(const std::string& outputDevice, const ws2811::SignalTiming signalTiming, const ws2811::SpeedMode speedMode); + + /// + /// Writes the led color values to the led-device + /// + /// @param ledValues The color-value per led + /// @return Zero on succes else negative + /// + virtual int write(const std::vector & ledValues); + + /// Switch the leds off + virtual int switchOff(); + + +private: + + /// + /// Fill the byte encoding table (_byteToSignalTable) for the specific timing option + /// + /// @param ledOption The timing option + /// + void fillEncodeTable(const ws2811::SignalTiming ledOption); + + /// Translation table of byte to signal/// + std::vector _byteToSignalTable; + + /// The buffer containing the packed RGB values + std::vector _ledBuffer; +};