DIY-контроллер LED панели на CPLD с использованием BAM модуляции

в 8:31, , рубрики: arduino, cpld, diy или сделай сам, led-экраны, stm32, Verilog

Некоторое время назад участвовал в обсуждении DIY проекта матричных светодиодных часов
И что меня удивило — в качестве устройства отображения использовались древние одноцветные светодиодные матрицы 8х8 с шагом 5 миллиметров. Причём под них разводились сложные печатные платы, делалась софтовая динамическая индикация. И это в то время, когда уже давно доступны по цене в районе 10-20$ готовые полноцветные LED панели 64х32 с шагом 3 мм. А общий ассортимент подобных панелей очень большой и имеет шаг пикселя от 2 до 10 мм и практически любой размер.

В то же время использовать такие панели в DIY конструкциях достаточно непросто — готовые контроллеры стоят довольно больших денег и не имеют нормального API. Сделать же достаточно быстрое сканирование панели на обычно используемых в DIY микроконтроллерах достаточно сложно. Причём временные интервалы должны выдерживаться с высокой точностью — иначе начинается заметная неравномерность яркости.

Есть неплохие решения на Adafruit, но они все достаточно дорогие и сложные.

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

В данной статье описана вторая, улучшенная версия контроллера.

Задача

В качестве базовой задачи хотелось иметь возможность управлять сборной панелью общим размером хотя бы 64х64, имея при этом возможность работы хотя бы в Highcolor (RGB565) с сохранением приемлемой частоты обновления экрана (не менее 50Гц). В первой версии контроллера базовая задача была полностью реализована, но возникла идея реализации задачи другим, весьма многообещающим методом, из чего и родилась вторая версия.

Базовые пояснение по устройству типовой LED панели

На входе интерфейс HUB75:

DIY-контроллер LED панели на CPLD с использованием BAM модуляции - 1

На каждом входе цвета стоит цепочка регистров типа HC595 (но специальные 16-ти канальные версии для светодиодов). Регистров столько, чтобы хватало на ширину панели. Входы клока, параллельной загрузки и разрешения выхода общие для всех регистров. Входы ABCDE — это выбор ряда — идут на обычный дешифратор.

Принцип работы:

  • выставляем данные на RGB входы, щелкаем клоком CLK. Повторяем, пока не загрузим всю строку
  • выключаем выходы OE = 1 (чтобы помех не было)
  • выдаём на дешифратор номер загруженного ряда
  • щелкаем параллельной загрузкой LAT — данные строки переносятся в выходные регистры
    включаем выходы OE = 0
  • повторяем для следующего ряда

То есть классическая динамическая индикация. Понятно, что при таком методе за один такой цикл мы можем каждый конкретный светодиод только включить/выключить.

Для того, чтобы получить градации яркости классическим PWM, такой цикл приходится повторять N-1 раз, где N — число градаций яркости (256 для RGB888). А учитывая, что при этом это всё еще и мерцает — всё это надо делать очень-очень быстро.

Есть обходной вариант — Bit Angle Modulation (BAM). При этом время свечения в каждом цикле пропорционально весу отображаемого бита. То есть для RGB888 надо всего 8 циклов отображения. Чуть более детально — здесь.

В первой версии контроллера использовался классический PWM, что накладывало жёсткое ограничение на количество циклов сканирования. Во второй версии реализован BAM, что дало огромный выигрыш в скорости.

Реализация

Совершенно очевидным было то, что обычный микроконтроллер тянет только маленькие панели — на большие просто не хватает скорости. Поэтому без CPLD или FPGA здесь не обойтись — выдавать десятки МБ/сек на недорогих микроконтроллерах физически невозможно.

В качестве памяти мне на форуме 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. Цена в Чип и Дип — 40р за штуку
  • Разъем IDC-10MS. Цена в Чип и Дип — 3 р/штуку
  • Разъем IDC-16MS. Цена в Чип и Дип — 8 р/штуку
  • Разъём IDC-14MS. Цена в Чип и Дип — 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 файлы для заказа

Плата подготовлена для первой версии, в которой не было управления RE. Для использования её со второй версией надо разрезать перемычку между выводами 23 и 24 AL422B и бросить проводок от вывода 28 EPM3064 (он выведен на контактную площадку) на вывод 24 AL422B.

При пайке платы не забыть запаять перемычки на питание на обратной стороне платы.

DIY-контроллер LED панели на CPLD с использованием BAM модуляции - 2

