"Ламподелам" на заметку.
Всем известная библиотека FastLED от рождения страдает тем что в ней нельзя динамически назначать пин вывода для подключения адресных лент и порядок следования цветов в них. Проблема известная и планов решить её нет и не предвидится.
В свое время билиотека создавалась под слабенькие, по нынешним меркам, 8-битные контроллеры с максимальной оптимизацией и вносить радикальные изменения в архитектуру похоже не планируется.
На гитхабе годами висят похожие тикеты #282, #826 с просьбами сделать что-нибудь с этим. А из предлагаемых костыльных вариантов есть только создание ветвистых конструкций из всех возможных наборов пинов и цветов
Если же надо задавать И пин И порядок цветов, то число комбинаций начинает расти в прогрессии. Всё это а) некрасиво б) приводит к генерации излишнего кода
Попробуем решить эту проблему более элегантным способом с помощью ООП и наследования. Условие - обойтись без правки самой библиотеки т.к. это сделает неудобным процесс установки/обновления.
В качестве платформы возмем чипы семейства esp32 как наиболее доступные и популярные сейчас для поделок на адресных лентах.
Изучаем документацию на чип и смотрим как реализован вывод данных на адресную ленту. В библиотеке под есп32 для этого есть поддержка двух движков - RMT и I2S. Оба этих движка позволяют подключать на вывод любой доступный пин через коммутационную матрицу. Т.е. технически абсолютно неважно в какой пин выводить данные, оптимизация на уровне компилируемого кода никак не завязанна на конкретные пины или связанные с ними регистры.
Теперь смотрим на реализацию FastLED - движёк RMT для адресных лент находится в файле clockless_rmt_esp32.h
Смотрим конструктор класса
и видим что, собственно, пин куда цепляется движёк задается при создании экземпляра класса, а не при компиляции! Т.е. по сути динамическая конфигурация по пину уже есть! Нужно только решить вопрос как создавать этот объект и цеплять его к движку фастлед.
Далее смотрим как происходит инициализация движка фастлед, обычно это строка в скетче типа
т.е. на этапе компиляции должно быть известно значение используемого чипа, вывод подключения и цветовой порядок. Нам это не подходит, сделать тут ничего нельзя. Ищем какие еще методы есть у
Т.е. можно прицепить к фастлед некий
Далее разбираем цепочки наследования классов в библиотеке и получаем примерно следующее:
вот и откопали - от
Т.е. что нам нужно - написать свои шаблоны классов по цепочке так что бы функционал и методы сохранились как в оригиналах, а из параметров шаблонов убрать пин и порядок цветов. Возможно ли это? Да, возможно! Хоть не так просто.
Надо сказать от пина в параметрах я избавился довольно легко, т.к. исходный класс движка уже был от него отвязан. А вот с чередованием цветов пришлось поломать голову.
Дело в том что метод
В итоге вызов
функция чисто виртуальная, т.е. она должна быть реализованна в дочерних классах и в качестве аргумента в ней идет объект от шаблона с параметром
Казалось бы в чем проблема? В том что нам нужно "научить" этот метод кушать не конкретный объект типа
как-то так?
и конечно же так сделать нельзя
Почему? А потому что виртуальные методы классов в с++ не могут быть основаны на шаблонах.
Эту забавную задачу можно решить с помошью crtp. Т.е. делегировать реализацию шаблона в дочерний класс, а в родительском оставить вызов на дочерний класс который пока еще не известен.
Что в итоге у меня получилось - легкий заголовочный файл с декларациями классов-обёрток над движком из FastLED полностью сохраняющих её АПИ.
Пользоваться им довольно просто - кладем заголовочник в каталог с проектом,включаем в скетч и создаем экземпляр нашего объекта адресной ленты с параметрами пина и цвета вычитанными из какого-нибудь джейсона или еепром или еще какого конфига.
Из негативных последствий использования модифицированного класса это, теоритически, более медленная работа вызова show() из-за добавленного условия сравнения с константой
Надо сказать спасибо авторам фастлед что классы они раскидали довольно аккуратно хоть и не без недостатков. Например в существующей реализации библиотеки нельзя на лету изменить конфигурацию движка - убрать ленту с одного пина и перевесить на другой без перезагрузки контроллера. Но это уже совсем экстравагантные случаи без которых вполне можно обойтись.
Всем известная библиотека FastLED от рождения страдает тем что в ней нельзя динамически назначать пин вывода для подключения адресных лент и порядок следования цветов в них. Проблема известная и планов решить её нет и не предвидится.
В свое время билиотека создавалась под слабенькие, по нынешним меркам, 8-битные контроллеры с максимальной оптимизацией и вносить радикальные изменения в архитектуру похоже не планируется.
На гитхабе годами висят похожие тикеты #282, #826 с просьбами сделать что-нибудь с этим. А из предлагаемых костыльных вариантов есть только создание ветвистых конструкций из всех возможных наборов пинов и цветов
switch:
// для пинов
switch (pin_num) {
case 1: FastLED.addLeds<WS2811,1>(leds, NUM_LEDS); break;
case 2: FastLED.addLeds<WS2811,2>(leds, NUM_LEDS); break;
case 3: FastLED.addLeds<WS2811,3>(leds, NUM_LEDS); break;
...
default: break;
}
// для цветов
switch (color_mode) {
case RGB: FastLED.addLeds<WS2811,PIN,RGB>(leds, NUM_LEDS); break;
case BGR: FastLED.addLeds<WS2811,PIN,BGR>(leds, NUM_LEDS); break;
...
default: break;
}
Попробуем решить эту проблему более элегантным способом с помощью ООП и наследования. Условие - обойтись без правки самой библиотеки т.к. это сделает неудобным процесс установки/обновления.
В качестве платформы возмем чипы семейства esp32 как наиболее доступные и популярные сейчас для поделок на адресных лентах.
Изучаем документацию на чип и смотрим как реализован вывод данных на адресную ленту. В библиотеке под есп32 для этого есть поддержка двух движков - RMT и I2S. Оба этих движка позволяют подключать на вывод любой доступный пин через коммутационную матрицу. Т.е. технически абсолютно неважно в какой пин выводить данные, оптимизация на уровне компилируемого кода никак не завязанна на конкретные пины или связанные с ними регистры.
Теперь смотрим на реализацию FastLED - движёк RMT для адресных лент находится в файле clockless_rmt_esp32.h
Смотрим конструктор класса
C++:
ESP32RMTController(int DATA_PIN, int T1, int T2, int T3, int maxChannel, int memBlocks);
Далее смотрим как происходит инициализация движка фастлед, обычно это строка в скетче типа
C++:
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS);
FastLED.addLeds
- это вызов метода экземпляра класса CFastLED
высокоуровнего контроллера, который собственно и управляет связкой кадрового буфера с движком вывода. Это шаблонизированный медод:
C++:
/// Add a clockless based CLEDController instance to the world.
template<template<uint8_t DATA_PIN, EOrder RGB_ORDER> class CHIPSET, uint8_t DATA_PIN, EOrder RGB_ORDER>
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
static CHIPSET<DATA_PIN, RGB_ORDER> c;
return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset);
}
CFastLED
которые не используют шаблоны и находим интересный метод
C++:
static CLEDController &addLeds(CLEDController *pLed, struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0);
CLEDController
объект и кадровый буфер. Похоже на то что нужно! Значит нам нужно создать объект CLEDController
или используя принципы наследования объектов что-то унаследованное от CLEDController
.Далее разбираем цепочки наследования классов в библиотеке и получаем примерно следующее:
наследование:
class CPixelLEDController : public CLEDController
class ClocklessController : public CPixelLEDController
class WS2812Controller800Khz : public ClocklessController
class WS2812B : public WS2812Controller800Khz
CLEDController
до WS2812B
лежит 4 промежуточных класса и почти все они написаны в виде шаблонов от различных параметров, в.т.ч. пина и чередования цветов.Т.е. что нам нужно - написать свои шаблоны классов по цепочке так что бы функционал и методы сохранились как в оригиналах, а из параметров шаблонов убрать пин и порядок цветов. Возможно ли это? Да, возможно! Хоть не так просто.
Надо сказать от пина в параметрах я избавился довольно легко, т.к. исходный класс движка уже был от него отвязан. А вот с чередованием цветов пришлось поломать голову.
Дело в том что метод
FastLED.show()
который используется для вывода кадра в ленту тоже использует шаблоны для компиляции оптимизированного кода преобразования цветов. Оптимизация, правда, там весьма своеобразная и состоит из статических функций возвращающих нужный байт из тройки RGB. Но от них никуда не дется если не переписывать саму библиотеку фастлед или не заниматься реимплементацией её методов (чего хотелось бы избежать).В итоге вызов
show()
сводится к такой функции класса CPixelLEDController
:
C++:
virtual void showPixels(PixelController<RGB_ORDER,LANES,MASK> & pixels) = 0;
RGB_ORDER
.Казалось бы в чем проблема? В том что нам нужно "научить" этот метод кушать не конкретный объект типа
PixelController<RGB_ORDER,LANES,MASK>
, а некое подмножество объектов с разными вариантами параметра RGB_ORDER
как-то так?
C++:
template<typename T>
virtual void showPixels(T & pixels) = 0;
Эту забавную задачу можно решить с помошью crtp. Т.е. делегировать реализацию шаблона в дочерний класс, а в родительском оставить вызов на дочерний класс который пока еще не известен.
Что в итоге у меня получилось - легкий заголовочный файл с декларациями классов-обёрток над движком из FastLED полностью сохраняющих её АПИ.
Пользоваться им довольно просто - кладем заголовочник в каталог с проектом,включаем в скетч и создаем экземпляр нашего объекта адресной ленты с параметрами пина и цвета вычитанными из какого-нибудь джейсона или еепром или еще какого конфига.
C++:
#include "w2812-rmt.hpp"
// задаём пин
int gpio_num = 10;
// задаём чередование цветов
EOrder color_order = GRB;
// размер буфера
int CRGB_buffersize = 256;
CRGB* CRGB_buffer;
setup(){
CRGB_buffer = new CRGB[numofleds];
// создаём объект ленты
ESP32RMT_WS2812B wsstrip(gpio_num, color_order);
// цепляем нашу ленту к контроллеру
FastLED.addLeds(&wsstrip, CRGB_buffer, CRGB_buffersize);
}
_rgb_order
определяющей порядок чередования цветов текущего экземпляра класса. Но на скоростях ЦПУ есп32 в сравнении с общим временем требуемым для последовательного вывода данных кадра в ленту практически измерить разницу врядли представляется хоть сколько-нибудь возможным.Надо сказать спасибо авторам фастлед что классы они раскидали довольно аккуратно хоть и не без недостатков. Например в существующей реализации библиотеки нельзя на лету изменить конфигурацию движка - убрать ленту с одного пина и перевесить на другой без перезагрузки контроллера. Но это уже совсем экстравагантные случаи без которых вполне можно обойтись.
Изменено: