Raspbery Pi Pico для управления адресуемыми светодиодами

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


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

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



Много лет назад Nokia мечтала получить светодиоды с интеллектуальным управлением. Со светодиодами тогда не срослось, но кое-что она получила — это была микросхема LP5521, которая потребляя очень мало энергии, моргала светодиодиком по программе внутри, не требуя никакого внимания со стороны основного процессора телефона.
В итоге почти все производители телефонов ставили такую микросхему и она побила все рекорды продаж. Года за 2-3 было продано 100 миллионов микросхем.
National Semiconductors разработчикам с таких барышей даже по кофточке выдал :)



Кофточка, кстати, оказалась очень хорошей — теплая и уютная. Супруга ее оценила и тут же отжала, несмотря на то, что ей она сильно великовата.

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



Очевидный недостаток — если один светодиод умирает — то все, что следуют за ним, перестают светится тоже. Вы наверняка такую ситуацию видели на рекламных дисплеях. Картинка ниже не надерганная из интернета, это один из наших дисплеев сдох.



В WS2813 эта проблема до какой-то степени решена с использованием двух линий данных — одна от выхода предыдущего светодиода, а вторая — от его входа. Если от выхода ничего нет — считаем, что предыдущий светодиод сдох и используем сигнал от его входа с задержкой на 1 пиксель. В итоге на экране будет одна неработающая точка — терпимо. Но если подряд умрет 2 светодиода — то погаснет вся линия.



Ставить конденсатор и резистор около каждого светодиода — головная боль. Но у производителя есть решение — светодиод со встроенным резистором и конденсатором. Но тут другая головная боль — как узнать при заказе, есть внутри конденсатор или нет? На названии светодиода наличие конденсатора никак не сказывается. Светодиоды размером 2020 с высокой вероятностью будут иметь конденсатор внутри.



Для программирования каждого пикселя нужно 24 бита — 8 бит на цвет. Управление яркостью производится с помощью ШИМ с частотой 2 кГц.



Светодиоды обычно бывают с двух типов по току — около 15-18 мА на светодиод или 5-7 мА. Для дисплеев 5мА подходят лучше, 15мА — слишком ярко. Приходится убирать лишнюю яркость за счет ШИМ. В итоге от 8 битов на цвет остается 6-7. Плюс человеческое зрение логарифмическое, а регулировка линейная. Для того, чтобы не искажать цвета при снижении яркости, необходимо использовать гамма-коррекцию.



От цветовой разрешающей способности вообще ничего не остается.



Недавно эта проблема была решена дико избыточно. Появились новые светодиоды — WS2816. У них уже 16 бит на цвет, частота ШИМ 10 кГц и впридачу ко всему встроенная гамма-коррекция. Но получается, что загружать их можно в 2 раза медленнее — при прежней скорости обмена передать уже нужно 48 бит вместе прежних 24.
Если было бы 10 бит на цвет со встроенной коррекцией — меня лично это устроило бы гораздо больше.

Еще одна проблема со светодиодами. Не знаю, обращали вы внимание или нет, но очень часто в холодную погоду изображение на рекламных дисплеях слегка краснеет. Дело в том, что световая отдача светодиодов зависит от температуры. И зависимость разных светодиодов разная. Зеленые и синие имеют более-менее одинаковую зависимость, а красные сильно другие. Если кому интересно, можете здесь прочитать, десятая страница.



Если вдруг соберетесь дисплей такого типа делать — выбирайте адекватного производителя панелей. Вот что пишут в спецификации:



Многие на это внимания не обращают, но это очень серьезно. Из-за влажности могут быть дефекты пайки. А когда последовательно стоит тысяча светодиодов и отказ любого из них приведет к отказу всей панели, причем, не сразу, а через какое-то время — панель придется демонтировать и ремонтировать. Это грустный опыт.

