Ардуино: графический ЖК дисплей Nokia 5110

Символьный дисплей, который мы уже изучили ранее, открыл перед нами большие возможности в плане вывода информации в «человеческом» виде. Можно выводить текстовые сообщения, значения различных параметров, показания датчиков. Если. захотеть, можно даже отобразить на символьном дисплее «псевдографику». Но что, если нам требуется ещё больший уровень информативности? Если для понимания процесса нам нужен, например, график? Что, если мы хотим отобразить состояние робота не сигналом светодиода, а картинкой? Изобразить эмоцию?

IMG_20150927_150442_

Поможет нам в этом деле графический дисплей, а именно Nokia 5110 (или 3310). Этот вид дисплеев широко распространен в мире учебных микроэлектронных платформ, таких как Ардуино. Он легко подключается, и легко управляется даже слабыми микроконтроллерами.

Подключение дисплея Nokia 5110

Дисплей монохромный, имеет разрешение 84×48 точек. Как правило, дисплеи Nokia 5110, предназначенные для таких как мы с вами энтузиастов, поставляются на плате в паре с контроллером PCD8544 и штыревым разъемом. У такого дисплейного модуля есть всего 8 ног:

  • RST — сброс (Reset);
  • CE — выбор устройства (Chip Select);
  • DC — выбор режима (Data/Command select);
  • DIn — данные (Data In);
  • Clk — тактирующий сигнал (Clock);
  • Vcc — питание 3 — 5 Вольт;
  • BL — подсветка;
  • Gnd — земля.

Схема подключения к Ардуино:

ЖК дисплей Nokia 5110RSTCEDCDInClkVccBLGnd
Ардуино Уно76543+5V+5VGND

Программа. Вывод текста

Для управления дисплеем нам потребуется две библиотеки Adafruit_GFX и Adafruit_PCD8544, которые можно установить через менеджер библиотек либо скачать с github:

https://github.com/adafruit/Adafruit-GFX-Library/archive/master.zip
https://github.com/adafruit/Adafruit-PCD8544-Nokia-5110-LCD-library/archive/master.zip

После установки библиотек, пробуем вывести на наш дисплей простой текст «Hello world!»:

#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
Adafruit_PCD8544 display = Adafruit_PCD8544(3, 4, 5, 6, 7);

void setup() {
    // инициализация и очистка дисплея
    display.begin();
    display.clearDisplay();
    display.display();
    
    display.setContrast(50); // установка контраста
    delay(1000);
    display.setTextSize(1);  // установка размера шрифта
    display.setTextColor(BLACK); // установка цвета текста
    display.setCursor(0,0); // установка позиции курсора
  
    display.println("Hello, world!");
    display.display();
}

void loop() {
}

Как видим, все достаточно просто. Функция setContrast — определяет контраст дисплея. Это сродни тому, как если бы мы крутим потенциометр контраста в схеме символьного дисплея. Если после запуска программы у нас на дисплее ничего не видно, или видно плохо — варьируем значение контраста.

Задаем размер шрифта функцией setTextSize. Если указываем 1, то каждая буква шрифта будет размером 5×7 пикселей. Размер 2 увеличит символы ровно в два раза по высоте и ширине.

Следующая полезная функция — setTextColor. У нас есть всего два цвета — черный и белый. Для них определены две константы: BLACK и WHITE.

Наконец, setCursor(x, y) устанавливает курсор в заданные координаты x и y, так же как в программе символьного дисплея.

Непосредственно, сам вывод текста осуществляется уже знакомой функцией println.

Если все сделать правильно, получим такую картинку:

IMG_20150927_150646_

Программа. Вывод геометрии

Успешно решив задачу с выводом текста, попробуем теперь поработать с примитивной графикой. Нарисуем треугольники, окружности и прочие фигуры.

За отрисовку окружности отвечает функция drawCircle. Пример вызова функции для окружности с центром в точке {20,20}, радиусом 5 пикселей и черным цветом:

drawCircle(20, 20, 5, BLACK);

Теперь попробуем прямоугольник с координатами верхнего левого угла {10,10}, нижнего правого {20,20}, и цветом BLACK (черный, то бишь).

drawRect(10, 10, 20, 20, BLACK);

Функция drawRoundRect — нарисует прямоугольник со скругленными углами. Также у этих двух функций есть варианты со сплошной заливкой внутренней части фигуры: fillRect и fillRoundRect.

