Buterbrott

DIY, Carpentry, Electronics, Arduino, ESP, LEDs, 3D prints https://t.me/buterbrott_channel
Пикабушник
поставил 1 плюс и 0 минусов
Награды:
5 лет на Пикабу
973 рейтинг 10 подписчиков 1 подписка 3 поста 3 в горячем

Светодиодные матрицы для "чайников" (часть 2)

Ну что, дружище, надеюсь ты освоил первую часть моего лонгрида и уже запилил свою лампу Гайвера с блэкджеком и всем остальным? Тогда вперед, через тернии к звездам, гексагонам, кругам, гирляндам.

Во многих подобных конструкциях раскладка светодиодов не имеет строк и столбцов, а порой она и вовсе хаотичная. Да и и соединены диоды не рядами, а как было удобно для разводки платы.

Возьмем, к примеру, мою новогоднюю звезду . Диоды там уложены концентрическими звездами, да еще и змейкой.

Светодиодные матрицы для "чайников" (часть 2) Arduino, Программирование, Длиннопост, Светодиоды, Видео, Без звука

Но отчаиваться не будем, попробуем мыслить логически. У каждого диода на звезде есть реальные координаты центра в миллиметрах, или координаты на картинке в пикселях.

Светодиодные матрицы для "чайников" (часть 2) Arduino, Программирование, Длиннопост, Светодиоды, Видео, Без звука

Мы можем создать таблицу соответствия порядкового номера диода и его координат. Изменим наш цикл обхода диодов по координатам на обход по номерам.Перепишем функцию мэппинга так, чтобы она искала координаты по индексу диода в таблице и дело в шляпе.

uint16_t mapTable[][2] = {

{ 206, 340 },

{ 181, 354 },

{ 156, 369 },

{ 132, 383 },

{ 107, 398 },

{ 89, 404 },

{ 89, 385 },

{ 95, 357 },

{ 101, 329 },

{ 107, 301 },

{ 114, 273 },

... // и так далее, для всех 180 диодов

};

struct ledCoords {

uint16_t x;

uint16_t y;

};

ledCoords mapIdxToXY( byte index ) {

return { mapTable[index][0], mapTable[index][1] };

}

void loop() {

for ( byte i = 0; i < NUM_LEDS; i++ ) {

ledCoords lc = mapIdxToXY(i);

leds[i] = effectColorByCoords(lc.x, lc.y);

}

FastLED.show();

}

На практике это оказывается не очень удачным решением. Координаты - штука относительная. У кого-то линейка в дюймах, а у кого-то 4К картинка с четырехзначными координатами. Замучаешься подгонять масштаб анимации под каждое устройство. Чтобы избежать этого выполняют нормализацию координат.
Звучит страшно, но на практике это просто пересчет в некую фиксированную систему координат. Например, давай примем за правило, что минимальная координата по X среди всех диодов это 0 в нашей "нормализованной" системе, а максимальная - 1. И все остальные координаты пересчитаем пропорционально Xn = ( x - Xmin )/(Xmax - Xmin). Аналогично поступим с координатами Y.

Теперь у нас все координаты на любом устройстве лежат в пространстве 0..1
Такой подход используется в программируемых контроллерах Pixelblaze.

Казалось бы, можно закончить занудствовать на этом, но позволь еще немного помучать тебя.

  • Во-первых все эти дробные координаты для контроллера являются числами с плавающей точкой (float) и он тебе не скажет спасибо за их использование. В отместку контроллер будет тратить кучу времени на расчеты даже простой арифметики, не говоря уж о корнях, степенях и тригонометрии.

  • Во-вторых, некоторые алгоритмы эффектов требуют расчета по всей площади, а не только в точках расположения диодов. Например операция размытия (blur) требует значения цветов всех соседних точек в пределах радиуса размытия.

  • Ну и в-третьих, тут уж мое личное мнение, куча дробных чисел в формулах снижает читабельность кода, а отладка такого кода вызывает у меня тихий ужас.

