mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2023-10-10 13:36:59 +02:00
Support SK9822 type LEDs with adaptive brightness control via SPI (#1017)
* Support SK9822 type LEDs with adaptive brightness control via SPI * SK9822 - minor refactorings
This commit is contained in:
parent
f11885fd8b
commit
6c68454bea
@ -466,6 +466,8 @@
|
|||||||
"edt_dev_spec_intervall_title": "Intervall",
|
"edt_dev_spec_intervall_title": "Intervall",
|
||||||
"edt_dev_spec_invert_title": "Invertiere Signal",
|
"edt_dev_spec_invert_title": "Invertiere Signal",
|
||||||
"edt_dev_spec_latchtime_title": "Sperrzeit",
|
"edt_dev_spec_latchtime_title": "Sperrzeit",
|
||||||
|
"edt_dev_spec_globalBrightnessControlMaxLevel_title": "Maximalstufe Stromstärke",
|
||||||
|
"edt_dev_spec_globalBrightnessControlThreshold_title": "Grenzwert für adaptive Stromstärke",
|
||||||
"edt_dev_spec_ledIndex_title": "LED-Index",
|
"edt_dev_spec_ledIndex_title": "LED-Index",
|
||||||
"edt_dev_spec_ledType_title": "LED-Typ",
|
"edt_dev_spec_ledType_title": "LED-Typ",
|
||||||
"edt_dev_spec_lightid_itemtitle": "ID",
|
"edt_dev_spec_lightid_itemtitle": "ID",
|
||||||
|
@ -466,6 +466,8 @@
|
|||||||
"edt_dev_spec_intervall_title": "Interval",
|
"edt_dev_spec_intervall_title": "Interval",
|
||||||
"edt_dev_spec_invert_title": "Invert signal",
|
"edt_dev_spec_invert_title": "Invert signal",
|
||||||
"edt_dev_spec_latchtime_title": "Latch time",
|
"edt_dev_spec_latchtime_title": "Latch time",
|
||||||
|
"edt_dev_spec_globalBrightnessControlMaxLevel_title": "Max Current Level",
|
||||||
|
"edt_dev_spec_globalBrightnessControlThreshold_title": "Adaptive Current Threshold",
|
||||||
"edt_dev_spec_ledIndex_title": "LED index",
|
"edt_dev_spec_ledIndex_title": "LED index",
|
||||||
"edt_dev_spec_ledType_title": "LED Type",
|
"edt_dev_spec_ledType_title": "LED Type",
|
||||||
"edt_dev_spec_lightid_itemtitle": "ID",
|
"edt_dev_spec_lightid_itemtitle": "ID",
|
||||||
|
@ -568,7 +568,7 @@ $(document).ready(function() {
|
|||||||
|
|
||||||
// create led device selection
|
// create led device selection
|
||||||
var ledDevices = window.serverInfo.ledDevices.available;
|
var ledDevices = window.serverInfo.ledDevices.available;
|
||||||
var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'ws2812spi'];
|
var devRPiSPI = ['apa102', 'apa104', 'ws2801', 'lpd6803', 'lpd8806', 'p9813', 'sk6812spi', 'sk6822spi', 'sk9822', 'ws2812spi'];
|
||||||
var devRPiPWM = ['ws281x'];
|
var devRPiPWM = ['ws281x'];
|
||||||
var devRPiGPIO = ['piblaster'];
|
var devRPiGPIO = ['piblaster'];
|
||||||
var devNET = ['atmoorb', 'fadecandy', 'philipshue', 'nanoleaf', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight'];
|
var devNET = ['atmoorb', 'fadecandy', 'philipshue', 'nanoleaf', 'tinkerforge', 'tpm2net', 'udpe131', 'udpartnet', 'udph801', 'udpraw', 'wled', 'yeelight'];
|
||||||
|
@ -31,6 +31,9 @@ The SK6812 are **3** wire leds, you could also drive them via spi.
|
|||||||
#### sk6822spi
|
#### sk6822spi
|
||||||
The SK6822 are **3** wire leds, you could also drive them via spi.
|
The SK6822 are **3** wire leds, you could also drive them via spi.
|
||||||
|
|
||||||
|
#### sk9822
|
||||||
|
The SK9822 are **4** wire leds compatible to APA 102 with addition of global brightness control.
|
||||||
|
|
||||||
#### ws2812spi
|
#### ws2812spi
|
||||||
The WS2812 are **3** wire leds, you could also drive them via spi.
|
The WS2812 are **3** wire leds, you could also drive them via spi.
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
{
|
{
|
||||||
"type" :
|
"type" :
|
||||||
{
|
{
|
||||||
"enum" : ["file", "apa102", "apa104", "ws2801", "lpd6803", "lpd8806", "p9813", "sk6812spi", "sk6822spi", "ws2812spi","ws281x", "piblaster", "adalight", "dmx", "atmo", "hyperionusbasp", "lightpack", "multilightpack", "paintpack", "rawhid", "sedu", "tpm2", "karate"]
|
"enum" : ["file", "apa102", "apa104", "ws2801", "lpd6803", "lpd8806", "p9813", "sk6812spi", "sk6822spi", "sk9822", "ws2812spi","ws281x", "piblaster", "adalight", "dmx", "atmo", "hyperionusbasp", "lightpack", "multilightpack", "paintpack", "rawhid", "sedu", "tpm2", "karate"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties" : true
|
"additionalProperties" : true
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
<file alias="schema-sedu">schemas/schema-sedu.json</file>
|
<file alias="schema-sedu">schemas/schema-sedu.json</file>
|
||||||
<file alias="schema-sk6812spi">schemas/schema-sk6812spi.json</file>
|
<file alias="schema-sk6812spi">schemas/schema-sk6812spi.json</file>
|
||||||
<file alias="schema-sk6822spi">schemas/schema-sk6822spi.json</file>
|
<file alias="schema-sk6822spi">schemas/schema-sk6822spi.json</file>
|
||||||
|
<file alias="schema-sk9822">schemas/schema-sk9822.json</file>
|
||||||
<file alias="schema-tinkerforge">schemas/schema-tinkerforge.json</file>
|
<file alias="schema-tinkerforge">schemas/schema-tinkerforge.json</file>
|
||||||
<file alias="schema-tpm2net">schemas/schema-tpm2net.json</file>
|
<file alias="schema-tpm2net">schemas/schema-tpm2net.json</file>
|
||||||
<file alias="schema-tpm2">schemas/schema-tpm2.json</file>
|
<file alias="schema-tpm2">schemas/schema-tpm2.json</file>
|
||||||
|
142
libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp
Normal file
142
libsrc/leddevice/dev_spi/LedDeviceSK9822.cpp
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
#include "LedDeviceSK9822.h"
|
||||||
|
|
||||||
|
// Local Hyperion includes
|
||||||
|
#include <utils/Logger.h>
|
||||||
|
|
||||||
|
|
||||||
|
/// The value that determines the higher bits of the SK9822 global brightness control field
|
||||||
|
const int SK9822_GBC_UPPER_BITS = 0xE0;
|
||||||
|
|
||||||
|
/// The maximal current level supported by the SK9822 global brightness control field, 31
|
||||||
|
const int SK9822_GBC_MAX_LEVEL = 0x1F;
|
||||||
|
|
||||||
|
LedDeviceSK9822::LedDeviceSK9822(const QJsonObject &deviceConfig)
|
||||||
|
: ProviderSpi(deviceConfig)
|
||||||
|
, _globalBrightnessControlThreshold(255)
|
||||||
|
, _globalBrightnessControlMaxLevel(SK9822_GBC_MAX_LEVEL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LedDevice *LedDeviceSK9822::construct(const QJsonObject &deviceConfig)
|
||||||
|
{
|
||||||
|
return new LedDeviceSK9822(deviceConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LedDeviceSK9822::init(const QJsonObject &deviceConfig)
|
||||||
|
{
|
||||||
|
bool isInitOK = false;
|
||||||
|
|
||||||
|
// Initialise sub-class
|
||||||
|
if (ProviderSpi::init(deviceConfig))
|
||||||
|
{
|
||||||
|
_globalBrightnessControlThreshold = deviceConfig["globalBrightnessControlThreshold"].toInt(255);
|
||||||
|
_globalBrightnessControlMaxLevel = deviceConfig["globalBrightnessControlMaxLevel"].toInt(SK9822_GBC_MAX_LEVEL);
|
||||||
|
Info(_log, "[SK9822] Using global brightness control with threshold of %d and max level of %d", _globalBrightnessControlThreshold, _globalBrightnessControlMaxLevel);
|
||||||
|
|
||||||
|
const unsigned int startFrameSize = 4;
|
||||||
|
const unsigned int endFrameSize = qMax<unsigned int>(((_ledCount + 15) / 16), 4);
|
||||||
|
const unsigned int bufferSize = (_ledCount * 4) + startFrameSize + endFrameSize;
|
||||||
|
|
||||||
|
_ledBuffer.resize(bufferSize, 0xFF);
|
||||||
|
_ledBuffer[0] = 0x00;
|
||||||
|
_ledBuffer[1] = 0x00;
|
||||||
|
_ledBuffer[2] = 0x00;
|
||||||
|
_ledBuffer[3] = 0x00;
|
||||||
|
|
||||||
|
isInitOK = true;
|
||||||
|
}
|
||||||
|
return isInitOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LedDeviceSK9822::bufferWithMaxCurrent(std::vector<uint8_t> &txBuf, const std::vector<ColorRgb> & ledValues, const int maxLevel) {
|
||||||
|
const int ledCount = static_cast<int>(_ledCount);
|
||||||
|
|
||||||
|
for (int iLed = 0; iLed < ledCount; ++iLed)
|
||||||
|
{
|
||||||
|
const ColorRgb &rgb = ledValues[iLed];
|
||||||
|
const uint8_t red = rgb.red;
|
||||||
|
const uint8_t green = rgb.green;
|
||||||
|
const uint8_t blue = rgb.blue;
|
||||||
|
|
||||||
|
/// The LED index in the buffer
|
||||||
|
const int b = 4 + iLed * 4;
|
||||||
|
|
||||||
|
// Use 0/31 LED-Current for Black, and full LED-Current for all other colors,
|
||||||
|
// with PWM control on RGB-Channels
|
||||||
|
const int ored = (red|green|blue);
|
||||||
|
|
||||||
|
txBuf[b + 0] = ((ored > 0) * (maxLevel & SK9822_GBC_MAX_LEVEL)) | SK9822_GBC_UPPER_BITS; // (ored > 0) is 1 for any r,g,b > 0, 0 otherwise; branch free
|
||||||
|
txBuf[b + 1] = red;
|
||||||
|
txBuf[b + 2] = green;
|
||||||
|
txBuf[b + 3] = blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline __attribute__((always_inline)) unsigned LedDeviceSK9822::scale(const uint8_t value, const int maxLevel, const uint16_t brightness) {
|
||||||
|
return (((maxLevel * value + (brightness >> 1)) / brightness));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LedDeviceSK9822::bufferWithAdjustedCurrent(std::vector<uint8_t> &txBuf, const std::vector<ColorRgb> & ledValues, const int threshold, const int maxLevel) {
|
||||||
|
const int ledCount = static_cast<int>(_ledCount);
|
||||||
|
|
||||||
|
for (int iLed = 0; iLed < ledCount; ++iLed)
|
||||||
|
{
|
||||||
|
const ColorRgb &rgb = ledValues[iLed];
|
||||||
|
uint8_t red = rgb.red;
|
||||||
|
uint8_t green = rgb.green;
|
||||||
|
uint8_t blue = rgb.blue;
|
||||||
|
uint8_t level;
|
||||||
|
|
||||||
|
/// The LED index in the buffer
|
||||||
|
const int b = 4 + iLed * 4;
|
||||||
|
|
||||||
|
/// The maximal r,g,b-channel grayscale value of the LED
|
||||||
|
const uint16_t /* expand to 16 bit! */ maxValue = std::max(std::max(red, green), blue);
|
||||||
|
|
||||||
|
if (maxValue == 0) {
|
||||||
|
// Use 0/31 LED-Current for Black
|
||||||
|
level = 0;
|
||||||
|
red = 0x00;
|
||||||
|
green = 0x00;
|
||||||
|
blue = 0x00;
|
||||||
|
} else if (maxValue >= threshold) {
|
||||||
|
// Use full LED-Current when maximal r,g,b-channel grayscale value >= threshold and just use PWM control
|
||||||
|
level = (maxLevel & SK9822_GBC_MAX_LEVEL);
|
||||||
|
} else {
|
||||||
|
// Use adjusted LED-Current for other r,g,b-channel grayscale values
|
||||||
|
// See also: https://github.com/FastLED/FastLED/issues/656
|
||||||
|
|
||||||
|
// Scale the r,g,b-channel grayscale values to adjusted current = brightness level
|
||||||
|
const uint16_t /* 16 bit! */ brightness = (((maxValue + 1) * maxLevel - 1) >> 8) + 1;
|
||||||
|
|
||||||
|
level = (brightness & SK9822_GBC_MAX_LEVEL);
|
||||||
|
red = scale(red, maxLevel, brightness);
|
||||||
|
green = scale(green, maxLevel, brightness);
|
||||||
|
blue = scale(blue, maxLevel, brightness);
|
||||||
|
}
|
||||||
|
|
||||||
|
txBuf[b + 0] = level | SK9822_GBC_UPPER_BITS;
|
||||||
|
txBuf[b + 1] = red;
|
||||||
|
txBuf[b + 2] = green;
|
||||||
|
txBuf[b + 3] = blue;
|
||||||
|
|
||||||
|
//if(iLed == 0) {
|
||||||
|
// std::cout << std::to_string((int)rgb.red) << "," << std::to_string((int)rgb.green) << "," << std::to_string((int)rgb.blue) << ": " << std::to_string(maxValue) << (maxValue >= threshold ? " >= " : " < ") << std::to_string(threshold) << " -> " << std::to_string((int)(level&SK9822_GBC_MAX_LEVEL))<< "@" << std::to_string((int)red) << "," << std::to_string((int)green) << "," << std::to_string((int)blue) << std::endl;
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int LedDeviceSK9822::write(const std::vector<ColorRgb> &ledValues)
|
||||||
|
{
|
||||||
|
const int threshold = _globalBrightnessControlThreshold;
|
||||||
|
const int maxLevel = _globalBrightnessControlMaxLevel;
|
||||||
|
|
||||||
|
if(threshold > 0) {
|
||||||
|
this->bufferWithAdjustedCurrent(_ledBuffer, ledValues, threshold, maxLevel);
|
||||||
|
} else {
|
||||||
|
this->bufferWithMaxCurrent(_ledBuffer, ledValues, maxLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return writeBytes(_ledBuffer.size(), _ledBuffer.data());
|
||||||
|
}
|
83
libsrc/leddevice/dev_spi/LedDeviceSK9822.h
Normal file
83
libsrc/leddevice/dev_spi/LedDeviceSK9822.h
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
#ifndef LEDEVICESK9822_H
|
||||||
|
#define LEDEVICESK9822_H
|
||||||
|
|
||||||
|
// hyperion includes
|
||||||
|
#include "ProviderSpi.h"
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Implementation of the LedDevice interface for writing to SK9822 led device via SPI.
|
||||||
|
///
|
||||||
|
class LedDeviceSK9822 : public ProviderSpi
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
///
|
||||||
|
/// @brief Constructs an SK9822 LED-device
|
||||||
|
///
|
||||||
|
/// @param deviceConfig Device's configuration as JSON-Object
|
||||||
|
///
|
||||||
|
explicit LedDeviceSK9822(const QJsonObject &deviceConfig);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// @brief Constructs the LED-device
|
||||||
|
///
|
||||||
|
/// @param[in] deviceConfig Device's configuration as JSON-Object
|
||||||
|
/// @return LedDevice constructed
|
||||||
|
///
|
||||||
|
static LedDevice* construct(const QJsonObject &deviceConfig);
|
||||||
|
|
||||||
|
private:
|
||||||
|
///
|
||||||
|
/// @brief Writes the RGB-Color values to the SPI Tx buffer setting SK9822 current level to maximal value.
|
||||||
|
///
|
||||||
|
/// @param[in,out] txBuf The packed spi transfer buffer of the LED's color values
|
||||||
|
/// @param[in] ledValues The RGB-color per LED
|
||||||
|
/// @param[in] maxLevel The maximal current level 1 .. 31 to use
|
||||||
|
///
|
||||||
|
void bufferWithMaxCurrent(std::vector<uint8_t> &txBuf, const std::vector<ColorRgb> & ledValues, const int maxLevel);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// @brief Writes the RGB-Color values to the SPI Tx buffer using an adjusted SK9822 current level for LED maximal rgb-grayscale values not exceeding the threshold, uses maximal level otherwise.
|
||||||
|
///
|
||||||
|
/// @param[in,out] txBuf The packed spi transfer buffer of the LED's color values
|
||||||
|
/// @param[in] ledValues The RGB-color per LED
|
||||||
|
/// @param[in] threshold The threshold 0 .. 255 that defines whether to use adjusted SK9822 current level per LED
|
||||||
|
/// @param[in] maxLevel The maximal current level 1 .. 31 to use
|
||||||
|
///
|
||||||
|
void bufferWithAdjustedCurrent(std::vector<uint8_t> &txBuf, const std::vector<ColorRgb> & ledValues, const int threshold, const int maxLevel);
|
||||||
|
|
||||||
|
/// The threshold that defines use of SK9822 global brightness control for maximal rgb grayscale values below.
|
||||||
|
/// i.e. global brightness control is used for rgb-values when max(r,g,b) < threshold.
|
||||||
|
int _globalBrightnessControlThreshold;
|
||||||
|
|
||||||
|
/// The maximal current level that is targeted. Possibile values 1 .. 31.
|
||||||
|
int _globalBrightnessControlMaxLevel;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// @brief Scales the given value such that a given grayscale stimulus is reached for the targeted brightness and defined max current value.
|
||||||
|
///
|
||||||
|
/// @param[in] value The grayscale value to scale
|
||||||
|
/// @param[in] maxLevel The maximal current level 1 .. 31 to use
|
||||||
|
/// @param[in] brightness The target brightness
|
||||||
|
/// @return The scaled grayscale stimulus
|
||||||
|
///
|
||||||
|
inline __attribute__((always_inline)) unsigned scale(const uint8_t value, const int maxLevel, const uint16_t brightness);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// @brief Initialise the device's configuration
|
||||||
|
///
|
||||||
|
/// @param[in] deviceConfig the JSON device configuration
|
||||||
|
/// @return True, if success
|
||||||
|
///
|
||||||
|
bool init(const QJsonObject &deviceConfig) override;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// @brief Writes the RGB-Color values to the LEDs.
|
||||||
|
///
|
||||||
|
/// @param[in] ledValues The RGB-color per LED
|
||||||
|
/// @return Zero on success, else negative
|
||||||
|
///
|
||||||
|
int write(const std::vector<ColorRgb> & ledValues) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LEDEVICESK9822_H
|
61
libsrc/leddevice/schemas/schema-sk9822.json
Normal file
61
libsrc/leddevice/schemas/schema-sk9822.json
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"type":"object",
|
||||||
|
"required":true,
|
||||||
|
"properties":{
|
||||||
|
"output": {
|
||||||
|
"type": "string",
|
||||||
|
"title":"edt_dev_spec_spipath_title",
|
||||||
|
"enum" : ["/dev/spidev0.0","/dev/spidev0.1"],
|
||||||
|
"default" : "/dev/spidev0.0",
|
||||||
|
"propertyOrder" : 1
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer",
|
||||||
|
"title":"edt_dev_spec_baudrate_title",
|
||||||
|
"default": 1000000,
|
||||||
|
"propertyOrder" : 2
|
||||||
|
},
|
||||||
|
"invert": {
|
||||||
|
"type": "boolean",
|
||||||
|
"title":"edt_dev_spec_invert_title",
|
||||||
|
"default": false,
|
||||||
|
"propertyOrder" : 3
|
||||||
|
},
|
||||||
|
"globalBrightnessControlMaxLevel": {
|
||||||
|
"type": "integer",
|
||||||
|
"title":"edt_dev_spec_globalBrightnessControlMaxLevel_title",
|
||||||
|
"default": 31,
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 31,
|
||||||
|
"propertyOrder" : 4
|
||||||
|
},
|
||||||
|
"globalBrightnessControlThreshold": {
|
||||||
|
"type": "integer",
|
||||||
|
"title":"edt_dev_spec_globalBrightnessControlThreshold_title",
|
||||||
|
"default": 255,
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 255,
|
||||||
|
"propertyOrder" : 5
|
||||||
|
},
|
||||||
|
"latchTime": {
|
||||||
|
"type": "integer",
|
||||||
|
"title":"edt_dev_spec_latchtime_title",
|
||||||
|
"default": 0,
|
||||||
|
"append" : "edt_append_ms",
|
||||||
|
"minimum": 0,
|
||||||
|
"maximum": 1000,
|
||||||
|
"access" : "expert",
|
||||||
|
"propertyOrder" : 6
|
||||||
|
},
|
||||||
|
"rewriteTime": {
|
||||||
|
"type": "integer",
|
||||||
|
"title":"edt_dev_general_rewriteTime_title",
|
||||||
|
"default": 1000,
|
||||||
|
"append" : "edt_append_ms",
|
||||||
|
"minimum": 0,
|
||||||
|
"access" : "expert",
|
||||||
|
"propertyOrder" : 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user