WS2812B в качестве экрана для самоделок на Arduino

Опубликовал | 26.02.2018

Всем доброго времени суток!
Хочу поделиться с Вами своими мыслями, о том можно ли приспособить WS2812B в качестве экрана для самоделок.
Конечного устройства я все еще не собрал, но вот небольшой дисплей, по-моему, получился. Всем кому интересно прощу под кат.
Давно хотелось попробовать поработать с такими светодиодами, но как обычно то руки не доходили, то зеленая в пупырышках голос подавала, а тут как то все сложилось и я стал счастливым обладателем 8 плат со светодиодами WS2812B.

От продавца каждая плата приходит запечатанной в антистатический пакет, так что поездку платы пережили хорошо.

Упаковка


Платы небольшие, размером всего лишь 3х3 см.

В конечном итоге после сборки дисплея его размер получился 6х12 см.

Сзади все выглядит не так хорошо как спереди, но это пока что тестовый образец.

На каждой плате есть две группы по 4 контакта, все контакты подписаны так, что сборка не составляет какого либо труда. Для правильной передачи сигнала данных в матрице главным условием является последовательное подключения контакта OUT предыдущей платы, к контакту IN последующей платы. Контакты VCC отвечают за "+" питания, их можно подключать к источнику питания в произвольном порядке. Идеальным решением будет параллельное подключение всех плат, что позволит обеспечить наиболее равномерную яркость светодиодов во всей матрице. Контакт GND отвечает за подачу "0" или "", все четыре контакта GND каждой платы соединены между собой и для работы платы достаточно подключить хотя бы один из них.

Как видно на фото, я использовал GND контакты для сборки всех плат в одну матрицу. Решение спорное, но для тестирования возможно. Так как изначально планировал разделить дисплей программно на четыре модуля, то и питание подавал на каждый из таких модулей отдельно. Линия данных в свою очередь подключена к каждой группе из двух плат последовательно: от нижнего модуля к верхнему и опять к нижнему. Можно было бы и не заводить данные именно так, но как сказал выше первой идеей было собрать матрицу состоящую из четырех логических индикаторов и для упрощения математики при нахождении номера требуемого светодиода в индикаторе решил собирать модули именно так.

На фото видно, что при такой сборке линии данных как раз и получается матрица из практически независимых сегментов. Для того что бы включить один и тот же светодиод в каждом сегменте достаточно знать номер светодиода для первого сегмента, количество светодиодов в сегменте (в моем случае их 32), и номер сегмента в котором нужно включить светодиод, а это уже всего лишь работа с прямоугольным массивом.
Но это я уже убежал немного вперед в программирование.
Так как дисплей получился не совсем жесткий, решил для него сделать временный корпус из пары кусочков полупрозрачного пластика (за пластик отдельное спасибо софтверной компании на букву М и ее детищу с цифрой 7 в названии).

Как оказалось, без рассеивателя диоды светят слишком жестко, поэтому в качестве рассеивателя был использован кусочек тонкого белого пластика.

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

Перед тем как окончательно перейти к программированию пару слов об энергопотреблении.

Согласно datasheet напряжение питания для таких светодиодов составляет от +3,5 до +5,3 вольта. А с током потребления все как то странно, в datasheet пишут 18 мА на цвет, другими словами при работе в белом цвете на максимальной яркости светодиод должен потреблять около 54 мА, а на форумах пишут о токе около 60-70 мА минимум.
Мой дисплей на 128 светодиодов питается напряжением 4,6В и при таком напряжении для белого света он потребляет ни много ни мало 2,49 А

Получается что ток 18 мА указанный в datasheet это ток не одного цвета, а всех трех.
Заливка одним из основных цветов (зеленым) потребляет 1.23 А.

А вот разноцветная мозаика потребляет всего 0,65А

Ну а теперь немного программирования:
Для управления дисплеем использовал библиотеку NeoPixel, поэтому все ниже сказанное относится в первую очередь к ней.
Сначала пару слов о том, как собственно можно управлять светодиодами с точки зрения программиста, не забираясь в то как сигнал передается по проводу.
Каждый светодиод в дисплее имеет свой порядковый номер, нумерация начинается с нуля, номера считаются последовательно в порядке подключения светодиодов. Для включения или выключения светодиода используется функция setPixelColor которая в качестве параметров принимает номер светодиода и его цвет в кодировке rgb. После установки/изменения цвета всех требуемых светодиодов необходимо выполнить функцию Show, которая отправит команду светодиодам принять установленный цвет.
По-большому счету только этих двух функций достаточно чтобы выводить на такой дисплей практически любую информацию.
Как только начал свои эксперименты с дисплеем, сразу обнаружил его главное преимущество: светодиоды запоминают полученный цвет. При работе с семисегментными индикаторами обычно требуется либо установить много регистров для хранения состояний каждого диода, либо динамически раз 40 в секунду обновлять картинку, а тут вывел один раз и хоть в сон отправляй контроллер.
Как и писал выше изначально думал поделить весь дисплей на 4 одинаковых индикатора и простым смещением на количество светодиодов выводить символы на нужный индикатор. Немного подумав, решил, что памяти пока что хватает, так что сделал единый массив констант, в который и занес массив номеров светодиодов.

Массив индексов
  Для отдельного индикатора  #define sm 32 // количество светодиодов в индикаторе  // матрица первого индикатора  byte seg[8][4]={{19,20,27,28},{18,21,26,29},{17,22,25,30},{16,23,24,31},{3,4,11,12},{2,5,10,13},{1,6,9,14},{0,7,8,15}};  //для индикатора N ID светодиода равно seg[Y][X]+(N*sm)    // матрица для все дисплея  byte seg[8][16]={{19,20,27,28,51,52,59,60,83,84,91,92,115,116,123,124}                  ,{18,21,26,29,50,53,58,61,82,85,90,93,114,117,122,125}                  ,{17,22,25,30,48,54,57,62,81,86,89,94,113,118,121,126}                  ,{16,23,24,31,48,55,56,63,80,87,88,95,112,119,120,128}                  ,{3 ,4 ,11,12,35,36,43,44,67,68,75,76,99,100,107,108}                  ,{2 ,5 ,10,13,34,37,42,45,66,69,74,77,98,101,106,109}                  ,{1 ,6 ,9 ,14,33,38,41,46,65,70,73,78,97,102,105,110}                  ,{0 ,7 ,8 ,15,32,39,40,47,64,71,72,79,96,103,104,111}                 };

Создание такого массива индексов позволяет просто по координатам X Y обратиться к любому светодиоду, не занимаясь вычислением его координат, что упрощает анимацию символов. Но с другой стороны такой подход, увеличивает размер программы и работать так с большими дисплеями не получится.
Следующим этапом было решить как хранить шрифт и где его нарисовать. Вспомнив, что в языке Arduino есть хорошая функция bitRead, которая позволяет обращаться непосредственно к каждому биту числа по его номеру, я решил, что хранить шрифт буду в виде массива двоичных чисел, а рисовать их получилось довольно удобно в Calc.

В итоге, получился файл с вот таким содержимым

Немного пояснения
Для закрашивания ячеек использована функция условного форматирования, а для формирования готового массива байт использовал функцию СЦЕПИТЬ.
В итоге, в ячейке получается готовый массив, который просто надо скопировать в код.
Например это цифра 5

  {B0100111,B1001001,B1001001,B0110001}

Итогом этого всего получился вот такой массив:

byte num[23][4]={{B01111110,B01000010,B01111110,B00000000}, //0                       {B01000100,B01111110,B01000000,B00000000}, //1                       {B01100100,B01010010,B01001100,B00000000}, //2                       {B01000010,B01001010,B01111110,B00000000}, //3                       {B00111000,B00100100,B01111110,B00000000}, //4                       {B01001110,B01001010,B01111010,B00000000}, //5                       {B01111110,B01001010,B01111010,B00000000}, //6                       {B01100010,B00010010,B00001110,B00000000}, //7                       {B01111110,B01001010,B01111110,B00000000}, //8                       {B01001110,B01001010,B01111110,B00000000}, //9                       {B00001000,B00011100,B00001000,B00000000}, //+                       {B00001000,B00001000,B00001000,B00000000}, //-                       {B01111110,B00000010,B01111110,B00000000},//П                       {B01111110,B01001010,B01110100,B00000000}, //В                       {B01111110,B01000010,B01100110,B00000000}, //С                       {B00011110,B00010000,B01111110,B00000000}, //Ч                       {B01111100,B00010000,B01111100,B00000000},//н                       {B00000100,B01111100,B00000100,B00000000},//т                       {B01111100,B00010100,B00011100,B00000000},//р                       {B01111100,B01010100,B01100100,B00000000},//б                       {B01111100,B01000100,B01101100,B00000000},//с                       {B00000000,B00001110,B00001010,B00001110},// градусы                       {B00000000,B00000000,B00000000,B00000000}// пустота        };

и функция, позволяющая вывести любой символ из из этого массива в любые координаты дисплея:

  #define matx 16  #define maty 8    void ShowSumbol(byte id, int x, int y, byte r, byte g, byte b,byte black)  // id индекс символа в таблице символов  //x,y координаты левого нижнего угла  //r,g,b цвет  //black выключить или нет неиспользуемые пиксели 1 выключать  {    for (int xi=0; xi<segx;xi++)      {        for (int yj=0; yj<segy;yj++)          {            if (bitRead(num[id][xi],yj)==1)              {                if ((y+yj)<maty && (x+xi)<matx)                  {                    if (((x+xi)>=0)&&((y+yj)>=0))  strip.setPixelColor(seg[y+yj][x+xi],strip.Color(r,g,b));                  }              }            if (bitRead(num[id][xi],yj)==0)                     {                    if (black==1) strip.setPixelColor(seg[y+yj][x+xi],strip.Color(0,0,0));                   }          }      }  }

Небольшое видео работы:

Еще попробовал использовать это дисплей для программного анализатора спекта работающего на основе библиотек быстрого преобразования фурье

Небольшое видео работы:

Для работы анализатора необходимо соединить левый или правый канал с аналоговым входом Ардуино (в моем случае вход 0), а общий провод соединить с землей.
На видео видео паразитный сигнал в первую очередь он вызван тем что не поставил на вход Ардуино даже элементарного фильтра, ну и конечно с качеством телефонного генератора.

Так же из за отсутствия ограничения уровня сигнала вся ответственность жизнь порта Ардуино ложиться на пользователя и подключать такой анализатор напрямую к выходу усилителя категорически не рекомендуется.
В конце кода есть строка задержки delay(15) добавил ее что бы картинка не так быстро мигала при работе с музыкой.

Исходный код
  #include <fix_fft.h>  #include <Adafruit_NeoPixel.h>  #define PIN 6  #define count_led 128 // количество светодиодов    Adafruit_NeoPixel strip = Adafruit_NeoPixel(count_led, PIN, NEO_GRB + NEO_KHZ800); //first number change does distance between colors  byte seg[8][16]={{0 ,7 ,8 ,15,32,39,40,47,64,71,72,79,96,103,104,111}                  ,{1 ,6 ,9 ,14,33,38,41,46,65,70,73,78,97,102,105,110}                  ,{2 ,5 ,10,13,34,37,42,45,66,69,74,77,98,101,106,109}                  ,{3 ,4 ,11,12,35,36,43,44,67,68,75,76,99,100,107,108}                  ,{16,23,24,31,48,55,56,63,80,87,88,95,112,119,120,128}                  ,{17,22,25,30,48,54,57,62,81,86,89,94,113,118,121,126}                  ,{18,21,26,29,50,53,58,61,82,85,90,93,114,117,122,125}                  ,{19,20,27,28,51,52,59,60,83,84,91,92,115,116,123,124}                 };  const int AUDIOPIN=A0;  char im[128], data[128], lastpass[64];  char data_a[16];   char data_d[16];  int i=0, val, vd=0;  char x=17, y=8;  int maxValue=30;    void setup() {       analogReference(DEFAULT);    for (int z=0; z<64; z++) {lastpass[z]=80;};     strip.begin();    strip.show();   }    void loop() {   for (i=0; i < 128; i++){                                         val = analogRead(AUDIOPIN);          data[i] = val;                                              im[i] = 0;                                                  }     fix_fft(data,im,7,0);   for (i=0; i< 64;i++){                                            data[i] = sqrt(data[i] * data[i] + im[i] * im[i]);          }   for (i=1; i<x; i++) {      data_a[i] = data[i*4] + data[i*4 + 1] + data[i*4 + 2] + data[i*4 + 3];  if(data_a[i]>maxValue)      {          data_a[i]=maxValue;      }      data_a[i] = map(data_a[i], 0, maxValue, 0,  y);      for (int j=0; j<y;j++)        {          if (data_a[i]<j)          {           strip.setPixelColor(seg[j][i-1],strip.Color(0,0,0));          }else          {            strip.setPixelColor(seg[j][i-1],strip.Color(map(j, 0, y, 0, 255),20,map(i, 0, x, 0, 255)));          }        }   }       strip.show();       delay(15);    }  

Выводы:
Использование светодиодов WS2812B для создания электронных табло почти идеально:
К плюсам можно отнести:
— простоту управления;
— простоту схемотехники;
— самостоятельное удержание картинки без необходимости обновления состояния светодиодов.
А вот к минусам можно отнести:
— большую по сравнения с обычными светодиодами цену;
— довольно крупный размер кристалла, из-за чего дисплей небольшого размера имеет очень маленькое разрешение.

Товар предоставлен для написания обзора магазином. Обзор опубликован в соответствии с п.18 Правил сайта.

Планирую купить 0 Добавить в избранное 0

(c) 2017 Источник материала

Рекламные ссылки