mirror of https://github.com/arendst/Tasmota.git
Replace NeoPixelBus with TasmotaLED on ESP32x (#22556)
* Replace NeoPixelBus with TasmotaLED on ESP32x * update changelog
This commit is contained in:
parent
8cb9345a97
commit
db0287e566
|
@ -11,12 +11,14 @@ All notable changes to this project will be documented in this file.
|
|||
- ESP32 Hybrid compile take custom boards settings in account (#22542)
|
||||
|
||||
### Breaking Changed
|
||||
- ArtNet on ESP32 switches from GRB to RGB encoding
|
||||
|
||||
### Changed
|
||||
- ESP32 max number of supported switches/buttons/relays from 28 to 32
|
||||
- ESP32 max number of interlocks from 14 to 16
|
||||
- ESP32 Platform from 2024.11.30 to 2024.11.31, Framework (Arduino Core) from v3.1.0.241030 to v3.1.0.241117 and IDF to 5.3.1.241024 (#22504)
|
||||
- Prevent active BLE operations with unencrypted MI-format beacons (#22453)
|
||||
- Replace NeoPixelBus with TasmotaLED on ESP32x
|
||||
|
||||
### Fixed
|
||||
- ESP32 upgrade by file upload response based on file size (#22500)
|
||||
|
|
|
@ -69,7 +69,7 @@ typedef struct {
|
|||
rmt_symbol_word_t reset_code;
|
||||
} rmt_led_strip_encoder_t;
|
||||
|
||||
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
|
||||
static IRAM_ATTR size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
|
||||
{
|
||||
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "TasmotaLED",
|
||||
"version": "0.1",
|
||||
"keywords": [
|
||||
"ws2816", "sk6812", "leds"
|
||||
],
|
||||
"description": "Lightweight implementation for adressable leds.",
|
||||
"repository":
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/arendst/Tasmota/lib/lib_basic/TasmotaLED"
|
||||
},
|
||||
"frameworks": "arduino",
|
||||
"platforms": [
|
||||
"espressif32"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
TasmotaLED.cpp - Lightweight implementation for adressable leds.
|
||||
|
||||
Copyright (C) 2024 Stephan Hadinger
|
||||
|
||||
This library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#ifdef ESP32
|
||||
|
||||
#include "TasmotaLEDPusher.h"
|
||||
#include "TasmotaLED.h"
|
||||
|
||||
// DRAM_ATTR to force in IRAM because we use this in show loop
|
||||
static const DRAM_ATTR uint8_t TASMOTALED_CHANNEL_ORDERS[6][3] = {
|
||||
{1, 0, 2}, // GRB (0)
|
||||
{2, 0, 1}, // GBR (1)
|
||||
{0, 1, 2}, // RGB (2)
|
||||
{0, 2, 1}, // RBG (3)
|
||||
{2, 1, 0}, // BRG (4)
|
||||
{1, 2, 0} // BGR (5)
|
||||
};
|
||||
|
||||
static const TasmotaLED_Timing TasmotaLED_Timings[] = {
|
||||
// WS2812
|
||||
// RmtBit0 0x00228010 RmtBit1 0x00128020 RmtReset 0x800207D0
|
||||
{
|
||||
.T0H = 400,
|
||||
.T0L = 850,
|
||||
.T1H = 800,
|
||||
.T1L = 450,
|
||||
.Reset = 80000 // it is 50000 for WS2812, but for compatibility with SK6812, we raise to 80000
|
||||
},
|
||||
// SK6812
|
||||
// RmtBit0 0x0024800C RmtBit1 0x00188018 RmtReset 0x80020C80
|
||||
{
|
||||
.T0H = 300,
|
||||
.T0L = 900,
|
||||
.T1H = 600,
|
||||
.T1L = 600,
|
||||
.Reset = 80000
|
||||
},
|
||||
};
|
||||
|
||||
// enable AddLog
|
||||
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
|
||||
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
|
||||
|
||||
|
||||
TasmotaLED::TasmotaLED(uint16_t type, uint16_t num_leds) :
|
||||
_type(type),
|
||||
_pixel_order((type >> 4) & 0x07),
|
||||
_w_before(type & 0x08),
|
||||
_timing((type >> 8) & 0xFF),
|
||||
_started(false),
|
||||
_dirty(true),
|
||||
_raw_format(false),
|
||||
_pixel_count(num_leds),
|
||||
_buf_work(nullptr),
|
||||
_buf_show(nullptr),
|
||||
_pixel_matrix(&TASMOTALED_CHANNEL_ORDERS[0]),
|
||||
_pusher(nullptr)
|
||||
{
|
||||
if (_timing > (TasmotaLed_TimingEnd >> 8)) {
|
||||
_timing = 0;
|
||||
}
|
||||
switch (_type & 0x0F) {
|
||||
// case TasmotaLed_1_W:
|
||||
// _pixel_size = 1;
|
||||
// break;
|
||||
case TasmotaLed_4_WRGB:
|
||||
_pixel_size = 4;
|
||||
break;
|
||||
case TasmotaLed_3_RGB:
|
||||
default: // fallback
|
||||
_pixel_size = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
_pixel_matrix = &TASMOTALED_CHANNEL_ORDERS[_pixel_order];
|
||||
|
||||
_buf_work = new uint8_t[_pixel_count * _pixel_size];
|
||||
memset(_buf_work, 0, _pixel_count * _pixel_size);
|
||||
_buf_show = new uint8_t[_pixel_count * _pixel_size];
|
||||
memset(_buf_show, 0, _pixel_count * _pixel_size);
|
||||
// AddLog(LOG_LEVEL_DEBUG, "LED: type=0x%04X pixel_order=0x%02X _timing=%i ", _type, _pixel_order, _timing);
|
||||
}
|
||||
|
||||
TasmotaLED::~TasmotaLED() {
|
||||
if (_pusher) {
|
||||
delete _pusher;
|
||||
_pusher = nullptr;
|
||||
}
|
||||
delete _buf_work;
|
||||
_buf_work = nullptr;
|
||||
delete _buf_show;
|
||||
_buf_show = nullptr;
|
||||
}
|
||||
|
||||
// Color is passed as 0xWWRRGGBB and copied as WWRRGGBB in _buf_work
|
||||
void TasmotaLED::ClearTo(uint32_t wrgb, int32_t first, int32_t last) {
|
||||
// adjust first and last to be in range of 0 to _pixel_count-1
|
||||
if (first <0) { first += _pixel_count; }
|
||||
if (last <0) { last += _pixel_count; }
|
||||
if (first < 0) { first = 0; }
|
||||
if (last >= _pixel_count) { last = _pixel_count - 1; }
|
||||
if (first > last) { return; }
|
||||
// adjust to pixel format
|
||||
uint8_t b0 = (wrgb >> 24) & 0xFF;
|
||||
uint8_t b1 = (wrgb >> 16) & 0xFF;
|
||||
uint8_t b2 = (wrgb >> 8) & 0xFF;
|
||||
uint8_t b3 = (wrgb ) & 0xFF;
|
||||
|
||||
if ((b0 | b1 | b2 | b3) == 0) {
|
||||
// special version for clearing to black
|
||||
memset(_buf_work + first * _pixel_size, 0, (last - first + 1) * _pixel_size);
|
||||
} else {
|
||||
// fill sub-buffer with RRGGBB or WWRRGGBB (or raw)
|
||||
uint8_t *buf = _buf_work + first * _pixel_size;
|
||||
for (uint32_t i = first; i <= last; i++) {
|
||||
if (_pixel_size == 4) { *buf++ = b0;}
|
||||
*buf++ = b1;
|
||||
*buf++ = b2;
|
||||
*buf++ = b3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TasmotaLED::Show(void) {
|
||||
if (_pusher) {
|
||||
_dirty = false; // we don't use the _dirty attribute and always show
|
||||
|
||||
// copy the input buffer to the work buffer in format to be understood by LED strip
|
||||
if (_raw_format) {
|
||||
memmove(_buf_show, _buf_work, _pixel_count * _pixel_size); // copy buffer in next buffer so we start with the current content
|
||||
} else {
|
||||
uint8_t *buf_from = _buf_work;
|
||||
uint8_t *buf_to = _buf_show;
|
||||
if (_pixel_size == 3) {
|
||||
// copying with swapping 512 pixels (1536 bytes) takes 124 microseconds to copy, so it's negligeable
|
||||
for (uint32_t i = 0; i < _pixel_count; i++) {
|
||||
buf_to[(*_pixel_matrix)[0]] = buf_from[0]; // R
|
||||
buf_to[(*_pixel_matrix)[1]] = buf_from[1]; // G
|
||||
buf_to[(*_pixel_matrix)[2]] = buf_from[2]; // B
|
||||
buf_to += 3;
|
||||
buf_from += 3;
|
||||
}
|
||||
} else if (_pixel_size == 4) {
|
||||
for (uint32_t i = 0; i < _pixel_count; i++) {
|
||||
if (_w_before) { *buf_to++ = buf_from[3]; }
|
||||
buf_to[(*_pixel_matrix)[0]] = buf_from[0]; // R
|
||||
buf_to[(*_pixel_matrix)[1]] = buf_from[1]; // G
|
||||
buf_to[(*_pixel_matrix)[2]] = buf_from[2]; // B
|
||||
if (!_w_before) { *buf_to++ = buf_from[3]; }
|
||||
buf_to += 3; // one increment already happened
|
||||
buf_from += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
_pusher->Push(_buf_show); // push to leds
|
||||
}
|
||||
}
|
||||
|
||||
void TasmotaLED::SetPixelColor(int32_t index, uint32_t wrgb) {
|
||||
if (index < 0) { index += _pixel_count; }
|
||||
if ((index >= 0) && (index < _pixel_count)) {
|
||||
uint8_t *buf = _buf_work + index * _pixel_size;
|
||||
uint8_t b0 = (wrgb >> 24) & 0xFF;
|
||||
uint8_t b1 = (wrgb >> 16) & 0xFF;
|
||||
uint8_t b2 = (wrgb >> 8) & 0xFF;
|
||||
uint8_t b3 = (wrgb ) & 0xFF;
|
||||
|
||||
if (_pixel_size == 4) { *buf++ = b0;}
|
||||
*buf++ = b1;
|
||||
*buf++ = b2;
|
||||
*buf++ = b3;
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t TasmotaLED::GetPixelColor(int32_t index) {
|
||||
if (index < 0) { index += _pixel_count; }
|
||||
if ((index >= 0) && (index < _pixel_count)) {
|
||||
uint8_t *buf = _buf_work + index * _pixel_size;
|
||||
uint32_t wrgb = 0;
|
||||
if (_pixel_size == 4) { wrgb = (*buf++) << 24; }
|
||||
wrgb |= (*buf++) << 16;
|
||||
wrgb |= (*buf++) << 8;
|
||||
wrgb |= (*buf++);
|
||||
return wrgb;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TasmotaLED::SetPusher(TasmotaLEDPusher *pusher) {
|
||||
if (_pusher) {
|
||||
delete _pusher;
|
||||
}
|
||||
_pusher = pusher;
|
||||
_started = false;
|
||||
}
|
||||
|
||||
bool TasmotaLED::Begin(void) {
|
||||
if (_pusher) {
|
||||
if (_started) {
|
||||
return true;
|
||||
} else {
|
||||
const TasmotaLED_Timing * timing = &TasmotaLED_Timings[_timing];
|
||||
// AddLog(LOG_LEVEL_DEBUG, "LED: T0H=%i T0L=%i T1H=%i T1L=%i Reset=%i", timing.T0H, timing.T0L, timing.T1H, timing.T1L, timing.Reset);
|
||||
return _pusher->Begin(_pixel_count, _pixel_size, timing);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TasmotaLED::CanShow(void) const {
|
||||
if (_pusher) {
|
||||
return _pusher->CanShow();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // ESP32
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
TasmotaLED.h - Lightweight implementation for adressable leds.
|
||||
|
||||
Copyright (C) 2024 Stephan Hadinger
|
||||
|
||||
This library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __TASMOTALED_H__
|
||||
#define __TASMOTALED_H__
|
||||
|
||||
enum TasmotaLEDTypesEncoding : uint16_t {
|
||||
// bits 0..3 encode for number of bytes per pixel
|
||||
TasmotaLed_1_W = 0x0, // 1 byte per pixel (not used yet)
|
||||
TasmotaLed_3_RGB = 0x1, // 3 bytes per pixel
|
||||
TasmotaLed_4_WRGB = 0x2, // 4 bytes per pixel
|
||||
// bits 4..6 encode for pixel order
|
||||
TasmotaLed_GRB = 0b000 << 4,
|
||||
TasmotaLed_GBR = 0b001 << 4,
|
||||
TasmotaLed_RGB = 0b010 << 4,
|
||||
TasmotaLed_RBG = 0b011 << 4,
|
||||
TasmotaLed_BRG = 0b100 << 4,
|
||||
TasmotaLed_BGR = 0b101 << 4,
|
||||
// bit 7 sets the position for W channel
|
||||
TasmotaLed_xxxW = 0b0 << 7, // W channel after color
|
||||
TasmotaLed_Wxxx = 0b1 << 7, // W channel before color
|
||||
// bits 8..15 encode for timing specifics
|
||||
TasmotaLed_WS2812 = 0 << 8,
|
||||
TasmotaLed_SK6812 = 1 << 8,
|
||||
TasmotaLed_TimingEnd = 2 << 8,
|
||||
};
|
||||
|
||||
enum TasmotaLEDHardware : uint32_t {
|
||||
// low-order bits are reserved for channels numbers and hardware flags - currenlty not useds
|
||||
// bits 16..23
|
||||
TasmotaLed_HW_Default = 0x0 << 16,
|
||||
TasmotaLed_RMT = 1 << 16,
|
||||
TasmotaLed_SPI = 2 << 16,
|
||||
TasmotaLed_I2S = 3 << 16,
|
||||
TasmotaLed_HW_None = 0xFF << 16, // indicates that the specified HW is not supported
|
||||
};
|
||||
|
||||
// Below is the encoding for full strips
|
||||
// We need to keep backwards compatibility so:
|
||||
// 0 = WS2812 (GRB)
|
||||
// 1 = SK6812 with White (GRBW)
|
||||
enum TasmotaLEDTypes : uint16_t {
|
||||
ws2812_grb = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_WS2812, // 1 for backwards compatibility
|
||||
sk6812_grbw = TasmotaLed_4_WRGB | TasmotaLed_GRB | TasmotaLed_xxxW | TasmotaLed_SK6812, // 2 for backwards compatibility
|
||||
sk6812_grb = TasmotaLed_3_RGB | TasmotaLed_GRB | TasmotaLed_SK6812,
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
/*******************************************************************************************\
|
||||
* class TasmotaLED
|
||||
*
|
||||
* This class is a lightweight replacement for NeoPixelBus library with a smaller
|
||||
* implementation focusing only on pushing a buffer to the leds.
|
||||
*
|
||||
* It supports:
|
||||
* - RMT and I2S hardware support
|
||||
* Possible enhancements could be considered with SPI and Serial
|
||||
* - Led size of 3 bytes (GRB) and 4 bytes (GRBW)
|
||||
* APIs take 0xRRGGBB or 0xRRGGBBWW as input
|
||||
* but Internal buffers use GGRRBB and GGRRBBWW
|
||||
* - Led type of WS2812 and SK6812
|
||||
* - There is no buffer swapping, the working buffer is copied to an internal
|
||||
* buffer just before display, so you can keep a reference to the buffer
|
||||
* and modify it without having to worry about the display
|
||||
* - buffer is cleared at start
|
||||
* - "Dirty" is kept for API compatibility with NeoPixelBus but is glbally ignored
|
||||
* so any call to `Show()` pushes the pixels even if they haven't changed.
|
||||
* Control for dirty pixels should be done by the caller if required.
|
||||
* - We tried to keep as close as possible to NeoPixelBus method names to ease transition
|
||||
\*******************************************************************************************/
|
||||
class TasmotaLEDPusher; // forward definition
|
||||
class TasmotaLED {
|
||||
public:
|
||||
TasmotaLED(uint16_t type, uint16_t num_leds);
|
||||
~TasmotaLED();
|
||||
|
||||
bool Begin(void);
|
||||
void SetPusher(TasmotaLEDPusher *pusher); // needs to be called before `Begin()`, sets the hardware implementation
|
||||
void Show(void); // pushes the pixels to the LED strip
|
||||
inline void SetRawFormat(bool raw_format) { _raw_format = raw_format; }
|
||||
|
||||
void ClearTo(uint32_t rgbw, int32_t first = 0, int32_t last = -1);
|
||||
void SetPixelColor(int32_t index, uint32_t wrgb);
|
||||
uint32_t GetPixelColor(int32_t index);
|
||||
|
||||
uint8_t GetType(void) const { return _type; }
|
||||
uint16_t PixelCount(void) const { return _pixel_count; }
|
||||
uint8_t PixelSize(void) const { return _pixel_size; }
|
||||
inline uint8_t * Pixels(void) const { return _buf_work; }
|
||||
inline bool IsDirty(void) const { return _dirty; }
|
||||
inline void Dirty(void) { _dirty = true; }
|
||||
|
||||
bool CanShow(void) const;
|
||||
|
||||
protected:
|
||||
uint16_t _type; // the composite type
|
||||
uint8_t _pixel_order; // permutation between RGB and position of W
|
||||
bool _w_before; // true if W channel comes first (4 channels only)
|
||||
uint8_t _timing; // timing code for strip, 0=WS2812, 1=SK6812...
|
||||
bool _started; // true if the hardware implementation is configured
|
||||
bool _dirty; // for NeoPixelBus compatibility, but ignored by `Push()`
|
||||
bool _raw_format; // if true, copy raw to leds, if false, convert from RGB to GRB or LED format
|
||||
uint16_t _pixel_count; // how many pixels in the strip
|
||||
uint8_t _pixel_size; // how many bytes per pixels, only 3 and 4 are supported
|
||||
uint8_t *_buf_work; // buffer used to draw into, can be modified directly by the caller
|
||||
uint8_t *_buf_show; // copy of the buffer used to push to leds, private to this class
|
||||
const uint8_t (*_pixel_matrix)[3]; // pointer to the pixer_order_matrix
|
||||
TasmotaLEDPusher *_pusher; // pixels pusher implementation based on hardware (RMT, I2S...)
|
||||
};
|
||||
|
||||
#endif // __cplusplus
|
||||
#endif // __TASMOTALED_H__
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
TasmotaLEDPusher.cpp - Implementation to push Leds via hardware acceleration
|
||||
|
||||
Copyright (C) 2024 Stephan Hadinger
|
||||
|
||||
This library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include "TasmotaLEDPusher.h"
|
||||
#include "TasmotaLED.h"
|
||||
|
||||
//**************************************************************************************************************
|
||||
// enable AddLog support within a C++ library
|
||||
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
|
||||
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
|
||||
//**************************************************************************************************************
|
||||
|
||||
|
||||
// convert to the appropriate hardware acceleration based on capacities of the SOC
|
||||
uint32_t TasmotaLEDPusher::ResolveHardware(uint32_t hw) {
|
||||
uint32_t hw_orig = hw;
|
||||
// Step 1. discard any unsupported hardware, and replace with TasmotaLed_HW_Default
|
||||
uint32_t hw_type = hw & 0xFF0000; // discard bits 0..15
|
||||
#if !TASMOTALED_HARDWARE_RMT
|
||||
if (hw_type == TasmotaLed_RMT) {
|
||||
hw = TasmotaLed_HW_None;
|
||||
}
|
||||
#endif // TASMOTALED_HARDWARE_RMT
|
||||
#if !TASMOTALED_HARDWARE_SPI
|
||||
if (hw_type == TasmotaLed_SPI) {
|
||||
hw = TasmotaLed_HW_None;
|
||||
}
|
||||
#endif // TASMOTALED_HARDWARE_SPI
|
||||
#if !TASMOTALED_HARDWARE_I2S
|
||||
if (hw_type == TasmotaLed_I2S) {
|
||||
hw = TasmotaLed_HW_None;
|
||||
}
|
||||
#endif // TASMOTALED_HARDWARE_I2S
|
||||
|
||||
// Step 2. If TasmotaLed_HW_Default, find a suitable scheme, RMT preferred
|
||||
#if TASMOTALED_HARDWARE_RMT
|
||||
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
|
||||
hw = TasmotaLed_RMT;
|
||||
}
|
||||
#endif // TASMOTALED_HARDWARE_RMT
|
||||
#if TASMOTALED_HARDWARE_I2S
|
||||
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
|
||||
hw = TasmotaLed_I2S;
|
||||
}
|
||||
#endif // TASMOTALED_HARDWARE_I2S
|
||||
#if TASMOTALED_HARDWARE_SPI
|
||||
if ((hw & 0xFF0000) == TasmotaLed_HW_Default) {
|
||||
hw = TasmotaLed_SPI;
|
||||
}
|
||||
#endif // TASMOTALED_HARDWARE_SPI
|
||||
return hw;
|
||||
}
|
||||
|
||||
|
||||
TasmotaLEDPusher * TasmotaLEDPusher::Create(uint32_t hw, int8_t gpio) {
|
||||
TasmotaLEDPusher * pusher = nullptr;
|
||||
|
||||
hw = TasmotaLEDPusher::ResolveHardware(hw);
|
||||
|
||||
switch (hw & 0XFF0000) {
|
||||
#if TASMOTALED_HARDWARE_RMT
|
||||
case TasmotaLed_RMT:
|
||||
pusher = new TasmotaLEDPusherRMT(gpio);
|
||||
AddLog(LOG_LEVEL_DEBUG, "LED: RMT gpio %i", gpio);
|
||||
break;
|
||||
#endif // TASMOTALED_HARDWARE_RMT
|
||||
#if TASMOTALED_HARDWARE_SPI
|
||||
case TasmotaLed_SPI:
|
||||
pusher = new TasmotaLEDPusherSPI(gpio);
|
||||
AddLog(LOG_LEVEL_DEBUG, "LED: SPI gpio %i", gpio);
|
||||
break;
|
||||
#endif // TASMOTALED_HARDWARE_SPI
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return pusher;
|
||||
}
|
||||
|
||||
#endif // ESP32
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
TasmotaLEDPusher.h - Abstract class for Leds pusher (RMT, SPI, I2S...)
|
||||
|
||||
Copyright (C) 2024 Stephan Hadinger
|
||||
|
||||
This library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __TASMOTALEDPUSHER_H__
|
||||
#define __TASMOTALEDPUSHER_H__
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Below are flags to enable of disable each hardware support: RMT, I2S, SPI
|
||||
// By default, only enable RMT support, and SPI is used as fallback if no protocol works
|
||||
//
|
||||
// Use de defines below:
|
||||
// #define TASMOTALED_HARDWARE_RMT 0/1
|
||||
// #define TASMOTALED_HARDWARE_I2S 0/1
|
||||
// #define TASMOTALED_HARDWARE_SPI 0/1
|
||||
//
|
||||
#ifndef TASMOTALED_HARDWARE_RMT
|
||||
#define TASMOTALED_HARDWARE_RMT 1
|
||||
#endif
|
||||
|
||||
#ifndef TASMOTALED_HARDWARE_I2S
|
||||
#define TASMOTALED_HARDWARE_I2S 0
|
||||
#endif
|
||||
|
||||
#ifndef TASMOTALED_HARDWARE_SPI
|
||||
#define TASMOTALED_HARDWARE_SPI 0
|
||||
#endif
|
||||
|
||||
// Disable any hardware if not supported by the SOC
|
||||
#if TASMOTALED_HARDWARE_RMT && !defined(SOC_RMT_SUPPORTED)
|
||||
#undef TASMOTALED_HARDWARE_RMT
|
||||
#define TASMOTALED_HARDWARE_RMT 0
|
||||
#endif
|
||||
|
||||
#if TASMOTALED_HARDWARE_I2S && !defined(SOC_I2S_SUPPORTED)
|
||||
#undef TASMOTALED_HARDWARE_I2S
|
||||
#define TASMOTALED_HARDWARE_I2S 0
|
||||
#endif
|
||||
|
||||
#if TASMOTALED_HARDWARE_SPI && !defined(SOC_GPSPI_SUPPORTED)
|
||||
#undef TASMOTALED_HARDWARE_SPI
|
||||
#define TASMOTALED_HARDWARE_SPI 0
|
||||
#endif
|
||||
|
||||
// if no protocol is defined, use SPI as fallback
|
||||
#if !TASMOTALED_HARDWARE_RMT && !TASMOTALED_HARDWARE_I2S && !TASMOTALED_HARDWARE_SPI
|
||||
#undef TASMOTALED_HARDWARE_SPI
|
||||
#define TASMOTALED_HARDWARE_SPI 1
|
||||
#endif
|
||||
|
||||
// Timing structure for LEDS - in nanoseconds
|
||||
// It is passed by TasmotaLed to the pushers
|
||||
typedef struct TasmotaLED_Timing {
|
||||
uint16_t T0H, T0L, T1H, T1L;
|
||||
uint32_t Reset;
|
||||
} TasmotaLED_Timing;
|
||||
|
||||
/*******************************************************************************************\
|
||||
* class TasmotaLEDPusher
|
||||
*
|
||||
* This is an virtual abstract class for Leds pusher (RMT, SPI, I2S...)
|
||||
*
|
||||
* Below are interfaces for current implementations
|
||||
\*******************************************************************************************/
|
||||
class TasmotaLEDPusher {
|
||||
public:
|
||||
TasmotaLEDPusher() : _pixel_count(0), _pixel_size(0), _led_timing(nullptr) {};
|
||||
virtual ~TasmotaLEDPusher() {};
|
||||
|
||||
virtual bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
|
||||
_pixel_count = pixel_count;
|
||||
_pixel_size = pixel_size;
|
||||
_led_timing = led_timing;
|
||||
return true;
|
||||
}
|
||||
virtual bool Push(uint8_t *buf) = 0;
|
||||
virtual bool CanShow(void) = 0;
|
||||
|
||||
static uint32_t ResolveHardware(uint32_t hw); // convert to the appropriate hardware acceleration based on capacities of the SOC
|
||||
static TasmotaLEDPusher * Create(uint32_t hw, int8_t gpio); // create instance for the provided type, or nullptr if failed
|
||||
|
||||
protected:
|
||||
uint16_t _pixel_count;
|
||||
uint16_t _pixel_size;
|
||||
const TasmotaLED_Timing * _led_timing;
|
||||
};
|
||||
|
||||
/*******************************************************************************************\
|
||||
* class TasmotaLEDPusherRMT
|
||||
*
|
||||
* Implementation based on RMT driver
|
||||
\*******************************************************************************************/
|
||||
#if TASMOTALED_HARDWARE_RMT
|
||||
#include "driver/rmt_tx.h"
|
||||
class TasmotaLEDPusherRMT : public TasmotaLEDPusher {
|
||||
public:
|
||||
TasmotaLEDPusherRMT(int8_t pin) : _pin(pin) {};
|
||||
~TasmotaLEDPusherRMT();
|
||||
|
||||
bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) override;
|
||||
|
||||
bool Push(uint8_t *buf) override;
|
||||
bool CanShow(void) override;
|
||||
protected:
|
||||
int8_t _pin;
|
||||
rmt_transmit_config_t _tx_config = {};
|
||||
rmt_channel_handle_t _channel = nullptr;;
|
||||
rmt_encoder_handle_t _led_encoder = nullptr;
|
||||
};
|
||||
#endif // TASMOTALED_HARDWARE_RMT
|
||||
|
||||
/*******************************************************************************************\
|
||||
* class TasmotaLEDPusherSPI
|
||||
*
|
||||
* Implementation based on SPI driver, mandatory for C2
|
||||
\*******************************************************************************************/
|
||||
#if TASMOTALED_HARDWARE_SPI
|
||||
#include <driver/spi_master.h>
|
||||
|
||||
typedef struct led_strip_spi_obj_t {
|
||||
uint8_t * pixel_buf;
|
||||
uint16_t strip_len;
|
||||
uint8_t bytes_per_pixel;
|
||||
spi_host_device_t spi_host;
|
||||
spi_device_handle_t spi_device;
|
||||
spi_transaction_t tx_conf; // transaction in process if any
|
||||
} led_strip_spi_obj;
|
||||
|
||||
class TasmotaLEDPusherSPI : public TasmotaLEDPusher {
|
||||
public:
|
||||
TasmotaLEDPusherSPI(int8_t pin) : _pin(pin), _spi_strip({}) {};
|
||||
~TasmotaLEDPusherSPI();
|
||||
|
||||
bool Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) override;
|
||||
|
||||
bool Push(uint8_t *buf) override;
|
||||
bool CanShow(void) override;
|
||||
|
||||
protected:
|
||||
int8_t _pin;
|
||||
struct led_strip_spi_obj_t _spi_strip;
|
||||
};
|
||||
#endif // TASMOTALED_HARDWARE_SPI
|
||||
|
||||
#endif // __TASMOTALEDPUSHER_H__
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
TasmotaLEDPusherRMT.cpp - Implementation to push Leds via RMT channel
|
||||
|
||||
Copyright (C) 2024 Stephan Hadinger
|
||||
|
||||
This library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include "TasmotaLEDPusher.h"
|
||||
#include "TasmotaLED.h"
|
||||
|
||||
#if TASMOTALED_HARDWARE_RMT
|
||||
#include <rom/gpio.h>
|
||||
#include <esp_check.h>
|
||||
|
||||
//**************************************************************************************************************
|
||||
// enable AddLog support within a C++ library
|
||||
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
|
||||
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
|
||||
//**************************************************************************************************************
|
||||
|
||||
/*******************************************************************************************\
|
||||
* Implementation for TasmotaLEDPusherRMT
|
||||
*
|
||||
* Code mostly copied from Tasmota patch to NeoPixelBus applied to support esp-idf 5.x
|
||||
* itself inspired from esp-idf example for RMT encoder from
|
||||
* https://github.com/espressif/esp-idf/tree/v5.3.1/examples/peripherals/rmt/ir_nec_transceiver
|
||||
\*******************************************************************************************/
|
||||
#define RMT_LED_STRIP_RESOLUTION_HZ 40000000 // 40MHz resolution, steps of 25 nanoseconds
|
||||
|
||||
// structure used to pass arguments to `rmt_new_led_strip_encoder`
|
||||
// currently only the encoder resolution in Hz
|
||||
typedef struct {
|
||||
uint32_t resolution; /*!< Encoder resolution, in Hz */
|
||||
} led_strip_encoder_config_t;
|
||||
|
||||
// structure used to store all the necessary information for the RMT encoder
|
||||
typedef struct {
|
||||
rmt_encoder_t base;
|
||||
rmt_encoder_t *bytes_encoder;
|
||||
rmt_encoder_t *copy_encoder;
|
||||
int32_t state;
|
||||
rmt_symbol_word_t reset_code;
|
||||
} rmt_led_strip_encoder_t;
|
||||
|
||||
static IRAM_ATTR size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
|
||||
{
|
||||
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
|
||||
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
|
||||
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
|
||||
rmt_encode_state_t state = RMT_ENCODING_RESET;
|
||||
size_t encoded_symbols = 0;
|
||||
switch (led_encoder->state) {
|
||||
case 0: // send RGB data
|
||||
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
|
||||
if (session_state & RMT_ENCODING_COMPLETE) {
|
||||
led_encoder->state = 1; // switch to next state when current encoding session finished
|
||||
}
|
||||
if (session_state & RMT_ENCODING_MEM_FULL) {
|
||||
state = static_cast<rmt_encode_state_t>(static_cast<uint8_t>(state) | static_cast<uint8_t>(RMT_ENCODING_MEM_FULL));
|
||||
goto out; // yield if there's no free space for encoding artifacts
|
||||
}
|
||||
// fall-through
|
||||
case 1: // send reset code
|
||||
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, sizeof(led_encoder->reset_code), &session_state);
|
||||
if (session_state & RMT_ENCODING_COMPLETE) {
|
||||
led_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session
|
||||
state = static_cast<rmt_encode_state_t>(static_cast<uint8_t>(state) | static_cast<uint8_t>(RMT_ENCODING_COMPLETE));
|
||||
}
|
||||
if (session_state & RMT_ENCODING_MEM_FULL) {
|
||||
state = static_cast<rmt_encode_state_t>(static_cast<uint8_t>(state) | static_cast<uint8_t>(RMT_ENCODING_MEM_FULL));
|
||||
goto out; // yield if there's no free space for encoding artifacts
|
||||
}
|
||||
}
|
||||
out:
|
||||
*ret_state = state;
|
||||
return encoded_symbols;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) {
|
||||
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||
rmt_del_encoder(led_encoder->bytes_encoder);
|
||||
rmt_del_encoder(led_encoder->copy_encoder);
|
||||
delete led_encoder;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) {
|
||||
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
|
||||
rmt_encoder_reset(led_encoder->bytes_encoder);
|
||||
rmt_encoder_reset(led_encoder->copy_encoder);
|
||||
led_encoder->state = RMT_ENCODING_RESET;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder, rmt_symbol_word_t bit0, rmt_symbol_word_t bit1, rmt_symbol_word_t reset_code) {
|
||||
static const char* TAG = "TASMOTA_RMT";
|
||||
esp_err_t ret = ESP_OK;
|
||||
rmt_led_strip_encoder_t *led_encoder = NULL;
|
||||
rmt_bytes_encoder_config_t bytes_encoder_config;
|
||||
rmt_copy_encoder_config_t copy_encoder_config = {};
|
||||
|
||||
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
led_encoder = new rmt_led_strip_encoder_t();
|
||||
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
|
||||
led_encoder->base.encode = rmt_encode_led_strip;
|
||||
led_encoder->base.del = rmt_del_led_strip_encoder;
|
||||
led_encoder->base.reset = rmt_led_strip_encoder_reset;
|
||||
led_encoder->reset_code = reset_code;
|
||||
|
||||
bytes_encoder_config.bit0 = bit0;
|
||||
bytes_encoder_config.bit1 = bit1;
|
||||
bytes_encoder_config.flags.msb_first = 1; // WS2812 transfer bit order: G7...G0R7...R0B7...B0 - TODO: more checks
|
||||
|
||||
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
|
||||
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
|
||||
|
||||
*ret_encoder = &led_encoder->base;
|
||||
return ret;
|
||||
err:
|
||||
AddLog(LOG_LEVEL_INFO, "RMT: could not init led encoder");
|
||||
if (led_encoder) {
|
||||
if (led_encoder->bytes_encoder) { rmt_del_encoder(led_encoder->bytes_encoder); }
|
||||
if (led_encoder->copy_encoder) { rmt_del_encoder(led_encoder->copy_encoder); }
|
||||
delete led_encoder;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TasmotaLEDPusherRMT::~TasmotaLEDPusherRMT() {
|
||||
if (_channel) {
|
||||
rmt_tx_wait_all_done(_channel, 10000 / portTICK_PERIOD_MS);
|
||||
rmt_del_channel(_channel);
|
||||
_channel = nullptr;
|
||||
}
|
||||
|
||||
if (_pin >= 0) {
|
||||
gpio_matrix_out(_pin, 0x100, false, false);
|
||||
pinMode(_pin, INPUT);
|
||||
_pin = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool TasmotaLEDPusherRMT::Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
|
||||
TasmotaLEDPusher::Begin(pixel_count, pixel_size, led_timing);
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
rmt_tx_channel_config_t config = {};
|
||||
config.clk_src = RMT_CLK_SRC_DEFAULT;
|
||||
config.gpio_num = static_cast<gpio_num_t>(_pin);
|
||||
config.mem_block_symbols = 192; // memory block size, 64 * 4 = 256 Bytes
|
||||
config.resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ; // 40 MHz tick resolution, i.e., 1 tick = 0.025 µs or 25 ns
|
||||
config.trans_queue_depth = 4; // set the number of transactions that can pend in the background
|
||||
config.flags.invert_out = false; // do not invert output signal
|
||||
config.flags.with_dma = false; // do not need DMA backend
|
||||
|
||||
ret = rmt_new_tx_channel(&config, &_channel);
|
||||
if (ret != ESP_OK) {
|
||||
AddLog(LOG_LEVEL_INFO, "RMT: cannot initialize Gpio %i err=%i", _pin, ret);
|
||||
return false;
|
||||
}
|
||||
led_strip_encoder_config_t encoder_config = {
|
||||
.resolution = RMT_LED_STRIP_RESOLUTION_HZ,
|
||||
};
|
||||
|
||||
_tx_config.loop_count = 0; // no loop
|
||||
|
||||
rmt_symbol_word_t RmtBit0 = {
|
||||
.duration0 = (uint16_t) (led_timing->T0H * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
|
||||
.level0 = 1,
|
||||
.duration1 = (uint16_t) (led_timing->T0L * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
|
||||
.level1 = 0,
|
||||
};
|
||||
rmt_symbol_word_t RmtBit1 = {
|
||||
.duration0 = (uint16_t) (led_timing->T1H * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
|
||||
.level0 = 1,
|
||||
.duration1 = (uint16_t) (led_timing->T1L * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
|
||||
.level1 = 0,
|
||||
};
|
||||
rmt_symbol_word_t RmtReset = {
|
||||
.duration0 = (uint16_t) (led_timing->Reset * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000),
|
||||
.level0 = 0,
|
||||
.duration1 = 50 * (RMT_LED_STRIP_RESOLUTION_HZ / 1000000) / 1000,
|
||||
.level1 = 1,
|
||||
};
|
||||
// AddLog(LOG_LEVEL_INFO, "RMT: RmtBit0 0x%08X RmtBit1 0x%08X RmtReset 0x%08X", RmtBit0.val, RmtBit1.val, RmtReset.val);
|
||||
ret = rmt_new_led_strip_encoder(&encoder_config, &_led_encoder, RmtBit0, RmtBit1, RmtReset);
|
||||
if (ret != ESP_OK) {
|
||||
// AddLog(LOG_LEVEL_INFO, "RMT: cannot initialize led strip encoder err=%i", ret);
|
||||
return false;
|
||||
}
|
||||
ret = rmt_enable(_channel);
|
||||
if (ret != ESP_OK) {
|
||||
// AddLog(LOG_LEVEL_INFO, "RMT: cannot enable channel err=%i", ret);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TasmotaLEDPusherRMT::CanShow(void) {
|
||||
if (_channel) {
|
||||
return (ESP_OK == rmt_tx_wait_all_done(_channel, 0));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TasmotaLEDPusherRMT::Push(uint8_t *buf) {
|
||||
|
||||
// wait for not actively sending data
|
||||
// this will time out at 1 second, an arbitrarily long period of time
|
||||
// and do nothing if this happens
|
||||
esp_err_t ret = rmt_tx_wait_all_done(_channel, 1000 / portTICK_PERIOD_MS);
|
||||
if (ESP_OK == ret) {
|
||||
// now start the RMT transmit with the editing buffer before we swap
|
||||
ret = rmt_transmit(_channel, _led_encoder, buf, _pixel_count * _pixel_size, &_tx_config);
|
||||
if (ESP_OK != ret) {
|
||||
AddLog(LOG_LEVEL_DEBUG, "RMT: cannot transmit err=%i", ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // TASMOTALED_HARDWARE_RMT
|
||||
#endif // ESP32
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
TasmotaLEDPusherRMT.cpp - Implementation to push Leds via SPI channel
|
||||
|
||||
Copyright (C) 2024 Stephan Hadinger
|
||||
|
||||
This library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef ESP32
|
||||
|
||||
#include "TasmotaLEDPusher.h"
|
||||
#include "TasmotaLED.h"
|
||||
|
||||
#if TASMOTALED_HARDWARE_SPI
|
||||
#include <rom/gpio.h>
|
||||
|
||||
//**************************************************************************************************************
|
||||
// enable AddLog support within a C++ library
|
||||
extern void AddLog(uint32_t loglevel, PGM_P formatP, ...);
|
||||
enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE};
|
||||
//**************************************************************************************************************
|
||||
|
||||
/*******************************************************************************************\
|
||||
* Implementation for TasmotaLEDPusherSPI
|
||||
*
|
||||
\*******************************************************************************************/
|
||||
|
||||
#define LED_STRIP_SPI_DEFAULT_RESOLUTION (25 * 100 * 1000) // 2.5MHz resolution
|
||||
#define LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE 4
|
||||
|
||||
#define SPI_BYTES_PER_COLOR_BYTE 3
|
||||
#define SPI_BITS_PER_COLOR_BYTE (SPI_BYTES_PER_COLOR_BYTE * 8)
|
||||
|
||||
static void __led_strip_spi_bit(uint8_t data, uint8_t *buf)
|
||||
{
|
||||
// Each color of 1 bit is represented by 3 bits of SPI, low_level:100 ,high_level:110
|
||||
// So a color byte occupies 3 bytes of SPI.
|
||||
buf[0] = (data & BIT(5) ? BIT(1) | BIT(0) : BIT(1)) | (data & BIT(6) ? BIT(4) | BIT(3) : BIT(4)) | (data & BIT(7) ? BIT(7) | BIT(6) : BIT(7));
|
||||
buf[1] = (BIT(0)) | (data & BIT(3) ? BIT(3) | BIT(2) : BIT(3)) | (data & BIT(4) ? BIT(6) | BIT(5) : BIT(6));
|
||||
buf[2] = (data & BIT(0) ? BIT(2) | BIT(1) : BIT(2)) | (data & BIT(1) ? BIT(5) | BIT(4) : BIT(5)) | (data & BIT(2) ? BIT(7) : 0x00);
|
||||
}
|
||||
|
||||
esp_err_t led_strip_spi_refresh(led_strip_spi_obj * spi_strip)
|
||||
{
|
||||
spi_strip->tx_conf.length = spi_strip->strip_len * spi_strip->bytes_per_pixel * SPI_BITS_PER_COLOR_BYTE;
|
||||
spi_strip->tx_conf.tx_buffer = spi_strip->pixel_buf;
|
||||
spi_strip->tx_conf.rx_buffer = NULL;
|
||||
spi_device_transmit(spi_strip->spi_device, &spi_strip->tx_conf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void led_strip_transmit_buffer(led_strip_spi_obj * spi_strip, uint8_t * buffer_rgbw) {
|
||||
// Timing for 512 pixels (extreme test)
|
||||
// Copying to buffer: 418 us
|
||||
// sending pixels: 16.2 ms
|
||||
uint8_t * buf = buffer_rgbw;
|
||||
uint8_t * pix_buf = spi_strip->pixel_buf;
|
||||
for (int i = 0; i < spi_strip->strip_len; i++) {
|
||||
// LED_PIXEL_FORMAT_GRB takes 72bits(9bytes)
|
||||
__led_strip_spi_bit(*buf++, pix_buf); pix_buf += SPI_BYTES_PER_COLOR_BYTE;
|
||||
__led_strip_spi_bit(*buf++, pix_buf); pix_buf += SPI_BYTES_PER_COLOR_BYTE;
|
||||
__led_strip_spi_bit(*buf++, pix_buf); pix_buf += SPI_BYTES_PER_COLOR_BYTE;
|
||||
if (spi_strip->bytes_per_pixel > 3) {
|
||||
__led_strip_spi_bit(*buf++, pix_buf); pix_buf += SPI_BYTES_PER_COLOR_BYTE;
|
||||
}
|
||||
}
|
||||
/* Refresh the strip to send data */
|
||||
led_strip_spi_refresh(spi_strip);
|
||||
}
|
||||
|
||||
|
||||
TasmotaLEDPusherSPI::~TasmotaLEDPusherSPI() {
|
||||
if (_spi_strip.spi_device) {
|
||||
spi_bus_remove_device(_spi_strip.spi_device);
|
||||
}
|
||||
if (_spi_strip.spi_host) {
|
||||
spi_bus_free(_spi_strip.spi_host);
|
||||
}
|
||||
|
||||
if (_pin >= 0) {
|
||||
gpio_matrix_out(_pin, 0x100, false, false);
|
||||
pinMode(_pin, INPUT);
|
||||
_pin = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool TasmotaLEDPusherSPI::Begin(uint16_t pixel_count, uint16_t pixel_size, const TasmotaLED_Timing * led_timing) {
|
||||
TasmotaLEDPusher::Begin(pixel_count, pixel_size, led_timing);
|
||||
_spi_strip.bytes_per_pixel = _pixel_size;
|
||||
_spi_strip.strip_len = _pixel_count;
|
||||
|
||||
esp_err_t err = ESP_OK;
|
||||
uint32_t mem_caps = MALLOC_CAP_DEFAULT;
|
||||
// spi_clock_source_t clk_src = SPI_CLK_SRC_DEFAULT;
|
||||
spi_bus_config_t spi_bus_cfg;
|
||||
spi_device_interface_config_t spi_dev_cfg;
|
||||
spi_host_device_t spi_host = SPI2_HOST;
|
||||
bool with_dma = true; /// TODO: pass value or compute based on pixelcount
|
||||
int clock_resolution_khz = 0;
|
||||
|
||||
if (with_dma) { // TODO
|
||||
// DMA buffer must be placed in internal SRAM
|
||||
mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA;
|
||||
}
|
||||
_spi_strip.pixel_buf = (uint8_t *)heap_caps_calloc(1, _pixel_count * _pixel_size * SPI_BYTES_PER_COLOR_BYTE, mem_caps);
|
||||
if (_spi_strip.pixel_buf == nullptr) {
|
||||
AddLog(LOG_LEVEL_INFO, PSTR("LED: Error no mem for spi strip"));
|
||||
goto err;
|
||||
}
|
||||
|
||||
spi_bus_cfg = {
|
||||
.mosi_io_num = _pin,
|
||||
//Only use MOSI to generate the signal, set -1 when other pins are not used.
|
||||
.miso_io_num = -1,
|
||||
.sclk_io_num = -1,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = _pixel_count * _pixel_size * SPI_BYTES_PER_COLOR_BYTE,
|
||||
};
|
||||
err = spi_bus_initialize(spi_host, &spi_bus_cfg, with_dma ? SPI_DMA_CH_AUTO : SPI_DMA_DISABLED);
|
||||
if (err != ESP_OK) {
|
||||
AddLog(LOG_LEVEL_INFO, PSTR("LED: Error create SPI bus failed"));
|
||||
goto err;
|
||||
}
|
||||
_spi_strip.spi_host = spi_host; // confirmed working, so keep it's value to free it later
|
||||
|
||||
spi_dev_cfg = {
|
||||
.command_bits = 0,
|
||||
.address_bits = 0,
|
||||
.dummy_bits = 0,
|
||||
.mode = 0,
|
||||
//set -1 when CS is not used
|
||||
.clock_source = SPI_CLK_SRC_DEFAULT, // clk_src,
|
||||
.clock_speed_hz = LED_STRIP_SPI_DEFAULT_RESOLUTION,
|
||||
.spics_io_num = -1,
|
||||
.queue_size = LED_STRIP_SPI_DEFAULT_TRANS_QUEUE_SIZE,
|
||||
};
|
||||
err = spi_bus_add_device(_spi_strip.spi_host, &spi_dev_cfg, &_spi_strip.spi_device);
|
||||
if (err != ESP_OK) {
|
||||
// AddLog(LOG_LEVEL_INFO, "LED: Error failed to add spi device");
|
||||
goto err;
|
||||
}
|
||||
|
||||
spi_device_get_actual_freq(_spi_strip.spi_device, &clock_resolution_khz);
|
||||
if (err != ESP_OK) {
|
||||
// AddLog(LOG_LEVEL_INFO, "LED: Error failed to get spi frequency");
|
||||
goto err;
|
||||
}
|
||||
// TODO: ideally we should decide the SPI_BYTES_PER_COLOR_BYTE by the real clock resolution
|
||||
// But now, let's fixed the resolution, the downside is, we don't support a clock source whose frequency is not multiple of LED_STRIP_SPI_DEFAULT_RESOLUTION
|
||||
if (clock_resolution_khz != LED_STRIP_SPI_DEFAULT_RESOLUTION / 1000) {
|
||||
// AddLog(LOG_LEVEL_INFO, "LED: Error unsupported clock resolution: %dKHz", clock_resolution_khz);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return true;
|
||||
err:
|
||||
if (_spi_strip.spi_device) {
|
||||
spi_bus_remove_device(_spi_strip.spi_device);
|
||||
}
|
||||
if (_spi_strip.spi_host) {
|
||||
spi_bus_free(_spi_strip.spi_host);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TasmotaLEDPusherSPI::CanShow(void) {
|
||||
return true; // TODO
|
||||
}
|
||||
|
||||
bool TasmotaLEDPusherSPI::Push(uint8_t *buf) {
|
||||
|
||||
if (CanShow()) {
|
||||
led_strip_transmit_buffer(&_spi_strip, buf);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // TASMOTALED_HARDWARE_SPI
|
||||
#endif // ESP32
|
|
@ -152,7 +152,7 @@ BERRY_LOCAL const bntvmodule_t* const be_module_table[] = {
|
|||
&be_native_module(unishox),
|
||||
#endif // USE_UNISHOX_COMPRESSION
|
||||
|
||||
#ifdef USE_WS2812
|
||||
#if defined(USE_WS2812) && !defined(USE_WS2812_FORCE_NEOPIXELBUS)
|
||||
&be_native_module(animate),
|
||||
#endif // USE_WS2812
|
||||
|
||||
|
@ -293,7 +293,7 @@ BERRY_LOCAL bclass_array be_class_table = {
|
|||
#ifdef USE_BERRY_TCPSERVER
|
||||
&be_native_class(tcpserver),
|
||||
#endif // USE_BERRY_TCPSERVER
|
||||
#ifdef USE_WS2812
|
||||
#if defined(USE_WS2812) && !defined(USE_WS2812_FORCE_NEOPIXELBUS)
|
||||
&be_native_class(Leds_ntv),
|
||||
&be_native_class(Leds),
|
||||
#endif // USE_WS2812
|
||||
|
|
|
@ -45,10 +45,10 @@ extern "C" {
|
|||
uint32_t g2 = (color_b >> 8) & 0xFF;
|
||||
uint32_t b2 = (color_b ) & 0xFF;
|
||||
uint32_t a2 = (color_b >> 24) & 0xFF;
|
||||
uint32_t r3 = changeUIntScale(alpha, 0, 255, r2, r);
|
||||
uint32_t g3 = changeUIntScale(alpha, 0, 255, g2, g);
|
||||
uint32_t b3 = changeUIntScale(alpha, 0, 255, b2, b);
|
||||
uint32_t a3 = changeUIntScale(alpha, 0, 255, a2, a);
|
||||
uint8_t r3 = changeUIntScale(alpha, 0, 255, r2, r);
|
||||
uint8_t g3 = changeUIntScale(alpha, 0, 255, g2, g);
|
||||
uint8_t b3 = changeUIntScale(alpha, 0, 255, b2, b);
|
||||
uint8_t a3 = changeUIntScale(alpha, 0, 255, a2, a);
|
||||
uint32_t rgb = (a3 << 24) | (r3 << 16) | (g3 << 8) | b3;
|
||||
be_pushint(vm, rgb);
|
||||
be_return(vm);
|
||||
|
@ -97,9 +97,9 @@ extern "C" {
|
|||
uint32_t fore_g = (fore_argb >> 8) & 0xFF;
|
||||
uint32_t back_b = (back_argb ) & 0xFF;
|
||||
uint32_t fore_b = (fore_argb ) & 0xFF;
|
||||
uint32_t dest_r_new = changeUIntScale(fore_alpha, 0, 255, fore_r, back_r);
|
||||
uint32_t dest_g_new = changeUIntScale(fore_alpha, 0, 255, fore_g, back_g);
|
||||
uint32_t dest_b_new = changeUIntScale(fore_alpha, 0, 255, fore_b, back_b);
|
||||
uint8_t dest_r_new = changeUIntScale(fore_alpha, 0, 255, fore_r, back_r);
|
||||
uint8_t dest_g_new = changeUIntScale(fore_alpha, 0, 255, fore_g, back_g);
|
||||
uint8_t dest_b_new = changeUIntScale(fore_alpha, 0, 255, fore_b, back_b);
|
||||
dest_rgb_new = (dest_r_new << 16) | (dest_g_new << 8) | dest_b_new;
|
||||
}
|
||||
dest[i] = dest_rgb_new;
|
||||
|
@ -135,7 +135,7 @@ extern "C" {
|
|||
|
||||
// Leds_frame.paste_pixels(neopixel:bytes(), led_buffer:bytes(), bri:int 0..100, gamma:bool)
|
||||
//
|
||||
// Copy from ARGB buffer to GRB
|
||||
// Copy from ARGB buffer to RGB
|
||||
int32_t be_leds_paste_pixels(bvm *vm);
|
||||
int32_t be_leds_paste_pixels(bvm *vm) {
|
||||
int32_t top = be_top(vm); // Get the number of arguments
|
||||
|
@ -162,8 +162,8 @@ extern "C" {
|
|||
uint32_t src_r = (src_argb >> 16) & 0xFF;
|
||||
uint32_t src_g = (src_argb >> 8) & 0xFF;
|
||||
uint32_t src_b = (src_argb ) & 0xFF;
|
||||
dest_buf[i * 3 + 0] = src_g;
|
||||
dest_buf[i * 3 + 1] = src_r;
|
||||
dest_buf[i * 3 + 0] = src_r;
|
||||
dest_buf[i * 3 + 1] = src_g;
|
||||
dest_buf[i * 3 + 2] = src_b;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
#ifdef USE_WS2812
|
||||
|
||||
extern int be_neopixelbus_call_native(bvm *vm);
|
||||
#include "TasmotaLED.h"
|
||||
|
||||
extern int be_tasmotaled_call_native(bvm *vm);
|
||||
extern int be_leds_blend_color(bvm *vm);
|
||||
extern int be_leds_apply_bri_gamma(bvm *vm);
|
||||
|
||||
|
@ -16,10 +18,15 @@ class be_class_Leds_ntv (scope: global, name: Leds_ntv, strings: weak) {
|
|||
_p, var
|
||||
_t, var
|
||||
|
||||
WS2812_GRB, int(1)
|
||||
SK6812_GRBW, int(2)
|
||||
WS2812_GRB, int(ws2812_grb)
|
||||
SK6812_GRBW, int(sk6812_grbw)
|
||||
SK6812_GRB, int(sk6812_grb)
|
||||
|
||||
call_native, func(be_neopixelbus_call_native)
|
||||
RMT, int(TasmotaLed_RMT)
|
||||
SPI, int(TasmotaLed_SPI)
|
||||
I2S, int(TasmotaLed_I2S)
|
||||
|
||||
call_native, func(be_tasmotaled_call_native)
|
||||
|
||||
blend_color, static_func(be_leds_blend_color)
|
||||
apply_bri_gamma, static_func(be_leds_apply_bri_gamma)
|
||||
|
|
|
@ -31,8 +31,8 @@ class Leds : Leds_ntv
|
|||
# leds:int = number of leds of the strip
|
||||
# gpio:int (optional) = GPIO for NeoPixel. If not specified, takes the WS2812 gpio
|
||||
# typ:int (optional) = Type of LED, defaults to WS2812 RGB
|
||||
# rmt:int (optional) = RMT hardware channel to use, leave default unless you have a good reason
|
||||
def init(leds, gpio_phy, typ, rmt) # rmt is optional
|
||||
# hardware:int (optional) = hardware support (Leds.RMT, Leds.SPI)
|
||||
def init(leds, gpio_phy, typ, hardware)
|
||||
import gpio
|
||||
self.gamma = true # gamma is enabled by default, it should be disabled explicitly if needed
|
||||
if (gpio_phy == nil) || (gpio_phy == gpio.pin(gpio.WS2812, 0))
|
||||
|
@ -47,7 +47,7 @@ class Leds : Leds_ntv
|
|||
self.bri = 127 # 50% brightness by default
|
||||
|
||||
# initialize the structure
|
||||
self.ctor(self.leds, gpio_phy, typ, rmt)
|
||||
self.ctor(self.leds, gpio_phy, typ, hardware)
|
||||
end
|
||||
|
||||
if self._p == nil raise "internal_error", "couldn't not initialize noepixelbus" end
|
||||
|
@ -56,44 +56,6 @@ class Leds : Leds_ntv
|
|||
self.begin()
|
||||
end
|
||||
|
||||
# assign RMT
|
||||
static def assign_rmt(gpio_phy)
|
||||
gpio_phy = int(gpio_phy)
|
||||
if gpio_phy < 0 raise "value_error", "invalid GPIO number" end
|
||||
|
||||
import global
|
||||
var rmt
|
||||
# if "_rmt" is not initialized, set to an array of GPIO of size MAX_RMT
|
||||
if !global.contains("_rmt")
|
||||
rmt = []
|
||||
global._rmt = rmt
|
||||
for i:0..gpio.MAX_RMT-1
|
||||
rmt.push(-1)
|
||||
end
|
||||
# if default WS2812 is set, assign RMT0
|
||||
if gpio.pin_used(gpio.WS2812, 0)
|
||||
rmt[0] = gpio.pin(gpio.WS2812, 0)
|
||||
end
|
||||
end
|
||||
|
||||
rmt = global._rmt
|
||||
# find an already assigned slot or try to assign a new one
|
||||
var i = 0
|
||||
var first_free = -1
|
||||
while i < gpio.MAX_RMT
|
||||
var elt = rmt[i]
|
||||
if elt == gpio_phy return i end # already assigned
|
||||
if elt < 0 && first_free < 0 first_free = i end # found a free slot
|
||||
i += 1
|
||||
end
|
||||
if first_free >= 0
|
||||
rmt[first_free] = gpio_phy
|
||||
return first_free
|
||||
end
|
||||
# no more slot
|
||||
raise "internal_error", "no more RMT channel available"
|
||||
end
|
||||
|
||||
def clear()
|
||||
self.clear_to(0x000000)
|
||||
self.show()
|
||||
|
@ -109,17 +71,14 @@ class Leds : Leds_ntv
|
|||
return self.bri
|
||||
end
|
||||
|
||||
def ctor(leds, gpio_phy, typ, rmt)
|
||||
def ctor(leds, gpio_phy, typ, hardware)
|
||||
if gpio_phy == nil
|
||||
self.call_native(0) # native driver
|
||||
else
|
||||
if typ == nil
|
||||
typ = self.WS2812_GRB
|
||||
end
|
||||
if rmt == nil
|
||||
rmt = self.assign_rmt(gpio_phy)
|
||||
end
|
||||
self.call_native(0, leds, gpio_phy, typ, rmt)
|
||||
self.call_native(0, leds, gpio_phy, typ, hardware)
|
||||
end
|
||||
end
|
||||
def begin()
|
||||
|
@ -155,9 +114,13 @@ class Leds : Leds_ntv
|
|||
def pixel_offset()
|
||||
return 0
|
||||
end
|
||||
def clear_to(col, bri)
|
||||
def clear_to(col, bri, index, len)
|
||||
if (bri == nil) bri = self.bri end
|
||||
self.call_native(9, self.to_gamma(col, bri))
|
||||
if index != nil && len != nil
|
||||
self.call_native(9, self.to_gamma(col, bri), index, len)
|
||||
else
|
||||
self.call_native(9, self.to_gamma(col, bri))
|
||||
end
|
||||
end
|
||||
def set_pixel_color(idx, col, bri)
|
||||
if (bri == nil) bri = self.bri end
|
||||
|
@ -403,15 +366,15 @@ anim()
|
|||
|
||||
#-
|
||||
|
||||
var s = Leds_matrix(5, 5, gpio.pin(gpio.WS2812, 1))
|
||||
var s = Leds(25, gpio.pin(gpio.WS2812, 1)).create_matrix(5, 5)
|
||||
s.set_alternate(true)
|
||||
s.clear_to(0x300000)
|
||||
s.clear_to(0x400000)
|
||||
s.show()
|
||||
x = 0
|
||||
y = 0
|
||||
|
||||
def anim()
|
||||
s.clear_to(0x300000)
|
||||
s.clear_to(0x400000)
|
||||
s.set_matrix_pixel_color(x, y, 0x004000)
|
||||
s.show()
|
||||
y = (y + 1) % 5
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -562,14 +562,7 @@
|
|||
// #define MAGICSWITCH_MASKING_WINDOW_LEN 5 // Overridable masking window (in number of 50ms loops)
|
||||
|
||||
// -- Optional light modules ----------------------
|
||||
#define USE_LIGHT // Add support for light control
|
||||
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
|
||||
// #define USE_WS2812_DMA // ESP8266 only, DMA supports only GPIO03 (= Serial RXD) (+1k mem). When USE_WS2812_DMA is enabled expect Exceptions on Pow
|
||||
#define USE_WS2812_RMT 0 // ESP32 only, hardware RMT support (default). Specify the RMT channel 0..7. This should be preferred to software bit bang.
|
||||
// #define USE_WS2812_I2S 0 // ESP32 only, hardware I2S support. Specify the I2S channel 0..2. This is exclusive from RMT. By default, prefer RMT support
|
||||
// #define USE_WS2812_INVERTED // Use inverted data signal
|
||||
#define USE_WS2812_HARDWARE NEO_HW_WS2812 // Hardware type (NEO_HW_WS2812, NEO_HW_WS2812X, NEO_HW_WS2813, NEO_HW_SK6812, NEO_HW_LC8812, NEO_HW_APA106, NEO_HW_P9813)
|
||||
#define USE_WS2812_CTYPE NEO_GRB // Color type (NEO_RGB, NEO_GRB, NEO_BRG, NEO_RBG, NEO_RGBW, NEO_GRBW)
|
||||
#define USE_LIGHT // Add support for light control
|
||||
#define USE_MY92X1 // Add support for MY92X1 RGBCW led controller as used in Sonoff B1, Ailight and Lohas
|
||||
#define USE_SM16716 // Add support for SM16716 RGB LED controller (+0k7 code)
|
||||
#define USE_SM2135 // Add support for SM2135 RGBCW led control as used in Action LSC (+0k6 code)
|
||||
|
@ -583,6 +576,18 @@
|
|||
#define USE_DGR_LIGHT_SEQUENCE // Add support for device group light sequencing (requires USE_DEVICE_GROUPS) (+0k2 code)
|
||||
//#define USE_LSC_MCSL // Add support for GPE Multi color smart light as sold by Action in the Netherlands (+1k1 code)
|
||||
|
||||
// -- Optional adressable leds ----------------------
|
||||
#define USE_WS2812 // WS2812 Led string using library NeoPixelBus (+5k code, +1k mem, 232 iram) - Disable by //
|
||||
// -------- below is for ESP8266 only
|
||||
// #define USE_WS2812_DMA // ESP8266 only, DMA supports only GPIO03 (= Serial RXD) (+1k mem). When USE_WS2812_DMA is enabled expect Exceptions on Pow
|
||||
#define USE_WS2812_RMT 0 // ESP32 only, hardware RMT support (default). Specify the RMT channel 0..7. This should be preferred to software bit bang.
|
||||
// #define USE_WS2812_I2S 0 // ESP32 only, hardware I2S support. Specify the I2S channel 0..2. This is exclusive from RMT. By default, prefer RMT support
|
||||
// #define USE_WS2812_INVERTED // Use inverted data signal
|
||||
#define USE_WS2812_HARDWARE NEO_HW_WS2812 // Hardware type (NEO_HW_WS2812, NEO_HW_WS2812X, NEO_HW_WS2813, NEO_HW_SK6812, NEO_HW_LC8812, NEO_HW_APA106, NEO_HW_P9813)
|
||||
#define USE_WS2812_CTYPE NEO_GRB // Color type (NEO_RGB, NEO_GRB, NEO_BRG, NEO_RBG, NEO_RGBW, NEO_GRBW)
|
||||
// -------- below if for ESP32x only -- ESP32 uses a lightweight library instead of NeoPixelBus
|
||||
// #define USE_WS2812_FORCE_NEOPIXELBUS // this option forces to use NeoPixelBus (like ESP866), which disables Berry support and limits features -- DO NOT USE unless you have a good reason
|
||||
|
||||
// #define USE_LIGHT_ARTNET // Add support for DMX/ArtNet via UDP on port 6454 (+3.5k code)
|
||||
#define USE_LIGHT_ARTNET_MCAST 239,255,25,54 // Multicast address used to listen: 239.255.25.54
|
||||
|
||||
|
|
|
@ -386,7 +386,7 @@ bool ArtNetStart(void) {
|
|||
Settings->light_rotation = 0;
|
||||
Ws2812InitStrip();
|
||||
} else {
|
||||
Ws2812Clear();
|
||||
Ws2812Clear(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,22 +24,8 @@
|
|||
|
||||
#ifdef USE_WS2812
|
||||
|
||||
#include <NeoPixelBus.h>
|
||||
|
||||
enum {
|
||||
ws2812_grb = 1,
|
||||
sk6812_grbw = 2,
|
||||
|
||||
neopixel_type_end
|
||||
};
|
||||
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C2
|
||||
typedef NeoPixelBus<NeoGrbFeature, NeoEsp32SpiN800KbpsMethod> neopixel_ws2812_grb_t;
|
||||
typedef NeoPixelBus<NeoGrbwFeature, NeoEsp32SpiNSk6812Method> neopixel_sk6812_grbw_t;
|
||||
#else
|
||||
typedef NeoPixelBus<NeoGrbFeature, NeoEsp32RmtN800KbpsMethod> neopixel_ws2812_grb_t;
|
||||
typedef NeoPixelBus<NeoGrbwFeature, NeoEsp32RmtNSk6812Method> neopixel_sk6812_grbw_t;
|
||||
#endif
|
||||
#include "TasmotaLED.h"
|
||||
#include "TasmotaLEDPusher.h"
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Functions from Tasmota WS2812 driver
|
||||
|
@ -86,12 +72,12 @@ extern "C" {
|
|||
// # 22 : ShiftLeft (rot:int [, first:int, last:int]) -> void
|
||||
// # 23 : ShiftRight (rot:int [, first:int, last:int]) -> void
|
||||
|
||||
void * be_get_neopixelbus(bvm *vm) {
|
||||
void * be_get_tasmotaled(bvm *vm) {
|
||||
be_getmember(vm, 1, "_p");
|
||||
void * strip = (void*) be_tocomptr(vm, -1);
|
||||
be_pop(vm, 1);
|
||||
if (strip == nullptr) {
|
||||
be_raise(vm, "internal_error", "neopixelbus object not initialized");
|
||||
be_raise(vm, "internal_error", "tasmotaled object not initialized");
|
||||
}
|
||||
return strip;
|
||||
}
|
||||
|
@ -99,70 +85,65 @@ extern "C" {
|
|||
be_getmember(vm, 1, "_t");
|
||||
int32_t type = be_toint(vm, -1);
|
||||
be_pop(vm, 1);
|
||||
if (type < 0 || type >= neopixel_type_end) {
|
||||
if (type < 0) {
|
||||
be_raise(vm, "internal_error", "invalid leds type");
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
int be_neopixelbus_call_native(bvm *vm);
|
||||
int be_neopixelbus_call_native(bvm *vm) {
|
||||
int be_tasmotaled_call_native(bvm *vm);
|
||||
int be_tasmotaled_call_native(bvm *vm) {
|
||||
int32_t argc = be_top(vm); // Get the number of arguments
|
||||
if (argc >= 2 && be_isint(vm, 2)) {
|
||||
int32_t cmd = be_toint(vm, 2);
|
||||
|
||||
if (0 == cmd) { // 00 : ctor (leds:int, gpio:int) -> void
|
||||
if ((argc != 2) && !(argc >= 6 && be_isint(vm, 3) && be_isint(vm, 4) && be_isint(vm, 5) && be_isint(vm, 6))) {
|
||||
be_raise(vm, "value_error", "bad arguments for neopixelbus:ctor");
|
||||
if ((argc != 2) && !(argc >= 5 && be_isint(vm, 3) && be_isint(vm, 4) && be_isint(vm, 5))) {
|
||||
be_raise(vm, "value_error", "bad arguments for tasmotaled:ctor");
|
||||
}
|
||||
int32_t leds = -1;
|
||||
int32_t gpio = -1;
|
||||
int32_t neopixel_type = 0;
|
||||
int32_t rmt = 0;
|
||||
void * strip = nullptr;
|
||||
int32_t led_type = 0;
|
||||
int32_t hardware = 0;
|
||||
if (argc >= 6 && be_isint(vm, 6)) {
|
||||
hardware = be_toint(vm, 6) & 0xFF0000; // remove the low 16 bits to avoid any interference with legacy parameter for RMT channels
|
||||
}
|
||||
TasmotaLED * strip = nullptr;
|
||||
if (argc > 2) {
|
||||
leds = be_toint(vm, 3);
|
||||
gpio = be_toint(vm, 4);
|
||||
neopixel_type = be_toint(vm, 5);
|
||||
rmt = be_toint(vm, 6);
|
||||
led_type = be_toint(vm, 5);
|
||||
}
|
||||
|
||||
if (-1 == gpio) {
|
||||
// if GPIO is '-1'
|
||||
neopixel_type = 0;
|
||||
Ws2812InitStrip(); // ensure the NeoPixelbus object is initialized, because Berry code runs before the driver is initialized
|
||||
strip = Ws2812GetStrip();
|
||||
led_type = 0;
|
||||
Ws2812InitStrip(); // ensure the tasmotaled object is initialized, because Berry code runs before the driver is initialized
|
||||
// strip = Ws2812GetStrip(); TODO
|
||||
} else {
|
||||
// allocate a new RMT
|
||||
if (neopixel_type < 1) { neopixel_type = 1; }
|
||||
if (neopixel_type >= neopixel_type_end) { neopixel_type = neopixel_type_end - 1; }
|
||||
if (rmt < 0) { rmt = 0; }
|
||||
if (rmt >= MAX_RMT) { rmt = MAX_RMT - 1; }
|
||||
|
||||
switch (neopixel_type) {
|
||||
case ws2812_grb: strip = new neopixel_ws2812_grb_t(leds, gpio, (NeoBusChannel) rmt);
|
||||
break;
|
||||
case sk6812_grbw: strip = new neopixel_sk6812_grbw_t(leds, gpio, (NeoBusChannel) rmt);
|
||||
break;
|
||||
if (led_type < 1) { led_type = 1; }
|
||||
TasmotaLEDPusher * pusher = TasmotaLEDPusher::Create(hardware, gpio);
|
||||
if (pusher == nullptr) {
|
||||
be_raise(vm, "value_error", "LED interface not supported");
|
||||
}
|
||||
strip = new TasmotaLED(led_type, leds);
|
||||
strip->SetPusher(pusher);
|
||||
}
|
||||
|
||||
// AddLog(LOG_LEVEL_DEBUG, "LED: leds %i gpio %i type %i", leds, gpio, led_type);
|
||||
// store type in attribute `_t`
|
||||
be_pushint(vm, neopixel_type);
|
||||
be_pushint(vm, led_type);
|
||||
be_setmember(vm, 1, "_t");
|
||||
be_pop(vm, 1);
|
||||
|
||||
be_pushcomptr(vm, (void*) strip);
|
||||
be_pushcomptr(vm, (void*) strip); // if native driver, it is NULL
|
||||
be_setmember(vm, 1, "_p");
|
||||
be_pop(vm, 1);
|
||||
be_pushnil(vm);
|
||||
} else {
|
||||
// all other commands need a valid neopixelbus pointer
|
||||
// all other commands need a valid tasmotaled pointer
|
||||
int32_t leds_type = be_get_leds_type(vm);
|
||||
const void * s = be_get_neopixelbus(vm); // raises an exception if pointer is invalid
|
||||
// initialize all possible variants
|
||||
neopixel_ws2812_grb_t * s_ws2812_grb = (leds_type == ws2812_grb) ? (neopixel_ws2812_grb_t*) s : nullptr;
|
||||
neopixel_sk6812_grbw_t * s_sk6812_grbw = (leds_type == sk6812_grbw) ? (neopixel_sk6812_grbw_t*) s : nullptr;
|
||||
TasmotaLED * strip = (TasmotaLED*) be_get_tasmotaled(vm); // raises an exception if pointer is invalid
|
||||
// detect native driver
|
||||
bool native = (leds_type == 0);
|
||||
|
||||
|
@ -171,8 +152,7 @@ extern "C" {
|
|||
switch (cmd) {
|
||||
case 1: // # 01 : begin void -> void
|
||||
if (native) Ws2812Begin();
|
||||
if (s_ws2812_grb) s_ws2812_grb->Begin();
|
||||
if (s_sk6812_grbw) s_sk6812_grbw->Begin();
|
||||
else if (strip) strip->Begin();
|
||||
break;
|
||||
case 2: // # 02 : show void -> void
|
||||
{
|
||||
|
@ -195,49 +175,50 @@ extern "C" {
|
|||
}
|
||||
}
|
||||
uint32_t pixels_size; // number of bytes to push
|
||||
if (native) { Ws2812Show(); pixels_size = Ws2812PixelsSize(); }
|
||||
if (s_ws2812_grb) { s_ws2812_grb->Show(); pixels_size = s_ws2812_grb->PixelsSize(); }
|
||||
if (s_sk6812_grbw) { s_sk6812_grbw->Show(); pixels_size = s_sk6812_grbw->PixelsSize(); }
|
||||
bool update_completed = false;
|
||||
if (native) {
|
||||
Ws2812Show();
|
||||
pixels_size = Ws2812PixelsSize();
|
||||
update_completed =Ws2812CanShow();
|
||||
}
|
||||
else if (strip) {
|
||||
strip->Show();
|
||||
pixels_size = strip->PixelCount() * strip->PixelSize();
|
||||
update_completed = strip->CanShow();
|
||||
}
|
||||
|
||||
// Wait for RMT/I2S to complete fixes distortion due to analogRead
|
||||
// 1ms is needed for 96 bytes
|
||||
SystemBusyDelay((pixels_size + 95) / 96);
|
||||
if (!update_completed) {
|
||||
SystemBusyDelay((pixels_size + 95) / 96);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 3: // # 03 : CanShow void -> bool
|
||||
if (native) be_pushbool(vm, Ws2812CanShow());
|
||||
if (s_ws2812_grb) be_pushbool(vm, s_ws2812_grb->CanShow());
|
||||
if (s_sk6812_grbw) be_pushbool(vm, s_sk6812_grbw->CanShow());
|
||||
else if (strip) be_pushbool(vm, strip->CanShow());
|
||||
break;
|
||||
case 4: // # 04 : IsDirty void -> bool
|
||||
if (native) be_pushbool(vm, Ws2812IsDirty());
|
||||
if (s_ws2812_grb) be_pushbool(vm, s_ws2812_grb->IsDirty());
|
||||
if (s_sk6812_grbw) be_pushbool(vm, s_sk6812_grbw->IsDirty());
|
||||
else if (strip) be_pushbool(vm, strip->IsDirty());
|
||||
break;
|
||||
case 5: // # 05 : Dirty void -> void
|
||||
if (native) Ws2812Dirty();
|
||||
if (s_ws2812_grb) s_ws2812_grb->Dirty();
|
||||
if (s_sk6812_grbw) s_sk6812_grbw->Dirty();
|
||||
else if (strip) strip->Dirty();
|
||||
break;
|
||||
case 6: // # 06 : Pixels void -> bytes() (mapped to the buffer)
|
||||
{
|
||||
uint8_t * pixels;
|
||||
if (native) pixels = Ws2812Pixels();
|
||||
if (s_ws2812_grb) pixels = s_ws2812_grb->Pixels();
|
||||
if (s_sk6812_grbw) pixels = s_sk6812_grbw->Pixels();
|
||||
|
||||
be_pushcomptr(vm, pixels);
|
||||
if (native) be_pushcomptr(vm, Ws2812Pixels());
|
||||
else if (strip) be_pushcomptr(vm, strip->Pixels());
|
||||
}
|
||||
break;
|
||||
case 7: // # 07 : PixelSize void -> int
|
||||
if (native) be_pushint(vm, Ws2812PixelSize());
|
||||
if (s_ws2812_grb) be_pushint(vm, s_ws2812_grb->PixelSize());
|
||||
if (s_sk6812_grbw) be_pushint(vm, s_sk6812_grbw->PixelSize());
|
||||
else if (strip) be_pushint(vm, strip->PixelSize());
|
||||
break;
|
||||
case 8: // # 08 : PixelCount void -> int
|
||||
if (native) be_pushint(vm, Ws2812PixelCount());
|
||||
if (s_ws2812_grb) be_pushint(vm, s_ws2812_grb->PixelCount());
|
||||
if (s_sk6812_grbw) be_pushint(vm, s_sk6812_grbw->PixelCount());
|
||||
else if (strip) be_pushint(vm, strip->PixelCount());
|
||||
break;
|
||||
case 9: // # 09 : ClearTo (color:??) -> void
|
||||
{
|
||||
|
@ -253,42 +234,35 @@ extern "C" {
|
|||
if (len < 0) { len = 0; }
|
||||
|
||||
if (native) Ws2812ClearTo(r, g, b, w, from, from + len - 1);
|
||||
if (s_ws2812_grb) s_ws2812_grb->ClearTo(RgbColor(r, g, b), from, from + len - 1);
|
||||
if (s_sk6812_grbw) s_sk6812_grbw->ClearTo(RgbwColor(r, g, b, w), from, from + len - 1);
|
||||
else if (strip) strip->ClearTo(rgbw, from, from + len - 1);
|
||||
} else {
|
||||
if (native) Ws2812ClearTo(r, g, b, w, -1, -1);
|
||||
if (s_ws2812_grb) s_ws2812_grb->ClearTo(RgbColor(r, g, b));
|
||||
if (s_sk6812_grbw) s_sk6812_grbw->ClearTo(RgbwColor(r, g, b, w));
|
||||
else if (strip) strip->ClearTo(rgbw);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 10: // # 10 : SetPixelColor (idx:int, color:??) -> void
|
||||
case 10: // # 10 : SetPixelColor (idx:int, color:int wrgb) -> void
|
||||
{
|
||||
int32_t idx = be_toint(vm, 3);
|
||||
uint32_t rgbw = be_toint(vm, 4);
|
||||
uint8_t w = (rgbw >> 24) & 0xFF;
|
||||
uint8_t r = (rgbw >> 16) & 0xFF;
|
||||
uint8_t g = (rgbw >> 8) & 0xFF;
|
||||
uint8_t b = (rgbw ) & 0xFF;
|
||||
if (native) Ws2812SetPixelColor(idx, r, g, b, w);
|
||||
if (s_ws2812_grb) s_ws2812_grb->SetPixelColor(idx, RgbColor(r, g, b));
|
||||
if (s_sk6812_grbw) s_sk6812_grbw->SetPixelColor(idx, RgbwColor(r, g, b, w));
|
||||
uint32_t wrgb = be_toint(vm, 4);
|
||||
if (native) {
|
||||
uint8_t w = (wrgb >> 24) & 0xFF;
|
||||
uint8_t r = (wrgb >> 16) & 0xFF;
|
||||
uint8_t g = (wrgb >> 8) & 0xFF;
|
||||
uint8_t b = (wrgb ) & 0xFF;
|
||||
Ws2812SetPixelColor(idx, r, g, b, w);
|
||||
} else if (strip) {
|
||||
strip->SetPixelColor(idx, wrgb);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 11: // # 11 : GetPixelColor (idx:int) -> color:??
|
||||
case 11: // # 11 : GetPixelColor (idx:int) -> color:int wrgb
|
||||
{
|
||||
int32_t idx = be_toint(vm, 3);
|
||||
|
||||
if (native) {
|
||||
be_pushint(vm, Ws2812GetPixelColor(idx));
|
||||
}
|
||||
if (s_ws2812_grb) {
|
||||
RgbColor rgb = s_ws2812_grb->GetPixelColor(idx);
|
||||
be_pushint(vm, (rgb.R << 16) | (rgb.G << 8) | rgb.B);
|
||||
}
|
||||
if (s_sk6812_grbw) {
|
||||
RgbwColor rgbw = s_sk6812_grbw->GetPixelColor(idx);
|
||||
be_pushint(vm, (rgbw.W << 24) | (rgbw.R << 16) | (rgbw.G << 8) | rgbw.B);
|
||||
} else if (strip) {
|
||||
be_pushint(vm, strip->GetPixelColor(idx));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP8266) || defined(USE_WS2812_FORCE_NEOPIXELBUS)
|
||||
#ifdef USE_LIGHT
|
||||
#ifdef USE_WS2812
|
||||
|
||||
|
@ -553,11 +554,14 @@ void Ws2812DDP(void)
|
|||
}
|
||||
#endif // USE_NETWORK_LIGHT_SCHEMES
|
||||
|
||||
void Ws2812Clear(void)
|
||||
void Ws2812Clear(bool display = true);
|
||||
void Ws2812Clear(bool display)
|
||||
{
|
||||
strip->ClearTo(0);
|
||||
Ws2812LibStripShow();
|
||||
Ws2812.show_next = 1;
|
||||
if (display) {
|
||||
Ws2812LibStripShow();
|
||||
Ws2812.show_next = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white)
|
||||
|
@ -965,3 +969,4 @@ bool Xlgt01(uint32_t function)
|
|||
|
||||
#endif // USE_WS2812
|
||||
#endif // USE_LIGHT
|
||||
#endif // defined(ESP8266) || defined(USE_WS2812_FORCE_NEOPIXELBUS)
|
||||
|
|
|
@ -0,0 +1,899 @@
|
|||
/*
|
||||
xlgt_01_ws2812.ino - led string support for Tasmota
|
||||
|
||||
Copyright (C) 2021 Theo Arends
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef ESP32
|
||||
#ifdef USE_LIGHT
|
||||
#if defined(USE_WS2812) && !defined(USE_WS2812_FORCE_NEOPIXELBUS)
|
||||
|
||||
/*********************************************************************************************\
|
||||
* WS2812 RGB / RGBW Leds using NeopixelBus library
|
||||
*
|
||||
* light_scheme WS2812 3+ Colors 1+2 Colors Effect
|
||||
* ------------ ------ --------- ---------- -----------------
|
||||
* 0 (5) yes no no Clock
|
||||
* 1 (6) yes no no Incandescent
|
||||
* 2 (7) yes no no RGB
|
||||
* 3 (8) yes no no Christmas
|
||||
* 4 (9) yes no no Hanukkah
|
||||
* 5 (10) yes no no Kwanzaa
|
||||
* 6 (11) yes no no Rainbow
|
||||
* 7 (12) yes no no Fire
|
||||
* 8 (13) yes no no Stairs
|
||||
* 9 (14) yes no no Clear (= Berry)
|
||||
* 10 (15) yes no no Optional DDP
|
||||
\*********************************************************************************************/
|
||||
|
||||
#define XLGT_01 1
|
||||
|
||||
const uint8_t WS2812_SCHEMES = 10; // Number of WS2812 schemes
|
||||
|
||||
const char kWs2812Commands[] PROGMEM = "|" // No prefix
|
||||
D_CMND_LED "|" D_CMND_PIXELS "|" D_CMND_ROTATION "|" D_CMND_WIDTH "|" D_CMND_STEPPIXELS ;
|
||||
|
||||
void (* const Ws2812Command[])(void) PROGMEM = {
|
||||
&CmndLed, &CmndPixels, &CmndRotation, &CmndWidth, &CmndStepPixels };
|
||||
|
||||
#include <TasmotaLED.h>
|
||||
|
||||
const uint16_t kLedType = 0;
|
||||
// select the right pixel size
|
||||
#if (USE_WS2812_CTYPE > NEO_3LED)
|
||||
const uint16_t kTasLed_PixelSize = TasmotaLed_4_WRGB;
|
||||
#else
|
||||
const uint16_t kTasLed_PixelSize = TasmotaLed_3_RGB;
|
||||
#endif
|
||||
|
||||
// select the right pixel order
|
||||
#if (USE_WS2812_CTYPE == NEO_GRB)
|
||||
const uint16_t kTasLed_PixelOrder = TasmotaLed_GRB;
|
||||
#elif (USE_WS2812_CTYPE == NEO_BRG)
|
||||
const uint16_t kTasLed_PixelOrder = TasmotaLed_BRG;
|
||||
#elif (USE_WS2812_CTYPE == NEO_RBG)
|
||||
const uint16_t kTasLed_PixelOrder = TasmotaLed_RBG;
|
||||
#elif (USE_WS2812_CTYPE == NEO_RGBW)
|
||||
const uint16_t kTasLed_PixelOrder = TasmotaLed_RGB;
|
||||
#elif (USE_WS2812_CTYPE == NEO_GRBW)
|
||||
const uint16_t kTasLed_PixelOrder = TasmotaLed_GRB;
|
||||
#else
|
||||
const uint16_t kTasLed_PixelOrder = TasmotaLed_RGB;
|
||||
#endif
|
||||
|
||||
// all leds have always W at the end
|
||||
const uint16_t kTasLed_PixelWhite = TasmotaLed_xxxW;
|
||||
|
||||
// We drop support for NEO_HW_P9813, as it is too hard to support with hardwarre
|
||||
#if (USE_WS2812_HARDWARE == NEO_HW_P9813)
|
||||
#error "P9813 is not supported by this library"
|
||||
#endif // USE_WS2812_CTYPE
|
||||
|
||||
// select timing
|
||||
#if (USE_WS2812_HARDWARE == NEO_HW_WS2812X)
|
||||
const uint16_t kTasLed_Timing = TasmotaLed_WS2812;
|
||||
#elif (USE_WS2812_HARDWARE == NEO_HW_SK6812)
|
||||
const uint16_t kTasLed_Timing = TasmotaLed_SK6812;
|
||||
#elif (USE_WS2812_HARDWARE == NEO_HW_APA106)
|
||||
#error "APA106 is not supported by this library"
|
||||
#else // USE_WS2812_HARDWARE
|
||||
const uint16_t kTasLed_Timing = TasmotaLed_WS2812;
|
||||
#endif // USE_WS2812_HARDWARE
|
||||
|
||||
const uint16_t kTasLed_Type = kTasLed_PixelSize | kTasLed_PixelOrder | kTasLed_PixelWhite | kTasLed_Timing;
|
||||
|
||||
// select hardware acceleration - bitbanging is not supported on ESP32 due to interference of interrupts
|
||||
#if CONFIG_IDF_TARGET_ESP32C2
|
||||
const uint32_t kTasLed_Hardware = TasmotaLed_I2S; // I2S
|
||||
#else // all other ESP32 variants
|
||||
#if defined(USE_WS2812_DMA)
|
||||
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
|
||||
#elif defined(USE_WS2812_RMT)
|
||||
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
|
||||
#elif defined(USE_WS2812_I2S)
|
||||
const uint32_t kTasLed_Hardware = TasmotaLed_I2S; // I2S
|
||||
#else
|
||||
const uint32_t kTasLed_Hardware = TasmotaLed_RMT; // default DMA to RMT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (USE_WS2812_HARDWARE == NEO_HW_P9813)
|
||||
#error "P9813 is not supported by this library"
|
||||
#endif
|
||||
|
||||
|
||||
/*******************************************************************************************\
|
||||
* Support for TasmotaLED
|
||||
*
|
||||
* From here we have defined:
|
||||
* kTasLed_Type: encodes pixel size, pixel order, pixel white, timing
|
||||
* kTasLed_Hardware: encodes the harware support to push to Leds: RMT, SPI, I2S
|
||||
*******************************************************************************************/
|
||||
TasmotaLED *strip = nullptr;
|
||||
|
||||
typedef union LedColor {
|
||||
uint32_t C; // encoded as 0xWWRRGGBB
|
||||
struct {
|
||||
uint8_t B, G, R, W; // WRGB in little endian
|
||||
};
|
||||
} LedColor;
|
||||
|
||||
struct ColorScheme {
|
||||
const LedColor* colors;
|
||||
uint8_t count;
|
||||
};
|
||||
|
||||
const LedColor kIncandescent[2] = { 0xFF8C14, 0x000000 };
|
||||
const LedColor kRgb[3] = { 0xFF0000, 0x00FF00, 0x0000FF };
|
||||
const LedColor kChristmas[2] = { 0xFF0000, 0x00FF00 };
|
||||
const LedColor kHanukkah[2] = { 0x0000FF, 0xFFFFFF };
|
||||
const LedColor kwanzaa[3] = { 0xFF0000, 0x000000, 0x00FF00 };
|
||||
const LedColor kRainbow[7] = { 0xFF0000, 0xFF8000, 0xFFFF00, 0x00FF00, 0x0000FF, 0x8000FF, 0xFF00FF };
|
||||
const LedColor kFire[3] = { 0xFF0000, 0xFF6600, 0xFFC000 };
|
||||
const LedColor kStairs[2] = { 0x000000, 0xFFFFFF0 };
|
||||
|
||||
const ColorScheme kSchemes[WS2812_SCHEMES -2] = { // Skip clock and clear scheme
|
||||
kIncandescent, 2,
|
||||
kRgb, 3,
|
||||
kChristmas, 2,
|
||||
kHanukkah, 2,
|
||||
kwanzaa, 3,
|
||||
kRainbow, 7,
|
||||
kFire, 3,
|
||||
kStairs, 2
|
||||
};
|
||||
|
||||
const uint8_t kWidth[5] = {
|
||||
1, // Small
|
||||
2, // Medium
|
||||
4, // Large
|
||||
8, // Largest
|
||||
255 // All
|
||||
};
|
||||
const uint8_t kWsRepeat[5] = {
|
||||
8, // Small
|
||||
6, // Medium
|
||||
4, // Large
|
||||
2, // Largest
|
||||
1 // All
|
||||
};
|
||||
|
||||
struct WS2812 {
|
||||
uint8_t show_next = 1;
|
||||
uint8_t scheme_offset = 0;
|
||||
bool suspend_update = false;
|
||||
} Ws2812;
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
// For some reason map fails to compile so renamed to wsmap
|
||||
long wsmap(long x, long in_min, long in_max, long out_min, long out_max) {
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
void Ws2812LibStripShow(void) {
|
||||
strip->Show();
|
||||
|
||||
#if defined(USE_WS2812_DMA) || defined(USE_WS2812_RMT) || defined(USE_WS2812_I2S)
|
||||
// Wait for DMA/RMT/I2S to complete fixes distortion due to analogRead
|
||||
// delay((Settings->light_pixels >> 6) +1); // 256 / 64 = 4 +1 = 5
|
||||
SystemBusyDelay( (Settings->light_pixels + 31) >> 5); // (256 + 32) / 32 = 8
|
||||
#endif
|
||||
}
|
||||
|
||||
void Ws2812StripShow(void)
|
||||
{
|
||||
LedColor c;
|
||||
|
||||
if (Settings->light_correction) {
|
||||
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
|
||||
c.C = strip->GetPixelColor(i);
|
||||
c.R = ledGamma(c.R);
|
||||
c.G = ledGamma(c.G);
|
||||
c.B = ledGamma(c.B);
|
||||
#if (USE_WS2812_CTYPE > NEO_3LED)
|
||||
c.W = ledGamma(c.W);
|
||||
#endif
|
||||
strip->SetPixelColor(i, c.C);
|
||||
}
|
||||
}
|
||||
Ws2812LibStripShow();
|
||||
}
|
||||
|
||||
int mod(int a, int b)
|
||||
{
|
||||
int ret = a % b;
|
||||
if (ret < 0) ret += b;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Ws2812UpdatePixelColor(int position, LedColor hand_color, float offset);
|
||||
void Ws2812UpdatePixelColor(int position, LedColor hand_color, float offset)
|
||||
{
|
||||
LedColor color;
|
||||
|
||||
uint32_t mod_position = mod(position, (int)Settings->light_pixels);
|
||||
|
||||
color.C = strip->GetPixelColor(mod_position);
|
||||
float dimmer = 100 / (float)Settings->light_dimmer;
|
||||
color.R = tmin(color.R + ((hand_color.R / dimmer) * offset), 255);
|
||||
color.G = tmin(color.G + ((hand_color.G / dimmer) * offset), 255);
|
||||
color.B = tmin(color.B + ((hand_color.B / dimmer) * offset), 255);
|
||||
strip->SetPixelColor(mod_position, color.C);
|
||||
}
|
||||
|
||||
void Ws2812UpdateHand(int position, uint32_t index)
|
||||
{
|
||||
uint32_t width = Settings->light_width;
|
||||
if (index < WS_MARKER) { width = Settings->ws_width[index]; }
|
||||
if (!width) { return; } // Skip
|
||||
|
||||
position = (position + Settings->light_rotation) % Settings->light_pixels;
|
||||
|
||||
if (Settings->flag.ws_clock_reverse) { // SetOption16 - Switch between clockwise or counter-clockwise
|
||||
position = Settings->light_pixels -position;
|
||||
}
|
||||
LedColor hand_color = {0};
|
||||
hand_color.R = Settings->ws_color[index][WS_RED];
|
||||
hand_color.G = Settings->ws_color[index][WS_GREEN];
|
||||
hand_color.B = Settings->ws_color[index][WS_BLUE];
|
||||
|
||||
Ws2812UpdatePixelColor(position, hand_color, 1);
|
||||
|
||||
uint32_t range = ((width -1) / 2) +1;
|
||||
for (uint32_t h = 1; h < range; h++) {
|
||||
float offset = (float)(range - h) / (float)range;
|
||||
Ws2812UpdatePixelColor(position -h, hand_color, offset);
|
||||
Ws2812UpdatePixelColor(position +h, hand_color, offset);
|
||||
}
|
||||
}
|
||||
|
||||
void Ws2812Clock(void)
|
||||
{
|
||||
strip->ClearTo(0); // Reset strip
|
||||
int clksize = 60000 / (int)Settings->light_pixels;
|
||||
|
||||
Ws2812UpdateHand((RtcTime.second * 1000) / clksize, WS_SECOND);
|
||||
Ws2812UpdateHand((RtcTime.minute * 1000) / clksize, WS_MINUTE);
|
||||
Ws2812UpdateHand((((RtcTime.hour % 12) * 5000) + ((RtcTime.minute * 1000) / 12 )) / clksize, WS_HOUR);
|
||||
if (Settings->ws_color[WS_MARKER][WS_RED] + Settings->ws_color[WS_MARKER][WS_GREEN] + Settings->ws_color[WS_MARKER][WS_BLUE]) {
|
||||
for (uint32_t i = 0; i < 12; i++) {
|
||||
Ws2812UpdateHand((i * 5000) / clksize, WS_MARKER);
|
||||
}
|
||||
}
|
||||
|
||||
Ws2812StripShow();
|
||||
}
|
||||
|
||||
void Ws2812GradientColor(uint32_t schemenr, LedColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i);
|
||||
void Ws2812GradientColor(uint32_t schemenr, LedColor* mColor, uint32_t range, uint32_t gradRange, uint32_t i)
|
||||
{
|
||||
/*
|
||||
* Compute the color of a pixel at position i using a gradient of the color scheme.
|
||||
* This function is used internally by the gradient function.
|
||||
*/
|
||||
ColorScheme scheme = kSchemes[schemenr];
|
||||
uint32_t curRange = i / range;
|
||||
uint32_t rangeIndex = i % range;
|
||||
uint32_t colorIndex = rangeIndex / gradRange;
|
||||
uint32_t start = colorIndex;
|
||||
uint32_t end = colorIndex +1;
|
||||
if (curRange % 2 != 0) {
|
||||
start = (scheme.count -1) - start;
|
||||
end = (scheme.count -1) - end;
|
||||
}
|
||||
float dimmer = 100 / (float)Settings->light_dimmer;
|
||||
float fmyRed = (float)wsmap(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].R, scheme.colors[end].R) / dimmer;
|
||||
float fmyGrn = (float)wsmap(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].G, scheme.colors[end].G) / dimmer;
|
||||
float fmyBlu = (float)wsmap(rangeIndex % gradRange, 0, gradRange, scheme.colors[start].B, scheme.colors[end].B) / dimmer;
|
||||
mColor->R = (uint8_t)fmyRed;
|
||||
mColor->G = (uint8_t)fmyGrn;
|
||||
mColor->B = (uint8_t)fmyBlu;
|
||||
}
|
||||
|
||||
void Ws2812Gradient(uint32_t schemenr)
|
||||
{
|
||||
/*
|
||||
* This routine courtesy Tony DiCola (Adafruit)
|
||||
* Display a gradient of colors for the current color scheme.
|
||||
* Repeat is the number of repetitions of the gradient (pick a multiple of 2 for smooth looping of the gradient).
|
||||
*/
|
||||
LedColor c;
|
||||
|
||||
ColorScheme scheme = kSchemes[schemenr];
|
||||
if (scheme.count < 2) { return; }
|
||||
|
||||
uint32_t repeat = kWsRepeat[Settings->light_width]; // number of scheme.count per ledcount
|
||||
uint32_t range = (uint32_t)ceil((float)Settings->light_pixels / (float)repeat);
|
||||
uint32_t gradRange = (uint32_t)ceil((float)range / (float)(scheme.count - 1));
|
||||
uint32_t speed = ((Settings->light_speed * 2) -1) * (STATES / 10);
|
||||
uint32_t offset = speed > 0 ? Light.strip_timer_counter / speed : 0;
|
||||
|
||||
LedColor oldColor, currentColor;
|
||||
Ws2812GradientColor(schemenr, &oldColor, range, gradRange, offset);
|
||||
currentColor = oldColor;
|
||||
speed = speed ? speed : 1; // should never happen, just avoid div0
|
||||
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
|
||||
if (kWsRepeat[Settings->light_width] > 1) {
|
||||
Ws2812GradientColor(schemenr, ¤tColor, range, gradRange, i + offset + 1);
|
||||
}
|
||||
// Blend old and current color based on time for smooth movement.
|
||||
c.R = wsmap(Light.strip_timer_counter % speed, 0, speed, oldColor.R, currentColor.R);
|
||||
c.G = wsmap(Light.strip_timer_counter % speed, 0, speed, oldColor.G, currentColor.G);
|
||||
c.B = wsmap(Light.strip_timer_counter % speed, 0, speed, oldColor.B, currentColor.B);
|
||||
strip->SetPixelColor(i, c.C);
|
||||
oldColor = currentColor;
|
||||
}
|
||||
Ws2812StripShow();
|
||||
}
|
||||
|
||||
void Ws2812Bars(uint32_t schemenr)
|
||||
{
|
||||
/*
|
||||
* This routine courtesy Tony DiCola (Adafruit)
|
||||
* Display solid bars of color for the current color scheme.
|
||||
* Width is the width of each bar in pixels/lights.
|
||||
*/
|
||||
LedColor c;
|
||||
|
||||
ColorScheme scheme = kSchemes[schemenr];
|
||||
|
||||
uint32_t maxSize = Settings->light_pixels / scheme.count;
|
||||
if (kWidth[Settings->light_width] > maxSize) { maxSize = 0; }
|
||||
|
||||
uint32_t speed = ((Settings->light_speed * 2) -1) * (STATES / 10);
|
||||
uint32_t offset = (speed > 0) ? Light.strip_timer_counter / speed : 0;
|
||||
|
||||
LedColor mcolor[scheme.count];
|
||||
memcpy(mcolor, scheme.colors, sizeof(mcolor));
|
||||
float dimmer = 100 / (float)Settings->light_dimmer;
|
||||
for (uint32_t i = 0; i < scheme.count; i++) {
|
||||
float fmyRed = (float)mcolor[i].R / dimmer;
|
||||
float fmyGrn = (float)mcolor[i].G / dimmer;
|
||||
float fmyBlu = (float)mcolor[i].B / dimmer;
|
||||
mcolor[i].R = (uint8_t)fmyRed;
|
||||
mcolor[i].G = (uint8_t)fmyGrn;
|
||||
mcolor[i].B = (uint8_t)fmyBlu;
|
||||
}
|
||||
uint32_t colorIndex = offset % scheme.count;
|
||||
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
|
||||
if (maxSize) { colorIndex = ((i + offset) % (scheme.count * kWidth[Settings->light_width])) / kWidth[Settings->light_width]; }
|
||||
c.R = mcolor[colorIndex].R;
|
||||
c.G = mcolor[colorIndex].G;
|
||||
c.B = mcolor[colorIndex].B;
|
||||
strip->SetPixelColor(i, c.C);
|
||||
}
|
||||
Ws2812StripShow();
|
||||
}
|
||||
|
||||
void Ws2812Steps(uint32_t schemenr) {
|
||||
LedColor c;
|
||||
|
||||
ColorScheme scheme = kSchemes[schemenr];
|
||||
// apply main color if current sheme == kStairs
|
||||
if (scheme.colors == kStairs) {
|
||||
// we patch the colors
|
||||
static LedColor colors_stairs[2] = {
|
||||
{ 0x000000 },
|
||||
{ .B = Settings->light_color[2], .G = Settings->light_color[1], .R = Settings->light_color[0] }
|
||||
};
|
||||
scheme.colors = colors_stairs;
|
||||
}
|
||||
|
||||
uint8_t scheme_count = scheme.count;
|
||||
if (Settings->light_fade) {
|
||||
scheme_count = Settings->ws_width[WS_HOUR]; // Width4
|
||||
}
|
||||
if (scheme_count < 2) {
|
||||
scheme_count = 2;
|
||||
}
|
||||
|
||||
LedColor mcolor[scheme_count];
|
||||
|
||||
uint8_t color_start = 0;
|
||||
uint8_t color_end = 1;
|
||||
if (Settings->light_rotation & 0x01) {
|
||||
color_start = 1;
|
||||
color_end = 0;
|
||||
}
|
||||
|
||||
if (Settings->light_fade) {
|
||||
// generate gradient (width = Width4)
|
||||
for (uint32_t i = 1; i < scheme_count - 1; i++) {
|
||||
mcolor[i].R = (uint8_t) wsmap(i, 0, scheme_count, scheme.colors[color_start].R, scheme.colors[color_end].R);
|
||||
mcolor[i].G = (uint8_t) wsmap(i, 0, scheme_count, scheme.colors[color_start].G, scheme.colors[color_end].G);
|
||||
mcolor[i].B = (uint8_t) wsmap(i, 0, scheme_count, scheme.colors[color_start].B, scheme.colors[color_end].B);
|
||||
}
|
||||
} else {
|
||||
memcpy(mcolor, scheme.colors, sizeof(mcolor));
|
||||
}
|
||||
// Repair first & last color in gradient; apply scheme rotation if fade==0
|
||||
mcolor[0].R = scheme.colors[color_start].R;
|
||||
mcolor[0].G = scheme.colors[color_start].G;
|
||||
mcolor[0].B = scheme.colors[color_start].B;
|
||||
mcolor[scheme_count-1].R = scheme.colors[color_end].R;
|
||||
mcolor[scheme_count-1].G = scheme.colors[color_end].G;
|
||||
mcolor[scheme_count-1].B = scheme.colors[color_end].B;
|
||||
|
||||
// Adjust to dimmer value
|
||||
float dimmer = 100 / (float)Settings->light_dimmer;
|
||||
for (uint32_t i = 0; i < scheme_count; i++) {
|
||||
float fmyRed = (float)mcolor[i].R / dimmer;
|
||||
float fmyGrn = (float)mcolor[i].G / dimmer;
|
||||
float fmyBlu = (float)mcolor[i].B / dimmer;
|
||||
mcolor[i].R = (uint8_t)fmyRed;
|
||||
mcolor[i].G = (uint8_t)fmyGrn;
|
||||
mcolor[i].B = (uint8_t)fmyBlu;
|
||||
}
|
||||
|
||||
uint32_t speed = Settings->light_speed;
|
||||
int32_t current_position = Light.strip_timer_counter / speed;
|
||||
|
||||
//all pixels are shown already | rotation change will not change current state
|
||||
if (current_position > Settings->light_pixels / Settings->light_step_pixels + scheme_count ) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t colorIndex;
|
||||
int32_t step_nr;
|
||||
|
||||
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
|
||||
step_nr = i / Settings->light_step_pixels;
|
||||
colorIndex = current_position - step_nr;
|
||||
if (colorIndex < 0) { colorIndex = 0; }
|
||||
if (colorIndex > scheme_count - 1) { colorIndex = scheme_count - 1; }
|
||||
c.R = mcolor[colorIndex].R;
|
||||
c.G = mcolor[colorIndex].G;
|
||||
c.B = mcolor[colorIndex].B;
|
||||
// Adjust the scheme rotation
|
||||
if (Settings->light_rotation & 0x02) {
|
||||
strip->SetPixelColor(Settings->light_pixels - i - 1, c.C);
|
||||
} else {
|
||||
strip->SetPixelColor(i, c.C);
|
||||
}
|
||||
}
|
||||
Ws2812StripShow();
|
||||
}
|
||||
|
||||
#ifdef USE_NETWORK_LIGHT_SCHEMES
|
||||
void Ws2812DDP(void)
|
||||
{
|
||||
LedColor c = {0};
|
||||
|
||||
// Can't be trying to initialize UDP too early.
|
||||
if (TasmotaGlobal.restart_flag || TasmotaGlobal.global_state.network_down) return;
|
||||
|
||||
// Start DDP listener, if fail, just set last ddp_color
|
||||
if (!ddp_udp_up) {
|
||||
if (!ddp_udp.begin(4048)) return;
|
||||
ddp_udp_up = 1;
|
||||
AddLog(LOG_LEVEL_DEBUG_MORE, "DDP: UDP Listener Started: WS2812 Scheme");
|
||||
}
|
||||
|
||||
// Get the DDP payload over UDP
|
||||
std::vector<uint8_t> payload;
|
||||
while (uint16_t packet_size = ddp_udp.parsePacket()) {
|
||||
payload.resize(packet_size);
|
||||
if (!ddp_udp.read(&payload[0], payload.size())) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// No verification checks performed against packet besides length
|
||||
if (payload.size() > (9+3*Settings->light_pixels)) {
|
||||
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
|
||||
c.R = payload[10+3*i];
|
||||
c.G = payload[11+3*i];
|
||||
c.B = payload[12+3*i];
|
||||
strip->SetPixelColor(i, c.C);
|
||||
}
|
||||
Ws2812StripShow();
|
||||
}
|
||||
}
|
||||
#endif // USE_NETWORK_LIGHT_SCHEMES
|
||||
|
||||
void Ws2812Clear(bool display = true);
|
||||
void Ws2812Clear(bool display)
|
||||
{
|
||||
strip->ClearTo(0);
|
||||
if (display) {
|
||||
Ws2812LibStripShow();
|
||||
Ws2812.show_next = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Ws2812SetColor(uint32_t led, uint8_t red, uint8_t green, uint8_t blue, uint8_t white)
|
||||
{
|
||||
LedColor lcolor = {0};
|
||||
|
||||
lcolor.R = red;
|
||||
lcolor.G = green;
|
||||
lcolor.B = blue;
|
||||
if (led) {
|
||||
strip->SetPixelColor(led -1, lcolor.C); // Led 1 is strip Led 0 -> substract offset 1
|
||||
} else {
|
||||
// strip->ClearTo(lcolor); // Set WS2812_MAX_LEDS pixels
|
||||
for (uint32_t i = 0; i < Settings->light_pixels; i++) {
|
||||
strip->SetPixelColor(i, lcolor.C);
|
||||
}
|
||||
}
|
||||
|
||||
if (!Ws2812.suspend_update) {
|
||||
Ws2812LibStripShow();
|
||||
Ws2812.show_next = 1;
|
||||
}
|
||||
}
|
||||
|
||||
char* Ws2812GetColor(uint32_t led, char* scolor)
|
||||
{
|
||||
uint8_t sl_ledcolor[4];
|
||||
|
||||
LedColor lcolor;
|
||||
lcolor.C = strip->GetPixelColor(led -1);
|
||||
sl_ledcolor[0] = lcolor.R;
|
||||
sl_ledcolor[1] = lcolor.G;
|
||||
sl_ledcolor[2] = lcolor.B;
|
||||
sl_ledcolor[3] = lcolor.W;
|
||||
scolor[0] = '\0';
|
||||
for (uint32_t i = 0; i < Light.subtype; i++) {
|
||||
if (Settings->flag.decimal_text) { // SetOption17 - Switch between decimal or hexadecimal output (0 = hexadecimal, 1 = decimal)
|
||||
snprintf_P(scolor, 25, PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", sl_ledcolor[i]);
|
||||
} else {
|
||||
snprintf_P(scolor, 25, PSTR("%s%02X"), scolor, sl_ledcolor[i]);
|
||||
}
|
||||
}
|
||||
return scolor;
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Public - used by scripter only
|
||||
\*********************************************************************************************/
|
||||
|
||||
void Ws2812ForceSuspend (void)
|
||||
{
|
||||
Ws2812.suspend_update = true;
|
||||
}
|
||||
|
||||
void Ws2812ForceUpdate (void)
|
||||
{
|
||||
Ws2812.suspend_update = false;
|
||||
Ws2812LibStripShow();
|
||||
Ws2812.show_next = 1;
|
||||
}
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
bool Ws2812SetChannels(void)
|
||||
{
|
||||
uint8_t *cur_col = (uint8_t*)XdrvMailbox.data;
|
||||
|
||||
Ws2812SetColor(0, cur_col[0], cur_col[1], cur_col[2], cur_col[3]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Ws2812ShowScheme(void)
|
||||
{
|
||||
uint32_t scheme = Settings->light_scheme - Ws2812.scheme_offset;
|
||||
|
||||
#ifdef USE_NETWORK_LIGHT_SCHEMES
|
||||
if ((scheme != 10) && (ddp_udp_up)) {
|
||||
ddp_udp.stop();
|
||||
ddp_udp_up = 0;
|
||||
AddLog(LOG_LEVEL_DEBUG_MORE, "DDP: UDP Stopped: WS2812 Scheme not DDP");
|
||||
}
|
||||
#endif
|
||||
switch (scheme) {
|
||||
case 0: // Clock
|
||||
if ((1 == TasmotaGlobal.state_250mS) || (Ws2812.show_next)) {
|
||||
Ws2812Clock();
|
||||
Ws2812.show_next = 0;
|
||||
}
|
||||
break;
|
||||
case 9: // Clear
|
||||
if (Settings->light_scheme != Light.last_scheme) {
|
||||
Ws2812Clear();
|
||||
}
|
||||
break;
|
||||
#ifdef USE_NETWORK_LIGHT_SCHEMES
|
||||
case 10:
|
||||
Ws2812DDP();
|
||||
break;
|
||||
#endif // USE_NETWORK_LIGHT_SCHEMES
|
||||
default:
|
||||
if(Settings->light_step_pixels > 0){
|
||||
Ws2812Steps(scheme -1);
|
||||
} else {
|
||||
if (1 == Settings->light_fade) {
|
||||
Ws2812Gradient(scheme -1);
|
||||
} else {
|
||||
Ws2812Bars(scheme -1);
|
||||
}
|
||||
}
|
||||
Ws2812.show_next = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Ws2812InitStrip(void)
|
||||
{
|
||||
if (strip != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PinUsed(GPIO_WS2812)) { // RGB led
|
||||
int32_t gpio = Pin(GPIO_WS2812);
|
||||
TasmotaLEDPusher * pusher = TasmotaLEDPusher::Create(kTasLed_Hardware, gpio);
|
||||
if (pusher == nullptr) {
|
||||
AddLog(LOG_LEVEL_ERROR, "LED: No hardware supported");
|
||||
return false;
|
||||
}
|
||||
strip = new TasmotaLED(kTasLed_Type, Settings->light_pixels);
|
||||
strip->SetPusher(pusher);
|
||||
strip->Begin();
|
||||
|
||||
Ws2812Clear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Ws2812ModuleSelected(void)
|
||||
{
|
||||
if (Ws2812InitStrip()) {
|
||||
Ws2812.scheme_offset = Light.max_scheme +1;
|
||||
Light.max_scheme += WS2812_SCHEMES;
|
||||
|
||||
#ifdef USE_NETWORK_LIGHT_SCHEMES
|
||||
Light.max_scheme++;
|
||||
#endif
|
||||
|
||||
#if (USE_WS2812_CTYPE > NEO_3LED)
|
||||
TasmotaGlobal.light_type = LT_RGBW;
|
||||
#else
|
||||
TasmotaGlobal.light_type = LT_RGB;
|
||||
#endif
|
||||
TasmotaGlobal.light_driver = XLGT_01;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ESP32
|
||||
#ifdef USE_BERRY
|
||||
/********************************************************************************************/
|
||||
// Callbacks for Berry driver
|
||||
//
|
||||
// Since we dont' want to export all the template stuff, we need to encapsulate the calls
|
||||
// in plain functions
|
||||
//
|
||||
void *Ws2812GetStrip(void) {
|
||||
return strip;
|
||||
}
|
||||
|
||||
void Ws2812Begin(void) {
|
||||
if (strip) { strip->Begin(); }
|
||||
}
|
||||
|
||||
void Ws2812Show(void) {
|
||||
if (strip) { strip->Show(); }
|
||||
}
|
||||
|
||||
uint32_t Ws2812PixelsSize(void) {
|
||||
if (strip) { return strip->PixelCount(); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Ws2812CanShow(void) {
|
||||
if (strip) { return strip->CanShow(); }
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Ws2812IsDirty(void) {
|
||||
if (strip) { return strip->IsDirty(); }
|
||||
return false;
|
||||
}
|
||||
|
||||
void Ws2812Dirty(void) {
|
||||
if (strip) { strip->Dirty(); }
|
||||
}
|
||||
|
||||
uint8_t * Ws2812Pixels(void) {
|
||||
if (strip) { return strip->Pixels(); }
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t Ws2812PixelSize(void) {
|
||||
if (strip) { return strip->PixelSize(); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Ws2812PixelCount(void) {
|
||||
if (strip) { return strip->PixelCount(); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Ws2812ClearTo(uint8_t r, uint8_t g, uint8_t b, uint8_t w, int32_t from, int32_t to) {
|
||||
LedColor lcolor;
|
||||
lcolor.W = w;
|
||||
lcolor.R = r;
|
||||
lcolor.G = g;
|
||||
lcolor.B = b;
|
||||
if (strip) {
|
||||
if (from < 0) {
|
||||
strip->ClearTo(lcolor.C);
|
||||
} else {
|
||||
strip->ClearTo(lcolor.C, from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ws2812SetPixelColor(uint32_t idx, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
|
||||
{
|
||||
LedColor lcolor;
|
||||
lcolor.W = w;
|
||||
lcolor.R = r;
|
||||
lcolor.G = g;
|
||||
lcolor.B = b;
|
||||
if (strip) {
|
||||
strip->SetPixelColor(idx, lcolor.C);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Ws2812GetPixelColor(uint32_t idx) {
|
||||
LedColor lcolor;
|
||||
if (strip) {
|
||||
lcolor.C = strip->GetPixelColor(idx);
|
||||
return lcolor.C; // already encoded as WWRRGGBB
|
||||
// return (lcolor.W << 24) | (lcolor.R << 16) | (lcolor.G << 8) | lcolor.B;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // ESP32
|
||||
#endif // USE_BERRY
|
||||
|
||||
/********************************************************************************************/
|
||||
|
||||
void CmndLed(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Settings->light_pixels)) {
|
||||
if (XdrvMailbox.data_len > 0) {
|
||||
char *p;
|
||||
uint16_t idx = XdrvMailbox.index;
|
||||
Ws2812ForceSuspend();
|
||||
for (char *color = strtok_r(XdrvMailbox.data, " ", &p); color; color = strtok_r(nullptr, " ", &p)) {
|
||||
if (LightColorEntry(color, strlen(color))) {
|
||||
Ws2812SetColor(idx, Light.entry_color[0], Light.entry_color[1], Light.entry_color[2], Light.entry_color[3]);
|
||||
idx++;
|
||||
if (idx > Settings->light_pixels) { break; }
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ws2812ForceUpdate();
|
||||
}
|
||||
char scolor[LIGHT_COLOR_SIZE];
|
||||
ResponseCmndIdxChar(Ws2812GetColor(XdrvMailbox.index, scolor));
|
||||
}
|
||||
}
|
||||
|
||||
void CmndPixels(void)
|
||||
{
|
||||
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= WS2812_MAX_LEDS)) {
|
||||
/*
|
||||
Settings->light_pixels = XdrvMailbox.payload;
|
||||
Settings->light_rotation = 0;
|
||||
Ws2812ReinitStrip(); -- does not work with latest NeoPixelBus driver
|
||||
Light.update = true;
|
||||
*/
|
||||
Ws2812Clear(); // Clear all known pixels
|
||||
Settings->light_pixels = XdrvMailbox.payload;
|
||||
Settings->light_rotation = 0;
|
||||
TasmotaGlobal.restart_flag = 2; // reboot instead
|
||||
}
|
||||
ResponseCmndNumber(Settings->light_pixels);
|
||||
}
|
||||
|
||||
void CmndStepPixels(void)
|
||||
{
|
||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 255)) {
|
||||
Settings->light_step_pixels = (XdrvMailbox.payload > WS2812_MAX_LEDS) ? WS2812_MAX_LEDS : XdrvMailbox.payload;
|
||||
// Ws2812ReinitStrip(); -- not sure it's actually needed
|
||||
Light.update = true;
|
||||
}
|
||||
ResponseCmndNumber(Settings->light_step_pixels);
|
||||
}
|
||||
|
||||
|
||||
void CmndRotation(void)
|
||||
{
|
||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < Settings->light_pixels)) {
|
||||
Settings->light_rotation = XdrvMailbox.payload;
|
||||
}
|
||||
ResponseCmndNumber(Settings->light_rotation);
|
||||
}
|
||||
|
||||
void CmndWidth(void)
|
||||
{
|
||||
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
|
||||
if (1 == XdrvMailbox.index) {
|
||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 4)) {
|
||||
Settings->light_width = XdrvMailbox.payload;
|
||||
}
|
||||
ResponseCmndNumber(Settings->light_width);
|
||||
} else {
|
||||
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload < 32)) {
|
||||
Settings->ws_width[XdrvMailbox.index -2] = XdrvMailbox.payload;
|
||||
}
|
||||
ResponseCmndIdxNumber(Settings->ws_width[XdrvMailbox.index -2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Internal calls for ArtNet
|
||||
\*********************************************************************************************/
|
||||
// check is the Neopixel strip is configured
|
||||
bool Ws2812StripConfigured(void) {
|
||||
return strip != nullptr;
|
||||
}
|
||||
size_t Ws2812StripGetPixelSize(void) {
|
||||
return strip->PixelSize();
|
||||
}
|
||||
// return true if strip was dirty and an actual refresh was triggered
|
||||
bool Ws2812StripRefresh(void) {
|
||||
if (strip->IsDirty()) {
|
||||
Ws2812LibStripShow();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void Ws2812CopyPixels(const uint8_t *buf, size_t len, size_t offset_in_matrix) {
|
||||
uint8_t *pixels = strip->Pixels();
|
||||
memmove(&pixels[offset_in_matrix], buf, len);
|
||||
strip->Dirty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Interface
|
||||
\*********************************************************************************************/
|
||||
|
||||
bool Xlgt01(uint32_t function)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
switch (function) {
|
||||
case FUNC_SET_CHANNELS:
|
||||
result = Ws2812SetChannels();
|
||||
break;
|
||||
case FUNC_SET_SCHEME:
|
||||
Ws2812ShowScheme();
|
||||
break;
|
||||
case FUNC_COMMAND:
|
||||
result = DecodeCommand(kWs2812Commands, Ws2812Command);
|
||||
break;
|
||||
case FUNC_MODULE_INIT:
|
||||
Ws2812ModuleSelected();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // USE_WS2812
|
||||
#endif // USE_LIGHT
|
||||
#endif // ESP32
|
Loading…
Reference in New Issue