Как стать автором
Обновить

Аналог ambilight из LED ленты WS2812, arduino и киндер-сюрприза

Время на прочтение 9 мин
Количество просмотров 201K
Ambilight — разработанная Philips технология боковой подсветки пространства за телеящиком, которая, по мнению создателей, помогает зрителю еще больше погружаться в происходящее на экране.

В сети можно найти довольно много упоминаний о DIY Ambilight-like проектах, известны также коммерческие реализации подобного функционала в продуктах сторонних производителей / opensource-проектах, например, Lightpack.

Около года назад я практически случайно приобрел LED ленту на базе управляемых RGB диодов WS2812, рассчитывая задействовать её в каком-нибудь Arduino-проекте. Нехватка времени и противоречивая информация о возможности совместной работы с AVR контроллерами (сиречь Arduino) привела к тому, что реализация отодвинулась почти на год. Каково же было мое удивление, когда весь мини-проект по созданию Ambilight и организации его совместной работы с XBMC занял всего два вечера, т.е. 5-6 часов, включая поиск рабочего решения, написание скетча для arduino и конфигурационного скрипта к boblight, отладку их совместной работы, резку, пайку и монтаж ленты, а также прокладку 8м кабеля от arduino к телевизору.
Цель данного топика — поделиться с сообществом опытом и удивлением по поводу того, насколько все было просто, и задать направление желающим повторить это у себя дома. Мне кажется, что при наличии необходимых компонентов, повторение моего опыта «на столе» займет не более получаса.

Пластиковый контейнер от киндер-сюрприза, использованный в качестве корпуса для LED контроллера, красиво светится в темноте:


