OLED дисплей SSD1306 (128х64px) и Raspberry Pi, подключение и эксперименты
Рассмотрены примеры работы с маленьким (128х64 пикселей) и экономичным OLED-дисплеем на основе микросхемы SSD1306. Для управления использована платформа Raspberry Pi, а программы будем писать на языке Python.
Приведу примеры кода для вывода на экран строчки текста, рисунков в формате PNG, а также проведу небольшой практический эксперимент с отображением на экране значения от датчика качества воздуха.
Содержание:
- Дисплей на основе SSD1306 и что такое OLED
- Принципиальная схема
- Подключение дисплея
- Библиотека для работы с SSD1306 на Python
- Управление дисплеем
- Выводим текст на экран
- Используем внешний TrueType шрифт (TTF)
- Отображаем символы кириллицы (TTF)
- Рисуем геометрические фигуры
- Отображаем рисунки
- Подготовка рисунков
- Индикатор качества воздуха
- Заключение
Дисплей на основе SSD1306 и что такое OLED
SSD1306 - это название универсальной микросхемы-контроллера, на основе которой построены миниатюрные, экономичные OLED-дисплеи.
Матрица таких дисплеев изготовляется по технологии OLED (Organic Light-Emitting Diode). Если простыми словами, то каждый пиксель такой матрици представляет из себя светоизлучающий прибор, который построен на основе органических соединений, при подаче электрического тока на такие соединения они начинают излучать свет.
В магазинах электронных компонентов можно найти достаточно много разных дисплеев которые построены на данной микросхеме. В данной статье я буду рассматривать дисплей с разрешением 128х64 пикселей (0,96 дюйма) и шиной для управления - I2C.
Рис. 1. Экономичный OLED дисплей 128х64 пикселей на SSD1306 с шиной I2C.
Основные характеристики дисплея:
- Драйвер - SSD1306;
- Размер экрана - 0.96 дюйма;
- Тип матрицы - OLED;
- Разъем для подключения - 4-pin (I2C + питание);
- Напряжение питания - от 3,3В до 5В;
- Потребляемый одним сегментом ток - 100 мкА;
- Общий потребляемый ток - примерно 15(20) мА;
- Разрешение матрицы дисплея - 128x64 пикселей;
- Угол обзора - более 160 градусов;
- Размеры - 11x27x27мм (толщина без разъема - 4мм);
- Вес - 4 грамма;
- Рабочая температура - от –30 до +70 градусов по Цельсию.
Каждый пиксель такого дисплея - это органический светодиод. Поэтому, потребляемый дисплеем ток прямо пропорционален количеству светящихся, в данный момент, пикселей.
Также важно знать, что через 3-5 лет своей работы, яркость свечения пикселей у дисплея может уменьшиться. Этого не стоит сильно опасаться, тем не менее, если дисплей не используется, то его пиксели все же лучше погасить (выключить) - это продлит его ресурс и сбережет некоторое количество энергии.
Приобретенный мною дисплей - двухцветный, верхняя часть (15 пикселей) светится желтым цветом, а остальная часть дисплея - синим. Благодаря двум цветам, на дисплее можно удобно разделить информационную и рабочую область, к примеру если вы строите самодельный GSM-мессенджер или еще что-то подобное, где вверху дисплея удобно выводить строку статуса и различные значки.
Дисплей SSD1306 отлично подойдет для какого-то маленького самодельного устройства, он миниатюрный, очень экономичный и имеет достаточно яркое, контрастное свечение.
При покупке такого OLED-дисплея нужно быть внимательным, поскольку из описания к нему часто можно подумать, что в нем каждый пиксель может светиться двумя разными цветами, но это не так. Двухцветная модель стоит немного дороже монохромной (один цвет, например белый).
Принципиальная схема
Ниже приведена принципиальная схема модуля с дисплеем SSD1306. Из схемы видно что на плате размещен сам дисплей, микросхема-стабилизатор напряжения (3,3В), а также несколько конденсаторов и резисторов для обвязки.
Резисторы R6 и R7 - подтягивающие для шины I2C. Если вы подключаете к шине несколько устройств то нужно оставить подтягивающие резисторы только на одном устройстве, а на остальных - выпаять. К примеру на платке с дисплеем можно резисторы оставить. а на всех остальных устройствах - выпаять! Об этой особенности я уже рассказывал в статье где мы знакомились с шиной I2C.
Рис. 2. Принципиальная схема модуля SSD1306.
Подключение дисплея
Для подключения дисплея к устройствам и управления отображением информации на нем используется шина I2C (4 провода - VCC, GND, SDA, SCL), о том как работать с этой шиной я писал в статье по ADC-DAC PCF8591.
Адрес дисплея на шине I2C по умолчания - 0x3c. Для подключения к одной шине двух таких дисплеев нужно задать им разные адреса, сделать это можно перепаяв перемычку (резистор) на плате дисплея под названием "IIC ADDRESS SELECT".
В моем варианте адреса на обратной стороне указаны не верно (привет китайцам): 0x78 и 0x7A, в реальности дисплей имеет адрес 0x3c, в этом можно убедиться подключив его к Raspberry Pi и просканировав устройства на шине при помощи команды "i2cdetect". После перепайки перемычки нужно будет определить новый реальный адрес, это может быть 0x3d или другой.
Для подключения трех и более таких дисплеев (или других устройств с одинаковым адресом) на одну шину I2C можно использовать I2C-мультиплексоры (I2C Multiplexers/Switches), например микросхему PCA9540BD или другие с нужными параметрами и количеством каналов.
Ниже приведена простая схема подключения дисплея к Raspberry Pi 2, для питания используем пин 1 (3,3В).
Рис. 3. Схема подключения дисплея SSD1306 к Raspberry Pi2 по шине I2C.
Библиотека для работы с SSD1306 на Python
Для экспериментов с дисплеем мы напишем несложные программы на языке Python, будем использовать готовые библиотеки написанные разработчиком Ричардом Халом (Richard Hull).
Первым делом создадим временный каталог для наших экспериментов:
mkdir ~/Temp && cd ~/Temp
Библиотеки для работы с SSD1306 на Python можно скачать из репозитория - https://github.com/rm-hull/ssd1306.
Примечание: на момент написания статьи была использована бибилиотека версии "0.2.0", сейчас же проект Ричарда вырос, появились новые возможности и зависимости, понятное дело что и нужные нам файлы также изменились. Поэтому для приведенных ниже примеров будем использовать архив - сохраненную копию библиотеки версии "0.2.0" без папки "doc".
Выполним на raspberry Pi приведенные ниже команды, создадим новый каталог, скачаем и локально распакуем библиотеку:
mkdir ~/Temp/ssd1306
cd ~/Temp/ssd1306
wget -c "https://ph0en1x.net/uploads/File/ssd1306-0.2.0-no-doc.tar.gz"
tar -xf ssd1306-0.2.0-no-doc.tar.gz
В приведенных ниже примерах мы не будим устанавливать в систему данную библиотеку. По сути, для использования в наших примерах нам нужны только два файла device.py и render.py, а также установленная библиотека Pillow (для работы с изображениями) для Python.
Чтобы проверить установлен ли модуль Pillow для Python в операционной системе нашего Raspberry Pi, выполним следующую команду, подключившись для этого к малинке по консоли:
sudo pip list | grep Pillow
Если библиотека присутствует то будет выведена ее название и версия, а если же вывод пуст то нужно установить данную библиотеку следующей командой:
sudo pip install Pillow
Теперь скопируем в текущую директорию (~/Temp) из недавно склонированного репозитория файлы device.py и render.py. Для этого выполним две команды:
cp ~/Temp/ssd1306/oled/device.py ~/Temp
cp ~/Temp/ssd1306/oled/render.py ~/Temp
Теперь все готово к проведению экспериментов!
Управление дисплеем
Управление дисплеем осуществляется отправкой команд на шину I2C с указанным адресом устройства. Ниже приведу несколько примеров команд для управления параметрами дисплея SSD1306, их можно использовать совместно с другими примерами из статьи.
Включение дисплея:
device.command(0xAF)
Выключение дисплея:
device.command(0xAE)
Минимальная контрастность:
device.command(0x81)
device.command(0x00)
Максимальная контрастность:
device.command(0x81)
device.command(0xFF)
Здесь первая команда со значением "0x81" указывает что нужно изменить контрастность, а последующая команда со значением "0x00" задает уровень контрастности. Значение контрастности может быть в пределах от 0x00 до 0xFF, например: 0x11, 0xCC и т.п.
Полное описание других команд для управления дисплеем можно узнать из даташита на SSD1306, в разделе "10. COMMAND DESCRIPTIONS".
Выводим текст на экран
В первом эксперименте с дисплеем SSD1306 мы выведем текст "Hello World" в двух строчках - сверху и по середине экрана. Откроем для редактирования новый файл:
nano ~/Temp/ssd1306_text_hello_wolrd.py
Скопируем в окно редактора следующий код:
#!/usr/bin/env python
# "Hello World" for display SSD1306 in Raspberry Pi
# 2018 https://ph0en1x.net
from device import ssd1306
from render import canvas
from PIL import ImageFont
from time import sleep
device = ssd1306(port=1, address=0x3C) # for RPi rev 2 port(smbus) = 1
font = ImageFont.load_default()
with canvas(device) as draw:
draw.text((10, 0), "*** Hello World ***", font=font, fill=255)
draw.text((20, 30), "Hello World! :)", font=font, fill=255)
sleep(3) # Wait 3 seconds.
device.command(0xAE) # Display OFF.
sleep(1) # Wait 1 second.
device.command(0xAF) # Display ON.
sleep(1)
device.command(0xAE)
sleep(1)
device.command(0xAF)
Выходим из редактора (Ctrl+X) и сохраняем файл (Y и Ентер), после этого можем запустить программу и посмотреть результат:
python ~/Temp/ssd1306_text_hello_wolrd.py
В результате дисплей должен "ожить" и на нем появятся строчки с приветствием и через 3 секунды изображение пропадет и появится 2 раза:
Рис. 4. Результат работы программы hello_world для ssd1306.
Код программы не сложный, всю основную "низкоуровневую" работу выполняют библиотеки и модуль Pilow, поэтому расскажу кратко. Сначала мы импортируем класс ssd1306 (для работы с дисплеем по шине I2C) из модуля device (файл deice.py), потом из модуля render (файл render.py) импортируем класс canvas (для подготовки области вывода на дисплей), оба файла должны лежать в одной директории с файлом программы. Также из библиотеки PIL (Pillow, установлена в системе) импортируем ImageFont для работы со шрифтами.
Для использования функции задержки по времени "sleep" выполнен ее импорт из модуля "time" - "from time import sleep".
Инициализация дисплея и получение экземпляра класса для устройства выполняется в строке "device = ssd1306(port=1, address=0x3C)", где адрес устройства на шине I2C - "0x3C", а адрес шины I2C - "1" (1 - для Raspberry Pi rev2, 0 - для rev1).
Для рисования выполним подготовку шрифта "font = ImageFont.load_default()" - здесь используется шрифт по умолчанию (позже в примерах мы попробуем использовать свой внешний шрифт).
С помощью "canvas(device)" мы выполняем подготовку области вывода в нужных размерах для отображения на экране, для отображения в этой области готовим две строчки с текстом, который рисуется пикселями с применением ранее подготовленного шрифта.
Если более детально, то в приведенном примере "draw" является экземпляром класса ImageDraw библиотеки Pillow, в котором уже содержится набор методов для рисования различной 2D-графики. Этот экземпляр нам подготовлен и передан из класса canvas (модуль render.py).
Строчкой кода "draw.text((10, 0), "*** Hello World ***", font=font, fill=255)" мы готовим к выводу текст "*** Hello World ***" в координатах экрана x=10, y=0 и с заполнением - 255 (если 0 - то не отображается).
В следующей строчке кода мы готовым к выводу еще одну строку текста, задаем ей координаты так чтобы она была помещена примерно по центру.
И напоследок, в конце программы реализовано включение и выключение дисплея с интервалом в 1 секунду "sleep(1)". Таким образом, выведенное на дисплей изображение два раза исчезнет и появится (мигнет).
Используем внешний TrueType шрифт (TTF)
Для следующей демонстрации мы скопируем из репозитория файл со шрифтом "C&C Red Alert [INET].ttf", выполним команду:
cp ~/Temp/ssd1306/fonts/C\&C\ Red\ Alert\ \[INET\].ttf ~/Temp/ra.ttf
В директории ~/Temp появится файл с названием "ra.ttf".
Теперь модифицируем немного предыдущую программу таким образом, чтобы текст по середине дисплея выводился с использованием шрифта из файла "ra.ttf" и был покрупнее в размере.
Создадим новый файл для этого эксперимента:
nano ~/Temp/ssd1306_text_hello_wolrd_font_ra.py
Поместим в редактор код для новой программы:
#!/usr/bin/env python
# "Hello World" for display SSD1306 in Raspberry Pi using TTF Font
# 2016 https://ph0en1x.net
from device import ssd1306
from render import canvas
from PIL import ImageFont
device = ssd1306(port=1, address=0x3C) # for RPi rev 2 port(smbus) = 1
font = ImageFont.load_default()
font_ra = ImageFont.truetype('ra.ttf', 20)
with canvas(device) as draw:
draw.text((10, 0), "*** Hello World ***", font=font, fill=255)
draw.text((20, 30), "Hello World! :)", font=font_ra, fill=255)
Выходим из редактора и сохраняем изменения. Здесь мы при помощи метода "ImageFont.truetype" подгрузили шрифт из файла "ra.ttf" , а потом указали при подготовке текста что нужно использовать экземпляр этого шрифта "font=font_ra".
Запускаем программу:
python ~/Temp/ssd1306_text_hello_wolrd_font_ra.py
Результат работы программы:
Рис. 5. Текст на дисплее ssd1306 с использованием шрифта TTF. Фото получилось размытое, экран достаточно контрастный и сфокусировать камеру было очень не просто.
Отображаем символы кириллицы (TTF)
Как оказалось, здесь также нет ничего сложного - используем нужный шрифт с поддержкой кириллицы, а также несколько хитростей при работе с кодом на Python.
Где взять подходящий шрифт? -Вот два источника со свободными шрифтами (для верстки, типографии, дизайна, разных поделок и т.п.):
Качаем понравившийся шрифт, распаковываем архив и используем нужный *.ttf файл.
Для примера: заходим на страничку со шрифтами от Гугла, справа в выпадающем списке "Languages" выбираем - "Cyrillic", нам ведь нужно чтобы шрифт поддерживал кириллицу. Выбираем понравившийся шрифт, жмем "Select this font" - снизу видим уведомление что шрифт выбран, жмем на это уведомление - всплывет окно, сверху справа будет иконка при нажатии на которую можно будет скачать весь пак со семейством шрифтов.
В примере ниже я использовал Open Sans - из архива извлек OpenSans-Regular.ttf. Шрифт должен лежать рядом с файлом программы, а также рядом с файлами device.py render.py (все в одной папке). Для размещения файла со шрифтом в другой директории в коде придется прописать полный путь к файлу.
Создадим новый файл для этого эксперимента:
nano ~/Temp/ssd1306_text_hello_wolrd_font_cyrillic.py
Код программы для вывода кириллици на дисплей SSD1306:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# "Hi, friend" program for display SSD1306 in Raspberry Pi using cyrillic TTF Font
# 2016 https://ph0en1x.net
from device import ssd1306
from render import canvas
from PIL import ImageFont
device = ssd1306(port=1, address=0x3C) # for RPi rev 2 port(smbus) = 1
font = ImageFont.load_default()
font_ra = ImageFont.truetype('OpenSans-Regular.ttf', 12)
with canvas(device) as draw:
draw.text((5, 0), "*** Hi, friend! ***", font=font, fill=255)
draw.text((0, 30), unicode("Доброго дня, друже! :)", 'utf-8'), font=font_ra, fill=255)
Вот что изменилось в программе по сравнению с предыдущей в которой мы также выводили текст:
- Указана внутренняя кодировка файла для Python: "# -*- coding: utf-8 -*-";
- Используем шрифт с поддержкой кириллицы, размером 12: "('OpenSans-Regular.ttf', 12)";
- Перед передачей текста на обработку конвертируем его в UTF-8: "unicode("Доброго дня, друже! :)", 'utf-8')".
Команда для запуска:
python ~/Temp/ssd1306_text_hello_wolrd_font_cyrillic.py
Результат работы:
Рис. 6. Отображаем кириллический текст (украинский, русский и другой) на дисплее SSD1306.
Кроме символов кириллицы можно попробовать отобразить другие буквы и символы в кодировке UTF-8 для разных языков (зависит от шрифта). К примеру со шрифтом Open Sans корректно отображается символ Ω (знак Ома U+03A9) и другие. Тестируйте сами нужную вам комбинацию.
Для удобного поиска и выбора символов в Linux есть программа KCharSelect, установку можно выполнить из репозитория (Ubuntu, Debian, Mint...) командой:
sudo apt-get install kcharselect
Все просто и работает отлично!
Рисуем геометрические фигуры
После того как научились выводить простой текст, неплохо бы попрактиковаться в выводе геометрических фигур. В следующем примере мы выведем на экран:
- 1-я точка;
- 2-я точка (по сути будем рисовать линию длиной в 1 пиксель);
- Линия (последовательность пикселей);
- Прямоугольник 40x30 пикселей, заполним его цветом. Для двухцветного дисплея получится прапор Украины (желто-синий);
- Колечко (елипс без заполнения цветом);
- Круг (елипс заполненный цветом);
- Треугольник (полигон, набор соединенных прямых линий).
Создадим новый файл для программы:
nano ~/Temp/ssd1306_geom_figures.py
Код программы:
#!/usr/bin/env python
# Geometric figures test for SSD1306 in Raspberry Pi
# 2016 https://ph0en1x.net
from device import ssd1306
from render import canvas
from PIL import ImageFont
device = ssd1306(port=1, address=0x3C) # for RPi rev 2 port(smbus) = 1
font = ImageFont.load_default()
with canvas(device) as draw:
print 'Display dimensions: ' + str(device.width) + 'x' + str(device.height)
# 1st point
draw.point((device.width-1, 0), fill=255)
# 2nd point
draw.line((device.width-1, 16, device.width, 16), fill=256)
# line
draw.line((device.width-25, 0, device.width, device.height-30), fill=256)
# rectangle filled with color
draw.rectangle((0, 0, 40, 30), outline=255, fill=255)
# circle
draw.ellipse((0, 35, 30, 64), outline=255, fill=0)
# filled circle
draw.ellipse((device.width-20, 40, device.width, 60), outline=0, fill=255)
# triangle
draw.polygon([(64, 64), (42,32), (86,32)], outline=255, fill=0)
Запускаем:
python ~/Temp/ssd1306_geom_figures.py
После запуска на экране отобразятся геометрические фигуры и точки, а в консоли будут выведены размеры экрана в пикселях. Ниже приведено фото того что должно получиться:
Рис. 7. Рисуем геометрические фигуры на дисплее ssd1306 используя Python и Raspberry Pi.
Более детальную информацию по работе с функциями рисования 2D-графики можно узнать почитав документацию по модулю ImageDraw для Pillow (PIL), смотри ссылку внизу статьи.
Отображаем рисунки
Используя приведенные выше функции можно нарисовать практически любой рисунок, элемент интерфейса, разные рамки и прочее. Отличным дополнением к данным возможностям станет отображение рисунков из графических файлов.
Для следующего примера скопируем из репозитория файл с логотипом Raspberry Pi, выполним команду:
cp ~/Temp/ssd1306/examples/images/pi_logo.png ~/Temp
Откроем новый файл для программы:
nano ~/Temp/ssd1306_image_test.py
Код программы:
#!/usr/bin/env python
# Image test for display SSD1306 using Raspberry Pi
# 2016 https://ph0en1x.net
from device import ssd1306
from render import canvas
from PIL import Image
device = ssd1306(port=1, address=0x3C) # for RPi rev 2 port(smbus) = 1
with canvas(device) as draw:
pic = Image.open('pi_logo.png')
draw.bitmap((32, 0), pic, fill=255)
Запустим программу:
python ~/Temp/ssd1306_image_test.py
Результат работы:
Рис. 8. Отображаем PNG-рисунок на дисплее ssd1306 используя Python и Raspberry Pi.
Как видим, все предельно просто благодаря использованию библиотеки Pillow!
Подготовка рисунков
Для приведенного выше примера вы можете загрузить в папку с экспериментом свой рисунок, а для его отображения нужно будет изменить строчку кода "pic = Image.open('pi_logo.png')", указав имя и путь для своего файла-рисунка вместо pi_logo.png. Можно также использовать графические файлы в формате JPG.
Прежде чем использовать рисунок для вывода на дисплей, его нужно подготовить. Рисунок должен быть сохранен в оттенках серого (режим Grayscale), в противном случае при выводе дисплей будет залит сплошным цветом.
Для подготовки рисунков отлично подойдет графический редактор GIMP. Создадим новый бланк рисунка (CTRL+N) с размерами 128 на 64 пикселя, поместим туда какое-то изображение (можно использовать буфер обмена для вставки рисунка - CTRL+V) или же нарисуем что либо при помощи инструментов редактора.
Ниже приведен оригинал изображения (иконка Адама Дженсена из Deus Ex HR, взята с просторов интернета) с которым будем работать:
Рис. 9. Исходный рисунок (128x64) для эксперимента с дисплеем ssd1306.
Поскольку дисплей на драйвере ssd1306 является черно-белым (белый цвет - это желтый и синий на дисплее) то нужно конвертировать рисунок в режиме "оттенки серого", для этого в редакторе GIMP выберем Image - Mode - Grayscale, получится вот такой рисунок:
Рис. 10. Черно-белый рисунок для эксперимента с дисплеем ssd1306.
В принципе уже можно экспортировать рисунок в файл формата JPG - жмем CTRL+E, указываем имя файла и в конце расширение ".jpg". При загрузке такого файла в нашей программе он будет автоматически конвертирован, цвета преобразованы с добавлением альфа-канала (Alpha-channel), на дисплее рисунок будет содержать только элементы которые имеют цвет или же содержат пустоту (прозрачные).
Рис. 11. Рисунок с Адамом Дженсеном, выведенный на OLED дисплей ssd1306.
Если сейчас попытаться экспортировать и сохранить рисунок в формат PNG, то при загрузке программы получим ошибку: "ValueError: bad transparency mask".
Перед экспортом в PNG проведем дополнительную подготовку. Используем рисунок, полученный на предыдущем этапе (черно-белый), выбираем пункты меню Image - Mode - RGB.
Теперь преобразуем рисунок таким образом, чтобы черный цвет у нас стал прозрачным, выбираем Colors - Color to Alpha, нажимаем выпадающий список и в палитре цветов выбираем черный. Вот что должно получиться:
Рис. 12. Черный цвет преобразован в Альфа-канал в редакторе GIMP (режим RGB).
После данной операции можем выполнить экспорт рисунка в файл формата PNG (CTRL+E) и загрузить его в нашу программу. Чтобы указать редактору GIMP что нужно сохранить рисунок в формате PNG достаточно изменить расширение сохраняемого файла на ".png".
Теперь на дисплее будет показано изображение и ошибка исчезнет. будут отрисованы все пиксели рисунка, которые имеют цвет и причем без учета интенсивности, оттенков.
Давайте преобразуем рисунок так, как он будет отображаться на дисплее, для этого используем инструмент постеризации. Выполним операции с самого начала:
- Созадим пустое изображение (CTRL+N) с размерами 128 на 64 пикселя;
- Скопируем откуда-то какой-то рисунок в буфер обмена и вставим его в редакторе (CTRL+V);
- Переконвертируем рисунок в черно-белом режиме: Image - Mode - Grayscale;
- Вернемся в цветной RGB режим: Image - Mode - RGB (рисунок останется в серых тонах).
У нас получится изображение как на рисунке 8. Теперь выберем в меню Colors - Posterize, укажем значение - 2 и жмем ОК. Вот что должно получиться:
Рис. 13. Постеризированный в редакторе GIMP рисунок (режим RGB).
Именно такое изображение отображалось на дисплее при загрузке в программу файла после его преобразования с альфа-каналом. Если же это изображение экспортировать в PNG и попытаться отобразить на дисплее то программа выдаст ту-же ошибку и это не удивительно, поскольку у нас нет Альфа-канала (прозрачности).
Преобразуем черный цвет в Альфа-канал: Colors - Color to Alpha, выбираем черный цвет и жмем ОК. Получится вот такой рисунок:
Рис. 14. Готовый рисунок после постеризации и преобразования черного цвета в прозрачность.
Теперь данный рисунок можно немного подкорректировать, что-то убрать или добавить, а потом выполнить экспорт в файл формата PNG. Загрузив такой файл программа без проблем его обработает и на дисплее мы увидим его точно 1 в 1 (посмотрите рисунок 11).
В редакторе GIMP есть еще масса разных полезных инструментов, эффектов и фильтров при помощи которых можно улучшить изображение и подогнать его до нужного уровня. GIMP - отличная, свободная замена платным редакторам уровня Adobe Photoshop!
Индикатор качества воздуха
Теперь попробуем собрать более-менее полезный проект с использованием дисплея SSD1306, АЦП-ЦАП PCF8591P и газового анализатора MQ-135. О том как сдружить последних два блока я рассказывал в статье про датчик MQ-135, поэтому детально останавливаться на этом здесь не буду.
Вот что будет делать наш индикатор качества воздуха:
- Отображать на дисплее в реальном времени числовое состояние входа АЦП PCF8591P, к которому подключен датчик MQ-135;
- При превышении допустимого значения вредных веществ в воздухе на экране отобразим рисунок означающий "токсичность".
Для данного эксперимента соберем вот такую схему:
Рис. 15. Схема подключения PCF8591P, MQ-135 и SSD1306 к Raspberry Pi 2.
Схема включения выглядит идентично той, которую я приводил в эксперименте с датчиком газов MQ-135, здесь лишь к шине I2C дополнительно подключен дисплей SSD1306.
Напряжения питания модулей в этой схеме:
- PCF8591P, SSD1306 ... +3,3В;
- MQ-135 ... +5В.
На платах каждого из модулей (PCF8591P, SSD1306) присутствуют подтягивающие резисторы для шины I2C. Поскольку на одну шину мы подключим только два устройства и в экспериментальных целях, то эти резисторы я не выпаивал - результирующее сопротивление (параллельное включение резисторов) уменьшится, но при этом ток через каждую из линий SCL, SDA будет все еще не большим.
После подключения схемы и загрузки Raspberry Pi подключимся к малинке и в консоли проверим какие устройства на шине I2C у нас присутствуют:
sudo i2cdetect -y 1
Пример вывода команды:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Здесь видно что на шине I2C присутствуют два устройства:
- адрес 48 - это PCF8591P;
- адрес 3с - это SSD1306.
Все отлично, можем приступать к программированию. Создадим новый файл для нашей программы:
nano ~/Temp/ssd1306_gas_sensor.py
Код программы:
#!/usr/bin/env python
# SSD1306 + gas sensor MQ135 + ADC-DAC PCF8591P
# 2016 https://ph0en1x.net
import os
import time
from smbus import SMBus
from device import ssd1306
from render import canvas
from PIL import Image
from PIL import ImageFont
display = ssd1306(port=1, address=0x3C) # for RPi rev 2 port(smbus) = 1
font_ra = ImageFont.truetype('ra.ttf', 18)
toxic_img = Image.open('toxic.png')
ADC_ADDR = 0x48
adc_channel = 0b1000010 # 0x42 (activate AIN2)
bus = SMBus(1) # 1 - I2C bus address for RPi rev.2
while(1):
os.system('clear')
print("Press Ctrl C to stop...\n")
# read sensor value from ADC
bus.write_byte(ADC_ADDR, adc_channel)
bus.read_byte(ADC_ADDR)
bus.read_byte(ADC_ADDR)
value = bus.read_byte(ADC_ADDR)
text = 'Gas level = ' + str(value)
# draw on display
with canvas(display) as draw:
draw.text((5, 0), text, font=font_ra, fill=255)
if value > 40:
draw.bitmap((20, 18), toxic_img, fill=1)
# pause 100 milliseconds
time.sleep(0.1)
Выполним запуск программы:
python ~/Temp/ssd1306_gas_sensor.py
Установить значение при котором появится рисунок можно изменив число в строке "if value > 40:". Для следующего фото я специально занизил это значение, поэтому на экране уровень с датчика равен 30 и отображается картинка со знаком токсичности.
На приведенном ниже фото и видео (одни из первых экспериментов) модули PCF8591 и MQ-135 подключены напрямую, без делителя напряжения - не безопасно и так делать не желательно, єто детально рассмотрено в предыдущей статье.
Рис. 16. Собранная экспериментальная конструкция из модулей с дисплеем SSD1306 в работе.
На видео что ниже продемонстрирована работа связки "SSD1306 + PCF8591 + MQ-135 + Raspberry Pi", здесь был установлен пороговый уровень для отображения рисунка "токсично" - 100.
Для теста использован быстро испаряющийся и достаточно токсичный растворитель - дихлорэтан. Нанесенной на кончик отвертки капельки этого вещества вполне достаточно для уверенной реакции датчика.
Заключение
Как видим, на миниатюрном дисплее SSD1306 можно выводить тексты на английском, украинском, русском и других языках, отображать рисунки и геометрические фигуры - отображать что угодно с разрешением 128x64 пикселей!
Все файлы и скрипты для приведенных выше примеров собрал в один архив - ssd1306-display-raspberry-pi.zip (149 КБ).
Миниатюрному и экономичному OLED экранчику на основе SSD1306 еще найдется не мало применений. К примеру, можно сделать табло отображающее ресурсы в ОС для Raspberry Pi, подключить дисплей к AVR микроконтроллеру и собрать на их основе миниатюрный вольт-амперметр...и т.д.
Веселого и удачного вам творчества!
Полезные ресурсы:
А как кириллицу вывести на экран?
Дополнил статью, добавил раздел Отображаем символы кириллици (TTF).
Обновил архив с примерами - добавлен шрифт и скрипт с примером кода.
Пользуйтесь!
Огромное спасибо, был шаге )
Если сделаю бегущую строку, обязательно поделюсь
Как и обещал, "бегущая строка":
у ричарда хала изменились файлы
device.py есть
render.py, нет
что делать? спасибо
dimsh, действительно библиотека значительно выросла и файлы изменились. Добавил в статью архив старой библиотеки версии 0.2.0 и переписал команды скачивания и установки бибилиотеки.
Также, в конце статьи есть ZIP-архив со всеми скриптами, файлами и примерами что были приведены здесь.
К тому же, старые релизы библиотеки Ричарда можно скачать на GitHub: https://github.com/rm-hull/luma.oled/releases?after=1.1.0
Есть ли возможность вывода на экран без конструкции with..as?
Можно обойтись и без конструкции "with..as", в таком случае файл render.py нам не понадобится и код будет выглядеть следующим образом:
Как установить эти файлы в качестве библиотеки? Как вывести кириллицу без with..as?
В примере из комментария #8 используется только один внешний файл (модуль) - device.py, также через PIP нужно установить модуль Pillow (в статье об этом написано). В коде программы модуль "device.py" подключается вот так: "from device import ssd1306" (из модуля импортируем класс "ssd1306").
О создании и использовании модулей в Python читайте в официальной документации по языку + черпайте информацию из книг. Здесь ведется обсуждение SSD1306, а не основ программирования на Python.
Как вывести кириллические символы на экран? - описано в статье. Переработав пример из статьи, используя для этого пример из комментария (код без with..as), получим примерно вот такую программу:
P.S. Прежде чем задавать вопросы, пожалуйста, ознакомьтесь со статьей, попробуйте примеры которые там приведены - это даст ответы на многие вопросы и позволит понять назначение файлов и кода из примеров. )
Добрый день! что может означать такая ошибка:
Здравствуйте!
"[Errno 121] Remote I/O error" - ошибка операции ввода/вывода при обращении к внешнему устройству. В данном случае это должен быть дисплей ssd1306, некоторые из возможных причин ошибки 121:
В одной из статей я уже рассказывал как работать с шиной I2C в Raspberry Pi.
Что можно предпринять? - включить шину I2C, правильно подключить дисплей, узнать адрес вашего модуля SSD1306 командой:
Если команда показала адрес - изменить его в коде и проверить работоспособность дисплея. Если же команда не показывает наличие устройства - подключить к Raspberry Pi любое другое рабочее устройство с шиной I2C и убедиться что сама шина работает, в таком случае очень вероятно что дисплей вышел из строя.
Снова я )
Нигде не нашел, как именно выключить экран?
del draw полагаю, просто очищает его?
Каждый пиксель OLED-дисплея является органическим светодиодом. Если светятся все пиксели - потребляемый ток максимален. Если все пиксели погашены (экран очищен) - потребляемый ток очень низкий, и лишь некоторый микро-ток нужен для питания самой микросхемы-драйвера SSD1306 и стабилизатора на 3,3В.
Также, можно управлять включением и выключением дисплея, контрастностью и другими параметрами, отправляя ему по шине I2C специально сформированные команды.
Дополнил информацию в первом разделе статьи, добавил новый раздел "Управление дисплеем".
Благодарю за столь исчерпывающую информацию!
автору спасибо за статью
Очень помогло с освоением
в новых версиях библиотеки PIL избежать ошибки "ValueError: bad transparency mask" можно поправив код так:
pic=Image.open('img/w1.png').convert("L")
Здесь L режим конвертации в 8 битные оттенки серого подробнее о режимах конвертирования https://pillow.readthedocs.io/en/latest/handbook/concepts.html#concept-modes
А как сделать, чтобы после отработки скрипта экран не отключался?
По завершению работы скриптов в примерах из статьи экран не гаснет и продолжает отображать переданную в него конфигурацию пикселей (картинку). Возможно у вас скрипт со своими особенностями, изучите его работу и попробуйте найти фрагмент кода, который очищает экран или выключает его.
Приветствую автора замечательной статьи.
Все хорошо работает до тех пор, пока я использую дисплей 0,96". Когда мне хочется использовать для лучшей видимости дисплей 1,3", то на экране видна только картинка из мелких точечек и черточек, из них только 4 строчки содержат небольшой фрагмент изображения. К сожалению, не могу приложить сюда фото. Я купил 2 шт. дисплеев OLED 1,3" в разных местах и в разное время, но результат не изменился, значит, это не ошибка «железа», то есть аппаратного обеспечения (HW).
Дисплей 1,3"может быть собран с чипом SSD1306 или SH1106, а дисплеи меньшего размера могут быть только с чипом SH1306.
https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
Resolution: 128 x 64 dot matrix panel
Embedded 128 x 64 bit SRAM display buffer
https://www.elecrow.com/download/SH1106%20datasheet.pdf
Support maximum 132 X 64 dot matrix panel
Embedded 132 X 64 bits SRAM
На форуме мне не смогли помочь, но такая ошибка встречалась и у других людей. В моем возрасте (мне 73 года) я не силен в программировании, но нельзя сказать, что совсем ничего не соображаю. Не мог бы ты написать программу так, чтобы все было нормально и с чипом 1106?
Меня зовут Янош, привет из Венгрии.
Здравствуйте, Янош.
У меня сейчас нету такого дисплея чтобы попробовать, поэтому постараюсь дать некоторые рекомендации по экспериментам, которые будете выполнять самостоятельно.
В моей статье используется небольшая библиотека, внутри которой есть класс с поддержкой дисплеев SH1106. Чтобы использовать именно этот дисплей, в примерах нужно изменить несколько строчек кода.
1) Меняем импорт класса дисплея:
на
2) Меняем название класса в строчке, где создается экземпляр импортированного дисплея:
на
В файле 'display.py' из библиотеки описаны классы для дисплеев SSD1306 и SH1106. Размеры дисплеев по умолчанию (в пикселях) указаны в функциях инициализации классов - '__init__'.
Это значит, что вы можете создавать экземпляр класса, указывая после аргументов порта и адреса еще два параметра - ширину и высоту дисплея. Например, для SH1106 с размерами 132 X 64, строчка которую мы исправляли выше будет иметь вид:
Таким образом мы задаем границы дисплея в пикселях.
Перед работой, проверьте с помощью команды 'i2cdetect что дисплей у вас имеет адрес 0x3C (вверху публикации есть ссылка на статью по работе с шиной I2C). Если адрес дисплея отличается, то внесите изменение в коде где значение аргумента "address=0x3C".
Если данные рекомендации не принесут желаемого результата, то проблема может быть в особенностях попавшихся дисплеев или же в том, что используемая для работы библиотека уже не подходит под некоторые дисплеи. Придется пробовать другие реализации библиотек, например проверить работу с новой библиотекой от Ричарда Хала - luma.oled (ссылка есть в начала статьи).
Kedves barátom!
Köszönöm a látatlanban adott kiváló tanácsot. Most már rendben működik a program. Üdv: János.
Дорогой ph0en1x!
работает как надо. Привет из Венгрии, Янош
Спасибо за великолепный совет, который Вы дали, не имея возможности попробовать свои рекомендации на практике на дисплее. Сейчас программа уже
Добрый день.
Можете показать, какой конкретно резистор (перемычку?) нужно перепаять, чтобы второму SD1306 задать другой адрес? Нигде не нашёл конкретную инфу об этом. Везде туманно пишут.
Приветствую!
Модули (платы) на основе чипа с дисплеем SD1306 могут отличаться расположением и назначением компонентов. В моем случае над одним из резисторов было явно указано "IIC ADDRESS SELECT", на рисунке в статье это видно. По умолчанию выбран адрес "0x3C", хотя на плате почему-то указано "0x78". Если перепаять такой резистор на контакт с правой стороны, то получим адрес "0x3D" (на плате указано "0x7A").
У разных модулей для шины I2C могут быть разные расположения деталей. В таком случае придется вычислять есть ли на плате перемычка или резистор, отвечающий за выбор адреса. Также может быть что такой резистор и вовсе не предусмотрен - распайка сделана только для одного адреса (по умолчанию).
В даташите на SD1306 (раздел 8.1.5) сказано:
Это значит что за установку адреса отвечает бит SA0, который в режиме I2C устанавливается с помощью пина "D/C#" (Data/Comamnd control pin).
Можно выбрать любой из двух адресов для I2C:
В начале раздела 6 даташита приведена распиновка шлейфа (30 контактов), который идет от чипа SD1306 и потом припаивается к платке модуля.
Вывод номер 15 - это и есть D/C#":
Берем мультиметр и переключаем его в режим прозвонки цепей, вычисляем подключен ли вывод номер 15 шлейфа SSD1306 к какому-то из резисторов на платке.
Если такой резистор найден, то нужно разобраться куда подключена его вторая ножка: к GND или VCC (выход стабилизатора на 3,3В - микросхема 662K). Переключив этот резистор с GND на VCC или наоборот получим смену адреса для модуля на основе SSD1306.
Но может быть и по другому.
Вот пример модуля с дисплеем SSD1306 (GM009605v4), в котором вывод 15 шлейфа подключен к GND:
В таком случае для смены адреса придется каким-то образом отключить вывод 15 шлейфа SSD1306 от GND и подключить его через резистор на 4,7К к выходу микросхемы-стабилизатора на 3,3В.
Но такая модификация может быть затруднительна, поскольку дорожка от GND, которая подключена к выводу 15, может проходить под шлейфом.
Благодарю, исчерпывающе! Придут экраны, буду смотреть по факту.