День #10: ULTRA мигалка! — Адресная RGB-гирлянда
Сегодня день #10 адвент-календаря «Let it Glow»!
Снова день мигалок — и какой грандиозный праздник света приготовил нам сегодняшний ящик! Внутри маленькой чёрной коробочки вы найдёте адресную RGB-гирлянду с точечными светодиодами — ещё один формат RGB-светодиодов, открывающий нам новые возможности для экспериментов!
До этого вы уже получили диффузные RGB (_GRB_) светодиоды, RGB-кольцо, а теперь эти _сверхмигающие_ RGB-гирлянды. Это значит, что у вас уже есть знания для написания программ для этих огней — но мы покажем вам несколько новых примеров, чтобы дать ещё больше способов поиграть.
ДАВАЙТЕ. МИГАТЬ!
Внимание: некоторые сегодняшние активности содержат быстро мигающий свет, который может быть неподходящим для людей с фотосенсорной эпилепсией.
Содержимое коробки #10
В этой коробке вы найдёте:
1x адресная RGB-гирлянда с 15 светодиодами (dot strand)
Сегодняшние активности
Сегодня мы покажем вам, как подключить гирлянду и адаптировать существующие примеры кода для RGB-светодиодов, чтобы они работали с ней. Мы также дадим вам новые примеры, используя некоторые из ваших компонентов, и несколько идей для реальных проектов!
Прежде чем начать, давайте кратко познакомимся с новым любимым форматом RGB-светодиодов!
Что такое адресная RGB-гирлянда с точечными светодиодами
Эти точечные гирлянды работают точно так же, как LED-кольцо из дня #8, просто в другом формате.
Внутри этих маленьких белых «капелек» находятся такие же адресные светодиоды, которыми ваш код может управлять — они просто имеют гораздо более гибкий формат для встраивания в ваши собственные проекты и украшения.
Специальные гирлянды в вашей коробке имеют всего три провода — 5V, Data IN и GND _(без вывода DOUT)_ — и оснащены джамперными концами, что делает их идеальными для прототипирования на макетной плате.
Сборка цепи
Как всегда, убедитесь, что ваш Pico отключён от USB-кабеля, когда работаете с цепью.
Подготовка макетной платы
Начнём с чистого листа — снимите все компоненты и провода, оставив на макетной плате только ваш Pico.
Установка гирлянды
ВАЖНО! Первое, что нам нужно сделать, — это найти полосатый провод. Один из проводов имеет пунктирные полоски вдоль него, и это вывод 5V. Нам очень важно правильно подключить этот вывод, иначе мы создадим проблемы!
Вот крупный план этих полосок:
Вставьте этот полосатый вывод 5V в макетную плату с левой стороны, затем установите два других, по порядку (они скреплены вместе), рядом с ним с промежутком между ними (см. изображение ниже).
Теперь у нас много запасных джамперных проводов, так что воспользуемся ими.
Подключите левый (полосатый) вывод к VBUS (физический контакт 40)
Подключите средний вывод к GPIO2 (физический контакт 4)
Подключите правый вывод к GND (физический контакт 3)
Ваша макетная плата должна выглядеть вот так:
Установка слайдер-потенциометра
Слайдер — это слишком весело, чтобы не включить его в работу, и он отлично сочетается с нашей гирляндой, поэтому давайте снова поставим его на макетную плату.
Мы устанавливаем его ровно на то же место, что и в день #7, с теми же используемыми контактами.
Вот напоминание о используемых контактах и их расположении:
Подключите нижнюю одиночную ножку к выводу земли — мы использовали физический контакт 28
Теперь подключите верхнюю левую ножку слайдера к выводу 3.3V — это физический контакт 36
Наконец, подключите верхнюю правую ножку слайдера к GPIO28 (физический контакт 34) — наш ADC-вывод
Ваша макетная плата должна выглядеть вот так:
_**Совет**: Вы можете подключить эту гирлянду прямо в отверстия макетной платы рядом с Pico, так как 5V (VBUS), GND и GPIO-вывод достаточно близко друг к другу, однако это добавляет риск неправильного подключения, поскольку видимость менее чёткая._
Активность #1: Простой тест гирлянды
Вы уже знаете, как управлять RGB-светодиодами, поэтому давайте быстро проверим, что всё подключено правильно и работает как надо.
Как и следовало ожидать, мы импортировали библиотеку neopixel, установили номер вывода гирлянды (GPIO2) и задали количество светодиодов (15).
Затем мы показываем красный цвет при низкой яркости (50,0,0) в течение 10 секунд — этого достаточно, чтобы убедиться, что всё работает.
Скопируйте код в Thonny и попробуйте:
# Импорты
from machine import Pin
from neopixel import NeoPixel
import time
# Определяем номер вывода гирлянды (2) и количество светодиодов (15)
strand = NeoPixel(Pin(2), 15)
# Заполняем красным на 10 секунд
strand.fill((50,0,0))
strand.write()
time.sleep(10)
# Выключаем
strand.fill((0,0,0))
strand.write()
Активность #2: Повторное использование (и улучшение) программ для LED-кольца
Классная вещь в нашем адресном кольце и гирлянде — они работают одинаково и оба RGB. Это означает, что вы можете повторно использовать любые примеры программ (и всё, что вы придумали сами), просто задав ссылку на вывод и количество светодиодов.
Возьмём, к примеру, нашу программу «Прыгающий огонь» из дня #8 — всё, что нам нужно сделать, это установить GPIO2 и 15 светодиодов для адресации нашей гирлянды:
strand = NeoPixel(Pin(2), 15)
Однако нам также потребуется изменить все ссылки на 12 светодиодов в коде, потому что в нашей гирлянде 15. Наш код «Прыгающего огня» мог бы быть лучше, если бы мы использовали переменные для количества светодиодов — тогда изменение одного значения исправляло бы его везде при каждом использовании нового типа RGB-светодиодов.
Переменные для GPIO-вывода, количества светодиодов и цвета
Давайте улучшим наш код, чтобы сделать его более удобным для повторного использования!
Мы создадим переменную ledcount, которую установим равной 15 — количеству светодиодов в нашей гирлянде.
Затем изменим строку настройки и функции range, чтобы они использовали эту переменную вместо фиксированного числа — это значит, что нам нужно будет изменить только одно значение для обновления всей программы, что очень удобно, когда программы становятся всё больше и больше.
Мы сделаем то же самое для номера GPIO-вывода с переменной GPIOnumber, чтобы упростить работу при возврате к старому коду позже.
Наконец, мы создали переменную mycolour — мы можем изменить её на любой RGB-код по своему желанию, и это обновит нашу программу там, где мы её использовали.
С этими изменениями запустите обновлённую программу ниже, и теперь у вас есть прыгающий огонь вдоль гирлянды!
# Импорты
from machine import Pin
from neopixel import NeoPixel
import time
# Параметры светодиодов
GPIOnumber = 2
LEDcount = 15
# Переменная цвета
mycolour = 255,0,0
# Определяем номер вывода и количество светодиодов из переменных
strand = NeoPixel(Pin(GPIOnumber), LEDcount)
# Выключаем все светодиоды перед запуском программы
strand.fill((0,0,0))
strand.write()
time.sleep(1)
while True:
for i in range(LEDcount):
strand[i] = (mycolour)
strand.write()
# Показываем огонь столько времени
time.sleep(0.09)
#Очищаем гирлянду в конце каждого цикла
strand.fill((0,0,0))
strand.write()
for i in reversed (range(LEDcount)):
strand[i] = (mycolour)
strand.write()
# Показываем огонь столько времени
time.sleep(0.09)
#Очищаем гирлянду в конце каждого цикла
strand.fill((0,0,0))
strand.write()
Активность #3: Начало/конец/шаг в range
Давайте попробуем что-то более продвинутое с функцией range, чтобы создать различные узоры на нашей гирлянде.
До сих пор мы использовали функцию range вот так с RGB-цветовыми кодами, что позволяло нам плавно зажигать и гасить светодиод:
for i in range(255):
Однако вы можете добавить несколько аргументов в эти скобки, чтобы задать значение начала диапазона, значение конца диапазона и шаг для прыжка при каждом цикле.
Компоновка следующая:
for i in range(start, stop, step):
Используя снова RGB-значения в качестве примера, представьте, что мы не хотим гасить светодиод до 0 (выключен), а на самом деле останавливаемся на значении 10, чтобы оставить светодиод включённым. Для этого мы могли бы использовать:
for i in range (255, 10, -1):
Это начинает значение светодиода с 255 (максимальная яркость), заканчивает значение на 10 (низкая яркость), а шаг равен -1, чтобы оно шло вниз от 255 до 10.
Код
Мы создали пример программы именно с этим! Наша гирлянда начинается с низкой интенсивности красного (10,0,0), затем переходит в цикл while.
Цикл while содержит цикл for, который проходит по каждому светодиоду в нашем индексе. Затем мы сразу переходим во вложенный цикл for, который устанавливает красную интенсивность каждого из этих зациклённых светодиодов от 255 до исходного 10 с очень коротким задержкой.
Это даёт эффект постоянно тусклой красной гирлянды с ярким красным светом, пульсирующим сквозь неё! Попробуйте пример ниже и читайте комментарии, чтобы понять, что делает каждая часть:
# Импорты
from machine import Pin
from neopixel import NeoPixel
import time
# Параметры светодиодов
GPIOnumber = 2
LEDcount = 15
# Определяем номер вывода и количество светодиодов из переменных
strand = NeoPixel(Pin(GPIOnumber), LEDcount)
# Список индексов светодиодов
ledindex = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
# Запускаем гирлянду красным с низкой интенсивностью
strand.fill((10,0,0))
strand.write()
time.sleep(1)
while True:
# Итерируем по каждому светодиоду
for led in ledindex:
# Плавно зажигаем/гасим каждый итерируемый светодиод
for i in range(255,10,-1):
strand[led] = (i,0,0) # Используем значение i для R в RGB
strand.write()
time.sleep(0.001) # Короткая задержка
…или пульс другого цвета!
Вы также можете добавить пару строк в конец программы, чтобы пульс другого цвета проходил сквозь красную гирлянду.
Сначала измените вложенный цикл for (пульс), чтобы использовать другой цвет. Мы хотели попробовать белый, поэтому установили RGB-цвет на использование i во всех трёх значениях с strand[led] = (i,i,i) — _да, так можно!_
Затем после окончания вложенного цикла for мы можем установить цвет этого итерируемого светодиода обратно на исходный красный с низкой интенсивностью.
Попробуйте и поэкспериментируйте с цветами:
# Импорты
from machine import Pin
from neopixel import NeoPixel
import time
# Параметры светодиодов
GPIOnumber = 2
LEDcount = 15
# Определяем номер вывода и количество светодиодов из переменных
strand = NeoPixel(Pin(GPIOnumber), LEDcount)
# Список индексов светодиодов
ledindex = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
# Запускаем гирлянду красным с низкой интенсивностью
strand.fill((10,0,0))
strand.write()
time.sleep(1)
while True:
# Итерируем по каждому светодиоду
for led in ledindex:
# Плавно зажигаем/гасим каждый итерируемый светодиод
for i in range(255,10,-1):
strand[led] = (i,i,i) # Используем i для всех значений (белый)
strand.write()
time.sleep(0.001) # Короткая задержка
# Возвращаем светодиоду исходный красный цвет
strand[led] = (10,0,0)
strand.write()
Активность #4: Статические чётные и нечётные
Мы можем создать _классический старомодный_ праздничный узор красный/зелёный на нашей гирлянде, приказав коду выбрать для нас нечётные и чётные номера светодиодов в нашем индексе.
Для этого мы используем оператор деления по модулю. _Звучит как плохая рок-группа 90-х, правда?_ Давайте объясним, что это такое…
Оператор деления по модулю
В MicroPython оператор деления по модулю — это символ процента %.
Когда мы используем его в коде, он может дать нам остаток от деления двух чисел.
Например:
Мы знаем, что 12 делённое на 5 = 2.4
Мы хотим знать, сколько раз можно получить 5 из 12, и что остаётся после этого
Мы можем получить 5 два раза из 12, и это оставляет нам 2 в остатке
Мы можем использовать модуль, чтобы найти этот остаток для нас, вот так (мы ввели это в оболочку):
>>> 12 % 5
2
Как оператор деления по модулю помогает с гирляндами?!
Мы можем использовать оператор деления по модулю, чтобы узнать, делится ли число на 2. Все чётные числа делятся на два и оставляют 0 в качестве остатка, поэтому этот тест скажет нам, является ли число чётным.
Мы можем приказать нашему коду посмотреть на индекс светодиода из нашего списка и сказать нам, делится ли он на 2 (_«остаток равен 0»_). Затем мы можем использовать операторы if, чтобы сказать _«делай это, если чётное»_ и _«делай это, если нет»_.
Код
Пример кода ниже включает список для нашего индекса светодиодов вместе с обычными строками настройки.
Нам не нужен цикл while, так как мы просто показываем статические цвета. Вместо этого мы используем цикл for для итерации по каждому светодиоду в нашем индексе.
Внутри этого цикла for находятся оператор if и оператор else.
Оператор if говорит _«для каждого индекса светодиода, по которому мы итерируем, если мы делим его на 2, остаток равен 0?»_
Если да, это означает, что число чётное, и эти чётные светодиоды загорятся красным
Оператор else захватывает всё, что не является чётным (нечётные числа), и зажигает их зелёным
Просто, правда? Попробуйте код ниже и убедитесь сами:
# Импорты
from machine import Pin
from neopixel import NeoPixel
import time
# Параметры светодиодов
GPIOnumber = 2
LEDcount = 15
# Определяем номер вывода и количество светодиодов из переменных
strand = NeoPixel(Pin(GPIOnumber), LEDcount)
# Переменные цветов
red = 255,0,0
green = 0,255,0
# Список индексов светодиодов
ledindex = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
# Выключаем все светодиоды перед запуском программы
strand.fill((0,0,0))
strand.write()
time.sleep(1)
for led in ledindex:
if (led % 2) == 0: #Если индекс светодиода чётный
strand[led] = (red)
else: # Если нет (нечётные числа)
strand[led] = (green)
strand.write()
Активность #5: Мигающие чётные и нечётные
Мы можем сделать пример выше более _ярким_, чередуя нечётные/чётные светодиоды между красным и зелёным. Столь же старомодно, просто немного веселее!
Нам не нужно много менять для этого — просто нужно:
Поместить наши циклы for внутрь цикла while
Добавить второй цикл for, чтобы сделать противоположные цвета к первому
Добавить задержку между каждым циклом
Ничего из этого не является для вас новым — это просто ещё один цикл for после первого, так что ознакомьтесь с примером ниже и комментариями к коду, затем попробуйте:
# Импорты
from machine import Pin
from neopixel import NeoPixel
import time
# Параметры светодиодов
GPIOnumber = 2
LEDcount = 15
# Определяем номер вывода и количество светодиодов из переменных
strand = NeoPixel(Pin(GPIOnumber), LEDcount)
# Переменные цветов
red = 255,0,0
green = 0,255,0
# Список индексов светодиодов
ledindex = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]
# Выключаем все светодиоды перед запуском программы
strand.fill((0,0,0))
strand.write()
time.sleep(1)
while True:
# Первый цикл: чётные красные и нечётные зелёные
for led in ledindex:
if (led % 2) == 0: #Если индекс светодиода чётный
strand[led] = (red)
else: # Если нет (нечётные числа)
strand[led] = (green)
strand.write()
# Задержка для отображения цветов перед их сменой
time.sleep(0.5)
# Второй цикл: чётные зелёные и нечётные красные
for led in ledindex:
if (led % 2) == 0: #Если индекс светодиода чётный
strand[led] = (green)
else: # Если нет (нечётные числа)
strand[led] = (red)
strand.write()
# Задержка для отображения цветов перед их сменой
time.sleep(0.5)
Активность #6: Гирлянда со слайдером!
Давайте вернём слайдер-потенциометр в код и используем его для запуска интересной программы с нашей гирляндой.
С нашим LED-кольцом мы использовали слайдер для затемнения светодиодов и управления скоростью мигания, но мы думаем, что было бы интереснее скользить светодиодами вверх и вниз по гирлянде!
Для этого нам нужно снова заняться математикой, разделив аналоговый диапазон на количество светодиодов. Поехали!
Разбор кода
Нам, конечно, нужно снова импортировать ADC, а также настроить правильный ADC-вывод для слайдера, как мы делали раньше (GPIO28).
Теперь мы хотим, чтобы наш слайдер с диапазоном 0 и 65535 управлял нашими светодиодами, которых у нас 15. Поэтому нам нужно преобразовать аналоговый диапазон в диапазон светодиодов.
В нашей настройке мы используем следующую строку, чтобы разделить аналоговый диапазон (65535) на количество светодиодов (15), давая нам переменную (LEDdivision), которую мы можем использовать для деления нашего показания:
# Делим аналоговый диапазон на количество светодиодов
LEDdivision = (65535/LEDcount)
Наш цикл while затем считывает аналоговое значение со слайдера, которое мы затем делим на нашу переменную LEDdivision, чтобы получить (почти) номер светодиода, который мы можем использовать в коде. Однако нам нужно округлённое число, поэтому мы одновременно применяем функцию round.
Мы решили сделать всё это в одной строке на этот раз, а не считывать значение отдельно, а затем манипулировать им (мы расставили пробелы, чтобы было легче читать — MicroPython не возражает):
reading = round( (potentiometer.read_u16()) / LEDdivision )
Как это работает? Вот рабочий пример:
65535 делённое на 15 = 4369
Так что _примерно_ каждые 4369 аналогового диапазона дадут нам 1, чтобы зажечь один из наших светодиодов
Мы считываем значение со слайдера, которое находится между 0 и 65535
Допустим, значение 15000 — мы делим это число на 4369, которое получили ранее = 3.43
Мы округляем это 3.43, получая 3
Затем мы говорим нашему коду зажечь столько светодиодов
Мы используем два цикла for, чтобы задать, какие светодиоды должны быть включены, а какие — выключены, поскольку нашему коду нужно снова выключать светодиоды при скольжении обратно вниз.
Первый цикл простой — он использует значение из вычисления выше как диапазон, и мы включаем эти светодиоды с цветом:
# Задаём, какие светодиоды должны быть ВКЛЮЧЕНЫ
for ledon in range (reading):
ring[ledon] = (0,0,255)
Второй цикл for использует параметры начало/конец/шаг, которые мы рассмотрели ранее, чтобы помочь ему определить все светодиоды, которые должны быть ВЫКЛЮЧЕНЫ.
Мы используем значение reading как начало, а количество светодиодов как конец, с шагом 1. Это даёт нам все светодиоды, которые не должны быть включены.
# Задаём, какие светодиоды должны быть ВЫКЛЮЧЕНЫ
for ledoff in range ((reading),LEDcount,1):
ring[ledoff] = (0,0,0)
Всё, что осталось сделать, — это записать данные в LED-полосу после того, как оба цикла for установили светодиоды для освещения, с небольшой задержкой для обеспечения плавного движения светодиодов при скольжении.
Полная программа ниже — попробуйте:
# Импорты
from machine import Pin, ADC
from neopixel import NeoPixel
import time
# Параметры светодиодов
GPIOnumber = 2
LEDcount = 15
# Определяем номер вывода и количество светодиодов
ring = NeoPixel(Pin(GPIOnumber), LEDcount)
# Настраиваем слайдер на ADC-вывод 28
potentiometer = ADC(Pin(28))
# Убеждаемся, что полоса очищена перед началом
ring.fill((0,0,0))
ring.write()
time.sleep(1)
# Делим аналоговый диапазон на количество светодиодов
LEDdivision = (65535/LEDcount)
while True:
# Считываем значение
# Делим значение на LEDdivision
# Округляем значение
reading = round( (potentiometer.read_u16()) / LEDdivision )
# Задаём, какие светодиоды должны быть ВКЛЮЧЕНЫ
for ledon in range (reading):
ring[ledon] = (0,0,255)
# Задаём, какие светодиоды должны быть ВЫКЛЮЧЕНЫ
for ledoff in range ((reading),LEDcount,1):
ring[ledoff] = (0,0,0)
# Записываем данные
ring.write()
time.sleep(0.1)
…и использование random делает всё веселее!
Мы можем немного изменить пример выше, чтобы заполнить гирлянду случайными цветами вместо фиксированного синего значения — _потому что функция random — лучшее, что есть в светодиодах!_
Она создаёт отличный эффект, потому что постоянно циклируется с задержкой 0.1 секунды — это означает, что вы можете скользить светодиодами, пока они мерцают множеством разных цветов, когда включены.
Всё, что нам нужно сделать, — импортировать random, позаимствовать код случайного RGB-цвета из Дня #7 (Активность #4), поместить его в наш цикл while, затем установить первый цикл for на использование переменных r, g и b для цвета светодиода.
Вот код:
# Импорты
from machine import Pin, ADC
from neopixel import NeoPixel
import time
import random
# Параметры светодиодов
GPIOnumber = 2
LEDcount = 15
# Определяем номер вывода и количество светодиодов
ring = NeoPixel(Pin(GPIOnumber), LEDcount)
# Настраиваем слайдер на ADC-вывод 28
potentiometer = ADC(Pin(28))
# Убеждаемся, что полоса очищена перед началом
ring.fill((0,0,0))
ring.write()
time.sleep(1)
# Делим аналоговый диапазон на количество светодиодов
LEDdivision = (65535/LEDcount)
while True:
# Считываем значение
# Делим значение на LEDdivision
# Округляем значение
reading = round( (potentiometer.read_u16()) / LEDdivision )
# Создаём переменные случайного RGB-кода
r = random.randint(0,255)
g = random.randint(0,255)
b = random.randint(0,255)
# Задаём, какие светодиоды должны быть ВКЛЮЧЕНЫ
for ledon in range (reading):
ring[ledon] = (r,g,b) # Используем переменные случайного RGB
# Задаём, какие светодиоды должны быть ВЫКЛЮЧЕНЫ
for ledoff in range ((reading),LEDcount,1):
ring[ledoff] = (0,0,0)
# Записываем данные
ring.write()
time.sleep(0.1)
_**Совет**: Если последняя активность оказалась для вас слишком яркой, просто уменьшите максимальный диапазон интенсивности случайных переменных до меньшего числа, например до 50. Например, **r = random.randint(0,50)**_
День #10 завершён!
Есть ли что-нибудь веселее RGB-светодиодов?!
Сегодня мы показали вам больше способов работы с адресными светодиодами, включая несколько новых приёмов и примеров, которые вы можете применить к вашему LED-кольцу и любым другим адресным светодиодам, с которыми вы будете работать в будущем.
Нам особенно нравятся эти гибкие гирлянды — они могут использоваться для создания отличных декораций и световых проектов:
Засуньте их в стеклянную банку для классного светильника в спальне
Приклейте их за монитором или под столом с помощью голубого Blu-tack
Оберните их вокруг фигур, чтобы создать оригинальные украшения
…или просто поместите их на небольшую ёлку!
Краткое содержание — сегодня вы:
Создали схему с адресной RGB-гирляндой
Научились повторно использовать программы и узнали лучшие практики для упрощения этого (с переменными)
Расширили свои знания о функции range с аргументами начало/конец/шаг
Познакомились с оператором деления по модулю
Использовали математику для преобразования диапазонов (аналоговый диапазон в диапазон светодиодов)
Повторно использовали множество навыков из предыдущих дней _(включая функцию random, потому что она потрясающая!)_
Завтра нас ждёт ещё один управляющий компонент, дающий нам ещё больше способов управлять нашими мигалками для создания ещё более классных проектов!
До встречи!