Что у меня было


  1. Домашний медиасервер/NAS на базе Intel Celeron G1610 с установленным Ubuntu Server 13.10
  2. Готовая arduino-совместимая плата, купленная на ebay
  3. 4 метра вот такой RGB LED ленты (для 32" экрана потребовалось менее трех метров, около 140 диодов из 240)
  4. Около 8 метров симметричного четырехжильного телефонного кабеля (таким, например, подключается ADSL-модем к сплиттеру), т.е. не «витая пара»

Итого затрат чуть более 2000 рублей, из которых 1800 руб. — LED лента с доставкой и 250 рублей за Arduino (провода и коробочка из-под киндер-сюрприза были найдены в закромах).

Как это работает


Светодиодная лента WS2812

Каждый метр ленты состоит из 60 SMD светодиодов типоразмера 5050 WS2812 со встроенным в каждый диод трехканальным ШИМ-контроллером. Отличие WS2812 от WS2811, с которыми их иногда путают, заключается собственно в наличии светодиодов (WS2811 — отдельная микросхема контроллера, WS2812 — светодиоды со встроенным контроллером). Не углубляясь в подробности, опишу в двух словах протокол управления диодами:
На ленте всего три шины: земля, питание и управление. С первыми двумя все очевидно из названия (требуется стабилизированный источник напряжения +5V), а управление работает так:
В течение первых 50 ms происходит инициализация ленты путем заземления управляющей шины. После этого контроллером отправляется пачка из пакетов по 24 бита (8 бит на каждый цветовой канал), содержащих информацию о яркости, каждый пакет предназначается для одного диода. Данные в пачке ничем не разделяются, т.е. каждый следующий пакет идет непосредственно за предыдущим. Получив всю пачку (длина пачки равна 24 бита x количество диодов в ленте), контроллер первого диода «откусывает» от нее свой пакет, использует информацию по назначению, а остальную пачку ретранслирует вперед. Таким образом, до последнего диода доходит пачка из одного 24-битного пакета.
Мне повезло: на глаза попалась библиотека для Arduino FastSPI_LED2, авторам которой удалось не только реализовать этот протокол, но и оставить нетронутым некоторое количество ресурсов контроллера. Общая логика её работы такова: необходимо инициализировать ленту (задается тип контроллера, управляющий пин arduino, количество диодов и их максимальная яркость), задать массив из восьмибитных значений яркости каждого цвета для каждого диода и дать команду на отображение данных, после чего происходит посылка управляющей пачки на ленту, и диоды меняют свой цвет.

Boblight

Opensource-проект boblight, по заверению разработчиков, является набором инструментов для управления светодиодами, подключенных к внешнему контроллеру. Я выбрал его по причине поддержки USB serial интерфейса, незатейливости его реализации, и, главное, наличия нативного клиента для XBMC.
С точки зрения пользователя, т.е. нас с вами, все достаточно прозрачно. Есть демон boblight, который получает информацию о выводимом на экран изображении по TCP/IP (с учетом наличия официального плагина boblight для XBMC, разбираться в работе этого протокола нет необходимости).
При поступлении данных со стороны клиента, демон проводит анализ видео в соответствии со своей конфигурацией и посылает внешнему контроллеру информацию о том, что и как должно гореть, в понятном контроллеру виде. Я выбрал вариант, в котором данные побайтно передаются через USB serial порт. Как и в случае с лентой, посылка данных предваряется инициализационной последовательностью, затем подряд идут данные для всех диодов.
Необходимым условием для запуска boblightd является наличие конфигурационного файла, в котором задается устройство вывода (у меня /dev/ttyACM0), количество каналов (равное утроенному количеству диодов, по каналу на каждый цвет каждого диода), инициализационная последовательность (по умолчанию 0x55AA), шестнадцатеричное значение каждого из основных цветов, а также задается каждый диод в следующем формате:

[light]
name right1 #имя, может быть любым, необходимо для удобства чтения конфигурационного файла
color red arduino 1 #номер канала, этот канал будет первым в очереди на отправку данных на контроллер
color green arduino 2
color blue arduino 3
hscan 80 100 #горизонтальный диапазон сканирования изображения (в процентах), в данном случае для всех диодов по правую сторону экрана значение будет одинаковым, т.к. таким образом задается глубина сканирования.
vscan 97 100#вертикальный диапазон сканирования, для диодов справа и слева будет соответствовать участку изображения, откуда берется информация о цвете для данного диода. Т.е. берем за 100 процентов количество диодов по вертикали и делим экран на равные части. Следующий по порядку диод будет, соответственно 94 97.


Писать такое вручную было лень, поэтому я написал небольшой скрипт, который принимает в качестве аргументов количество диодов по вертикали и горизонтали и глубину сканирования в процентах, и выдает готовый конфигурационный файл для boblightd (внимание, для смены устройства вывода, скорости последовательного интерфейса или частоты обновления, необходимо отредактировать скрипт):

Скрипт для генерации конфигурационного файла для boblightd
#!/bin/bash
# A shell script to make boblight.conf file
# Written by: TPertenava, based on default boblight.conf example
# Last updated on: 22.10.2013

#set vars
# -v vertical LED count
# -h horizontal LED count
# -d deepness of color detection (in percent)

usage() { echo "Usage: $0 [-v <1|100>] [-h <1|100>] [-d <0|100>]" 1>&2; exit 1; }

HOR=
VER=
DEEPNESS=

while getopts "v:h:d:" OPTION; do
        case ${OPTION} in
                h)
                        HOR=${OPTARG}
                        ;;
                v)
                        VER=$OPTARG
                        ;;
                d)
                        DEEPNESS=$OPTARG
                        ;;
        esac
        #echo "s= ${o}"
        #${o} = ${OPTARG}
done

if [ -z "${VER}" ] || [ -z "${HOR}" ] || [ -z "${DEEPNESS}" ]; then
    usage
fi

echo "#Leds in vertical = $VER"
echo "#Leds in horizontal = $HOR"

function output {
# $1 location {right top bottom left}
# $2 hscan low
# $3 hscan high
exit
}

cat <<EOF
[global]
interface       127.0.0.1
port            19333

[device]
name            arduino
output          /dev/ttyACM0
EOF
echo channels        $(((2*$VER+2*$HOR)*3))
cat <<EOF
#number of led's multiplied by 3
type            momo
interval        50000
# update interval in mks, 20000 for 50hertz
rate            38400
prefix          55 AA
# only for momo devices, divides RGB send, ie LEDs