DIY-контроллер LED панели на CPLD с использованием BAM модуляции - 3

Расчёты

Расчёты нужных параметров довольно сложны.

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

Запускаются процессы одновременно, но заканчиваются в разное время, поэтому процесс, выполненный быстрее, ждёт завершения работы более длинного процесса.

→ Для расчёта была сделана Excel-табличка

Исходные данные:

  • CRYSTAL_FRQ (MHz) — частота генератора (50 МГц)
  • PIXEL_COUNT — количество пикселей в строке загрузки. Более детально в разделе коммутации
  • RGB_INPUTS — количество RGB входов, используемых в HUB75E интерфейсе используемой панели. 1 или 2
  • BYTES_PER_PIXEL — байт на пиксель. В нашем случае всегда 3 — RGB888
  • SCAN_LINES — количество линий сканирования в используемой панели. 8/16/32

Подбираемые параметры:

  • PRE_DELAY — задержка от сигнала LAT до включения OE, задаётся в тактах
  • PRESCALER — прескейлер для основного счётчика. То есть если прейскейлер 8 и вес текущего бита 4, то OE будет включено на 8*4 = 32 такта
  • POST_DELAY — минимальная задержка от выключения OE до следующего сигнала LAT, задаётся в тактах

Например, у нас панель 32х32, имеющая 8 линий сканирования и 2 RGB входа. Такая панель имеет два HUB75E разъема, то есть физически это две панели 32х16. Мы соединяем эти панели последовательно, то есть логически эта панель будет выглядеть как 64х16.

PRE_DELAY и POST_DELAY — это интервалы гашения до и после output enable (OE) для того, чтобы мультиплексоры успели переключить выходы и ключи открыться/закрыться. Без них будут «тянучки» от горящих пикселей на соседние строки. Значения подбираются экспериментально для конкретной панели. Обычно достаточно 15 тактов (задаются в тактах).

Тут возникает вопрос выбора prescaler — как его выбрать.

Малое значение прескейлера даёт малое время отображения кадра, но уменьшает общую яркость. Большое значение прескейлера увеличивает время отображения кадра, то есть при переборе приводит в мерцанию экрана.

Попробуем PRESCALER = 1

Получим:

OE_EFFICIENCY — 8,3% то есть панель будет работать всего на 8.3% от возможной максимальной яркости
FRAMES_PER_SECOND — 2034 к/с — зато частота обновления картинки будет огромной — больше 2000 к/с.

Потеря яркости уж очень большая.

Попробуем PRESCALER = 16

Получим:

OE_EFFICIENCY — 72,9% то есть панель будет работать на 72,9% от возможной максимальной яркости
FRAMES_PER_SECOND — 1117 — и частота обновления картинки очень хорошая — больше 1000 к/с.
Ну вот, вполне нормально — эффективность больше 50% вполне нормальная и частота кадров очень хороша.

Общее эмпирическое правило — PRESCALER примерно в 8 раз меньше произведения PIXEL_COUNT*RGB_INPUTS

Ну и дальше считать и проверять.

Коммутация LED панелей

Все панели соединяются последовательно. Схема соединения: сперва справа-налево, потом снизу-вверх. То есть сперва соединяем горизонтали последовательно, потом выход нижнего ряда ко входу второго снизу ряда и т.д. до верхнего ряда.

Контроллер цепляется к правой нижней панели.

Бывают панели, которые имеют по два входных и два выходных разъёма. Такие панели по сути являются просто механической сборкой двух панелей по вертикали. Коммутируются как две независимые панели.

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

Прошивка FPGA

Все необходимые файлы лежат на github. Скачивать нужно прямо папкой.

С сайта Altera после регистрации необходимо скачать и установить Quartus II 13.0sp1. Качать надо ИМЕННО ЭТУ версию — более новые версии уже не поддерживают серию MAX3000. Ломать её не надо — достаточно Web edition (free) версии. При скачивании не забудьте поставить галочки на поддержке MAX3000 и Programmer. На всякий случай предупреждаю — пакет большой, порядка двух гигов. Еще понадобится Altera USB Blaster — обычная цена на ali порядка 3$.

Открываем проект al422_bam.qpf. Слева открываем закладку файл и открываем файл al422_bam.v — это основной файл проекта. В нём надо настроить параметры:

