День #11 адвент-календаря для мейкеров: OMG OLED!
Добро пожаловать на одиннадцатый день вашего 12 проектов Codemas Advent Calendar. Сегодня мы поиграем с очень весёлым и полезным компонентом, который вы будете использовать снова и снова в своих проектах — с миниатюрным I2C OLED-дисплеем!
Несмотря на маленькие размеры, эти дисплеи отлично подходят для отображения данных проекта, таких как показания датчиков, очки, состояние пинов, оповещения и другая полезная информация. Мы совместим его с некоторыми компонентами из предыдущих наборов, чтобы показать вам, насколько они удобны.
Поехали!
Содержимое набора #11
В этом наборе вы найдёте:
1x OLED I2C дисплей 0,96» с припаянными выводами (128x32)
4x Провода-перемычки папа-папа
Проект дня
Сегодня мы будем выводить данные на наш миниатюрный I2C OLED-дисплей. Код для него относительно прост, но требует установки _библиотеки_ (которую мы импортируем в начале программы) — мы проведём вас через этот процесс.
Что такое OLED?
Дисплей в вашем наборе — это OLED-дисплей. OLED расшифровывается как Organic Light-Emitting Diode (органический светодиод). Возможно, вы встречали это упоминание в рекламе телевизоров, так как многие модели используют эту технологию.
Это тип цифровой дисплейной технологии, которая использует светодиоды и слои тонкой органической плёнки между двумя электродами. При подаче электрического тока дисплей излучает свет. Наш код указывает дисплею, где и когда показывать свет.
Что такое I2C?
I2C, иногда называемый IIC, расшифровывается как Inter-Integrated Circuit (межинтегральная схема). Это ещё один тип протокола связи (помните 1-wire из дня #8?), который позволяет нескольким I2C-устройствам общаться с контроллером, например нашим Pico.
I2C требует всего двух проводов для связи (плюс 3,3 В и GND для нашего дисплея) и имеет преимущества перед некоторыми другими вариантами связи — но мы не будем утомлять вас этим прямо сейчас, так как это станет актуальным только тогда, когда вы продвинетесь намного дальше в своём пути мейкера.
Для использования I2C нам нужно импортировать его в наш код, что мы покажем вам буквально через минуту!
Сборка схемы
Сначала отключите Raspberry Pi Pico от компьютера, убедившись, что питание отключено.
Сегодня мы наконец убираем светодиоды и зуммер, так как «входные» компоненты (например, кнопки и датчики) более уместны и полезны с этим дисплеем. Если вы ещё не сделали этого, уберите всё, чтобы вернуться к чистой макетной плате только с Pico, как здесь:
Затем установите одну из кнопок и OLED-дисплей на макетную плату, как мы показали ниже ( убедитесь, что оставили зазор выше обоих для добавления проводов-перемычек).
Вы также можете снять защитную плёнку с дисплея на этом этапе:
Затем добавим подключения 3,3 В.
Проведите провод-перемычку от пина 3,3 В (физический пин 36) к верхнему красному каналу, затем подключите левый пин кнопки к тому же красному каналу, а также второй пин OLED, обозначенный как „VCC“, к тому же каналу, как показано ниже:
Следующий — пин GND. Подключите первый пин OLED, обозначенный как „GND“, к ближайшему пину GND на физическом пине 18, как мы показали ниже:
Теперь три пина GPIO, которые будут общаться с нашим Pico:
Подключите правую ножку кнопки к GPIO8 (физический пин 11)
Подключите 3-й пин OLED, обозначенный как „SCL“, к GPIO1 (физический пин 2)
Подключите 4-й пин OLED, обозначенный как „SDA“, к GPIO0 (физический пин 1)
Сегодня в наших примерах мы будем использовать датчик освещённости из набора #6, поэтому нам нужно подключить и его. Воспользуемся мини-макетной платой.
Установите датчик освещённости и резистор 10 кОм на макетную плату, как мы показали ниже. Длинная ножка датчика освещённости должна быть слева:
Теперь подключим датчик освещённости к нашему Pico:
Подключите левую ножку резистора к соединению GND на физическом пине 28
Подключите левую ножку датчика освещённости (между ножкой и резистором) к GPIO26 (физический пин 31)
Подключите правую ножку датчика освещённости к 3,3 В на физическом пине 36
Теперь поверните макетные платы на 90 градусов так, чтобы OLED был обращён к вам. Это упростит просмотр и чтение текста, который мы выводим на него.
Установка библиотек кода
Подключите Pico обратно к USB-порту компьютера и убедитесь, что он распознан, затем давайте установим всё необходимое для работы этого дисплея.
Нашему дисплею нужен пакет библиотеки кода, который по умолчанию не включён в MicroPython, поэтому нам нужно установить его перед тем, как начать использовать этот дисплей. Затем мы импортируем эту библиотеку при написании кода.
К счастью, это очень легко сделать — всё обрабатывается внутри Thonny, так что давайте установим её. Следующие шаги могут немного отличаться в зависимости от типа вашего компьютера.
—
Важно: Если приведённые ниже шаги не работают и вы видите ошибки сертификата или аналогичные сообщения, вам может потребоваться установить библиотеку вручную.
Для этого:
Выделите весь код на этой странице (используйте Ctrl+A) и скопируйте его в Thonny
В Thonny выберите File > Save as и выберите Raspberry Pi Pico в качестве места назначения
Назовите файл ssd1306.py
Нажмите OK
ВАЖНО — Теперь вы должны закрыть этот файл ssd1306.py (используйте маленький X на вкладке), затем откройте новый для вашего кода (File > New), иначе вы будете перезаписывать этот файл и всё запутаете :)
—
В Thonny в строке меню вверху выберите Tools > Manage packages. Вы должны увидеть что-то похожее на окно ниже:
В строке поиска введите «micropython-ssd1306» и нажмите кнопку поиска. Вы должны увидеть результаты, как у нас ниже, включая нужный нам модуль micropython-ssd1306 вверху списка:
Выберите эту строку, чтобы перейти на экран информации об этом модуле, затем нажмите кнопку „Install“ внизу:
После установки пакета он появится в вашем списке установленных пакетов слева. Теперь он готов к использованию!
Задание 1: Простой вывод текста
Начнём с отображения одной строки текста на дисплее для разминки. Конечно, это будет традиционное «Hello World!»…
Настройка I2C и дисплея
Для использования этого дисплея нам нужно импортировать I2C, а также только что установленную библиотеку. Вы увидите это в первых нескольких строках вместе с другими импортами, как обычно.
Затем нам нужно настроить I2C и дисплей, что вы увидите на строке 7. Это включает пины GPIO, которые мы используем (GPIO0 и GPIO1) — пины SDA и SCL, используемые для I2C-соединений. После этой строки нам всегда нужно подождать около 1 секунды, иначе I2C может «расстроиться» и сломаться!
Далее мы определяем размер дисплея (в пикселях) и тип микросхемы драйвера, которую он использует. Наш дисплей использует драйвер SSD1306 и имеет размер 128x32 пикселей, что отражено на строке 13. На этом настройка завершена.
Отображение текста
Когда мы хотим что-то отобразить на дисплее, мы всегда проходим один и тот же процесс:
Очистить дисплей
Определить, какой контент мы хотим показать на дисплее
Передать контент на дисплей
Если мы не будем очищать дисплей каждый раз, он будет писать новый контент поверх старого, что создаст беспорядок из смешанных символов. Нехорошо!
В нашем примере мы очищаем дисплей с помощью display.fill(0), затем отправляем «Hello World!» с помощью display.text(«Hello World!»,0,0), и наконец передаём это на дисплей с помощью display.show().
Код
Что делают аргументы 0,0 — разберём в следующем задании, а пока скопируйте это в Thonny и посмотрите сами!
# Импорты
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
import time
# Настройка I2C и пинов, которые мы используем для него
i2c=I2C(0,sda=Pin(0), scl=Pin(1), freq=400000)
# Короткая задержка, чтобы I2C не сломался
time.sleep(1)
# Определяем дисплей и размер (128x32)
display = SSD1306_I2C(128, 32, i2c)
# Сначала очищаем дисплей
display.fill(0)
# Записываем строку текста на дисплей
display.text("Hello World!",0,0)
# Обновляем дисплей
display.show()
Задание 2: Несколько строк текста
Пойдём на шаг дальше, добавив больше строк и изменив положение текста, но сначала разберём эти аргументы…
Аргументы
Вы заметите, что после текста, который мы передаём на дисплей, есть некоторые аргументы (0,0):
display.text("Hello World!",0,0)
Первый аргумент определяет, сколько пикселей по горизонтали дисплея должен начинаться контент (ось x), а второй аргумент определяет, сколько пикселей от верхней части должен начинаться контент (ось y) — вниз.
Наш дисплей имеет размер 128x32 пикселя, поэтому у нас 128 пикселей по горизонтали по оси x и 32 пикселя сверху вниз по оси y.
В нашем примере ниже нам удалось уместить три строки текста, задав каждой разную высоту, что требует лишь немного проб и ошибок. Первая строка находится на 0, следующая строка начинается с 12, а последняя — с 24. Если добавить больше строк, они не будут полностью видны.
Мы также сдвинули строку 2 вправо, введя 50 для первого аргумента — тоже методом проб и ошибок.
Попробуйте пример ниже, а затем поиграйте со значениями аргументов, чтобы увидеть изменения самостоятельно.
# Импорты
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
import time
# Настройка I2C и пинов, которые мы используем для него
i2c=I2C(0,sda=Pin(0), scl=Pin(1), freq=400000)
# Короткая задержка, чтобы I2C не сломался
time.sleep(1)
# Определяем дисплей и размер (128x32)
display = SSD1306_I2C(128, 32, i2c)
# Записываем три строки на дисплей
display.text("Line 1",0,0)
display.text("Line 2",50,12)
display.text("Line 3",0,24)
# Обновляем дисплей
display.show()
Задание 3: Бесконечный счётчик!
Маленькие OLED-дисплеи отлично подходят для отображения данных, и они могут обновляться достаточно быстро, чтобы показывать изменения данных с очень высокой скоростью.
Давайте создадим бесконечный (ну, почти!) счётчик, чтобы показать ещё один способ использования этих дисплеев. Когда вы будете создавать собственные проекты, подобные вещи будут очень полезны.
Мы не используем здесь никаких физических входов, только счётчик в нашем коде, который увеличивается на +1 при каждом цикле.
Код
Новая проблема — наш счётчик является числом (целым числом), но наш требовательный маленький дисплей показывает только текст (строку), поэтому нам нужно преобразовывать наш счётчик в строку при каждом запуске цикла.
Преобразование целых чисел в строки
Мы преобразуем целое число счётчика в текстовую строку с помощью следующей строки: display.text((str(counter)),0,24). Таким образом, вместо ввода текста в скобках, как мы делали в предыдущем задании, мы используем (str(counter), чтобы превратить нашу переменную счётчика в строку для нашего дисплея.
В остальном всё довольно просто — добавляем +1 к нашему счётчику в конце каждого цикла, чтобы постоянно его увеличивать. Вы можете поиграть с задержкой внутри цикла while, чтобы увидеть, как быстро он может работать:
# Импорты
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
import time
# Настройка I2C и пинов, которые мы используем для него
i2c=I2C(0,sda=Pin(0), scl=Pin(1), freq=400000)
# Короткая задержка, чтобы I2C не сломался
time.sleep(1)
# Определяем дисплей и размер (128x32)
display = SSD1306_I2C(128, 32, i2c)
counter = 0 # Начинаем счётчик с нуля
while True: # Цикл навсегда
display.fill(0) # Очищаем дисплей
print(counter) # Печатаем текущее значение счётчика
# Показываем счётчик на дисплее
# Библиотека дисплея принимает только строки
# Счётчик — это число (целое), поэтому мы конвертируем его в текст (строку) с помощью 'str'
display.text("The Endless",0,0)
display.text("Counter!",0,12)
display.text((str(counter)),0,24)
# Обновляем дисплей
display.show()
# Короткая задержка
time.sleep(0.1)
# Добавляем 1 к нашему счётчику
counter += 1
Задание 4: Игра «Хороший или Плохой»!
Теперь мы создадим игру, которая решит вашу праздничную судьбу! Давайте сделаем игру «Хороший или Плохой» (Naughty or Nice), которая скажет вам, находитесь ли вы в хорошем списке в этом году (не волнуйтесь, ребята, это просто веселье — мы уверены, что вы все вели себя хорошо в этом году!).
Этот проект показывает «Плохой» и «Хороший» с маркером, который очень быстро переключается между ними. Когда вы нажимаете кнопку, он останавливается на том варианте, на котором находится маркер в этот момент — ваша цель — попасть на «Хороший».
Код
Наш пример ниже использует все наши обычные ингредиенты (импорты, настройка дисплея, пины кнопок…) и создаёт переменную состояния, которую мы использовали раньше. Мы используем эту переменную для переключения между двумя операторами if внутри нашего цикла while. Но зачем нам это нужно?
Мы хотим, чтобы наш дисплей постоянно перемещал маркер > между «Плохой» и «Хороший». Для этого у нас два оператора if, которые срабатывают в зависимости от того, равна ли наша переменная состояния 0 или 1. Каждый оператор if начинается с отображения текста с маркером в разном положении.
Если состояние равно 0, текст, переданный на дисплей, показывает маркер у «Плохой». Если состояние равно 1, маркер находится у «Хороший». Каждый оператор if также меняет состояние, так что в следующем цикле сработает другой оператор if — что заставляет маркер прыгать между двумя вариантами.
В каждом из операторов if есть вложенный оператор if, который проверяет, нажата ли кнопка в этот самый момент. Если да, код внутри обновляет дисплей, чтобы подтвердить, какой вариант был выбран, после чего следует пауза в 2 секунды:
# Импорты
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
import time
# Настройка кнопки
button = Pin(8, Pin.IN, Pin.PULL_DOWN)
# Настройка I2C и пинов, которые мы используем для него
i2c=I2C(0,sda=Pin(0), scl=Pin(1), freq=400000)
# Короткая задержка, чтобы I2C не сломался
time.sleep(1)
# Определяем дисплей и размер (128x32)
display = SSD1306_I2C(128, 32, i2c)
# Создаём переменные
state = 0
while True: # Цикл навсегда
time.sleep(0.1) # Задержка перед сменой маркера
if state == 0: # Если состояние равно 0
display.fill(0) # Очищаем дисплей
display.text("Naughty or Nice?",0,0) # Строка 1
display.text(">Naughty Nice",0,24) # Строка 3
display.show() # Обновляем дисплей
state = 1 # Смена состояния
if button.value() == 1: # Если кнопка нажата
display.fill(0) # Очищаем дисплей
display.text("Oh no!",0,0) # Строка 1
display.text(">Naughty Nice",0,24) # Строка 3
display.show() # Обновляем дисплей
time.sleep(2) # Задержка
elif state == 1: # Если состояние равно 1
display.fill(0) # Очищаем дисплей
display.text("Naughty or Nice?",0,0) # Строка 1
display.text(" Naughty >Nice",0,24) # Строка 3
display.show() # Обновляем дисплей
state = 0 # Смена состояния
if button.value() == 1: # Если кнопка нажата
display.fill(0) # Очищаем дисплей
display.text("Yay!",0,0) # Строка 1
display.text(" Naughty >Nice",0,24) # Строка 3
display.show() # Обновляем дисплей
time.sleep(2) # Задержка
Задание 5: Покажи мне свет!
Отображение показаний датчика на вашем OLED — это, пожалуй, один из самых полезных и весёлых способов использовать дисплей по максимуму.
Мы запустим пример, в котором непрерывно проверяем показания датчика освещённости и передаём их на дисплей — он даже может показывать символ %!
Приведённый ниже пример включает похожий код из набора с датчиком освещённости (день #6) и тот же код и подход к OLED-дисплею, которые мы использовали выше, так что большинство из этого должно быть понятно, если вы следили за нами.
Код
Присутствует тот же исходный код импортов и настройки OLED, и мы добавили обратно настройку пина датчика освещённости, связав его с GPIO26 вместе с необходимым импортом ADC.
Затем мы запускаем цикл while, который берёт показание с датчика на строке 25. Эта строка делает сразу много вещей, так что давайте разберём её:
Она создаёт переменную с именем „light“
Берёт показание датчика
Превращает показание в процент
Округляет процент показания до одного десятичного знака.
Мы делали что-то подобное в день #6, но на этот раз делаем всё в одну строку кода. Мы избегали этого до сих пор, так как это может быть немного ошеломляющим, но мы уже на дне #11, так что думаем, вы справитесь!
Затем мы передаём эти данные на наш дисплей. Мы используем первую строку дисплея для отображения простого текста, а вторую — для показания. Точно так же, как в предыдущих примерах, мы должны преобразовать нашу переменную „light“ в строку, чтобы дисплей мог её использовать. Мы также добавляем символ % в конец.
Скопируйте код ниже в Thonny и попробуйте:
# Импорты
from machine import Pin, I2C, ADC
from ssd1306 import SSD1306_I2C
import time
# Настройка I2C и пинов, которые мы используем для него
i2c=I2C(0,sda=Pin(0), scl=Pin(1), freq=400000)
# Короткая задержка, чтобы I2C не сломался
time.sleep(1)
# Определяем дисплей и размер (128x32)
display = SSD1306_I2C(128, 32, i2c)
# Определяем пин для нашего датчика
lightsensor = ADC(Pin(26))
while True:
# Задержка
time.sleep(0.5)
# Считываем значение датчика, превращаем в процент, округляем до 1 знака
# Сохраняем это в переменную 'light'
light = round((lightsensor.read_u16())/65535*100,1)
# Печатаем процент показания освещённости
print(light)
# Очищаем дисплей
display.fill(0)
# Записываем две строки на дисплей
# Строка превращает нашу переменную light в строку и добавляет '%' в конец
display.text("Light level:",0,0)
display.text((str(light) + "%"),0,12)
# Обновляем дисплей
display.show()
День #11 завершён!
Разве OLED-дисплеи — не просто кладезь веселья? Мы только поцарапали поверхность! Есть масса умных способов использовать такие дисплеи с графикой, шрифтами и другими трюками, но мы хотели сохранить наши задания относительно простыми для начинающих.
В интернете вы найдёте тонну ресурсов и других примеров, которые можно использовать — в том числе есть собственный графический туториал, который можно применить и к этим дисплеям (с небольшими изменениями кода).
Давайте подведём итог — что мы узнали в день #11? Сегодня мы:
Научились подключать OLED к Raspberry Pi Pico
Познакомились с протоколом связи I2C
Научились устанавливать пакеты в Thonny
Научились программировать I2C OLED-дисплей, включая:
Как записывать текст на дисплей
Как записывать несколько строк текста
Как менять положение текста
Как отображать данные датчика на OLED-дисплеях
Несколько маленьких трюков, например маркеры!
Повторно использовали знания и компоненты из предыдущих наборов, например:
Переменные состояния
Преобразование целых чисел в строки
Вложенные операторы if
Датчики освещённости
…и многое другое!
Ну что же, народ, мы почти у финала наших двенадцати дней. Вы можете разобрать схему сегодняшнего дня, так как завтра нас ждёт совершенно другой вид компонента — не терпится! До встречи завтра…
—
Для создания диаграмм подключения на макетной плате использовалась программа Fritzing.