У треугольника следует задать все три пары координат:

drawTriangle(10, 10, 20, 10, 20, 20, BLACK);

И у него тоже есть вариант с заливкой — fillTriangle.

Наконец, просто отрезок из точки {0,0} к точке {20,20}:

drawLine(0, 0, 20, 20, BLACK);

И самое простое, что можно нарисовать — один пиксель:

drawPixel(10, 10, BLACK);

А вот как будет выглядеть программа, если мы захотим нарисовать некоторые из этих фигур, по-очереди:

#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>

Adafruit_PCD8544 display = Adafruit_PCD8544(3, 4, 5, 6, 7);

void setup() {
    // инициализация и очистка дисплея
    display.begin();
    display.clearDisplay();
    display.display();

    
    display.setContrast(50); // установка контраста
    delay(1000);
}

void loop() {
    // пиксел
    display.clearDisplay();
    display.drawPixel(10, 10, BLACK);
    display.display();
    delay(1000);

    // линия
    display.clearDisplay();
    display.drawLine(0, 0, 50, 30, BLACK);
    display.display();
    delay(1000); 

    // прямоугольник
    display.clearDisplay();
    display.drawRect(0, 0, 10, 10, BLACK);
    display.display();
    delay(1000); 

    // прямоугольник залитый
    display.clearDisplay();
    display.fillRect(0, 0, 10, 10, BLACK);
    display.display();
    delay(1000); 

    // треугольник
    display.clearDisplay();
    display.drawTriangle(0, 0, 40, 40, 30, 20, BLACK);
    display.display();
    delay(1000); 

    // окружность в центре
    display.clearDisplay();
    display.drawCircle(display.width()/2, 
                       display.height()/2, 10, BLACK);
    display.display();
    delay(1000); 
}

Программа. Подготовка изображений

А теперь, воспользуемся всей мощью графического дисплея — выведем на него изображение!

Чтобы отобразить на дисплее картинку, нам потребуется привести её к нужному размеру и формату. Как уже упоминалось, дисплей Nokia 5110 имеет разрешение 84×48 точек. Значит и наша картинка должна быть точно таким же размером.

Уменьшить и обрезать картинку до требуемых размеров можно в самом простом редакторе mspaint. Там же нужно сохранить картинку в формате BMP с 256 цветами.

imgtocode_paint
imgtocode_save

Следующий шаг — конвертировать bmp файл в массив нулей и единиц, который мы и вставим в программу для Arduino. Для конвертации воспользуемся онлайн сервисом на нашем сайте: http://git.robotclass.ru/tools/bmptobin.html

Просто выбираем подготовленный bmp файл на локальном компьютере, и указываем одну из систем счисления: двоичную или шестнадцатеричную. Обе одинаково подходят для нашей цели.

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

#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>

Adafruit_PCD8544 display = Adafruit_PCD8544(3, 4, 5, 6, 7);

const unsigned char PROGMEM smileBmp[] = {
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x1f, 0x80, 0x0, 0x0, 0x3c, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x7e, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x79, 0xc0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x71, 0xc0, 0x0, 0x1, 0xef, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x73, 0xc0, 0x0, 0x1, 0xc7, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x73, 0xc0, 0x0, 0x1, 0xc7, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x1, 0xef, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x0, 0xfc, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x1e, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x1f, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x1, 0xe0, 0x0, 0x0, 
0x0, 0x0, 0x3, 0xff, 0x0, 0x0, 0x0, 0xff, 0xf0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x7f, 0xfc, 0x0, 0x7, 0xff, 0xe0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0xf, 0xff, 0xff, 0xff, 0xfe, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x1, 0xff, 0xff, 0xff, 0x80, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x3, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
};

void setup() {
 // инициализация и очистка дисплея
 display.begin();
 display.clearDisplay();
 display.display();

 // установка контраста
 display.setContrast(50);
 delay(1000);

 // отрисовка изображения
 display.drawBitmap(0, 0, smileBmp, 84, 44, BLACK); 
 display.display();
}

void loop() {
}

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

IMG_20150927_032235_

Вот и все на сегодня! В следующем уроке на тему Nokia 5110 мы попробуем анимировать изображение на дисплее. Попробуем даже сделать небольшую игру!

ezgif.com-optimize

К размышлению