Теперь начнем считать. Скажем, мы хотим отображать 30 кадров в секунду. Значит, у нас есть время для загрузки линии 33.3 мсек. Частота сигнала для светодиодов — 800кГц. Для одной точки нужно 24 импульса — по 8 на каждый цвет. На загрузку пикселя уходит 30 микросекунд. Значит, за время отображения кадра можно загрузить 1111 пиксель. С учетом возможных временнЫх допусков, реально я использовал в линии до 1300 светодиодов.

Если используем готовые панели 16х16 точек — можно подключить к одной линии до 5 панелей — 1280 светодиодов. Можно и 4-мя ограничиться — будет 1024 точки.

Теперь поговорим про управление. Я с товарищем несколько лет назад делал систему управления такими дисплеями. Самый большой был вморожен в лед, размер — 9 на 9 метров, но разрешающая способность довольно низкая — 288х288 пикселей. Были дисплеи меньше размерами, но с бОльшим разрешением. Каждый микроконтроллер управлял или 8 или 16 линиями светодиодов, одновременно работало несколько контроллеров. Самый тяжелый случай был — 14 контроллеров по 8 линий. Но это была разработка на заказ, поэтому дополнительной информации по ней не будет. Кое-что без деталей реализации можно почитать здесь:
Большой дисплей. Замороженный проект.



Мне захотелось попробовать сделать управление дисплеем на 5-баксовом Raspberry Pi Pico (двухядерный Cortex-M0+ RP2040), из-за наличия возможности программирования машины состояния управления вводом-выводом на собственном ассемблере. Мне показалось, что реализовать управление таким дисплеем будет не просто, а очень просто.

Будем делать систему управления для 16 линий — этого хватит для 20480 светодиодов. Если мало — добавляем микроконтроллеры, благо копейки стоят. Для небольшого рекламного дисплея должно хватить.
Не забудьте про блок питания — если у вас светодиоды на 15мА, то понадобится чуть больше 900 ампер — обычной USB зарядки может не хватить :), все-таки почти 5 киловатт. А если взять 5 миллиамперные светодиоды — то каких-то 1.5 киловатт. Если белым светом не злоупотреблять, то мощность можно существенно уменьшить.

Код ниже — это самое важное в статье, остальное все — от лукавого и можно не читать. А этот код — это все, что действительно надо, остальное любой разработчик легко сам доделает.

Итак, код для PIO:
.define public T1 2
.define public T2 5
.define public T3 3

.wrap_target
out x, 32
mov pins, !null [T1-1]
mov pins, x [T2-1]
mov pins, null [T3-2]
.wrap


Все!

Забираем из входного регистра данные — до 32 бит, реально надо только 16. Выбрасываем в 16-разрядный порт все единицы и задержка. Выбрасываем на выход содержимое введеное из входного регистра — и задержка. И, наконец, выбрасываем нули и задержка.
Все это будет работать абсолютно независимо от процессора и сформирует сигналы управления светодиодом для всех (у нас будет их 16) линий.
Вместе с загрузкой программы PIO и настройкой выводов это будет выглядеть так (файл ws2812.pio)

.program ws2812_parallel

.define public T1 2
.define public T2 5
.define public T3 3

.wrap_target
out x, 32
mov pins, !null [T1-1]
mov pins, x [T2-1]
mov pins, null [T3-2]
.wrap

% c-sdk {
#include "hardware/clocks.h"

static inline void ws2812_parallel_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) {
for(uint i=pin_base; i<pin_base+pin_count; i++) pio_gpio_init(pio, i);
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true);

pio_sm_config c = ws2812_parallel_program_get_default_config(offset);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_out_pins(&c, pin_base, pin_count);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);

int cycles_per_bit = ws2812_parallel_T1 + ws2812_parallel_T2 + ws2812_parallel_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);

pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}


Идем сюда и воспользуемся онлайн компилятором.

Скомпилированный текст сохраняем в файле ws2812.pio.h

// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //

#pragma once

#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif

// --------------- //
// ws2812_parallel //
// --------------- //

#define ws2812_parallel_wrap_target 0
#define ws2812_parallel_wrap 3

#define ws2812_parallel_T1 2
#define ws2812_parallel_T2 5
#define ws2812_parallel_T3 3

static const uint16_t ws2812_parallel_program_instructions[] = {
            //     .wrap_target
    0x6020, //  0: out    x, 32                      
    0xa10b, //  1: mov    pins, !null            [1] 
    0xa401, //  2: mov    pins, x                [4] 
    0xa103, //  3: mov    pins, null             [1] 
            //     .wrap
};

#if !PICO_NO_HARDWARE
static const struct pio_program ws2812_parallel_program = {
    .instructions = ws2812_parallel_program_instructions,
    .length = 4,
    .origin = -1,
};

static inline pio_sm_config ws2812_parallel_program_get_default_config(uint offset) {
    pio_sm_config c = pio_get_default_sm_config();
    sm_config_set_wrap(&c, offset + ws2812_parallel_wrap_target, offset + ws2812_parallel_wrap);
    return c;
}

#include "hardware/clocks.h"
static inline void ws2812_parallel_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) {
    for(uint i=pin_base; i<pin_base+pin_count; i++) pio_gpio_init(pio, i);
    pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true);
    pio_sm_config c = ws2812_parallel_program_get_default_config(offset);
    sm_config_set_out_shift(&c, true, true, 32);
    sm_config_set_out_pins(&c, pin_base, pin_count);
    //sm_config_set_set_pins(&c, pin_base, pin_count);
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
    int cycles_per_bit = ws2812_parallel_T1 + ws2812_parallel_T2 + ws2812_parallel_T3;
    float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
    sm_config_set_clkdiv(&c, div);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

#endif


Теперь в основной программе инициализируем DMA — все будет работать автоматом, безо всякого участия процессора. Я использовал Platformio для компиляции.

#include <Arduino.h>
#include "ws2812.pio.h"
#include "hardware/dma.h"
#include "hardware/pio.h"

#define LEDS_IN_STRING 1280

#define DMA_CHANNEL 0

uint16_t DataArray[LEDS_IN_STRING*24];
// 30720
void dma_init(PIO pio, uint sm) 
{
  // (X/Y)*sys_clk, where X is the first 16 bytes and Y is the second
  // sys_clk is 125 MHz unless changed in code
  // we need 800 kHz - divider 156.25 x=4   y=625( 0x271)
  dma_hw->timer[0] = 0x00040271; 
  dma_channel_config c = dma_channel_get_default_config(DMA_CHANNEL);
  channel_config_set_dreq(&c, 0x3b);  // 0x3b -> Select Timer 0 as TREQ
  channel_config_set_transfer_data_size(&c, DMA_SIZE_16);
  channel_config_set_read_increment(&c, true);
  channel_config_set_write_increment(&c, false);
  dma_channel_configure(
                        DMA_CHANNEL,    // Channel to be configured
                        &c,  // The configuration we just created
                        &pio->txf[sm],  // The initial write address
                        NULL,           // The initial read address - set later
                        LEDS_IN_STRING*24,    // Number of transfers;
                        false           // Start immediately?
                        );
}

void setup() 
{
  PIO pio = pio0;
  int sm = 0;
  uint offset = pio_add_program(pio, &ws2812_parallel_program);
  ws2812_parallel_program_init(pio, sm, offset, 0, 16, 800000);
  dma_init(pio, sm);
  // здесь заполните буфер чем-нибудь сами
}

void loop() 
{
  dma_hw->ch[DMA_CHANNEL].al3_read_addr_trig = (uintptr_t) DataArray;
  delay(50);
}



В реальном времени использовать такой дисплей вряд ли получится. Никакого входа для видео тут нет.
Отображаемые данные нужно подготовить заранее, вытащив данные из изображения, учесть место расположения каждой точки — здесь у нас не как в телевизоре — строка за строкой.



Если есть видео — его надо разложить по кадрам. При необходимости добавить переходные эффекты по вкусу. У меня этим занимается написанная на Python программа — но это уже выходит за рамки статьи, иначе она никогда не кончится.



