From 78e606a1c4ec71d6864b0c5d2e5ad546018f28eb Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Fri, 4 Mar 2016 00:07:02 -0800 Subject: [PATCH 1/2] Add new WS281x driver Add a new WS281x driver which is a wrapper around jgarff's ws281x library which works on Pi B+, Pi 2, Pi Zero and probably Pi 3 as well. jgarff's ws281x library is included as a submodule Former-commit-id: e473dfd36d31b14598da5e56e4b8bf9f2aa7bb24 --- .gitmodules | 3 ++ CMakeLists.txt | 7 +++ HyperionConfig.h.in | 3 ++ dependencies/CMakeLists.txt | 7 +++ dependencies/external/rpi_ws281x | 1 + libsrc/leddevice/CMakeLists.txt | 28 +++++++--- libsrc/leddevice/LedDeviceFactory.cpp | 16 ++++++ libsrc/leddevice/LedDeviceWS281x.cpp | 78 +++++++++++++++++++++++++++ libsrc/leddevice/LedDeviceWS281x.h | 43 +++++++++++++++ 9 files changed, 180 insertions(+), 6 deletions(-) create mode 160000 dependencies/external/rpi_ws281x create mode 100644 libsrc/leddevice/LedDeviceWS281x.cpp create mode 100644 libsrc/leddevice/LedDeviceWS281x.h diff --git a/.gitmodules b/.gitmodules index aa392190..c23ec3d2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "dependencies/external/protobuf"] path = dependencies/external/protobuf url = https://github.com/tvdzwan/protobuf.git +[submodule "dependencies/external/rpi_ws281x"] + path = dependencies/external/rpi_ws281x + url = https://github.com/jgarff/rpi_ws281x diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c8e077a..58718d71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,9 @@ message(STATUS "ENABLE_V4L2 = " ${ENABLE_V4L2}) option(ENABLE_WS2812BPWM "Enable the WS2812b-PWM device" OFF) message(STATUS "ENABLE_WS2812BPWM = " ${ENABLE_WS2812BPWM}) +option(ENABLE_WS281XPWM "Enable the WS281x-PWM device" OFF) +message(STATUS "ENABLE_WS281XPWM = " ${ENABLE_WS281XPWM}) + option(ENABLE_X11 "Enable the X11 grabber" OFF) message(STATUS "ENABLE_X11 = " ${ENABLE_X11}) @@ -56,6 +59,10 @@ if(ENABLE_OSX AND ENABLE_DISPMANX) message(FATAL_ERROR "dispmanx grabber and osx grabber cannot be used at the same time") endif(ENABLE_OSX AND ENABLE_DISPMANX) +if(ENABLE_WS2812BPWM AND ENABLE_WS281XPWM) + message(FATAL_ERROR "WS2812b and WS281x drivers cannot be used at the same time") +endif(ENABLE_WS2812BPWM AND ENABLE_WS281XPWM) + #if(ENABLE_QT5) # TODO vs ENABLE_QT4? #endif(ENABLE_QT5) diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index f5bd3ce1..69147bb2 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -12,6 +12,9 @@ // Define to enable the ws2812b-pwm-device #cmakedefine ENABLE_WS2812BPWM +// Define to enable the ws281x-pwm-via-dma-device using jgarff's library +#cmakedefine ENABLE_WS281XPWM + // Define to enable the spi-device #cmakedefine ENABLE_TINKERFORGE diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 17747bd5..43569460 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -4,6 +4,13 @@ add_subdirectory(build/jsoncpp) add_subdirectory(build/serial) add_subdirectory(build/tinkerforge) +if(ENABLE_WS281XPWM) + add_library(ws281x + external/rpi_ws281x/mailbox.c external/rpi_ws281x/ws2811.c + external/rpi_ws281x/pwm.c external/rpi_ws281x/dma.c + external/rpi_ws281x/rpihw.c) +endif(ENABLE_WS281XPWM) + if(ENABLE_PROTOBUF) set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared protobuf library") add_subdirectory(external/protobuf) diff --git a/dependencies/external/rpi_ws281x b/dependencies/external/rpi_ws281x new file mode 160000 index 00000000..34c917e2 --- /dev/null +++ b/dependencies/external/rpi_ws281x @@ -0,0 +1 @@ +Subproject commit 34c917e25044a7aca4f6dc9326c48c1474b8f28c diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt index 15d2801d..da1251ca 100755 --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -33,9 +33,9 @@ SET(Leddevice_HEADERS ${CURRENT_SOURCE_DIR}/LedDevicePaintpack.h ${CURRENT_SOURCE_DIR}/LedDevicePiBlaster.h ${CURRENT_SOURCE_DIR}/LedDeviceSedu.h - ${CURRENT_SOURCE_DIR}/LedDeviceTest.h - ${CURRENT_SOURCE_DIR}/LedDeviceFadeCandy.h - ${CURRENT_SOURCE_DIR}/LedDeviceUdp.h + ${CURRENT_SOURCE_DIR}/LedDeviceTest.h + ${CURRENT_SOURCE_DIR}/LedDeviceFadeCandy.h + ${CURRENT_SOURCE_DIR}/LedDeviceUdp.h ${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.h ${CURRENT_SOURCE_DIR}/LedDeviceTpm2.h ${CURRENT_SOURCE_DIR}/LedDeviceAtmo.h @@ -56,9 +56,9 @@ SET(Leddevice_SOURCES ${CURRENT_SOURCE_DIR}/LedDevicePaintpack.cpp ${CURRENT_SOURCE_DIR}/LedDevicePiBlaster.cpp ${CURRENT_SOURCE_DIR}/LedDeviceSedu.cpp - ${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp - ${CURRENT_SOURCE_DIR}/LedDeviceFadeCandy.cpp - ${CURRENT_SOURCE_DIR}/LedDeviceUdp.cpp + ${CURRENT_SOURCE_DIR}/LedDeviceTest.cpp + ${CURRENT_SOURCE_DIR}/LedDeviceFadeCandy.cpp + ${CURRENT_SOURCE_DIR}/LedDeviceUdp.cpp ${CURRENT_SOURCE_DIR}/LedDeviceHyperionUsbasp.cpp ${CURRENT_SOURCE_DIR}/LedDevicePhilipsHue.cpp ${CURRENT_SOURCE_DIR}/LedDeviceTpm2.cpp @@ -97,6 +97,18 @@ SET(Leddevice_SOURCES ) endif(ENABLE_WS2812BPWM) +if(ENABLE_WS281XPWM) +include_directories(../../dependencies/external/rpi_ws281x) +SET(Leddevice_HEADERS + ${Leddevice_HEADERS} + ${CURRENT_SOURCE_DIR}/LedDeviceWS281x.h + ) +SET(Leddevice_SOURCES + ${Leddevice_SOURCES} + ${CURRENT_SOURCE_DIR}/LedDeviceWS281x.cpp +) +endif(ENABLE_WS281XPWM) + if(ENABLE_TINKERFORGE) SET(Leddevice_HEADERS ${Leddevice_HEADERS} @@ -138,6 +150,10 @@ if(ENABLE_TINKERFORGE) target_link_libraries(leddevice tinkerforge) endif() +if(ENABLE_WS281XPWM) + target_link_libraries(leddevice ws281x) +endif() + if(APPLE) target_link_libraries(leddevice hidapi-mac) else() diff --git a/libsrc/leddevice/LedDeviceFactory.cpp b/libsrc/leddevice/LedDeviceFactory.cpp index fed6d82d..0b8deb1e 100755 --- a/libsrc/leddevice/LedDeviceFactory.cpp +++ b/libsrc/leddevice/LedDeviceFactory.cpp @@ -42,6 +42,10 @@ #include "LedDeviceWS2812b.h" #endif +#ifdef ENABLE_WS281XPWM + #include "LedDeviceWS281x.h" +#endif + LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) { std::cout << "Device configuration: " << deviceConfig << std::endl; @@ -285,6 +289,18 @@ LedDevice * LedDeviceFactory::construct(const Json::Value & deviceConfig) LedDeviceWS2812b * ledDeviceWS2812b = new LedDeviceWS2812b(); device = ledDeviceWS2812b; } +#endif +#ifdef ENABLE_WS281XPWM + else if (type == "ws281x") + { + const int gpio = deviceConfig.get("gpio", 18).asInt(); + const int leds = deviceConfig.get("leds", 12).asInt(); + const uint32_t freq = deviceConfig.get("freq", (Json::UInt)800000ul).asInt(); + const int dmanum = deviceConfig.get("dmanum", 5).asInt(); + + LedDeviceWS281x * ledDeviceWS281x = new LedDeviceWS281x(gpio, leds, freq, dmanum); + device = ledDeviceWS281x; + } #endif else { diff --git a/libsrc/leddevice/LedDeviceWS281x.cpp b/libsrc/leddevice/LedDeviceWS281x.cpp new file mode 100644 index 00000000..10b5d2e0 --- /dev/null +++ b/libsrc/leddevice/LedDeviceWS281x.cpp @@ -0,0 +1,78 @@ +#include + +#include "LedDeviceWS281x.h" + +// Constructor +LedDeviceWS281x::LedDeviceWS281x(const int gpio, const int leds, const uint32_t freq, const int dmanum) +{ + initialized = false; + led_string.freq = freq; + led_string.dmanum = dmanum; + led_string.channel[0].gpionum = gpio; + led_string.channel[0].invert = 0; + led_string.channel[0].count = leds; + led_string.channel[0].brightness = 255; + led_string.channel[0].strip_type = WS2811_STRIP_RGB; + + led_string.channel[1].gpionum = 0; + led_string.channel[1].invert = 0; + led_string.channel[1].count = 0; + led_string.channel[1].brightness = 0; + led_string.channel[0].strip_type = WS2811_STRIP_RGB; + if (ws2811_init(&led_string) < 0) { + std::cout << "Unable to initialize ws281x library." << std::endl; + throw -1; + } + initialized = true; +} + +// Send new values down the LED chain +int LedDeviceWS281x::write(const std::vector &ledValues) +{ + if (!initialized) + return -1; + + int idx = 0; + for (const ColorRgb& color : ledValues) + { + if (idx >= led_string.channel[0].count) + break; + led_string.channel[0].leds[idx++] = ((uint32_t)color.red << 16) + ((uint32_t)color.green << 8) + color.blue; + } + while (idx < led_string.channel[0].count) + led_string.channel[0].leds[idx++] = 0; + + if (ws2811_render(&led_string)) + return -1; + + return 0; +} + +// Turn off the LEDs by sending 000000's +// TODO Allow optional power switch out another gpio, if this code handles it can +// make it more likely we don't accidentally drive data into an off strip +int LedDeviceWS281x::switchOff() +{ + if (!initialized) + return -1; + + int idx = 0; + while (idx < led_string.channel[0].count) + led_string.channel[0].leds[idx++] = 0; + + if (ws2811_render(&led_string)) + return -1; + + return 0; +} + +// Destructor +LedDeviceWS281x::~LedDeviceWS281x() +{ + if (initialized) + { + std::cout << "Shutdown WS281x PWM and DMA channel" << std::endl; + ws2811_fini(&led_string); + } + initialized = false; +} diff --git a/libsrc/leddevice/LedDeviceWS281x.h b/libsrc/leddevice/LedDeviceWS281x.h new file mode 100644 index 00000000..429a092f --- /dev/null +++ b/libsrc/leddevice/LedDeviceWS281x.h @@ -0,0 +1,43 @@ +#ifndef LEDDEVICEWS281X_H_ +#define LEDDEVICEWS281X_H_ + +#pragma once + +#include +#include + +class LedDeviceWS281x : public LedDevice +{ +public: + /// + /// Constructs the LedDevice for WS281x (one wire 800kHz) + /// + /// @param gpio The gpio pin to use (BCM chip counting, default is 18) + /// @param leds The number of leds attached to the gpio pin + /// @param freq The target frequency for the data line, default is 800000 + /// @param dmanum The DMA channel to use, default is 5 + /// + LedDeviceWS281x(const int gpio, const int leds, const uint32_t freq, int dmanum); + + /// + /// Destructor of the LedDevice, waits for DMA to complete and then cleans up + /// + ~LedDeviceWS281x(); + + /// + /// 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: + ws2811_t led_string; + bool initialized; +}; + +#endif /* LEDDEVICEWS281X_H_ */ From 7cb34733fdc15989603b4f2574b822343cb6546b Mon Sep 17 00:00:00 2001 From: Mark Walker Date: Wed, 9 Mar 2016 12:27:07 -0800 Subject: [PATCH 2/2] Actually, you can enable both flavors of PWM If you do a build with both ENABLE_WS2812BPWM and ENABLE_WS281XPWM and choose which LED driver using the device.type in the config.json Former-commit-id: 35ae88ad8739cac406a9b8792e2223010bf5d144 --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 58718d71..3e808141 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,10 +59,6 @@ if(ENABLE_OSX AND ENABLE_DISPMANX) message(FATAL_ERROR "dispmanx grabber and osx grabber cannot be used at the same time") endif(ENABLE_OSX AND ENABLE_DISPMANX) -if(ENABLE_WS2812BPWM AND ENABLE_WS281XPWM) - message(FATAL_ERROR "WS2812b and WS281x drivers cannot be used at the same time") -endif(ENABLE_WS2812BPWM AND ENABLE_WS281XPWM) - #if(ENABLE_QT5) # TODO vs ENABLE_QT4? #endif(ENABLE_QT5)