238 lines
7.0 KiB
C++
238 lines
7.0 KiB
C++
/*
|
|
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
|