Статьи

Отображение изображений на матричном светодиодном дисплее с Node.js

Матричные светодиодные дисплеи — это весело. С каким разработчиком не понравится чистый холст огней? Недавно я соединил 32×16 матричный дисплей от Freetronics с Node.js и получил его для отображения черно-белых изображений PNG. В этой статье я объясню, как все это работает.

Матричный светодиодный дисплей с точечной матрицей (также известный как точечный матричный дисплей или DMD) — это дисплей с сеткой светодиодных ламп, которые можно включать и выключать для отображения текста и фигур. Некоторые из них имеют несколько цветов, в то время как другие только один цвет. Тот, который мы будем использовать в этой демонстрации, имеет только один цвет, поэтому мы ограничены черно-белыми изображениями. Важно отметить — светодиодный дисплей сильно отличается от ЖК- дисплея. В ЖК-дисплеях используются необычные световые кристаллы, которые используются для отображения на видеомагнитофонах, часах, калькуляторах и т. Несколько недель назад я написал статью об отображении веб-API на ЖК-дисплее Arduino с помощью Node.js. Посмотрите на это, если вы хотите сравнить два.

Для этой конкретной демонстрации требуется матричный дисплей Freetronics 32 × 16, поскольку он опирается на библиотеку Freetronics DMD.

Демо-код

Если вы заинтересованы в том, чтобы взять код и попробовать его самостоятельно, вы можете найти его здесь, на GitHub .

Библиотека Freetronics DMD

Рисование линий, форм и текста на нашем светодиодном точечном матричном дисплее выполняется через библиотеку Freetronics DMD . Чтобы использовать его, выполните следующие действия:

  1. Загрузите библиотеку DMD из их репозитория GitHub .
  2. Скопируйте эти файлы в папку /Arduino/libraries/ под своим именем папки. Для меня на моем Mac я поместил его в папку в /Users/username/Documents/Arduino/libraries/DMD-master .
  3. Загрузите библиотеку TimerOne и поместите ее также в папку /Arduino/libraries/ . например, для пользователей Mac: /Users/username/Documents/Arduino/libraries/TimerOne-r11 .

Наш Arduino Эскиз

Большая часть функциональности для отображения элементов на нашем DMD будет реализована в нашем коде эскиза Arduino. Код скетча будет следить за сообщениями через последовательный порт и изменять отображение, отображаемое на основе этих сообщений.

Эскиз начинается с наших включений и констант. Мы включаем SoftwareSerial.h чтобы позволить нам получить доступ к последовательному порту и определить ширину и высоту нашего DMD (32 × 16 в нашем случае). BUFLENGTH хранит количество получаемых нами источников света, так как это максимальный размер сообщения, которое мы хотим отправить нашему Arduino. В нашем случае 32 умножается на 16, что составляет 512.

 #include <SoftwareSerial.h> #define SCREEN_WIDTH 32 #define SCREEN_HEIGHT 16 #define BUFLENGTH 512 

Далее, у нас есть специальные предложения для Freetronics DMD. Все они должны быть доступны из файлов, которые мы скопировали в нашу папку библиотек Arduino ранее.

 #include <SPI.h> #include <DMD.h> #include <TimerOne.h> 

Затем у нас есть две константы DISPLAYS_ACROSS и DISPLAYS_DOWN которые используются, чтобы определить, сколько светодиодных дисплеев мы объединили. Я предполагаю, что вы находитесь в той же ситуации, что и я, и у вас только один дисплей, поэтому оба они равны одному. Затем мы передаем это в нашу библиотеку DMD, чтобы запустить ее с помощью DMD dmd() .

 #define DISPLAYS_ACROSS 1 #define DISPLAYS_DOWN 1 DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN); 

Следующий бит кода включен в образцы DMD. Это функция, которую мы вызываем, чтобы заставить библиотеку DMD обновлять отображение через заданный интервал. Мы определяем этот интервал немного дальше вниз.

 void ScanDMD() { dmd.scanDisplayBySPI(); } 

