Управление адресной лентой ws2812 с использованием библиотеки Adafruit NeoPixel.
О чем это я и для кого?Я хочу рассказать как быстро и просто можно оживить ленту на базе "умных" светодиодов типа WS2811(12), SK6812, INK1003 и других без лишних затрат, имея только ардуино, библиотеку Adafruit NeoPixel, кусок ленты и час свободного времени, чтобы получить подобную занятную иллюминацию или что то еще в соответствии с вашей целью. Моя цель — не эта гирлянда или готовый скетч. Весь код в тексте и в прилагаемом скетче служит только для того, чтобы показать возможности сторонней библиотеки от компании Adafruit, накидан на скорую руку, и никак не является образцом для подражания или пособием по программированию. Он всего лишь работает с кусочком ленты и демонстрирует несложные эффекты. Вам не нужно ничего тупо повторять. Считайте это подробным справочником по нескольким функциям NeoPixel с примерами их использования в коде СИ. Посему просьба оставить мокрые тапки для своих котов)Для кого обзор? Конечно для тех, кто только начинает знакомство со смарт-лентами и имеет представление о том, как работает ардуино и как пишется под него код.
Теперь поехали)Достаточно популярными "умные ленты" стали уже давно. Среди них существует множество китайских клонов, собранных как на обычных диодах RGB и внешними управляющими регистрами, так и "полноценные", с использованием ws2812 со встроенным в корпус 5050 контроллером. Принцип работы практически одинаков для всех лент и матриц с SPI (последовательным) интерфейсом. Это относится к WS2811(12), SK6812, INK1003 и другим подобным драйверам. На вход первого контроллера ленты передается непрерывный поток данных, в каждом пакете которого содержится 3 или 4 (для RGBW пикселей)байта со значением цвета для каждого из 3 или 4 светодиодов в пикселе. Контроллер первого пикселя "откусывает" себе первые 3(4) байта, устанавливает свой цвет в соответствии с содержимым, а остальной поток пропускает к следующим пикселям, где происходит аналогичное "откусывание" и дальнейшая передача "из рук в руки" до тех пор, пока в потоке не возникнет пауза в 50 мкс, означающая что пора принимать данные с начала)В зависимости от типа контроллера, тактовая частота может составлять 400 или 800 кГц. Это позволяет не видеть глазу переходных процессов при реализации достаточно сложных световых эффектов. В различных контроллерах может быть разной тактовая частота, разное кодирование 0 и 1 Lo-Hi уровнями и наоборот, разное следование цвета в пакете и другие мелочи, значительно усложняющие жизнь нормальным людям при выборе средств реализации задуманной елочной гирлянды или пересвета днища в 14-ке) Ленты могут иметь и 4 вывода — два питающих, один управляющий, и один тактируемый. В моем примере лента имеет три вывода — питание 12В и управление. Так же есть моменты, связанные с питанием ленты — это опять же напряжение — 5 или 12В и ток потребления на метр. Я использовал отдельный блок питания 12В. При этом нужно учитывать, что питание ленты и ардуино никак не должны между собой быть связаны, если у вас разные напряжения питания! Только по общему проводу для передачи данных. С ардуинки нужно подключить вывод GND к минусовому проводу ленты и управляющий пин 6 (или какой назначите) ко входу ленты D in. +12В подается на ленту от своегоисточника питания, ардуинка питается от своего (либо от USB).
Капиталисты из Adafruit рекомендуют сигнальный провод подключать через резистор порядка нескольких сот Ом, а на саму ленту перед первым контроллером вешать по питанию электролит 1000-2000uF, но у меня все прекрасно работает и напрямую, и через килоОмный резистор при длине шлейфа около 0,5 метра и без каких либо кондеров на ленте. Все страхи актуальны имхо при гораздо бОльших отрезках ленты, чем мой)С подключением в общем то ничего хитрого нет. Рассмотрим программную часть.Для управления ws2812 настоящие true-программисты напишут true-код на ассемблере и прошьют true-контроллер от фирмы майкрочип, но для простых смертных выход тоже есть! Добрые дяди из Adafruit написали замечательную библиотеку, заточенную под ардуино, при помощи которой можно на простом cи из г.на и костылей (как в моем случае) соорудить вполне себе работающий г.но код и не заботиться об организации интерфейса и не заглядывать в даташиты контроллеров лент. Об этом уже позаботились авторы библиотеки и нам не нужно ничего знать, кроме ее функций и конфигурирования двух строк в привычной среде.Что еще следует понимать — каждый пиксель — это память. И она не бесконечна. Например ардуинка uno R3 потянет не более 500 пикселей.
С мегой можно развернуться и пошире)А пока хочу немного рассказать после пары дней экспериментов про использование возможностей библиотеки, назначение ее функций и конфигурирование в привязке к типу ленты и количества пикселей в ней. Сам к ардуине подключал unoR3 и отрезок ленты с INK1003 всего на 6 пикселей (каждый из 3-х отдельных RGB диодов 5050?, бывает и такое в Китае). Но об этом немного позже, а сейчас начнем с самого начала — установки.Устанавливается библиотека просто — в программной среде для ардуино в меню "Скетч" выбираем "Подключить библиотеку" --> "Управлять библиотеками".Откроется окно со списком библиотек, которые уже установлены. В форме поиска вводим "Adafruit NeoPixel", находим, скачиваем и устанавливаем.Теперь остается до функциии void setup()прописать заголовок библиотеки — #include <adafruit_NeoPixel.h>, несколько строк конфигурации и можно начинать кодить)Чтобы лучше понять, что мы получили, давайте посмотрим, что содержат файлы библиотеки)Каталог Adafruit_NeoPixel/examples/ — содержит несколько примеров использования библиотеки:RGBWstrandtest — не интересен, так как моя лента просто RGB)buttoncycler — демонстрирует переключение 3-х эффектов нажатием кнопкиsimple.ino показывает как покрасить ленту зеленымstrandtest.ino — те же три эффекта, но без нажатия кнопокПримеры очень полезные и интересные, но лучше пойдем дальше и сделаем что то свое, пусть и не так красиво).Нужно условиться, что дальше по тексту под словом "пиксель" я подразумеваю три (или 4) разных светодиода в одном корпусе. Поэтому "красный цвет пикселя" и "красный цвет диода" — понятия совершенно разные. Первый — это совокупность цветов. Второй — восьмибитная яркость отдельного красного светодиода из этой совокупности — от 0 до 255.Идем дальше, файл — Adafruit_NeoPixel.cppСодержит функции, с которыми мы будем работать. Желающие ознакомиться с оригинальным (на английский езыке) руководством можно здесь — learn.adafruit.com/adafru…?view=all#arduino-library, а для ленивых продолжу излагать своим языком как умею)Кстати — по ссылке много интересных проектов для ардуины с описанием и кодом не только для ленточных изделий)Первая чуть ли не самая важная функция — strip.setPixelColor(n, red, green, blue, white); Она устанавливает цвет каждого диода (красного-зеленого-синего) в n-пикселе в соответствии с переданными функции параметрами.Т.е. n здесь — номер пикселя в ленте, следующие за ним три или четыре параметра — цвета диодов от 0 до 255. Если используем ленту с дополнительным белым диодом в пикселе — мы задаем 4 параметра (32 бита информации, по байту на цвет) или 3 (24 бита информации), как в моем случае.Например, strip.setPixelColor(45, 255, 0, 0); задаст красный цвет 46-му пикселю в ленте (нужно помнить, что в программе первый пиксель — это "0").Альтернативно — strip.setPixelColor(n, color); Здесь n так же — номер пикселя в ленте, но уже 32 битный цвет, например — uint32_t magenta = strip.Color(255, 0, 255);Еще одна функция strip.Color(255, 0, 255); — как раз конвертирует в одно 32 битное значение три разных параметра цвета для отдельных диодов — RGB. В этом примере (255, 0, 255)- одинаковые по яркости красный и синий диоды. Теперь если мы вызовем strip.setPixelColor с 32 битным параметром magenta, которому ранее присвоили значение функцией strip.Color, мы зададим 46 пикселю этот цвет одним значением: strip.setPixelColor(45, magenta); Итого — всего два параметра) Просто вместо трех восьми битных имеем один 32 битный с местом под значение четвертого диода, даже если его в пикселях ленты нет.Но как и сколько бы мы не задавали цвет, с лентой ничего не произойдет. Передачу каждого пакета данных с заданными цветами каждого диода в каждом пикселе нужно инициализировать. Поэтому ваш код должен быть как можно эффективнее, ибо для вывода сложных эффектов нужна максимальная частота обновления пикселей, а выполнение вашего кода тоже требует процессорного времени. Допустим, вы назначили каждому пикселю набор цветов. Пора вызывать функцию поважнее "сет_пиксел_колора" — strip.show(); Она вызывается без параметров и обновляет все пиксели ленты, один за другим с частотой 400 или 800 кГц). Теперь изменения можно увидеть). Эта же функция является обязательной в разделе void setup(). Перед ней в этом разделе так же обязан находится вызов функции strip.begin(), т.е. примерно так:void setup()Продолжим по функциям, нужным для комфортного кодинга) Раз уж есть функция, "запихивающая" три байта в одно значение, логичным будет и обратный процесс. Но увы, его нет.Есть только возможность средствами библиотеки извлечь тот же 32-битный цвет из конкретного пикселя. Занимается этим делом uint32_t color = strip.getPixelColor(11); Хотя при необходимости и желании можно уже "вручную" вытащить нужные цвета и определить каждый по своему байту)Например так — //---------------------------------------long i = strip.getPixelColor(15); //Получаем цвет 16-го пикселяuint8_t r = (uint8_t)((i >> 16) & 0xff), //Первый байт — красный цветuint8_t g = (uint8_t)((i >> 8) & 0xff), //Второй байт — зеленый цветuint8_t b = (uint8_t)(i & 0xff); //Третий байт — синий цветstrip.setPixelColor(16, r, g, b); //Назначаем полученные значения отдельным диодам 17-го пикселя//--------------------------------------- Это желательно оформить в свою отдельную функцию и для своего же удобства и упрощения кода. Хотя и необязательно.Здесь важно понимать один момент — несмотря на название getPixelColor, функция не выполняет физического обращения к ленте для получения цвета заданного вами номера пикселя. Это невозможно в SPI, работающего по принципу ниппеля — только "туда". Но при первом же вызове setPixelColor в массив pixels[n] для значений пикселей записываются по порядку все устанавливаемые цвета всех диодов в 32-битном значении и хранятся там до следующего вызова этой же функции, которая опять обновит и ленту, и массив. В промежутке можно извлечь из этого массива 32-битный цвет из нужного пикселя с помощью getPixelColor. Это значит, что вы можете в любой момент в любом месте кода выполнять любые действия, не связанные с управлением лентой, и затем вернуться к установленным и включенным хоть 5 минут назад цветам в совсем другой функции и продолжить с ними работать "с того же места".Можно легко задать (установить) или изменить полученный 32-битный цвет в HEX формате. В качестве аргумента мы можем передать параметр цвета пикселя как 0xFF9C00 и функция это так же переварит.Здесь то же самое, только FF — это красный диод с диапазоном значений от 0x00 до 0xFF, 9C — зеленый, и 00 — синий.Аналогично "вытаскиванию" восьмибитных десятичных значений каждого цвета мы можем побайтно изменить исходную величину.Для этого я набросал небольшую функцию, которая изменяет HEX представление цвета SummColor на передаваемые значения RGB —HEX_R, HEX_G, HEX_B.
uint32_t HEX_Math(uint32_t SummColor, uint32_t HEX_R, uint32_t HEX_G, uint32_t HEX_B)Если мы имели 0x9000FE и 0x03, 0x02, 0x01, функция вернет значение 9302FF.Следующая "штатная" функция — uint16_t n = strip.numPixels(); — возвращает задекларированное количество пикселей в ленте. Полезна в тех случаях, когда у вас гора кода и своих функций, а применяться код будет к разным лентам с разным количеством пикселей в них. Чтобы не перебивать все в коде, следует заранее использовать в счетчиках этот вызов.strip.setBrightness(100); — устанавливает яркость всей ленты, вызывается в setup() единожды. В коде использовать нельзя, для этого задавайте параметры яркости значениями RGB. (55,0,0) — тоже красный, но не такой яркий как (255,0,0). Полезность этого для себя нашел в том, что неудобно отлаживать код, когда рядом на столе бьет в глаз в разнобой разный злой колор с уровнями 200-255). А так — установил временно strip.setBrightness(30) и работай себе спокойно. Выше этого значения ни один диод не прыгнет, как бы вы не задавали уровень в коде. Просто все итоговые цвета пикселей будут пропорционально пересчитаны для этого значения, как самого яркого.Подключение нескольких лент к разным выходам ардуины — тоже не вопрос. Пишем в заголовке еще одну ленту с нужным количеством пикселей и пин управления.Adafruit_NeoPixel strip_a = Adafruit_NeoPixel(24, 5);Adafruit_NeoPixel strip_b = Adafruit_NeoPixel(45, 6);Я этим не пользовался, но сложного в этом ничего не вижу.Остается файл Adafruit_NeoPixel.h — заголовок библиотеки, но в нем нам искать нечего. Там прописаны конфигурации пинов, смещения цветов для RGB и RGBW, частоты для разных типов лент, объявлены уже рассмотренные нами функции а так же различные типы и константы.keywords.txt содержит перечень имен функций и констант.Это собственно все, что нам дает библиотека Adafruit NeoPixel — вполне достаточно для написания чего нибудь светящегося, мерцающего и переливающегося)Теперь, имея некий "справочник" можно прописать параметры и начать кодить что нибудь свое, родное)
Вот пример готового скетча, в котором нужно только поправить параметры Adafruit_NeoPixel(12, PIN, NEO_GRB + NEO_KHZ800); под свою ленту.
//***********************************************************************************************#include <adafruit_NeoPixel.h> // подключаем библиотеку, здесь все просто. копи-паст.#define PIN 6 // назначаем управляющий лентой пин. Пусть будет шестой, как в примере оригинальной статьи.Adafruit_NeoPixel strip = Adafruit_NeoPixel(12, PIN, NEO_GRB + NEO_KHZ800); // здесь нужно указать параметры своей ленты.// 12 — количество пикселей в ленте. У меня в отрезке было 6, хотя физически диодов было 18.// Просто один контроллер INK1003 управлял одновременно тремя 5050 RGB сборками.// NEO_GRB — порядок следования цветов. У меня обычный — RGB. Если у вас так же — меняйте этот параметр на NEO_RGB// В любом случае с лентой страшного ничего не произойдет, просто будут перепутаны красный с зеленым, исправит никогда не поздно)// NEO_KHZ800 — частота работы контроллеров пикселей. Смотрите или в даташите к вашей ленте или опять же методом научного тыка.// Так как варианта всего два — запутаться сложно. Либо светит, либо нет.
//***********************************************************************************************void setup() //Это мы уже рассмотрели выше, забежав малость вперед, зато теперь все ясно и понятно —//***********************************************************************************************void loop() // Здесь непосредственно будет выполняться код и вызываться функции, код которых вы напишите за пределами петли//***********************************************************************************************void SetMyColor(uint8_t r, uint8_t g, uint8_t b) //Тело функции 1//***********************************************************************************************void TurnOffSecondPixel() //Тело функции 2
//***********************************************************************************************По ссылке под видео скетч содержит больше десятка функций, реализованных с помощью библиотеки Adafruit NeoPixel. Кроме этого в примерах к библиотеке так же есть несколько интересных реализаций, на основе которых можно строить свои занятные вещи.