Сколько RGB входов на панели — на панелях со входом HUB75 может быть 1 или 2 входа RGB. Выяснить — сколько именно входов можно таким способом — берём количество пикселей на панели по вертикали. Делим её на число линий сканирования (указано в обозначении панели как 8S, например). Делим на количество входных разъёмов (1 или 2). Например — у меня панель 32х32, сканирование 8S и два входных разъёма — 32/8/2=2 — значит два входа RGB.

`define RGB_outs	2

Сколько линий сканирования на панели — так как поддерживается стандарт 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_LAT_ACTIVE_LOW	1
`define LED_OE_ACTIVE_LOW	1
//`define LED_CLK_ON_FALL		1

Пред и пост задержки сигнала OE относительно LAT и прескейлер для основного счётчика. См. выше.

`define OE_PRESCALER	16
`define OE_PREDELAY	31
`define OE_POSTDELAY	31

Всё, нажимаем ctrl-L — проект компилируется. Если нигде не напортачили — будет несколько warnings, но не должно быть никаких ошибок. Дальше цепляем спаянную плату к USB Blaster, подаём питание на плату. В Quartus идём в tools — programmer. Выбираем в Hardware setup USB-blaster, нажимаем Start. Всё, CPLD запрограммирована.

Микроконтроллерная часть

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

а) Надо много памяти. Даже небольшая панель 32х32 в режиме RGB888 требует 3кБайта памяти под экранный буфер. Обычные ардуино на базе 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. Данные идут через DMA, поэтому можно использовать любой вариант оптимизации.

Коммутация происходит по следующей схеме:

Жёстко прибиты в библиотеке:
A0..A7 → DI0..DI7 AL422B
B0 → WCLK AL422B
B1 → WRST AL422B

Назначается в скетче на контроллер:
B10 → WE AL422B

Общий провод:
G → GND

Ну и не забудьте подать питание 5В/GND от панели на соответствующие пины контроллера.

Распиновку разъема на контроллере брать со схемы.

DIY-контроллер LED панели на CPLD с использованием BAM модуляции - 4

Программная часть

Так как ставилась задача сделать всё максимально просто и доступно, то весь софт сделан под среду Arduino и оформлен в виде библиотеки LED_PANEL.

Библиотека LED-PANEL активно использует библиотеку Adafruit GFX, поэтому она должна быть инсталлирована.

Я настоятельно рекомендую не ставить библиотеку LED_PANEL в каталог libraries, а оставить её в папке со скетчем. Дело в том, что там много железно привязанных параметров и если вы захотите перенести работу на более «жирный» микроконтроллер, то придётся много чего менять в самом коде.

Инициализация идёт примерно в таком виде:

#include "LED_PANEL.h"
 
#define width 32
#define height 32
#define bpp 3
 
#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 — байт на пиксель, 3 для RGB888. BAM версия работает только в 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), то после начала новой передачи процедура не вернёт управления до завершения передачи.

В общем-то — всё.

DIY-контроллер LED панели на CPLD с использованием BAM модуляции - 5

Некоторые замечания по софту:

а) Гамма-коррекция вводится при пересчёте цвета из RGB565 (который использует библиотека) функцией ExpandColor. Во всех прочих случаях используется линейная трансфер-функция, то есть яркость прямо пропорциональна значению.
б) Софт позволяет подключать несколько LED-контроллеров к одной микроконтроллерной плате. Для этого надо отдать на контроллеры параллельно шину данных, линии RST и CLK. Нужный контроллер выбирается через линию WE. В софте нужно создать отдельный экземпляр класса LED_PANEL для каждого контроллера, при этом у каждого экземпляра при инициализации должны быть указаны разные линии WE (последний параметр).

TO DO

— Разобраться с «наводкой» цветов на соседние ряды. Похоже на плохую разводку самой панели (ключи мусорят), но надо проверить. Только приехала новая панель — буду проверять;
— Сделать новую версию платы — с уже разведённым RE и добавлением выходных преобразователей уровней в 5В;
— Сделать класс META_LED_PANEL, который позволит объединять несколько LED_PANEL в один виртуальный экран — это даст возможность создавать очень большие экраны с несколькими контроллерами;
— В перспективе уйти на более мощную серию CPLD, например CycloneIV. Это бы существенно расширило возможности при сохранении невысокой стоимости (EP4CE6E22 стоит у китайцев порядка 5$ штука, при этом там в 100 раз больше макроячеек и порядка 32 кБ встроенной памяти). Но этим я буду заниматься когда-нибудь потом. Если захочу. Так как подобные разработки отнимают уж слишком много времени.

Автор: Андрей Коробейников

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js