Затем мы определяем наши последние две переменные. Эти два относятся к получению сообщений через последовательный порт. Во-первых, buf[BUFLENGTH] хранит буфер сообщений последовательного порта, на которых должны включаться и выключаться светодиоды. Во-вторых, bufCount будет использоваться для хранения количества байтов в этом буфере для чтения.

 char buf[BUFLENGTH]; int bufCount; 

Наша функция setup() начинает весь процесс, используя наши константы и определенные библиотеки. Он начинается прослушиванием порта 57600 сообщений последовательного порта.

 void setup() { Serial.begin(57600); 

Затем мы инициализируем таймер с помощью библиотеки TimerOne, которую мы включили ранее. Мы говорим это отсчет от четырех миллисекунд. В примерах Freetronics они рекомендуют не устанавливать это значение более пяти миллисекунд, чтобы избежать мерцания на нашем дисплее.

 Timer1.initialize(4000); 

Затем мы устанавливаем его для запуска функции ScanDMD() когда истекает время нашего таймера, что, в свою очередь, обновляет дисплей.

 Timer1.attachInterrupt(ScanDMD); 

Наконец, в нашей функции setup() мы dmd.clearScreen() все пиксели на дисплее, передавая true в dmd.clearScreen() . Если вы передадите false этой функции, каждый пиксель включится!

 dmd.clearScreen(true); 

В функции loop() нашего Arduino мы следим за любыми сообщениями на последовательном порту. Мы смотрим, сколько байтов доступно для чтения с последовательного порта. Если есть доступные байты, то у нас есть поток сообщений, и мы запускаем serialParse() .

 void loop() { if (Serial.available() > 0) { serialParse(); } } 

Внутри serialParse() мы устанавливаем bufCount -1 чтобы сбросить значение счетчика. Затем мы читаем 512 элементов из этого массива (наш BUFLENGTH ), используя Serial.readBytesUntil() . Если есть символ \n , он также прекратит чтение массива. Главная цель здесь — сохранить последовательное сообщение в пределах длины нашей светодиодной сетки.

 void serialParse(void) { bufCount = -1; bufCount = Serial.readBytesUntil('\n', buf, BUFLENGTH); 

Если у нас есть сообщение в нашем буфере, то мы отправляем его через parseBuffer() который проанализирует и отобразит его на нашем экране.

 if (bufCount > 0) { String message = String(buf); parseBuffer(message); } } 

В функции parseBuffer() мы начинаем с очистки готового для нас экрана, чтобы он parseBuffer() новым рисунком. Затем мы создаем целое число i чтобы отслеживать, какую позицию в массиве мы читаем.

Затем мы выполняем итерацию по каждому символу в нашем буфере, слева направо, через x SCREEN_WIDTH по циклу до SCREEN_WIDTH , и сверху вниз через y , SCREEN_HEIGHT по циклу вниз до SCREEN_HEIGHT . Это считывает наш одномерный массив в двухмерное отображение нашего DMD. Для каждого символа мы проверяем, является ли это '1' . Если это так, то мы рисуем этот светодиод на дисплее в x и y . Это будет использоваться для частей нашего изображения, которые являются черными. Если это не '1' , то мы переходим к следующей позиции и так далее. В конце концов, вытягивая все наше изображение.

 void parseBuffer(char* buf) { dmd.clearScreen(true); int i = 0; for (byte y = 0; y < SCREEN_HEIGHT; y++) { for (byte x = 0; x < SCREEN_WIDTH; x++) { if ((char)buf[i] == '1') { dmd.drawFilledBox(x, y, x, y, GRAPHICS_NORMAL); } i++; } } } 

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

Наш код узла

Наш JavaScript начинается с двух важных модулей npm. serialport — это то, что позволит нам отправлять сообщения через последовательный порт на Arduino, а png-js — это то, что будет читаться на наших изображениях PNG.

 var SerialPort = require('serialport').SerialPort, PNG = require('png-js'), 

Затем мы настраиваем обмен сообщениями через последовательный порт. Мы настроили объект SerialPort внутри переменной serialPort , с настройками, к какому порту подключен наш Arduino и с какой скоростью передачи данных мы будем прослушивать сообщения последовательного порта.

 serialPort = new SerialPort('/dev/tty.usbmodem1431', { baudrate: 57600 }), 

Если вы не уверены, к какому порту подключен ваш Arduino (например, у меня есть '/dev/tty.usbmodem1431' ), подключите его к ПК, откройте IDE Arduino, перейдите в Инструменты> Порт и посмотрите, какой из них выбран.

Поиск имени порта Arduino

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

Затем мы инициализируем serialMessage переменную с именем serialMessage которая будет хранить полную строку единиц и нулей, которые мы отправим через наш последовательный порт.

 serialMessage = ''; 

Наш объект serialPort имеет прослушиватель событий 'open' который он реагирует, когда определенный последовательный порт открыт и готов к доступу из нашего JavaScript. В этом случае мы запускаем console.log чтобы быть уверенными в том, что с последовательным портом сообщений все в порядке.

 serialPort.on('open', function() { console.log('Serial port open'); 

Как только мы узнаем, что наш последовательный порт готов к сообщениям, мы запускаем PNG.decode() для чтения в нашем файле изображения PNG. В нашей демонстрации у нас есть изображение PNG в той же папке, что и наш файл Node с именем sitepointlogo-withsmile.png , поэтому мы передаем это имя файла. Затем у нас есть функция обратного вызова, которая предоставляет нам данные PNG-файла через переменную data .

 PNG.decode('sitepointlogo-withsmile.png', function(data) { // We'll read in data here 

data возвращаемые нашей PNG.decode() будут массивом значений от 0 до 255. Они перебирают каждый пиксель с серией из четырех элементов для каждого — красного, зеленого, синего и альфа-значения. Мы не будем использовать альфа-значение в нашей демонстрации, поскольку имеем дело только с черно-белыми изображениями, но теоретически вы можете, если хотите. Примерный массив выглядит так:

 [255,255,255,255,0,0,0,255] 

Вышеуказанный массив представляет один белый пиксель с 255,255,255,255 и один черный пиксель с 0,0,0,255 . Это продолжается снова и снова для каждого пикселя, пока мы не представим все изображение.

В нашей функции обратного вызова мы сбрасываем serialMessage в пустую строку, а затем начинаем перебирать массив data в наборах по четыре. Мы устанавливаем локальную переменную red , green и blue чтобы соответствовать значению каждого пикселя.

 serialMessage = ''; for (i = 0; i < data.length; i+=4) { var red = data[i], green = data[i+1], blue = data[i+2], 

Чтобы иметь возможность работать со значениями оттенков серого, которые не являются полностью черными или белыми, у нас также есть проверка яркости. Функция ниже определяет, насколько темный или светлый цвет пикселя:

 luminance = ((red * 299) + (green * 587) + (blue * 114)) / 1000; 

Если это значение больше 150, то мы предполагаем, что это довольно светлый цвет и устанавливаем его в 0 (белый). В противном случае мы устанавливаем значение 1 и делаем его черным. Мы добавляем любое значение в строку serialMessage .

 if (luminance > 150) { serialMessage += '0'; } else { serialMessage += '1'; } } 

Как только мы пройдем каждый пиксель и выделим либо ноль, либо единицу для его представления, мы отправим это сообщение через последовательный порт, используя serialPort.write() . Весь этот процесс чтения изображения и итераций на самом деле, кажется, быстрее, чем время, необходимое для готовности дисплея к его принятию, поэтому я поместил его в setTimeout чтобы он подождал две секунды перед запуском ,

 setTimeout(function() { serialPort.write(serialMessage); }, 2000); 

Запуск нашего демо

Если вы загрузите эскиз, подключите дисплей к вашему Arduino и запустите код сервера узла через node serialDMD.js (не забудьте сначала npm install все npm install ), вы увидите, что он загорается вместе с вашим файлом PNG следующим образом:

Наш светодиод в действии

Вывод

Есть много способов расширить это. Это сервер Node, так что вы можете подключить его к API и отображать изображения, проходящие через него. Вы можете настроить отображение другого изображения в зависимости от времени суток, состояния подключенного к Интернету устройства в вашем доме, погоды или любого другого количества вещей!

Если вы расширите эту идею до чего-то действительно изящного, дайте мне знать в комментариях или свяжитесь со мной в Twitter ( @thatpatrickguy ), я хочу это увидеть!