#arduino bootloader runs when opening the serial port for the first time
#delay transmission one second after opening so we don't send shit to the bootloader
delayafteropen  1000000
#debug          on

[color]
name            red
rgb             0000FF

[color]
name            green
rgb             00FF00

[color]
name            blue
rgb             FF0000

EOF

for (( i=1; i<=2*$HOR+2*$VER; i++)) do
        if ((1<=$i && $i<=$VER))
        then
                LOCATION="right"
                VSCAN_HIGH=$((100-100*($i-1)/$VER))
                VSCAN_LOW=$((100-100*$i/$VER))
                HSCAN_LOW=$((100-$DEEPNESS))
                HSCAN_HIGH=100

        elif (($VER+1<=$i && $i<=$VER+$HOR))
        then
                LOCATION="top"
                HSCAN_HIGH=$(echo "100-(($i-($VER+1))*100/$HOR)" | bc)
                HSCAN_LOW=$(echo "100-(($i-$VER))*100/$HOR" |bc)
                VSCAN_LOW=0
                VSCAN_HIGH=$DEEPNESS

        elif (($VER+$HOR+1<=$i && $i<=2*$VER+$HOR))
        then
                LOCATION="left"
                VSCAN_LOW=$((($i-($VER+$HOR+1))*100/$VER))
                VSCAN_HIGH=$((($i-($VER+$HOR))*100/$VER))
                HSCAN_LOW=0
                HSCAN_HIGH=$DEEPNESS

        elif ((2*$VER+$HOR+1<=$i && $i<=2*$VER+2*2*$HOR))
        then
                LOCATION="bottom"
                HSCAN_LOW=$((($i-(2*$VER+$HOR+1))*100/$HOR))
                HSCAN_HIGH=$((($i-(2*$VER+$HOR))*100/$HOR))
                VSCAN_LOW=$((100-$DEEPNESS))
                VSCAN_HIGH=100

        fi

        echo [light]
        echo name       $LOCATION$i
        echo color      red     arduino $((3*$i-2))
        echo color      green   arduino $((3*$i-1))
        echo color      blue    arduino $((3*$i))
        echo hscan      $HSCAN_LOW $HSCAN_HIGH
        echo vscan      $VSCAN_LOW $VSCAN_HIGH
        echo

done



Синтаксис использования скрипта создания конфигурации (у меня 45 диодов по горизонтали и 28 по вертикали, глубина сканирования — 20 процентов, лента начинается из правого нижнего края экрана и наклеена против часовой стрелки):
# ./boblight_config_generator -h 45 -v 28 -d 20 > /etc/boblight.conf
# boblightd -f

Таким образом мы настроили boblightd и запустили его на исполнение в фоновом режиме (с ключом -f).

Подробное описание конфигурационного файла можно найти в официальном репозитории проекта boblight.

Официальный плагин для boblight ставится прямо из меню “дополнения” XBMC:


В набор инструментов boblight входит также программа boblight-constant, с помощью которой можно вывести на контроллер произвольное значение цвета для всех диодов сразу.
Так, при выполнении команды
$ boblight-constant FFFFFF

, лента загорится белым светом с максимально разрешенной контроллером яркостью. Я использовал эту программу для отладки взаимодействия boblightd и скетча Arduino, к которому мы сейчас перейдем.

Arduino


Как ни странно, этот этап тоже дался поразительно легко, благодаря вышеупомянутой библиотеке FastSPI_LED2. Скетч для Arduino прост до безобразия, он ждет начальной последовательности (0x55AA) с последовательного порта, дождавшись, инициализирует массив данных для LED, а затем запускает встроенную в библиотеку функцию вывода массива на ленту. Стоит отметить отдельно, что, строго говоря, WS2812 является не RGB, а BRG диодом, т.е. первой по порядку идет информация синего, затем красного и зеленого каналов.

Скетч для Arduino
// boblightd receiver for WS_2811, boblightd device type=momo, prefix 55 AA
// capable at least for 20Hz input stream if LED count <= 150
// by TPertenava
// last modified 22.10.13

#include "FastSPI_LED2.h"

#define NUM_LEDS 138 // LED count
#define CHANNELS NUM_LEDS*3 // each output for R, G and B
#define LED_PIN 16 // arduino output pin
#define BRIGHTNESS 96 // maximum brightness
#define SPEED 38400 // virtual serial port speed, must be the same in boblight_config 

CRGB leds[NUM_LEDS];

void setup()
{
  delay(2000);
  Serial.begin(SPEED);
  LEDS.setBrightness(BRIGHTNESS);
  LEDS.addLeds<WS2811, LED_PIN, BRG>(leds, NUM_LEDS);
}

byte values[NUM_LEDS][3]; // 2-level array, 1 level is for led number, 2 level is for rgb values

void WaitForPrefix()
{
  uint8_t first = 0, second = 0;
  while (second != 0x55 || first != 0xAA)
  {
    while (!Serial.available());
    second = first;
    first = Serial.read();
  }
}

void loop() { 

  WaitForPrefix();
  
  for (byte Led = 0; Led<NUM_LEDS; Led++) {
    for (byte Color = 0; Color<3; Color++) {
        while(!Serial.available());
        values[Led][Color] = Serial.read();
    }
  }  
  
  memset(leds, 0,  NUM_LEDS * sizeof(struct CRGB)); //filling Led array by zeroes

  for (byte Led = 0; Led < NUM_LEDS; Led++)
  {
    
    byte red = values[Led][0];
    byte green = values[Led][1];
    byte blue = values[Led][2];
    leds[Led] = CRGB(blue, red, green);
   } 
    
  LEDS.show();
}



Аппаратно-железячная часть


Питание

Самый главный вопрос — электропитание. Arduino в моем случае получает питание по USB, а сама лента — от отдельного блока питания. Для уравнивания потенциалов земли Arduino и источника питания соединены. Правда, через восьмиметровый телефонный провод сопротивлением в пару Ом. Номинальная мощность ленты, указанная на упаковке, 18Вт/м, или 0,3Вт/диод. Таким образом, номинальная мощность отрезка ленты из 140 диодов — 42Вт. В качестве источника питания ленты я использовал компактный импульсный БП от USB-хаба, 5В 3A, т.е. максимальная выдаваемая мощность БП равна 15Вт. В соответствии с этим расчетом была уменьшена максимальная яркость диодов (см. выше скетч Arduino). (Да, я знаю, что в datasheet на диоды указано напряжение питания 6..7В, по этому поводу ничего не могу сказать, работает хорошо на +5В, потребление линейки не измерял).
Лента

При ближайшем рассмотрении лента выглядит так:

Обратите внимание на направление стрелок, у каждого диода есть вход и выход!
Лента подключалась к контроллеру довольно длинным кабелем (~8м), здесь была доля риска, однако мои опасения не подтвердились — судя по всему, коэффициент битовых ошибок при передаче очень низок — иначе в отсутствии сигнала некоторые диоды время от времени «моргали» бы вместо того, чтобы быть выключенными. Впрочем, возможно, в других условиях, при наличии электромагнитных помех на частотах, близкой к несущей сигнала управления (ок. 800 кГц и гармоники), результат был бы иным.
Подозреваю, что неплохо бы было защитить выход Arduino ограничительным резистором для предотвращения последствий случайного замыкания информационного входа на плюс питания или от внешних наводок (например, от грозы) на длинный провод. Я этого делать не стал, т.к. мысль пришла позже, а паять внутри киндер-сюрприза было бы очень неудобно.
Монтаж

Монтаж благодаря клейкому основанию ленты также не составил труда:


Для уменьшения просадки по питанию, стыки ленты в поворотах я осуществлял довольно толстым проводом AWG26:


«Ну а сейчас будут слайды», или видео работы моего Ambilight





В заключение остается добавить, что я с радостью отвечу на все возникшие вопросы.
Спасибо за внимание!
Теги:
Хабы:
+41
Комментарии 49
Комментарии Комментарии 49

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн