Arduino и OLED: управление дисплеем

Система управления светодиодным табло

Аппаратная часть – Arduino

В качестве основных мозгов устройства, естественно, выступил микроконтроллер Arduino с родным шилдом и некоторым навесным:

Во-первых, нужен оригинальный шилд (или его точная китайская копия) с USB-COM преобразователем на Atmega16u2, т.к. более простые аналоги обычно не поддерживают высокую скорость передачи данных (у меня – 2 мегабода в секунду) или работают на ней с большим количеством ошибок. Также, такую скорость должен поддерживать сам контроллер USB вашего компьютера и его драйвер.

Дополнительное оборудование

Так как основная функция моего табло – это часы, оно должно работать постоянно, независимо от компьютера. Для этого необходим резервный источник питания, для которого отлично подошёл адаптер питания для смартфона – HTC E250. Его номинал – 5V 1A, но по факту он и полторашку выдаёт без проблем. Также этот адаптер сертифицирован по пятому классу энергоэффективности, что говорит о его низком нагреве и высоком КПД при работе.

Для переключения между питанием от компьютера и этим устройством служит обычная релюха, правда я решил ничего не паять и просто приклеил к самому шилду термоклеем и подключил обычный пятивольтовый модуль реле для Arduino.

Фоторезистор – датчик освещённости. Разумеется, необходима мгновенная автоматическая регулировка яркости изображения на табло – включили свет в комнате – яркость выше, ночью – ниже.

Пара конденсаторов – для того, чтобы в момент выключения компьютера и перещёлкивания реле питание на контроллере не пропадало и часы, соответственно, не сбивались. Действительно, помогает, даже без диодов.

Схему подключения приводить не буду: всё и так понятно по фото, а если нет – изучение скетча окончательно прояснит ситуацию.

Драйвер (управляющее ПО для компьютера)

Управляющую прогу я написал на Delphi с использованием библиотеки ComPort Library. По умолчанию она сидит в трее, но при разворачивании выглядит вот так:

Исходники выкладывать не буду, т.к. там всё написано кривовато и на скорую руку, под себя. К тому же я планирую перевести её функции полностью на Arduino, чтобы табло вообще не зависело от компьютера.

Как видите на скрине, в моей проге предусмотрен редактор начертаний всех символов, их можно перерисовывать на лету, причем не только буквы, но и разные пиктограммы:

Можно вывести любой текст или последовательность чисел. Узнаёте эту?

Предусмотрены не только строчные буквы, но и прописные:

Но основное его назначение – вывод текущего трека (Now Playing) с LastFM. Вот так это выглядит вживую (Сектор Газа):

Или вот вариант с зарубежным исполнителем:

Выводится эта бегущая строка каждые 30 секунд.

Также, при просмотре какого-нибудь фильма или сериала в Media Player Clssic – Home Cinema, на экран сначала выводится название видеофайла и путь к нему:

А потом текущее время, состояние воспроизведения и оставшееся время до конца файла:

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

Перспективы

Как я уже сказал, в перспективе я планирую не только отказаться от ПО на компьютере, перенеся все функции на сам микроконтроллер, но и добавить новые, такие, как вывод текущей температуры в помещении и на улице, влажности и т.д. используя замечательный датчик DHT22. Разумеется, я об этом обязательно расскажу!

Позже я планирую сделать аналогичное табло у себя в зале, только длиной оно будет метров 5. Для этого, естественно, понадобится много ленты, но я думаю, что при грамотном управлении и красивом исполнении (встраивании в стену) это табло / бегущая строка, будет очень круто смотреться, особенно с полной интеграцией в систему “умный дом”, которую я также планирую внедрять и, конечно же, рассказывать обо всём вам, так что подписывайтесь на новые статьи, чтобы ничего не пропустить!

А, ещё у меня есть идея вывода на это табло бегущей строкой приходящих на Android уведомлений! Было бы чётко!

Custom Character

lcd.print() function supports only ASCII characters. If you want to display a special character or symbol (e.g. heart, angry bird), you need to use the below character generator.

LCD 16×2 can display 32 characters (2 rows and 16 columns). Each character is composed of 40 pixels (8 rows and 5 columns).