Дисплей Nokia5110 — очень популярен, но с каждым днём его всё сложнее найти. Дело в том, что именно этот ЖК дисплей производился для сотового телефона, который уже давным-давно был снят с производства.

Чем же заменить Nokia5110? Варианты есть, например:

А если вместо Ардуино использовать более мощные микроконтроллеры ESP32 или STM32, то можно рассмотреть широкий спектр TFT дисплеев с различным разрешением и даже варианты с сенсорной панелью.


Изменено:

Ардуино: графический ЖК дисплей Nokia 5110: 35 комментариев

      • Здравствуйте
        Откуда у вас такие широкие познания в программировании?
        И можно ссылочку на ваш следующий урок?

  1. а конвертер в бинарный массив работает?
    у меня все элементы массива равны 0x00

    • Работает!
      Обязательно нужно проверить формат исходного файла. Должен быть 256-цветный BMP. 8 бит на пиксел.

  2. C другого сайта брал подобный скетч, вылезла проблема, все выводит, а текст почему-то нет. В тот промежуток времени, кода должен выводиться текст просто пустой экран. Взял пример с http://playground.arduino.cc/Code/PCD8544 и у меня все выводит. Где собака порылась, даже в Proteus точно так-же себя ведет.

  3. а где ссылка на урок с анимацией ужо 2017 за два года можно было сделать?

    • Да, почему-то обещанного урока с анимацией мы так и не дождались. Сделайте нам урок, пожалуйста, особенно интересно было бы подробно узнать про меню и навигацию по нему.

  4. Здравствуйте!
    Интересная тема!
    Спасибо за Уроки и примеры!

    Бегло, в этой строке, в комментариях увидел ошибку.
    display.setCursor(0,0); // установка цвета текста
    Это «//установка курсора по заданным координатам»

  5. пока не изменил на display.setContrast(61); // установка контраста
    ничего не увидел.

  6. Чтобы заработало внес изменения в скетч:
    1.вставить вначале скетча строку
    #include
    2.Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3);
    3.увеличить контраст на 10 единиц
    display.setContrast(60);
    Спасибо!

  7. При вставке в коментарий ,почему то затирается название библиотеки,а остаётся только #include и больше никаких символов.
    Эта библиотека SPI для Arduino.

  8. День добрый!
    Не работает вообще конвертер, не могли бы скинуть рабочую ссылку или аналогичный конвертер?

    • Должен работать, постоянно им пользуемся. Может быть файл у вас имеет неподходящий формат? Как и в чем его сохраняете?

      • Пробовал варианты сохранения в paint в формате bmp (8, 16, 32 бита), не пошло, затем в Ps CC тоже в bmp с различной глубиной цвета

  9. Почините же пожалуйста конвертер. Поле «результат» остаётся пустым, если выбрать прямую организацию матрицы. Неоднократно уже использовал сервис, а тут такая подстава 🙁 или хоть дайте ссылку на офлайновый инструмент. LCD Assistant не заработал, пробовал разные настройки.

    • Хм, а выложите пожалуйста свой файл куда-нибудь со ссылкой, проверим. Сохраняете ровно в том формате, который указан в статье?

      • Ну, вот, например файл, который не получается сконвертировать.
        https://drive.google.com/file/d/1nmPx_n0kh934_-SPPbNrV9jX_Lcf18O2/view?usp=sharing

        Создан в MS Paint, сохранён в bmp с 256 цветами. При этом как обычно вылезало предупреждение о том, что цветопередача может быть искажена и всё. При этом попробовал картинки, которые раньше конвертировал, с ними получилось, хотя сохранялось так же.

  10. Укажите, пожалуйста, что кроме библиотеки Adafruit_GFX нужна еще библиотека Adafruit_PCD8544.
    Мелочь, конечно, но пару вечеров пока разобрался убил.

    • ну так в чем проблема самому догадаться, как заставить анимацию крутиться? ))) Берете разные наборы массивов и подставляете их с той частотой, с которой будет анимация. Сначала один блок показываете, потом другой, потом снова первый. Вот уже и анимация в 2 кадра

    • const unsigned char PROGMEM smileBmp[] = {
      0x0,0xF8,0x0,0x0,0x88,0x0,0x0,0x8C,0x0,
      0x0,0x98,0x0,0x0,0x70,0x7,0x0,0x20,0x87,
      0x0,0x20,0x87,0x0,0x23,0x0,0x7,0xFE,0x0,
      0x4,0x20,0x0,0x4,0x20,0x0,0x4,0x20,0x0,
      0xC,0x20,0x0,0x0,0x20,0x0,0x0,0x7F,0x80,
      0x0,0x40,0x80,0x0,0x40,0x80,0x0,0x40,0x80,
      0x0,0x40,0xC0,0x0,0x40,0x0,0x0,0x40,0x0,
      0x0,0x40,0x0,0x0,0xC0,0x0,0x0,0x0,0x0
      };
      const unsigned char PROGMEM smileBmp2[] = {
      0x0,0xF8,0x0,0x0,0x88,0x0,0x0,0x8C,0x0,
      0x0,0x98,0x0,0x0,0x70,0x0,0x0,0x20,0x0,
      0x0,0x20,0x0,0x0,0x20,0x0,0x0,0xF8,0x0,
      0x0,0xA8,0x0,0x0,0xA8,0x0,0x0,0xA8,0x0,
      0x1,0xAC,0x0,0x1,0x24,0x0,0x1,0x74,0x0,
      0x0,0x50,0x0,0x0,0x50,0x0,0x0,0x50,0x0,
      0x0,0x50,0x0,0x0,0x53,0x80,0x0,0x53,0x80,
      0x0,0x53,0x80,0x0,0xD8,0x0,0x0,0x0,0x0
      };
      void loop() {
      // Создать из BMP array HEX github.com/Jekahome/BMP_to_HEX_array
      // Анимация
      int X = random(display.width());
      int Y = 0;
      int SPEED = random(5) + 1;
      int w = 24;
      int h = 24;

      while (1) {
      //1. нарисовать
      display.drawBitmap(X, Y, smileBmp, w, h, BLACK);
      display.display();
      delay(200);

      //2. стереть
      display.drawBitmap(X, Y, smileBmp, w, h, WHITE);

      //3. перемещение
      X += SPEED;
      if (X > display.width()) {
      X = random(display.width()/2);// по горизонтали рандом появления
      Y = random(display.height()/2);// по вертикали рандом появления
      SPEED = random(5) + 1;// скорость рандом
      }

      //4. нарисовать
      display.drawBitmap(X, Y, smileBmp2, w, h, BLACK);
      display.display();
      delay(200);

      //5. стереть
      display.drawBitmap(X, Y, smileBmp2, w, h, WHITE);
      }
      }

  11. Преобразование намалеванного bmp файла в HEX последовательность на Rust’e

    «`

    use std::io::Write;
    use image::{
    GenericImageView,
    ImageBuffer,
    Luma
    };
    /// Dependencies
    ///
    /// [dependencies]
    /// image = «0.23»

    /// Example
    ///
    /// $ cargo run ../member2.bmp
    fn main() {
    if std::env::args_os().len() >1 {
    let args: Vec = std::env::args().collect();
    let filename = &args.last().unwrap();
    let file_path = std::path::Path::new(filename);
    if file_path.exists() {
    let source = image::open(file_path).unwrap();
    let (width, height) = source.dimensions();
    let gray = source.to_luma();
    let len:usize = ((width*height)/8).try_into().unwrap();
    let mut buf: Vec = Vec::with_capacity(len);
    let mut one_unit = String::from(«»);
    for (c,pix) in gray.to_vec().iter().enumerate(){
    let logic= match pix{
    n if n > &1 => «0»,
    _ => «1»
    };
    one_unit.push_str(logic);
    if one_unit.len() == 8{
    buf.push(one_unit);
    one_unit = «».to_owned();
    }
    }
    if one_unit.len() > 0{
    buf.push(one_unit);

    }
    for (c,i) in buf.iter().enumerate(){
    if c%(width/8) as usize==0 {
    println!(«»);
    }
    print!(«{:#X},», u32::from_str_radix(i, 2).unwrap());
    }
    let mut out = std::fs::File::create(«../output_hex»).unwrap();
    let mut buf_out = std::io::BufWriter::new(out);

    for (c,i) in buf.iter().enumerate(){
    if c%(width/8) as usize==0 {
    writeln!(buf_out,»»);
    }
    write!(buf_out, «{:#X},», u32::from_str_radix(i, 2).unwrap()).unwrap();
    }
    }else{
    println!(«Warning:\n File {:?} not found in this scope»,filename);
    }
    }else{
    println!(«Run:\n $cargo run «);
    }
    }

    «`

Добавить комментарий для Yurchil Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.