Прототип светодиодного табло на 262.144 комбинации цветов и 64 пикселя

Хочу поделиться опытом создания светодиодного табло 8×8 пикселей, 262к комбинаций цветов (18 бит), частотой кадров 180 FPS и подключением к USB. Также готов выслушать предложения по оптимизации и доработке. В дальнейшем планирую использовать наработки для создания дисплея домашней метеостанции.

Предисловие

Началось все с простейшей схемы управления линейкой из 8 светодиодов через LPT-порт. Следующей версией было табло 5×8 из трехцветных светодиодов, которое также подключалось к LPT и по сути представляло собой массив из пятнадцати 8-битных буферов с дешифратором для их адресации.
Позже, после знакомства с микроконтроллерами, задался целью создать аналогичное табло, но с подключением к USB. Изначально рассчитывал использовать только 8 цветов. Впоследствии нашел способ управления яркостью каждого диода с помощью таймера по аналогии с PWM, и после доработки программной части получился текущий девайс. Теоретически можно работать и с 16млн цветов, но обычные светодиоды не подойдут для такого режима по цветопередаче и повторимости. К тому же проблемы с цветом разных диодов уже на текущей конфигурации заметны.

Описание работы

В основе устройства лежит микроконтроллер PIC18F4550, работающий на частоте 48МГц. Используются встроенный USB-контроллер и готовая библиотека для работы с ним, Timer0 в режиме 8 бит, реализующий динамическую индикацию. Для хранения трех цветов в одном столбце использованы три 8-битных триггера на 74F374. Использование такого буфера позволяет сократить время отображения одного кадра в 3 раза. Примечание: Когда я выбирал буфер 74F374, не обратил внимания на разводку его ножек, а понял это только уже на монтажном стенде, поэтому пришлось существенно усложнить плату. Лучше использовать более удобные аналоги. Например, 74HC574.
Светодиоды подключаются через ключи ULN2803 и UDN2982. Токоограничивающие сопротивления стоят только в красном канале, т.к. их напряжение питания ниже синего и зеленого. Для синего и зеленого сопротивления не установлены, т.к. достаточно падения напряжения на ключах. Примечание: Для более точной цветопередачи лучше подобрать более точные токоограничивающие сопротивления в каждый канал.
Микроконтроллер в бесконечном цикле выполняет опрос состояния USB и, при поступлении пакета данных, в зависимости от команды, запускает/останавливает индикацию или подготавливает данные для индикации. В связи с ограничением размера одного пакета в 64 байта, данные для каждого цвета передаются отдельным пакетом в 48 байт — по 6 байт на каждый из 8 столбцов, кодирующие яркость каждого светодиода в столбце. После получения каждого пакета он копируется из памяти USB в массив своего цвета.
После поступления команды запуска индикации МК активирует таймер в режиме 8 бит и делителем на 128. Таймер использует в качестве тактовых импульсов рабочую частоту микроконтроллера. Увеличение счетчика таймера происходит каждые 4 такта. Минимальный период таймера составляет 10,6 мкс (1/48*4*128), что примерно в 2,8 раза больше времени обработки прерывания (46 операций, против 128 отсчетов таймера).
При переполнении таймера выполняется прерывание по высокому вектору. Обработчик прерывания отключает индикацию, выполняет обновление данных в буферах, перенося по 1 байту из каждого массива цвета согласно курсору, затем включает индикацию. Заносит новое значение в таймер из временного буфера, декрементирует курсор, сдвигает временный буфер для таймера. Если буфер таймера превысил максимальный показатель, т.е. сдвигался больше 5 раз, то буфер таймера сбрасывается в минимальное значение и сдвигается указатель выбранной колонки.
В итоге получается следующий алгоритм динамической индикации:

  1. Берем первую группу 3 байт из трех массивов и помещаем в буферы каждого цвета в столбце.
  2. Активируем таймер с минимальным временем задержки в 128 тактов.
  3. Берем следующую группу 3 байт из трех массивов и помещаем в буферы каждого цвета в столбце.
  4. Активируем таймер удвоенной задержкой относительно предыдущего шага.
  5. Повторяем выборку еще 4 раза и каждый раз удваиваем время задержки.
  6. Сбрасываем таймер и начинаем обработку следующего столбца с п.1.

Таким образом мы можем задать 2^6=64 варианта яркости для каждого диода в столбце. Комбинируя яркость каждого из трех базовых цветов, получаем 64*64*64=262144 цвета. Время обработки одного столбца составляет (2^6-1)*10,6мкс=672мкс. Время на один кадр из 8 столбцов — 672*8=5.4мс, что примерно соответствует 186 кадрам в секунду.

Использованные компоненты

  • PIC18F4550 — Микроконтроллер
  • 74F374 — Триггер для хранения текущих значений столбца
  • ULN2803 — Ключ для управления катодами
  • UDN2982 — Ключ для управления анодами
  • 4-х выводные RGB светодиоды с общим катодом (можно использовать любые светодиоды)

Схема

Схема в формате dsn — скачать

Графика

Плата

Чертежи в формате lay6 — скачать

Графика

основной модуль сторона 1

основной модуль сторона 2

модуль светодиодов (обратите внимание, что синим отмечена проволочная перемычка, соединяющая столбцы)

матрица крепления светодиодов

Прошивка

Исходники и собранный HEX в MPLABX X IDE v2.30 — скачать