The character generator represents a character (40 pixels). You just need to do the following steps:

Click on each pixel to select/deselect

Clear

Copy below custom character code

Replace the customChar in the below code

#include <LiquidCrystal.h>

const int RS = 11, EN = 12, D4 = 2, D5 = 3, D6 = 4, D7 = 5;
LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);

byte customChar = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

void setup()
{
lcd.begin(16, 2);

lcd.createChar(0, customChar);

lcd.setCursor(2, 0);
lcd.write((byte)0);
}

void loop()
{

}

Result on LCD:

Multiple custom characters

We can create up to 8 custom characters (indexed 0 to 7). The below example creates and displays three characters.

#include <LiquidCrystal.h>

const int RS = 11, EN = 12, D4 = 2, D5 = 3, D6 = 4, D7 = 5;
LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);

byte customChar0 = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

byte customChar1 = {
0b00100,
0b01110,
0b11111,
0b00100,
0b00100,
0b00100,
0b00100,
0b00100
};

byte customChar2 = {
0b00100,
0b00100,
0b00100,
0b00100,
0b00100,
0b11111,
0b01110,
0b00100
};

void setup()
{
lcd.begin(16, 2);

lcd.createChar(0, customChar0);
lcd.createChar(1, customChar1);
lcd.createChar(2, customChar2);

lcd.setCursor(2, 0);
lcd.write((byte)0);

lcd.setCursor(4, 0);
lcd.write((byte)1);

lcd.setCursor(6, 0);
lcd.write((byte)2);
}

void loop()
{
}

Result on LCD:

Summary: how to use custom character on LCD

  • Use the above character generator to create binary code for the custom character.

  • Declare the binary code for the custom character (copy from above step)

byte customChar = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

Create custom character and assign to an index value (from 0 to 7) in setup() function

lcd.createChar(index, customChar);

Print the custom character in LCD anytime, anywhere (in setup() or loop() function)

Сборка

Картонная основа для табло

Начал я, естественно, с нарезки оставшейся ленты на 5 кусков по 39 светодиодов каждый, после чего начал готовить поверхность для их наклеивания. Так как табло должно было быть достаточно жёстким (но эксплуатироваться исключительно в помещении), я взял прямоугольный лист очень плотного картона и отрезал от него 2 полосы нужной ширины, после чего склеил их между собой в длину, приклеив на шов с обратной стороны 2 пластиковых карточки, которые жёстко скрепили эти две полоски между собой: получилась одна длинная картонная полоса из двух половинок, чуть длиннее, чем сами отрезки ленты.

Чтобы двухсторонний скотч 3M, на котором лента сидела по заводу, хорошо приклеился к получившейся картонной полосе, я обклеил всю её лицевую поверхность бумажными глянцевыми страницами каталога какого-то строймаркета из почтового ящика.

Наклеивание ленты

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

Коммутация

Естественно, всё должно было выглядеть цивильно, поэтому все провода я решил спрятать с обратной стороны изделия. Для этого я с обеих сторон каждого отрезка светодиодной ленты пробил по 3 отверстия для проводов (Земля, 5V и управление):

И начал последовательно продевать и припаивать соответствующие провода:

После этого я замотал места пайки с обеих сторон чёрной изолентой:

А с обратной стороны соединил между собой все группы силовых проводов, заизолировав соединения термоусадкой:

С управляющим проводом я пошёл на небольшую хитрость: дело в том, что ленты нужно было соединить последовательно, только вот в каком порядке? Чтобы сэкономить провод, можно было бы, конечно, просто соединять концы ленты то слева, то справа, но, в итоге, это бы вылилось в полнейший ад при написании прошивки, ведь для вывода определённого символа в определённую позицию табло нужно знать конкретные номера затрагиваемых этим символом светодиодов. А как быстро вычислить номера светодиодов на конкретной позиции, когда, например, на нечётных строках они считаются слева направо, на чётных – справа налево? Это куча лишних вычислений, вместо которых можно было бы пару лишних раз обновить содержимое всего табло.

