Мобільна версія Форум Arduino Документація Гарантійні умови 0 0
UA RU
Графік роботи магазину:
Пн-Пт: 8.00 - 19.00
Сб: 10.00 - 17.00
Нд: вихідний
Каталог
Напиши статтю і отримай знижку!

Arduino Leonardo и Micro для управления WS2812B

2015-09-11

Всі статті →

Распространение малогабаритных, корпусированных в SMD5050 сверхъярких RGB-светодиодов вместе с трехканальными восьмиразрядными контроллерами, управляющими их яркостью, дает возможность построить интересные и разнообразные конструкции подсветки и освещения. В Сети можно найти немало готовых решений, ориентированных либо на последовательности, собранные из модулей WS2812B, либо на уже готовые светодиодные ленты, совместимые с ними по алгоритмам управления. Немалая часть этих решений опирается на контроллеры Arduino (например, https://www.tweaking4all.com/hardware/arduino/arduino-ws2812-led/). В Сети также доступны библиотеки, призванные облегчить управление светодиодами в таких проектах (например, Adafruit NeoPixel). Желающих использовать готовые решения, однако, могут поджидать неприятные сюрпризы, если ваш выбор модификации (версии) Arduino не совпадает с предпочтениями автора библиотеки (а чаще всего библиотеки ориентированы на Arduino Uno).



Фото: W2812B за работой: зеленый включен на 1/255 максимальной яркости, красный -- на 3/255, а синий -- на 2/255


Сложность в том, что для управления модулем WS2812B не подходит стандартная универсальная функция digitalWrite(), принятая в среде разработки Arduino. Не подходит потому, что длительности импульсов, которые нужны для управления, слишком малы для работы через вызовы функций высокого уровня (нужно формировать импульсы длительностью 400 и 850 нс). Собственно именно универсальность (возможность исполнения на любом контроллере из класса Arduino) и служит причиной того, что digitalWrite(pin, value) исполняется медленно (время, по видимому, уходит на преобразование параметра «pin» в конкретный разряд конкретного порта используемой модификации контроллера — ведь не зря в среде разработки вы настраивали параметр Board в меню Tools). Чтобы сформировать достаточно короткие импульсы приходится манипулировать непосредственно портом ввода/вывода (см. о работе с портами, например, http://arduino.ru/Tutorial/Upravlenie_portami_cherez_registry). Но одни и те же выводы платы в разных модификациях подключены к разным портам. Например, pin 6, к которому подключают вход данных светодиодного модуля в упомянутом выше решении, — это шестой разряд порта D (Arduino Uno), но если мы возьмем Arduino Leonardo, то окажется, что pin6 связан с седьмым разрядом этого порта. А, скажем, pin7 и вовсе попадет в другой порт. Есть и еще одно не менее существенное отличие в модификациях контроллеров: в Leonardo и Micro поддержкой USB порта занят тот же процессор, который исполняет и ваш скетч, а значит, есть конкуренция за ресурс процессора, которая может помешать корректной работе библиотеки, это не учитывающей.

Arduino Leonardo и Micro, тем не менее, могут быть желанными прежде всего из-за физических размеров контроллера (Arduino Pro Micro имеет размер всего 18х33 мм), да и цена немаловажна. А управление цепочкой WS2812B вполне может быть реализовано на этой модификации Arduino. Более того, мы даже обойдемся без создания библиотеки.

WS2812B

Прежде, чем описать программу управления несколько слов о том, что такое модуль WS2812B (http://www.world-semi.com/uploads/soft/150522/1-150522091P5.pdf). Те, кто уже знаком с работой этих модулей или лент на основе драйверов WS2811 и RGB-светодиодов, могут смело пропустить этот раздел.

Модуль WS2812B (см. фото) имеет всего четыре контакта, причем два из них — земля и питание (+3,5..5 В). Из двух оставшихся контактов один — вход данных, другой — выход данных (для следующего в цепочке модуля — см. рис. 1).

 

 

Рис. 1. Соединение модулей WS2812B в цепочку

 

Поступающие на вход модуля данные принимаются встроенным в модуль WS2812B чипом контроллера/драйвера светодиодов (между источником данных и входом модуля рекомендуют ставить небольшой резистор (пару сотен ом), чтобы защитить вход микросхемы). Поток данных (о его структуре чуть ниже) должен содержать в последовательном коде управляющие значения для всех микросхем в цепочке. Первый чип принимает весь поток, но использует только три первых байта (по байту для управления зеленым, красным и синим светодиодами), а все, что следует за этими тремя байтами, выдает на выход. Таким образом, каждый чип принимает все данные для себя и всех расположенных за ним в цепочке чипов. Если в цепочке, например, 30 модулей, то на первый модуль поступает 3*30=90 байт, на выходе первого модуля появляются 90-3=87 байт (кроме первых трех из последовательности в 90 байт), на выходе второго — 87-3=84 байта и т.д. До входа последнего модуля дойдет три байта, которые определяют яркость свечения его светодиодов. Данные для управления цветом передаются, начиная со старшего разряда, в порядке зеленый-красный-синий (см. рис. 2). Данные для второго модуля в цепочке передаются сразу же после первого без пауз.

Рис. 2. Структура данных, управляющих цветом одного модуля

 

В WS2812B не предусмотрена внешняя синхронизация передаваемых данных (вообще-то есть варианты модулей, в которых присутствует отдельная линия синхронизации, но это ведь дополнительный провод — лучше «помучаться» с управляющей программой, и сэкономить лишний провод). А поскольку нет внешней синхронизации, то передача каждого бита происходит с самосинхронизацией. Каждый бит начинается с формирования нарастающего фронта (переход от низкого уровня к высокому). Чип контроллера, обнаруживая фронт, начинает отсчет времени и опрашивает, примерно через 625 наносекунд уровень данных. Если обнаруживает высокий уровень — оценивает бит как равный "1", если уровень низкий — "0". Следующие 625 нс нужны чипу для сохранения полученного бита и переходу к ожиданию следующего фронта. Разумеется, и при формировании данных, и при их считывании возможны погрешности, поэтому справочный листок на WS2812B задает довольно широкие допуски на формирование передаваемых данных (см. рис. 3). При тактовой частоте 16 МГц используемого в Arduino Leonardo и Arduino Micro процессора ATmega32u4, один цикл процессора занимает 62,5 нс, следовательно, для формирования номинальной длительности «короткого» импульса ("0") — 400 нс — в нашем распоряжении 6 циклов. Этого вполне достаточно, если не использовать сложные высокоуровневые функции (маленький секрет: если интервал до следующего нарастающего фронта будет чуть больше 1250 нс — скорее всего, это никак не скажется на приеме данных. Хотя лучше, конечно, не злоупотреблять «терпением» чипа).

Рис. 3. Передача бита (временные параметры)

 

Чтобы не возвращаться к алгоритмам работы WS2812B заметим, что признаком начала передачи управляющей последовательности байтов служит интервал не менее 50 мкс перед нарастающим фронтом на линии входных данных. Вот это условие как раз легко выполняется стандартными средствами среды Arduino.

В завершение описания корпусированного в SMD5050 модуля WS2812B, отметим, что он, как и следует из названия этого конструктива (surface mounted device — модули для поверхностного монтажа), не предназначен для проводного макетирования (зато отлично монтируются на ленты!) Поэтому на практике часто используют микро-платы, на которых монтируют сам элемент WS2812B и его развязку по питанию. На фото вы видите как раз такую плату, на которой сразу смонтированы конденсатор развязки по питанию и резистор защиты входа — очень удобно.

Формирование импульсов данных

Для формирования коротких импульсов нам придется работать с портом ATmega32u4 напрямую. И первое, что необходимо сделать, определить каким разрядом какого порта мы будем управлять. Автору заметок было удобно выбрать для передачи данных pin8 в Arduino Leonardo, но, разумеется, для читателя не составит труда выбрать любой другой выход. Открыв схему своей платы (в нашем случае это https://www.arduino.cc/en/uploads/Main/arduino-leonardo-schematic_3b.pdf) видим, что pin8 соответствует четвертому разряду порта B. Таким образом, команды быстрой установки высокого и низкого уровня на выходе pin8 (и, соответственно, на входе первого в цепочке модуля WS2812B) будут такими:

 

PORTB |= _BV(PB4); // установить высокий уровень на pin8
PORTB &= ~_BV(PB4); // установить низкий уровень на pin8

 

Используем «пустую» команду (nop\n\t), чтобы сформировать необходимую продолжительность импульса в соответствии со значением передаваемого разряда (полагаем, что это старший разряд байта D_in):

PORTB |= _BV(PB4); // включили высокий уровень на D(in)

__asm__(«nop\n\t»); __asm__(«nop\n\t»); // 2 командных цикла — 125 ns
__asm__(«nop\n\t»);

if ( D_IN >= 0x80 )
{
__asm__(«nop\n\t»); __asm__(«nop\n\t»);
__asm__(«nop\n\t»); __asm__(«nop\n\t»);
__asm__(«nop\n\t»); __asm__(«nop\n\t»);
};

PORTB &= ~_BV(PB4); // выключили высокий уровень на D(in)
// удерживаем низкий до конца бита
__asm__(«nop\n\t»); __asm__(«nop\n\t»);
__asm__(«nop\n\t»);

if ( D_IN < 0x80 )
{
__asm__(«nop\n\t»); __asm__(«nop\n\t»);
__asm__(«nop\n\t»); __asm__(«nop\n\t»);
__asm__(«nop\n\t»); __asm__(«nop\n\t»);
};

Хотя в приведенном фрагменте всегда исполняется лишь 12 формирующих задержки команд NOP (их общая длительность только 750), но время расходуют и другие команды: проверки условий и записи в порт. А когда мы организуем циклы, чтобы передавать все биты всех байтов для всех модулей, то в длительность формирования бита добавятся еще и команды управления циклами и выбора значения передаваемого разряда — так и сформируется полная длительность бита.

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

#define defNoDiods 2
volatile byte glG8[defNoDiods], glR8[defNoDiods], glB8[defNoDiods];

Эксперименты автор проводил с двумя модулями (поэтому, во избежание переусердствования со стороны оптимизатора среды Arduino, указано volatile), но памяти в Arduino Leonardo достаточно для размещения данных на 500 модулей. Теперь мы готовы сделать подпрограмму, которая будет загружать данные в цепочку светодиодных модулей. Надо только не забыть перед началом генерации сигнала для входа цепочки запретить прерывания, чтобы они не помешали выработке интервалов между переключениями порта, и разрешить их по окончании генерации.

Оператор setup () в нашем случае тривиален: настраиваем выход и устанавливаем его в ноль. Чтобы быть уверенным, что первая же последовательность, которую мы загрузим, будет воспринята правильно делаем задержку заведомо большую, чем требуется для опознавания начала загрузки данных.

Содержимое оператора loop () определяется теми задачами, ради которых используются светодиодные модули. Ну а для примера (или, например, проверки цепочки) можно при каждом проходе в петле loop наращивать элементы массива. В примере для двух светодиодов это выглядит так, как на скриншоте (свечение двух источников изменяется, но не совпадает более 1700 часов, так как инкремент компонентов задан простыми числами). Впрочем, для проверки возможны и любые другие комбинации — широкие возможности управления практически не ограничивают полет фантазии.

 

Борис Оболикшто
www.facebook.com/obolikshto
bob (at) bso (точка) org (точка) ua

Ваша оцінка статті:

Відмінно
Добре
Задовільно
Погано
Дуже погано

Дякуємо Вам за звернення! Ваш відгук з'явиться після модерації адміністратором.
Поки немає відгуків на цю статтю.
оплата картами Visa і MasterCard