Основной код

#ifndef MAIN_C #define MAIN_C  // Local includes #include "config.h" #include "usb.h" #include "HardwareProfile.h" #include "usb_function_hid.h" #include "genericHID.h"  #define UdnOn           LATA&=0b11111110 #define UdnOff          LATA|=0b00000001  #define UlnOn           LATD #define UlnOff          LATD =0b00000000  #define LineBufer       LATB  #define WriteR          LATE|=0b00000001 #define WriteG          LATE|=0b00000010 #define WriteB          LATE|=0b00000100 #define WriteRst        LATE =0b00000000  #define Columns         8 #define BrightLevels    6 #define BlockSize       (Columns*BrightLevels)  #define MinBright       0b11111111  unsigned char cursor; unsigned char bright; unsigned char column; unsigned char dataR[BlockSize]; unsigned char dataG[BlockSize]; unsigned char dataB[BlockSize];  void ProcessIO(void) {     unsigned char temp = BlockSize + 1;      // If we are not in the configured state just return     if ((USBDeviceState < CONFIGURED_STATE) || (USBSuspendControl == 1)) return;      //Check if data was received from the host.     if (!HIDRxHandleBusy(USBOutHandle))     {         switch (ReceivedDataBuffer[0])         {             case 0x80: // get red packet                 while (--temp) dataR[temp-1] = ReceivedDataBuffer[temp];                 break;              case 0x81: // get green packet                 while (--temp) dataG[temp-1] = ReceivedDataBuffer[temp];                 break;              case 0x82: // get blue packet                 while (--temp) dataB[temp-1] = ReceivedDataBuffer[temp];                 break;              case 0x90: // start                 column = 0b00000001;                 cursor = BlockSize;                 bright = MinBright;                 TMR0ON = 1;                 SWDTEN = 0;                 break;              case 0x91: // stop                 UdnOff;                 UlnOff;                 TMR0ON = 0;                 SWDTEN = 0;                 break;              case 0x92: // power off                 UdnOff;                 UlnOff;                 TMR0ON = 0;                 SWDTEN = 0;                 SLEEP();                 break;         }          // Re-arm the OUT endpoint for the next packet         USBOutHandle = HIDRxPacket(HID_EP, (BYTE*) & ReceivedDataBuffer, 64);     } }  void main(void) {     // Set all port as digital input/output     PCFG3   = 1;      // Clear all ports     //          76543210     PORTA   = 0b00000000;     PORTB   = 0b00000000;     PORTC   = 0b00000000;     PORTD   = 0b00000000;     PORTE   = 0b00000000;      // Configure ports (1 - inputs; 0 - outputs)     //          76543210     TRISA   = 0b00000000;     TRISB   = 0b00000000;     TRISC   = 0b00000000;     TRISD   = 0b00000000;     TRISE   = 0b00000000;      // Configure interrupts for Timer0     //          76543210     INTCON  = 0b10100000;      // Configure Timer0 as 8bit and 128 prescaler     //          76543210     T0CON   = 0b01000110;      USBDeviceInit();      while(1)     {         // Check bus status and service USB interrupts.         USBDeviceTasks();          // Application-specific tasks.         ProcessIO();     }; }  void interrupt tc_int() // High priority interrupt {     UdnOff;     UlnOff;     LineBufer = dataR[cursor-1]; WriteR;     LineBufer = dataG[cursor-1]; WriteG;     LineBufer = dataB[cursor-1]; WriteB;     UdnOn;     UlnOn = column;     WriteRst;     TMR0L = bright;      if (!--cursor) cursor = BlockSize;      bright <<= 1;     asm("BTFSS _bright, 5, 0"); asm("RLNCF _column, 1, 0");     asm("BTFSS _bright, 5, 0"); bright = MinBright;      TMR0IF = 0; } #endif 

Устройство в работе

Для управления я использую плеер интернет радио, написанный на Си, в основе которого библиотека BASS.DLL. Демо с градиентом по всей доступной палитре цветов работает во время паузы, частота обновления кадров (передаваемых пакетов в устройство) — 20Гц. При проигрывании музыки работает визуализатор, использующий FFT-массив, получаемый средствами BASS.DLL, частота обновления кадров (передаваемых пакетов в устройство) в этом режиме — 29Гц.

Градиент

Визуализатор


музыка: Tape Five — Soulsalicious

Примечание: Видео снимал через стекло от солнцезащитных очков (так не видно черных точек кадровой развертки) и без матового стекла (оно мешает фокусировке). Т.к. светодиоды у меня не матовые, я сточил линзу на них и обработал гравером.

Фото в собранном виде




Что можно улучшить

  • заменить ключи на более быстрые (особенно это касается UDN)
  • реализовать работу с USB через прерывания
  • использовать матовые светодиоды или smd для упрощения рассеивания и смешивания света
  • в место 74F374 лучше использовать 74HC574, что значительно упростит разводку платы
  • добавить емкости к каждой схеме 74F374 для защиты от помех
  • для выборки столбцов можно использовать дешифратор 74HC138, что позволит сэкономить ножки МК
  • для удешевления схемы можно использовать светодиоды с общим анодом и использовать 3 более дешёвых ключа ULN, вместо UDN

Источник

led, microchip, microcontrollers, pic16, дисплей, программирование микроконтроллеров

Читайте также