Раньше у меня была проблема — в гараже стояло несколько больших светодиодных панелей, место занимали. Не так давно все-таки удалось их отдать хозяевам. Естественно, спустя небольшое время такая панель понадобилось. Пришлось заказать пару дешевых панелей на Али. Размер 16х16, итого 256 светодиодов. За 10 евро неплохо — всего 4 цента за точку и паять ничего не надо. Я как-то покупал готовые ленты — там цена вообще получалась 3 цента за светодиод.
Сколько отдельно стоят светодиоды в небольших партиях (в районе 100 тысяч штук) — я точно не знаю, заказывал не я. Думаю, что в районе 2 центов.



К платке Raspbery Pi Pico понадобилось подключить только SD карточку. По хорошему, на выход нужно ставить преобразователь уровня в худшем случае или драйвер дифференциальной линии в лучшем. Но для теста программного обеспечения и так сойдет.



Новая система управления в окружении предшественников:



Совершенно случайно нашелся рояль в кустах старый переделаный источник питания, он может 60 Ампер по 5 Вольтам выдать. А двум панелям в динамике 10 ампер хватает. Системы выглядит даже как-то убого:



Две панели — 512 точек. Панель реально маленькая для такой системы управления — на каждую линию можно подключить до 5 таких панелек, имеем 16 линий — итого 80 панелей. А у меня всего 2 :( — только попробовать хватит. Вот что получилось:



Как я говорил, яркость избыточна. Видео снималась в ярко освещенной комнате, изображение просто по глазам бьет яркостью. А камера воспринимает, как съемку в темноте.

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



Видео с одним из старых дисплеев:



В моей старой системе данные, кадр за кадром, гонятся по проводному Ethernet.
Сейчас я попробовал скопировать блок данных на SD. Вылезла обычная ардуиновская проблема — все работает абы как. Быстро чтение данных не работает, я не могу считывать 30 кадров в секунду. Все можно переделать — когда я делал оригинальную систему, то тоже использовал Ethernet библиотеки, заимствованные из Ардуино. Ethernet работал, но очень медленно. Пришлось весь код перерывать — ошибки нашлись в очень мелких деталях. Сейчас копать библиотеки просто не хочется, да и нужды нет. Я хотел проверить только, как вывод на светодиоды работает, функционирующее изделие никому не нужно. «Будет хлеб — будут и песни» — как говорил дорогой Леонид Ильич.

Ну господь с ней, с Ардуиной. Есть же компания, которая продвигает этот проект. Должна же быть у Raspberry поддержка SD карт в их SDK. Как бы не так, как они пишут:

The pico_sd_card code is SDIO only at the moment. It is not really yet in shape for prime-time unless you are feeling brave.


Файловой системой там даже не пахнет — сам прикручивай.
Нет, я не трус, но я боюсь. Бежать впереди паровоза — не самое благодарное занятие. Мне не к спеху, подожду, пока они допилят свои библиотеки. Или для Ардуино кто-то допилит, на что надежда очень маленькая. Какая-то библиотека есть, а для большинства применений достаточно и низкой скорости доступа.
Источник: https://habr.com/ru/post/694598/


Интересные статьи

Интересные статьи

Менеджеры обладают всеми возможностями, чтобы заставить команду страдатьКак менеджеры, мы находимся в наилучшем положении, чтобы погрузить в уныние наши команды. Если вы менеджер, стремящийся действит...
Если вы подыскиваете программнное обеспечение для управления проектами, то Asana или Jira будут отличным выбором. В этой статье обсудим плюсы и минусы каждого из них, чтобы вы смогли выбрать самое под...
Одной из распространенных привычек среди мультиинструменталистов, когда дело доходит до игры на стандартной фортепианной клавиатуре, является попытка применить выразитель...
Много всякого сыпется в мой ящик, в том числе и от Битрикса (справедливости ради стоит отметить, что я когда-то регистрировался на их сайте). Но вот мне надоели эти письма и я решил отписатьс...
Довольно давно у меня появилась мысль установить управляющую панель, расположенную рядом с входной дверью внутри квартиры/дома. Существует несколько вариантов реализации этой задумки: сенсор...