mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00: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:
		| @@ -466,6 +466,8 @@ | ||||
|     "edt_dev_spec_intervall_title": "Intervall", | ||||
|     "edt_dev_spec_invert_title": "Invertiere Signal", | ||||
|     "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_ledType_title": "LED-Typ", | ||||
|     "edt_dev_spec_lightid_itemtitle": "ID", | ||||
| @@ -917,4 +919,4 @@ | ||||
|     "wiz_yeelight_noLights": "Es wurden keine Yeelights gefunden! Bitte verbinde die Yeelights mit dem Netzwerk oder konfiguriere sie manuell.", | ||||
|     "wiz_yeelight_title": "Yeelight Einrichtungsassistent", | ||||
|     "wiz_yeelight_unsupported": "Nicht unterstützt" | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -466,6 +466,8 @@ | ||||
|     "edt_dev_spec_intervall_title": "Interval", | ||||
|     "edt_dev_spec_invert_title": "Invert signal", | ||||
|     "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_ledType_title": "LED Type", | ||||
|     "edt_dev_spec_lightid_itemtitle": "ID", | ||||
| @@ -917,4 +919,4 @@ | ||||
|     "wiz_yeelight_noLights": "No Yeelights found! Please get the lights connected to the network or configure them manually.", | ||||
|     "wiz_yeelight_title": "Yeelight Wizard", | ||||
|     "wiz_yeelight_unsupported": "Unsupported" | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -568,7 +568,7 @@ $(document).ready(function() { | ||||
|  | ||||
| 	// create led device selection | ||||
| 	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 devRPiGPIO = ['piblaster']; | ||||
| 	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 | ||||
| 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 | ||||
| The WS2812 are **3** wire leds, you could also drive them via spi. | ||||
|  | ||||
|   | ||||
| @@ -39,7 +39,7 @@ | ||||
| 			{ | ||||
| 				"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 | ||||
|   | ||||
| @@ -20,6 +20,7 @@ | ||||
| 		<file alias="schema-sedu">schemas/schema-sedu.json</file> | ||||
| 		<file alias="schema-sk6812spi">schemas/schema-sk6812spi.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-tpm2net">schemas/schema-tpm2net.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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user