Поэтому я выбрал другой способ – конец каждой полосы соединяется с началом каждой следующей. Для этого пришлось протянуть 4 длинных провода через всё изделие, но я использовал обычную витую пару пятой категории: у меня её столько, что не жалко совсем. Зато для адресации каждого нижестоящего светодиода теперь надо было просто добавить к номеру текущего 39 – количество светодиодов в одной строке табло. Вот и всё, никакого алгоритма, никаких сложных вычислений – отличное решение!

Для подключения табло к Arduino я использовал обычный МГТФ.

Само табло я, разумеется, решил запитать от компьютера, для чего я, также как и в статье про Эмбилайт на Arduino, выбрал толстые провода, чтобы на них минимально проседало напряжение, ведь табло должно располагаться в нескольких метрах от источника питания, а сам источник (Corsair AX860i) – с цифровым управлением и по пятивольтовой шине выдаёт ровно 5.00 вольт, так что любые потери на кабеле – это потери яркости готового устройства.

Все соединения силовых кабелей я решил сделать модульными, используя такие разъёмы “папа”:

И такие разъёмы “мама”:

Естественно, это дало возможность делать удлинители, тем самым подбирая длину кабеля в зависимости от места установки. Для подключения к компьютерному БП я использовал штыри, подходящие по диаметру к разъёму Molex.

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

Придание “товарного” вида

Так как табло всё ещё выглядело как чёрт знает что, ему нужно было придать товарный вид. И тут я не нашёл ничего лучше, кроме как нарезать полоски определённой ширины из белых листов бумаги формата A4 и заклеить ими всё межсветодиодное пространство на морде. Сначала вертикальные полосы:

А потом и горизонтальные:

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

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

About LCD 16×2

Pinout

LCD has up to 16 pins. In the most common uses, we do NOT use all pins.

With the support of LiquidCrystal library, we even can use LCD WITHOUT knowing the meaning of these pins. However, if you are curious or want to know in-depth, let’s see these pins and their functionality:

  • GND pin: needs to be connected to GND (0V).

  • VCC pin: the power supply for the LCD, needs to be connected to VCC (5V).

  • Vo (LCD Contrast) pin: controls the contrast and brightness of the LCD, can be connected to 5V (the highest contrast and brightness), or connected to a potentiometer (to adjust to the contrast and brightness)

  • RS (Register Select) pin: There are two kinds of data that need to send to LCD: command (to control LCD) and data. These two are sent on the same data bus. RS pin tells the LCD whether the data on the data bus is the commands or the data.

    • If we want to send the command to control LCD, we need to set RS pin to LOW (like set the cursor to a specific location, clear the display …).

    • If we want to send the data to display on LCD, we need to set RS pin to HIGH.

  • R/W (Read/Write) pin: is to select READ mode or WRITE mode.

    • If we want to read data from LCD, this pin needs to be set to HIGH.

    • If we want to send data to LCD, this pin needs to be set to LOW. Since we’re just using this LCD as an OUTPUT device, we’re going to tie this pin LOW.

  • EN (Enable) pin: is used to enable the LCD. HIGH to enable the LCD, LOW to disable the LCD.

  • D0-D7 (Data Bus) pins: carries data and command between Arduino and LCD. There are two modes to send data: 4-bit mode and 8-bit mode.

  • A-K (Anode & Cathode) pins: are used to power the LCD backlight. A pin needs to be connected to VCC. K pin needs to be connected to GND.

4-bit mode and 8-bit mode

  • 8-bit mode: 8 bits of a byte are sent at the same time in pin D0 to D7.

  • 4-bit mode: 8 bits of a byte is sent two times, each time 4 bits in pin D4 to D7.

8-bit mode is faster than the 4-bit mode, but use more pins than 4-bit mode. The mode selection is performed at the initialization process by sending a command to LCD.

This tutorial uses 4-bit mode, which is the most common-used.

In this mode, LCD’s pins:

  • 6 pins (RS, EN, D4, D5, D6, and D7) are connected to Arduino’s pin.

  • 4 pins (D0, D1, D2, and D3) are NOT connected.

  • 6 remaining pins are connected to GND/VCC or potentiometer.

LCD pin table in 4-bit mode

LCD PIN CONNECTED TO
01 GND GND
02 VCC 5V
03 Vo 5V or potentiometer’s pin
04 RS An Arduino’s pin
05 R/W GND
06 EN An Arduino’s pin
07 D0 NOT connected
08 D1 NOT connected
09 D2 NOT connected
10 D3 NOT connected
11 D4 An Arduino’s pin
12 D5 An Arduino’s pin
13 D6 An Arduino’s pin
14 D7 An Arduino’s pin
15 A 5V
16 K GND

LCD 16×2 includes 16 columns and 2 rows. the conlums and rows are indexed from 0.

How It Works

This section is the in-depth knowledge. DON’T worry if you don’t understand. Ignore this section if it overloads you, and come back in another day. Keep reading the next sections.

The process of sending data (to be displayed) to LCD:

  1. Arduino sets RS pin to HIGH (to select data register)
  2. Arduino writes data to D4 → D7 pins (data bus).
  3. LCD receives data on the data bus.
  4. LCD stores the received data in the data resistor since the RS pin is HIGH. Then, LCD displays the data on the screen

The process of sending command (to control) to LCD (e.g, blink LCD, set the cursor to a specific location, clear the display …):

  1. Arduino sets RS pin to LOW (to select command register)
  2. Arduino writes command to D4 → D7 pins (data bus).
  3. LCD receives data on the data bus.
  4. LCD stores the received data in the command resistor since the RS pin is LOW. Then, LCD takes action based on the value of the command.

How To Program For LCD

#include <LiquidCrystal.h>

Define which Arduino’s pin connected to six LCD’s pins: RS, EN, D4, D4, D6, D7

const int RS = 11, EN = 12, D4 = 2, D5 = 3, D6 = 4, D7 = 5;

One of the advantages of the library is that Arduino’s pin connected to LCD is settable. This makes it flexible when you connect Arduino with LCD and other sensors/actuators.

Declare a LiquidCrystal object:

LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);

Set up the LCD’s number of columns and rows.

lcd.begin(16, 2);

Move cursor to the desired position (column_index, row_index)

lcd.setCursor(column_index, row_index);

Print a message to the LCD.

lcd.print(“Hello World!”);

There are many things more that we can do with LCD (see Do More with LCD part)

Проектирование

Пиксельное разрешение

Первое, о чём нужно было подумать – разрешение этого табло. Так как я планировал выводить на него, в первую очередь, текстовую и числовую информацию, я сразу прикинул, каким должно быть разрешение, то есть, количество пикселей (светодиодов) по вертикали. Я начал прикидывать в уме изображение всех арабских цифр: для них полностью хватало высоты в 5 строк. С буквами, конечно, было посложнее, тем не менее все, как минимум, заглавные буквы русского и английского алфавитов также более менее умещались в этот формат, за исключением таких букв, как Ё и Й.

Так как у меня оставалось всего 198 светодиодов, нетрудно было подсчитать, что табло должно было получиться с разрешением 39×5 пикселей.

Управление

Так как у меня отсутствовал модуль Ethernet, да и программирование на голом C меня не особо-то и радовало, я решил оставить на Arduino лишь базовые функции – часы, яркость и способность удалённого управления каждым пикселем с компьютера по USB, а уже на компьютере написать ПО (или драйвер, если будет так угодно), которое бы устанавливало время, получало текущую песню и выводило любую информацию на табло. У такого подхода есть как минусы (зависимость работы дополнительных функций от компьютера), так и неоспоримые плюсы:

  • Можно сделать удобный редактор шрифта и менять варианты начертания каждого символа “на лету”, не перепрошивая каждый раз микроконтроллер.
  • Можно придумать любое количество шрифтов / символов без ограничения встроенной памяти микроконтроллера (хотя надо очень постараться, чтобы для этого не хватило 32х килобайт Atmega328P)
  • Более высокоуровневый язык программирования означает повышенную простоту и уменьшенное время разработки любых решений.
  • Возможность выводить не только инфу, полученную из внешних источников, но и какие-либо данные с самого компьютера (не реализовал за ненадобностью).
Like this post? Please share to your friends: