Как сделать очень заметный информер из светодиодного модуля P10 и Arduino.
Цель: Быстро подключить большую светодиодную матрицу P10 (16х32см) к ПК или другому устройству, превратив все это в очень заметный и яркий информер с динамической сменой выводимой информации. Применений такой вещи можно найти массу и как показывает практика она очень привлекает внимание. Вы только представьте, теперь точно все будут знать что у вас работает кондиционер и дверь предполагается закрывать!
Засветка не от окна, а от панели ;)
Однажды посмотрев на унылые матрицы 8х8 светодиодов для Arduino и на их стоимость мне стало грустно. Было решено капнуть в сторону готовых бегущих строк для наружной рекламы и каково было мое удивление когда все они оказались стандартные и все как одна не знали ничего про динамическое обновление информации через внешний порт. Копнув глубже обнаружилось что во всей подобной продукции используются типовые светодиодные модули (светодиодные матрицы).
Все видели яркие экраны наружной рекламы от простых бегущих строк до огромных телевизоров. Эти экраны собираются из цепочки таких светодиодных модулей и управляются спец контроллерами, цена которых растет прямо пропорционально размеру экрана.
Эти светодиодных модулей довольно дешевы. Порядка 6$, цена зависит от типоразмера и цветности. С контроллерами сложнее. Самые простые по цене сравнимы с одной светодиодной панелью. Однако большинство из них "заточено" на работу в режиме демонстрации заранее заготовленных "презентаций" и не имеют возможности динамически менять выводимую информацию. Признаюсь что только бегло ознакомился с функционалом самых простых контроллеров, но этого оказалось достаточно чтобы понять — дешевле и быстрее сделать необходимое на универсальном контроллере Arduino. Это позволит подключить совершенно спокойно несколько модулей. Но мы начнем с одного.
Светодиодный модуль (Р10) выглядит примерно так:
http://digital-wizard.net/avr_projects/p10_led_display_panel_interface
По сути это просто матрица светодиодов которые припаяны к выходам сдвиговых регистров, не будем вдаваться в дебри схемотехники, модули все стандартные и отличаются лишь размерами. Р10 означает что между двумя соседними диодами 10мм. Бываю панели одноцветные, двухцветные и даже RGB. Для интересующихся подробностями тут уже есть аналогичная статья, но с уклоном в более низкий уровень.
Аппаратная часть
А для тех кто хочет быстрее получить результат, который можно "пощупать", потребуется:
- Одна светодиодная матрица.
- Arduino. (Я использовал mini, но удобнее будет nano чтобы не использовать доп. переходники для связи с ПК).
- Блок питания на 5В/3А. Матрица прожорлива, если зажечь все диоды то питания надо много.
- Шлейф подключения. (Обычно он идет в комплекте с матрицей.)
- Желание довести дело до конца.
Будем делать монолитную конструкцию которую достаточно лишь воткнуть в розетку и ПК чтобы отображать нашу драгоценную информацию (например курс биткоина).
Необходимо взять шлейф от матрицы, разрезав его напополам припаять по простой схеме к Arduino.
Если использовать Arduino Mini или UNO то паять надо к соответствующим пинам по аналогии.
Если по какой-то причине у вас не оказалось шлейфа то его можно заменить на MIDI кабель которым в старых звуковых картах подключается к самой плате MIDI разъем (вариант для старожил) или заменить на два штекера Dupont (мама) по 8 контактов. В принципе разъем стандартный, ищется довольно легко.
У меня же за 10 минут получилась следующая конструкция, болтается еще дополнительный USBtoUART переходник которого у вас не будет если использовать не Arduino Mini.
Аппаратная часть готова, необходимо только подключить дополнительный БП на 5В/3А для питания матрицы. Для этого на ней есть отдельный разъем, плюс к плюсу, минус к минусу. Наоборот можно, но работать не будет. Хотя странно, так как всем известно что электроны, по обыкновению, текут от плюса к минусу. А если учесть что электроны отрицательно заряженные то вообще не понятно почему они текут к отрицательному полюсу… ;) Ерунды навыдумывали короче.
Я надеюсь что вы подключите питание более надежно, а мой способ отлично годится только если у вас много запасных БП. Ардуину можно прикрутить болтом (не саморезом!) к самой панели, в панели есть монтажные резьбовые отверстия.
Вот собственно и все, да простят меня боги DIY, но сегодня обошлось без синей изоленты. Негоже такой ценный ресурс тратить на подобные никчемные вещи.
Программная часть
Если вы подумали что нужно будет разбираться как программировать эту штуку из трех букв (SPI интерфейс) то могу вас разочаровать — все значительно проще. Как всегда все велосипеды придуманы до нас и не единожды. Собственно как и эта статья. Есть готовые библиотеки DMD и DMD2 и рисование сводится к тому что в скетче нужно просто указать что, как и куда выводить, все — Профит!
Немного поигравшись с формой квадрата этими библиотеками я пришел к выводу что скролинг текста во DMD2 выглядит "печально", хотя там есть больше возможностей и управление яркостью. В итоге остановился на первой и вспомнив что "ты ж программист" добавил в нее примитивное управление яркостью. Тут стоит упомянуть что светят эти матрицы очень ярко. К концу отладки выжжет роговицу.
В общем все что вам надо сделать это загрузить скетч в плату, но именно из той директории куда распакуете мой архив, так как я немного доработал напильником некоторые библиотеки, которые лежат там же. Добавилось управление яркостью и чуть чуть разогнан SPI, может еще что, уже и не помню. Рабатет? Не трогай!
Для большей наглядности я добавил датчик температуры 18в20. Если вы такой не подключите то просто не будет выводится температура. У меня по задумке там будет выводится некий вес в граммах, вы же можете выводить просто число. Чтобы изменить текст который прокручивается снизу нужно его просто "вгрузить" в COM порт Arduino используя специальный формат, некое подобие ESC последовательности. Прилагается скрипт на Python который просто посылает на панель текущее время.
Формат управляющих команд:
- 0x1B (ESC) + 0x40 ("@") // Опознание, возвращает строку ID устройства «P10_DISPLAY_V01\r\n»
- 0xOC (CLR) // Очистка экрана.
- 0x1F (US) + 0x58 («X») + 0x80 // Задать якрость экрана, 0x80 максимальная яркость.
- 0x1F (US) + 0x52 («B») + Строка // Задать нижнюю строку которая скролится.
- 0x1F (US) + 0x0A («LF») + 0x31, 0x32, 0x33, 0x34, 0x35 // Передать пятизначно число отображаемое вверху «12345»
Я не нашел готового и пришлось нарисовать русский шрифт 5*7. Наверно велосипед, но разве мы делаем всегда все рационально?
Скетч:
//====================== OneWire ====================================
#include <OneWire.h>
OneWire ds2 (2);
String res = "";
byte type_s;
byte data[12];
byte addr_ds2[8];
bool temp_sensor_2_found = false;
float temp1 = 999;
//===================================================================
#include <SPI.h>
#include "DMD.h"
#include "TimerOne.h"
#include "SystemFont5x7rus.h"
#define CLR 0x0C
#define US 0x1F
#define ESC 0x1B
#define LF 0x0A
static const char ID[] = "P10_DISPLAY_V01\r\n"; // Device ID
#define DISPLAYS_ACROSS 1
#define DISPLAYS_DOWN 1
DMD dmd(DISPLAYS_ACROSS, DISPLAYS_DOWN); // Конфигурация экрана, панелей может быть не одна.
#define max_char1 6
#define max_char2 176
unsigned char message1[max_char1] = {0x20,0x30,0x30,0x30,0x30,0x00}; // stores you message
unsigned char message2[max_char2]; // stores you message
unsigned char r_char;
byte index1 = 4;
byte index2 = 176;
int i;
long scrollSpeed = 25;
char test[] = { 0x84,0x80,0x20,0x87,0x84,0x90,0x80,0x82,0x91,0x92,0x82,0x93,0x85,0x92,0x20,0x92,
0x8E,0x20,0x81,0x8B,0x80,0x83,0x8E,0x84,0x80,0x90,0x9F,0x20,0x97,0x85,0x8C,0x93,
0x20,0x8C,0x9B,0x20,0x8D,0x85,0x91,0x8C,0x8E,0x92,0x90,0x9F,0x20,0x8D,0x88,0x20,
0x8D,0x80,0x20,0x97,0x92,0x8E,0x20,0x88,0x20,0x82,0x8E,0x8F,0x90,0x85,0x8A,0x88,
0x20,0x82,0x91,0x85,0x8C,0x93,0x21,0x00};
//=============================================================================
bool get_sensor(byte addr[8], OneWire ds) {
// Ищем адрес датчика
if (! ds.search (addr)) {
ds.reset_search ();
delay (250);
return false;
}
// Проверяем не было ли помех при передаче
if (OneWire::crc8(addr, 7) != addr[7])
{
Serial.println("get_sensor:CRC is not valid!");
return false;
}
// Определяем серию датчика
switch (addr[0])
{
case 0x10:
//Chip = DS18S20
type_s = 1;
break;
case 0x28:
//Chip = DS18B20
type_s = 0;
break;
case 0x22:
//Chip = DS1822
type_s = 0;
break;
default:
Serial.println("get_sensor:Device is not a DS18x20 family device.");
return false;
}
return true;
}
//=============================================================================
double get_temp(byte addr[8], OneWire ds) {
double celsius;
ds.reset ();
ds.select (addr); // Выбираем адрес
ds.write (0x44, 1); // Производим замер, в режиме паразитного питания
delay (750);
ds.reset ();
ds.select (addr);
ds.write (0xBE); // Считываем оперативную память датчика
for (int i = 0; i < 9; i++) { // Заполняем массив считанными данными
data[i] = ds.read ();
}
// Данные о температуре содержатся в первых двух байтах, переведем их в одно значение и преобразуем в шестнадцатиразрядное число
int raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
raw = (raw & 0xFFF0) + 12 - data[6];
}
}
else
{
byte cfg = (data[4] & 0x60);
if (cfg == 0x00) raw = raw << 3;
else if (cfg == 0x20) raw = raw << 2;
else if (cfg == 0x40) raw = raw << 1;
}
celsius = (double)raw / 16.0;
return celsius;
};
//=============================================================================
void massage2BufClear ()
{
for(i=0; i<max_char2; i++)
{
message2[i] = '\0';
}
index2=0;
}
//--------------------------------------------------------------
void massage1BufClear ()
{
message1[0] = 0x20;
message1[1] = 0x30;
message1[2] = 0x30;
message1[3] = 0x30;
message1[4] = 0x30;
message1[5] = 0x00;
index1=0;
}
//--------------------------------------------------------------
void massage1Normalization ()
{
char buf[5];
buf[0] = 0x20;
buf[1] = 0x30;
buf[2] = 0x30;
buf[3] = 0x30;
buf[4] = 0x30;
int char_count = index1;
for(i = char_count - 1; i >= 0; i--)
{
buf[i] = message1[i];
}
massage1BufClear();
for(i = char_count - 1; i >= 0; i--)
{
message1[i+5-char_count] = buf[i];
}
}
//------------------------------------------------------------------
void ScanDMD()
{
dmd.scanDisplayBySPI();
}
//------------------------------------------------------------------
void setup(void)
{
Timer1.initialize( 500 );
Timer1.attachInterrupt( ScanDMD );
dmd.clearScreen( true );
Serial.begin(9600);
strcpy(message2,test);
dmd.brightness = 70;
temp_sensor_2_found = get_sensor(addr_ds2, ds2);
dmd.selectFont(SystemFont5x7);
}
//------------------------------------------------------------------
void loop(void)
{
dmd.drawMarquee(message2 ,index2,(32*DISPLAYS_ACROSS)-1 ,8);
if (temp_sensor_2_found) // !!!!!!!
{ // !!!!!!!
res = String(get_temp(addr_ds2, ds2)); // !!!!!!!
res.toCharArray(message1, 5); // !!!!!!!
message1[4] = 0xF8; // !!!!!!!
} // !!!!!!!
long start=millis();
long timer=start;
boolean ret=false;
while(!ret)
{
if ((timer + scrollSpeed) < millis()) {
dmd.drawFilledBox( 0,0,31,7, GRAPHICS_INVERSE);
ret=dmd.stepMarquee(-1,0);
timer=millis();
dmd.drawString( 1, 0, message1, 5, GRAPHICS_NORMAL );
if(Serial.available())
break;
}
}
}
//-----------------------------------------------------------------------------------
void serialEvent() {
delay(25); // Wait all data
r_char = Serial.read();
if(r_char < 0x20)
{
switch (r_char)
{
case ESC:
r_char = Serial.read();
if(r_char=='@')
{
Serial.write(ID);
}
break;
case CLR:
massage1BufClear();
massage2BufClear();
break;
case US:
r_char = Serial.read();
if(r_char=='X')
{
dmd.brightness = Serial.read();
}
if(r_char=='B'){
massage2BufClear();
while(Serial.available() > 0)
{
r_char = Serial.read();
if(index2 < (max_char2-1))
{
if(r_char > 0x1F)
{
message2[index2] = r_char;
index2++;
}
}
}
}
if(r_char==LF){
massage1BufClear();
while(Serial.available() > 0)
{
r_char = Serial.read();
if(index1 < (max_char1-1))
{
if((r_char > 0x29) and (r_char < 0x3A))
{
message1[index1] = r_char;
index1++;
}
}
}
massage1Normalization();
}
dmd.drawFilledBox( 0,8,31,15, GRAPHICS_INVERSE);
break;
default:
break;
}
}
while (Serial.available()) Serial.read();
dmd.writePixel(0,0,GRAPHICS_NORMAL,1);
}
Работа на полной яркости:
Работа с человеческой яркостью:
Работа с динамической подгрузкой времени с ПК при помощи скрипта на Python:
Все неровности с прокруткой обусловлены съемкой на камеру, для глаза с нормального растояния все выглядит вполне плавно и читабельно.
Стоит отметить что совершенно свободно можно соеденить несколько модулей последовательно и всего лишь указав это в скетче получить экран большего размера. Ограничения начинаются лишь после определенного количества панелей и связанны они с ограниченной производительностью Arduino и ее SPI интерфейса. Спасибо ElectricFromUfa за подробнейшее и развернутый обзор панелей и не только!
Возможно в будущем все тоже самое я проделают на STM32F103C8T6 и на ESP-12F, там все должно шевелится побыстрее.
Ссылки:
1. Ссылка на архив со скетчем и сопутствующим.
2. Еще ссылка на BitBucket
http://conture.by/post/1100
http://ediy.com.my/blog/item/116-arduino-driving-a-32x16-dot-matrix-display-panel
http://blog.vettore.org/building-a-large-led-sign-with-inexpensive-standard-modules-and-arduino/
http://digital-wizard.net/avr_projects/p10_led_display_panel_interface
https://geektimes.ru/post/275548/
https://forum.arduino.cc/index.php?topic=260320.0
https://www.youtube.com/channel/UChButpZaL5kUUl_zTyIDFkQ
Комментарии (26)
Syzd
27.06.2017 14:07Где его дешево купить?
svavan
27.06.2017 17:54Что именно?
Frolv
28.06.2017 08:52светодиодную матрицу.
svavan
28.06.2017 08:54На алиэкспрессе какой-то не адекват с ними, такое впечатление что их там продают только оптом. Я брал в укр. интернет магазине синюю за 11$, красные 6$, но это тоже завышенная цена, думаю у вас тоже должны быть аналогичные магазины. Еще я связывался с местными фирмами которые занимаются наружной рекламой, у них есть. И банально радиорынки, даже у нас их предлагают по 1000р.
apple01
27.06.2017 17:57> Хотя странно, так как всем известно что электроны, по обыкновению, текут от плюса к минусу. А если учесть что >электроны отрицательно заряженные то вообще не понятно почему они текут к отрицательному полюсу…
За направление электрического тока принимается направление движения положительных частиц, поэтому и говорят, что ток движется от плюса к минусу. Электроны же имеют отрицательный заряд, поэтому их направление движение будет обратным.Nansen09
28.06.2017 19:02+1Ток идёт от плюса к минусу, электроны — от минуса к плюсу, всё это внутри тоненького провода, всё это трётся друг об друга и об провод.
И всё это имеет ощутимые последствия: провод нагревается от этого трения электронов об ток.
Это тоже юмор.
bigbrotherwatchingyou
28.06.2017 07:49+1Всё это хорошо, но когда каждая вывеска как «вырви-глаз» — это уже начинает поднадоедать
extempl
28.06.2017 08:47Вот!
Пожалуйста, не надо делать очень заметные информеры. Вообще. Никогда.
А ещё (если уж делаете), то проверяйте насколько оно читабельно с расстояния в 100 метров в полной темноте.
Практика показывает, что часто видно одно яркое информ-пятно, которое информирует исключительно о желании сделать поярче и позаметней, но абсолютно бесполезно с точки зрения донесения информации.
avs24rus
30.06.2017 06:47Все хорошо, но зачем вы постоянно зачеркиваете текст? Думаете, что так выразительнее будет?
Ну так зачеркните всю статью тогда!
kostus1974
спа-си-бо! ))
как раз ищу дешёвое решение для вывода строк. делайте готовое решение: всё то же самое + аккуратней + короб + человеческий интерфейс (эзернет; можно просто через конвертер), и у вас будут это покупать. тогда конечно на стм и конвертер типа моховского nport (впрочем, это уже опция). главное тут — наработка на отказ и температурный режим. плюс простая закачка шрифтов (псевдографики по большому счёту).
а может я тоже буду это делать.
svavan
Просто мало кому надо динамически выводить что-то, а нам вот понадобилось. Тут собственно нечего и продавать.
kostus1974
вы не в промышленности, надо полагать.
сфера применения очень узкая, но, блин, нормальных решений тут поэтому и нет (или есть, но за невменяемые деньги; при этом вам тот же интегратор оборудования заломит бабки именно за кастомную разработку).
живые примеры:
— номера очередей для кпп с выводом номера очередного автомобиля + температура + реклама; это для сельского хозяйства, для предприятий складирования и переработки;
— температура + время + что угодно для обогатительных фабрик; здесь на каждом предприятии их десятки.
и та, и другая область казалось бы, при деньгах. это так. но деньги идут не в оборудование (у нас, в рф). на оборудование тут хотят копейки тратить.
на одном из наших предприятий поставили в качестве табло китайскую рекламную панель, которая выводит в себя малюсенький кусочек из потока vga. писец. то есть для этого нужен ещё и вообще отдельный пк с vga. да хрен бы ним, но такой костылище… а потому, что нет дешёвого и «правильного» решения.
svavan
Собственно к раскуриванию этих панелей и сподвигло отсутствие большого экрана покупателя для ритейла, хоть за сколь вменяемые деньги.
nikoloza
Присоединяюсь: готовых решений для последующей интеграции в другую систему — кот наплакал. Давно искал что-ти с ethernet и IP, в итоге пришлось колхозить на NodeMCU и заказывать пресс-формы у китайцев.