И тут ты подкидываешь наивную идею: "А можно как-то так чтобы координаты остались целыми, но не такими большими и более-менее схожей размерности на разных устройствах?"

Можно!

Потребуется всего лишь привести реальные координаты диодов к прямоугольной матрице низкого разрешения. Представь, что мы взяли листок бумаги в клеточку и положили поверх нашей звезды и пометили клетки в которые попали центры диодов.

Светодиодные матрицы для "чайников" (часть 2) Arduino, Программирование, Длиннопост, Светодиоды, Видео, Без звука

Теперь у каждого диода есть простые координаты в координатной системе листочка в клеточку. И эти координаты гораздо более удобоваримые чем реальные. И если мы захотим посчитать наш эффект для каждой клеточки на этом листочке, то даже наш не супер-мощный контроллер это осилит. Попробуем записать результат в таблицу мэппинга.

byte mapTable[][2] = {

{ 12, 18 },

{ 11, 19 },

{ 9, 20 },

{ 7, 21 },

{ 6, 22 },

{ 5, 22 },

{ 5, 21 },

{ 5, 19 },

{ 6, 18 },

{ 6, 16 },

{ 6, 15 },

... // и так далее, для всех 180 диодов

}

Вроде неплохо получается.

А чтобы все алгоритмы были похожи на работу с обычной прямоугольной матрицей,можно развернуть мэппинг. Будем хранить в таблице не координаты, а индексы диодов для каждой ячейки нашей матрицы 27x23
Только как же быть с пустыми клеточками, ведь их нужно как-то пропустить при обработке? Да запиши в них просто какое-то значение индекса которое ты отловишь в цикле и пропустишь обработку. Например любое число большее общего количества диодов. И таблица примет такой вид:

byte mapTableIndex[23][27] = {

{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 27,

255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },

{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 26, 255,

28, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },

{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 25, 77,

29, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },

{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 78, 255,

76, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },

{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 24, 255, 255,

255, 30, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },

{ 255, 255, 255, 255, 255, 255, 255, 255, 255, 23, 255, 79, 117,

75, 255, 31, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },

... // и еще 17 строк

}

Тогда функция мэппинга и рабочий цикл будут выглядеть как-то так:

byte mapXYtoIdx( byte x, byte y ) {

return mapTableIndex[y][x];

}

void loop() {

for ( byte y = 0; y < 23; y++ ) {

for ( byte x = 0; x < 27; x++ ) {

byte ledIndex = mapXYToIdx(x,y);

if ( ledIndex == 255 ) continue;

leds[ledIndex] = effectColorByCoords(x, y);

}

}

FastLED.show();

}

Конечно, хранить такие большие массивы с таблицами мэппинга в оперативной памяти не стоит, обычно их загоняют во flash используя PROGMEM.

Собираем все полученные знания в кучу и зажигаем)

(Продолжение следует, stay tuned)

Показать полностью 3 1

Светодиодные матрицы для "чайников" (часть 1)

Дружок, присаживайся поудобнее, расскажу тебе что такое матрицы из адресных светодиодов.

Светодиодные матрицы для "чайников" (часть 1) Arduino, Программирование, Длиннопост, Светодиоды, Видео, Без звука

Итак, адресный RGB светодиод отличается от обычного трехцветного наличием встроенного контроллера, вон тот черный прямоугольник на картинке. Эта чудесная микросхема принимает со входа Din пакет данных, откусывает от него первые 24 бита и отправляет оставшиеся данные на выход Dout. А откушенный кусочек превращает в прекрасное свечение определенного цвета.

На мой взгляд, определение “адресный” не совсем подходит для этого контекста, так как адреса и нет вовсе, а есть только порядковый номер диода в конкретной конструкции. Если снесут дом на улице, остальные дома останутся при своих номерах, а если убрать на светодиодной ленте первый диод, то второй станет первым.

