
- Цена: $5
И что меня удивило — в качестве устройства отображения использовались древние одноцветные светодиодные матрицы 8х8 с шагом 5 миллиметров. Причём под них разводились сложные печатные платы, делалась софтовая динамическая индикация. И это в то время, когда уже давно доступны по цене в районе 10-20$ готовые полноцветные панели 64х32 с шагом 3 мм. А общий ассортимент подобных панелей очень большой и имеет шаг пикселя от 2 до 10 мм и практически любой размер.
В то же время использовать такие панели в DIY конструкциях достаточно непросто — готовые контроллеры стоят довольно больших денег и не имеют нормального API. Сделать же достаточно быстрое сканирование панели на обычно используемых в DIY микроконтроллерах достаточно сложно. Причём временные интервалы должны выдерживаться с высокой точностью — иначе начинается заметная неравномерность яркости.
Есть неплохие решения на adafruit, но они все достаточно дорогие и сложные.
После некоторого раздумья возникла мысль — а почему бы не сделать предельно недорогую плату, которая будет мостом между обычной копеечной платой типа ардуино и LED панелью? После какой-то пары месяцев возни родилось что-то работающее :)
Задача и проблемы
Хотелось бы иметь возможность управлять сборной панелью общим размером хотя бы 64х64, имея при этом возможность работы хотя бы в Highcolor (RGB565) с сохранением приемлемой частоты обновления экрана (не менее 50Гц).
Основная сложность заключается в том, что на LED панелях градации цвета формируются только через PWM, причём последовательный. Таким образом для того, чтобы сформировать 64 уровня яркости — за время индикации одного кадра надо 64 раза выдать картинку. То есть 64х50=3200 Гц частота выдачи полной «однобитной» картинки. Следовательно — для выдачи экрана 64х64 надо выдавать данные пикселей со скоростью 3200х64х64=13 Мпикселей/секунду.
Реализация
Все файлы на GITHUB
Совершенно очевидным было то, что без CPLD или FPGA здесь не обойтись — выдавать 26 МБ/сек на недорогих микроконтроллерах физически невозможно.
В качестве памяти мне на форуме IXBT порекомендовали очень интересную FIFO память Averlogic AL422B, которая имеет примерно 400кбайт памяти и может работать на частотах до 50МГц.
Учитывая, что моим основным требованием была максимальная дешевизна компонентов, чтобы готовая платка была доступна самодельщикам — была выбрана Altera EPM3064 — CPLD c 64мя макроячейками. В то же время столь малое количество макроячеек не позволяет сделать динамически конфигурируемую плату — конфигурацию необходимо компилировать непосредственно в CPLD.
Получившаяся схема лежит здесь
Детали:
CPLD EPM3064ATC44-10 — цена на Ali примерно 13-15$ за десяток
FIFO RAM AL422B — цена на Ali примерно 15$ за десяток
Кварцевый генератор на 50МГц. На плате предусмотрена установка в корпусах DIP14/DIP8/7050. Цена на Ali примерно 6-7$ за десяток
Стабилизатор на 3.3В в корпусе SOT223 — chipdip — 40р за штуку
Разъем IDC-10MS — chipdip — 3 р/штуку
Разъем IDC-16MS — chipdip — 8 р/штуку
Разъём IDC-14MS — chipdip — 7 р/штуку
Конденсаторы 1мкФ 0805 — 8 штук примерно по 1 р/штуку
Конденсатор 0,1мкФ 0805 — примерно по 1 р/штуку
Резистор 10к 0805 — копейки :)
Итого по деталям получается 1,5+1,5+0,7=3,7$ и 40+3+8+7+8*1+1=67 р. Всё вместе в пределах 5$ — копейки.
Исходный рисунок платы лежит здесь
Подготовленные gerber файлы для заказа
При пайке платы не забыть запаять перемычки на питание на обратной стороне платы.
Расчёт частоты обновления и ограничения по размерам
Прежде всего необходимо понимать ограничения по размерам сборной панели. Общий размер ограничен частотой кадров (мерцания панели).
Для RGB888 частота регенерации = 50МГц/3/256/кол-во пикселей, то есть, например, для панели 32х32 — 64Гц.
Для RGB565 частота регенерации = 50МГц/2/64/кол-во пикселей, то есть для панели 32х32 — 381Гц.
Глаз более-менее терпит частоту от 50Гц, поэтому для RGB888 максимальный размер сборки примерно 32х32 или 64х16. Для RGB565 — 128х64 или 256х32 (частота регенерации 48Гц).
Коммутация LED панелей
Все панели соединяются последовательно. Схема соединения: сперва справа-налево, потом снизу-вверх. То есть сперва соединяем горизонтали последовательно, потом выход нижнего ряда ко входу второго снизу ряда и т.д. до верхнего ряда.
Контроллер цепляется к правой нижней панели.
Бывают панели, которые имеют по два входных и два выходных разъёма. Такие панели по сути являются просто механической сборкой двух панелей по вертикали. Коммутируются как две независимые панели.
После сборки надо посчитать общую длину цепочки в пикселях — для этого смотрим — сколько всего панелей получилось в цепочке и умножаем это число на ширину панели в пикселях. Это число потом надо будет вбить в значение PIXEL_COUNT при конфигурации FPGA.
Прошивка FPGA
Все необходимые файлы лежат на github. Скачивать нужно прямо папкой.
С сайта Altera после регистрации необходимо скачать и установить Quartus II 13.0sp1. Качать надо ИМЕННО ЭТУ версию — более новые версии уже не поддерживают серию MAX3000. Ломать её не надо — достаточно Web edition (free) версии. При скачивании не забудьте поставить галочки на поддержке MAX3000 и Programmer.
Еще понадобится Altera USB Blaster — обычная цена на ali порядка 3$
Открываем проект al422b_2rgb_8s.qpf
Слева открываем закладку файл и открываем файл led_al422_main.v — это основной файл проекта. В нём надо настроить параметры:
Используемое цветовое пространство. TRUECOLOR — 3 байта на пиксель, формат RGB888. HIGHCOLOR — 2 байта на пиксель, формат RGB565.
Должна быть раскомментирована только одна нужная строка:
//`define TRUECOLOR 1 `define HIGHCOLOR 1
Сколько RGB входов на панели — на панелях со входом HUB75 может быть 1 или 2 входа RGB. Выяснить — сколько именно входов можно таким способом — берём количество пикселей на панели по вертикали. Делим её на число линий сканирования (указано в обозначении панели как 8S, например). Делим на количество входных разъёмов (1 или 2). Например — у меня панель 32х32, сканирование 8S и два входных разъёма — 32/8/2=2 — значит два входа RGB.
Должна быть раскомментирована только одна нужная строка:
//`define RGB_out1 1 `define RGB_out2 1
Сколько линий сканирования на панели — так как поддерживается стандарт HUB75E, то может быть до 32х. Количество линий сканирования обычно есть в названии панели в виде 8S/16S/32S соответственно.
Должна быть раскомментирована только одна нужная строка:
`define SCAN_x8 1 //`define SCAN_x16 1 //`define SCAN_x32 1
Общее число пикселей по горизонтали в цепочке. Считаются пиксели во всей цепочке панелей — см. раздел выше «Коммутация LED панелей»
`define PIXEL_COUNT 64
Фазы выходных сигналов. Наиболее типичная конфигурация такая — OE активен по низкому уровню (коммент снят), CLK работает по фронту (коммент стоит), LAT активен по высокому уровню (коммент стоит). Возможно всякие странные варианты. Выяснять какой именно у вас только экспериментальным способом или снятием схемы и поиском даташитов на используемые микросхемы).
`define LED_OE_ACTIVE_LOW 1 //`define LED_CLK_ON_FALL 1 //`define LED_LAT_ACTIVE_LOW 1
Пред и пост задержки сигнала OE относительно LAT. Предназначены для подавления расплывания пикселей по вертикали в момент коммутации рядов (коммутаторы обычно сильно тормозные). Обычные значения 2/2 — меньше лучше не делать. Увеличение значений приводят к уменьшению общей яркости, поэтому можно использовать для снижения максимальной яркости. Ограничение — в сумме должны быть меньше, чем PIXEL_COUNT.
`define OE_PREDELAY 2 `define OE_POSTDELAY 2
Всё, нажимаем ctrl-L — проект компилируется. Если нигде не напортачили — будет несколько warnings, но не должно быть никаких ошибок. Дальше цепляем спаянную плату к USB Blaster, подаём питание на плату. В Quartus идём в tools — programmer. Выбираем в Hardware setup USB-blaster, нажимаем Start. Всё, CPLD запрограммирована.
Микроконтроллерная часть
Выдача данных на контроллер, в общем-то, предельно проста — сбрасываем адрес записи и потом последовательно выдаём байты данных, стробируя их сигналом WCLK. И вроде бы даже банальной ардуинки хватит для работы. Но есть две проблемы:
а) Надо много памяти. Даже небольшая панель 32х32 в режиме RGB565 требует 2кБайта памяти под экранный буфер. Обычные ардуино на базе Atmega328 содержат всего 2кбайта оперативки. Можно, конечно, использовать плату Mega на базе Atmega2560, которая содержит аж 8 кБайт оперативки, но даже этого мало для панелей нормального размера — панель 128х64 в режиме RGB565 требует 16кБайт памяти.
б) В процессе работы с AL422B вылез не документированный нигде глюк — при записи данных со скоростью меньше 2МБ/сек счётчик адресов работает некорректно и пишет данные «не туда». Возможно это глюк имеющейся у меня партии. Возможно нет. Но этот глюк приходится обходить. Учитывая, что AVR8 работает на 16МГц, то получить с неё данные на нужных скоростях почти нереально.
Предлагаемое решение — уйти на дешёвые платки на базе 32-х битного контроллера STM32F103C8T6. Такая платка стоит на Ali примерно 2.5$ поштучно или около 1.7$ при покупке десятком, то есть дешевле даже Arduino Nano. При этом мы получаем полноценный 32-х битный микроконтроллер, работающий на 72 МГЦ и имеющий 20 кБ оперативной памяти и 64 кБ флеша (сравните с 2кБ/8кБ Atmega328, которая стоит на Nano).
При этом такие платы вполне успешно программируются в среде Arduino. Про это есть неплохая статья на гиктаймс, поэтому я не буду её дублировать. В общем — делайте всё, как описано в статье.
В среде ардуино выбирайте плату Generic STM32F103C, variant STM32F103C8. Рекомендую выбирать режим оптимизации Fastest (-O3). Впрочем, сейчас данные всё равно идут через DMA, поэтому можно использовать любой вариант.
Коммутация происходит по следующей схеме:
Жёстко прибиты в библиотеке:
A0..A7 -> DI0..DI7 AL422B
B0 -> WCLK AL422B
B1 -> WRST AL422B
Назначается в скетче на контроллер:
B10 -> WE AL422B
Общий провод:
G -> GND
Ну и не забудьте подать питание 5В/GND от панели на соответствующие пины контроллера.
Распиновку разъема на контроллере брать со схемы
Программная часть
Так как ставилась задача сделать всё максимально просто и доступно, то весь софт сделан под среду Arduino и оформлен в виде библиотеки LED_PANEL
Библиотека LED-PANEL активно использует библиотеку Adafruit GFX, поэтому она должна быть инсталлирована.
Я настоятельно рекомендую не ставить библиотеку LED_PANEL в каталог libraries, а оставить её в папке со скетчем. Дело в том, что там много железно привязанных параметров и если вы захотите перенести работу на более «жирный» микроконтроллер, то придётся много чего менять в самом коде.
Инициализация идёт примерно в таком виде:
#include "LED_PANEL.h" #define width 32 #define height 32 #define bpp 2 #define scan_lines 8 #define RGB_inputs 2 #define we_out_pin PB10 LED_PANEL led_panel = LED_PANEL(width, height, bpp, scan_lines, RGB_inputs, we_out_pin);
то есть создаём экземпляр класса LED_PANEL, для которого указываем параметры:
width — общая ширина панели в пикселях (всего)
height — общая высота панели в пикселях (всего)
bpp — байт на пиксель, соответственно 2 для RGB565 или 3 для RGB888. Должно соответствовать режиму, прошитому в контроллер.
scan_lines — количество линий сканирования — 8/16/32. Должно соответствовать режиму, прошитому в контроллер.
RGB_inputs — количество RGB входов в разъеме HUB75 — 1/2. Должно соответствовать режиму, прошитому в контроллер.
we_out_pin — пин, к которому подцеплен вывод WE
Обращаю внимание, что при инициализации задаётся только пин WE. Все остальные пины прописаны жёстко в коде, так как они привязаны к используемым каналам таймера и DMA и их изменение повлечёт существенные изменения в коде.
Запуск и очистка экрана в разделе setup:
led_panel.begin(); led_panel.clear();
begin инициализирует нужные пины на выход, подключает таймер и DMA
clear очищает буфер
Для рисования можно использовать все стандартные процедуры библиотеки Adafruit GFX — от простейшего drawPixel до вывода текста.
Для выдачи нарисованного в буфер используется процедуры:
led_panel.show();
В таком виде show инициирует передачу данных на контроллер по DMA и немедленно возвращает управление. Узнать — закончилась ли передача можно с помощью функции led_panel.OutIsFree() — если она говорит true, то значит передача закончилась. Есть особенность — если вызвать show, когда передача еще не завершилась — она будет просто проигнорирована.
led_panel.show(false);
аналог show(), но если вызвать show(false), а передача еще не завершилась, то процедура подождёт завершения передачи, потом начнёт новую передачу и вернёт управление
led_panel.show(true);
аналог show(false), но если вызвать show(true), то после начала новой передачи процедура не вернёт управления до завершения передачи
В общем-то — всё.
Некоторые замечания по софту:
а) гамма-коррекция вводится только при использовании режима панели RGB888 при пересчёте цвета из RGB565 (который использует библиотека) функцией ExpandColor. Во всех прочих случаях используется линейная трансфер-функция, то есть яркость прямо пропорциональна значению.
б) софт позволяет подключать несколько LED-контроллеров к одной микроконтроллерной плате. Для этого надо отдать на контроллеры параллельно шину данных, линии RST и CLK. Нужный контроллер выбирается через линию WE. В софте нужно создать отдельный экземпляр класса LED_PANEL для каждого контроллера, при этом у каждого экземпляра при инициализации должны быть указаны разные линии WE (последний параметр).
Ну и теперь —
TO DO
— разобраться с «наводкой» цветов на соседние ряды. Похоже на плохую разводку самой панели (ключи мусорят), но надо проверить.
— сделать класс META_LED_PANEL, который позволит объединять несколько LED_PANEL в один виртуальный экран — это даст возможность создавать большие экраны
— в перспективе уйти на более мощную серию CPLD, например MAXII. Это бы существенно расширило возможности при сохранении невысокой стоимости (EPM1270, который в 20 раз мощнее, стоит 4-5$ штука). Но этим я буду заниматься когда-нибудь потом. Если захочу :) Так как подобные разработки отнимают уж слишком много времени.
(c) 2017 Источник материала