В программе контроллера работа с адресными диодами обычно сводится к созданию массива с количеством элементов равным количеству диодов в сборке, где каждый элемент определяет цвет соответствующего диода. Этот массив нужно передать на диодную сборку.На примере самой популярной библиотеки FastLED это выглядит так:

CRGB leds[NUM_LEDS];
leds[0] = CRGB::Red;
FastLED.show();

И все, мы зажгли первый светодиод на ленте красным цветом.

Я опустил инициализацию библиотеки для конкретной ленты, подсмотришь в последующих примерах или документации на FastLED.

Большинство эффектов для светодиодных конструкций представляют собой некую функцию определяющую цвет светодиода в текущий момент времени по его координатам в конструкции.

С лентами вроде все понятно, одномерный объект, одна координата равная порядковому номеру диода на ленте.

А как быть с матрицами? Они уже двумерные, там у каждого диода есть две координаты - столбец и строка. Не паникуй, оказывается среднестатистическая матрица это та же лента уложенная рядочками. Так что, технически, обращение к конкретному диоду выполняется все так же, по его номеру. Слышу твое возмущение, и ты конечно же прав, матрица двумерная и обращаться к диодам хотелось бы по понятным двум координатам. Выручит небольшая функция, которая будет пересчитывать две координаты в номер диода. В общем случае, для прямоугольной матрицы размером WIDTH х HEIGHT, получится как-то так:

int XY( int x, int y ) {
return y * WIDTH + x;
}

Это преобразование обычно называют мэппингом ( mapping )

Ты уже подпрыгиваешь на месте и хочешь собрать что-то сверкающее и красивое? Не вижу причин отказывать тебе в этом, погнали.

Берем самую распространенную китайскую матрицу 16х16 с диодами WS2812, первый диод в левом верхнем углу, там же и начало координат. Все координаты и индексы начинаются с нуля. Цепляем матрицу по схемам из интернета к контроллеру и пишем первую программу.

#include <FastLED.h>
#define DATA_PIN 12
#define WIDTH 16
#define HEIGHT 16
#define NUM_LEDS WIDTH * HEIGHT

CRGB leds[NUM_LEDS];

byte XY( byte x, byte y ) {
return y * WIDTH + x;
}

void setup() {
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
FastLED.clear();
for ( byte y = 0; y < HEIGHT; y++ ) {
for ( byte x = 0; x < WIDTH; x++ ) {
leds[XY(x,y)] = CRGB::Red;
FastLED.show();
delay(100);
}
}
}

void loop() {
}

Компилируем, прошиваем. Работает!!!

Так, стоп! У нас цикл для X идет по возрастанию от 0 до 15, почему же тогда каждая вторая строка матрицы заполняется в обратном порядке?
Помнишь я говорил что матрица это лента уложенная рядочками? Так вот ленту удобнее укладывать змейкой, что производители и делают) Раскладка так и называется - serpentine ( aнгл. змеевидный ).

Что же делать с нашим примером, ведь мы хотим зажечь диод с координатами (2,1) а зажигается диод (13,1). Немного поскрипим извилинами и изменим функцию мэппинга так чтобы каждый второй ряд считался в обратном порядке.

byte XY( byte x, byte y ) {
if ( y%2 == 0 ) {
return y * WIDTH + x;
} else {
return ( y + 1 ) * WIDTH - x - 1;
}
}

Не буду разжевывать математику, прикинешь на пальцах, сообразишь сам что к чему.

Теперь все ок.

С адресацией матрицы разобрались, можем творить красоту.

(Продолжение следует. Stay tuned)

Показать полностью 3

Новогодняя звезда - 180 адресных светодиодов!

Мой долгострой. Проект далек от завершения. Но промежуточные результаты уже не стыдно показать.
Плата моей разработки, заказана у китайцев. 180 диодов ws2812b-mini, wemos d1 mini в качестве контроллера.
Эксперименты с генерацией палитр и нестандартными таблицами мэппинга.

Отличная работа, все прочитано!