Let it Glow. Адвент-календарь. День #12: Потрясающие дисплеи!

Let it Glow Адвент-календарь День 12: Потрясающие дисплеи!

Это финальный день Адвент-календаря Let it Glow!

Сегодня мы поиграем с Ж идкокристаллическим Д исплеем ( ЖКД, он же LCD) — это своеобразный гибрид, поскольку у него есть светодиодная подсветка (немного мигалок), а ещё он умеет показывать информацию из нашей программы — что делает его вдвойне интересным.

Это ещё один невероятно популярный компонент среди мейкеров по всему миру — из-за того, насколько он полезен в качестве интерфейса проекта для отображения данных.

Давайте настроим его!

Содержимое коробки #12

В этой коробке вы найдёте:

  • 1x 16x2 LCD (с I2C-платой)

  • 4x соединительных провода «папа-мама»

Важно: На задней панели LCD должен быть 2-контактный разъём с маленькой чёрной перемычкой/джампером, уже установленной на нём. Если её нет, проверьте пакетик и наденьте её на этот 2-контактный разъём.

Содержимое коробки день 12

Сегодняшний проект

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

Мы воплотим это в жизнь с помощью нескольких интересных проектов, а затем — вам карты в руки, делайте… что хотите!

Сначала поговорим о самом дисплее…

Что такое LCD?

В вашей коробке находится LCDЖ идкокристаллический Д исплей.

Возможно, вы видели подобные дисплеи на различных терминалах и устройствах. Они не так широко распространены, как раньше, поскольку мир переходит на маленькие сенсорные экраны, но они по-прежнему идеально подходят для проектов мейкеров!

Мы не будем углубляться в то, как работают жидкие кристаллы в этих дисплеях и как они отображают символы — это серьёзная тема и отдельный урок сам по себе. Просто знайте: всё началось с моркови в 1888 году!

Платы расширения упрощают жизнь

Вы заметите, что на передней панели LCD много выводов — шестнадцать, если точнее. Традиционно нужно было подключать большинство из них к микроконтроллеру, что было довольно утомительной задачей. Однако LCD в вашей коробке включает *I2C-плату расширения (backpack)*.

Эти платы подключаются к выводам LCD и предоставляют нам гораздо более удобный интерфейс I2C для управления дисплеем. Плата позволяет подключить LCD к нашему Pico всего четырьмя выводами!

Сборка схемы

Как всегда, убедитесь, что Pico отключён от USB-кабеля при работе со схемой.

Подготовка макетной платы

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

Ваша начальная точка должна выглядеть так:

Начальная точка день 12

Подключение LCD

Сегодня у вас другой набор соединительных проводов — «папа-мама». Мы вставим гнездовые концы в I2C-плату LCD, а штыревые концы — в макетную плату. Лучше всего разделить провода заранее, чтобы они легко дотянулись до нужных выводов.

Нам нужно подключить четыре вывода: GND, VCC (5V), SDA и SCL. Они подписаны на задней панели дисплея — это удобно для проверки правильности подключения.

Два вывода на другой стороне платы должны иметь маленькую чёрную перемычку на них. Она просто включает подсветку LED. Если эта маленькая перемычка находится в вашем пакетике и не подключена — наденьте её на эти два вывода сейчас.

Приступим к подключению:

  • Подключите GND на LCD к выводу GND Pico. Мы используем физический вывод 38

  • Подключите вывод VCC к выводу 5V VBUS ( физический вывод 40). Мы уже используем этот вывод для светодиодной ленты, но там есть два свободных отверстия (или сделайте шину питания по желанию)

  • Подключите вывод SDA к GPIO14 ( физический вывод 19). Он будет общим с датчиком температуры (рядом с этим выводом на макетной плате есть два свободных отверстия)

  • Подключите вывод SCL к GPIO15 ( физический вывод 20). Он тоже будет общим с датчиком температуры

Вот как должно выглядеть подключение на данном этапе:

LCD подключён день 12

Подключение датчика температуры

Установим датчик температуры в верхнем левом углу макетной платы, стороной «вафля» вперёд — так же, как в день #9.

Подключаем слева направо:

  • Подключите первый вывод (левый) к выводу 3V3 ( физический вывод 36)

  • Подключите второй вывод к GPIO14 ( физический вывод 19). Он общий с LCD.

  • Подключите третий вывод к GND. Мы используем физический вывод 18.

  • Подключите четвёртый вывод к GPIO15 ( физический вывод 20)

Очень рекомендуем проверить всё троекратно! Вот как должна выглядеть завершённая схема:

Завершённая схема день 12

Установка библиотеки кода

Как и в день #9 с датчиком температуры, нам нужно установить библиотеку кода для работы с этим дисплеем. На этот раз нужно установить два файла на Pico, но процесс тот же.

Мы будем использовать отличную библиотеку RPI-PICO-I2C-LCD от пользователя GitHub T-622 (Tyler Peppy). Эта библиотека адаптирует код другой LCD-библиотеки, чтобы всё прекрасно работало на Pico. Замечательно!

Итак, переходим к нашим двум файлам библиотеки. Поскольку код библиотеки довольно длинный, мы направим вас на страницы с его сырыми версиями для копирования и вставки в Thonny.

Файл #1 — lcd_api.py

Перейдите по следующей ссылке, чтобы открыть исходный код этого файла библиотеки. После открытия используйте Ctrl+A для выбора всего кода, Ctrl+C для копирования, затем вернитесь в Thonny и вставьте его в новую вкладку (Ctrl+V):

https://raw.githubusercontent.com/T-622/RPI-PICO-I2C-LCD/main/lcd_api.py

Сохраните файл на Pico (File > Save As) как lcd_api.py, затем закройте эту вкладку.

Файл #2 — pico_i2c_lcd.py

Теперь перейдите по этой ссылке и сделайте то же самое — используйте Ctrl+A для выбора всего кода, Ctrl+C для копирования, затем вернитесь в Thonny и вставьте его в новую вкладку (Ctrl+V):

https://raw.githubusercontent.com/T-622/RPI-PICO-I2C-LCD/main/pico_i2c_lcd.py

Сохраните файл на Pico (File > Save As) как pico_i2c_lcd.py, затем закройте эту вкладку.

Закрытие файлов библиотеки

Важно! Как и прежде, после сохранения обоих файлов библиотеки на Pico закройте вкладку скрипта, нажав „X“ на ней!

Перед началом программирования…

«…Вы пробовали выключить и снова включить?»

Наша схема использует два I2C-устройства — датчик температуры и дисплей — на одной шине I2C (подробнее об этом позже).

Мы обнаружили, что очень редко нужно отключить USB-кабель Pico и снова подключить, чтобы он нашёл оба I2C-устройства/адреса правильно, без сообщения об ошибке (»EIO» или «bad pin»). В большинстве случаев этого не происходит, но если произойдёт — теперь вы знаете, что делать!

Если у вас возникнут проблемы на любом этапе, сначала проверьте подключение, затем отключите Pico от USB-порта, подождите несколько секунд и снова подключите.

Задание #1: Тестовая программа и настройки

Нам нужно убедиться, что всё подключено и работает правильно, поэтому выведем простой текст на LCD и проверим настройку дисплея.

Этот скрипт просто показывает «Hello, world!» в первой строке LCD и при этом печатает текст в окне командной строки.

В отличие от других дней, мы пока не будем объяснять, как работает код — это будет в следующем задании. Этот шаг нужен только для того, чтобы подготовить наши LCD к работе.

Настройка контрастности

Одна из главных причин этого первого теста — проверить и при необходимости отрегулировать контрастность дисплея.

На задней панели дисплея есть маленький синий квадрат с крестиком посередине. Это небольшой потенциометр, который может (а может и не понадобиться) нужно отрегулировать, чтобы символы на дисплее были видны.

Запустите скрипт ниже, убедитесь, что Thonny показывает строку вывода (»Display is now showing characters»), затем проверьте переднюю панель LCD на наличие «Hello, World!» в первой строке.

Текст не виден?

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

Потенциометр хрупкий и не вращается бесконечно — он остановится на обоих концах своего диапазона, поэтому не применяйте силу!

Вот тестовый код:

from machine import I2C
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd

# Define LCD I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

# Set up LCD I2C
lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

# Show a string on the LCD
lcd.putstr("Hello, World!")

print("*********************************")
print("Display is now showing characters")
print("*********************************")

Задание #2: Скрипт «всё включено»!

Пора объяснить, как использовать эту библиотеку с LCD, и на этот раз мы сделаем всё немного наоборот — сначала запустим код, а потом объясним, как он работает.

Библиотека поставляется с тестовым файлом, демонстрирующим её возможности. Мы хотели немного разбить его, расставить пробелы и добавить много подробных комментариев, чтобы дать вам что-то предельно ясное, к чему можно возвращаться как к справочнику.

Возьмите пример кода ниже, запустите его в Thonny несколько раз, стараясь следить за комментариями, а затем продолжайте читать ниже для объяснений каждой части:

from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
import time

# Define LCD I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

# Our variables
mystring = "String variable" # String
myinteger = 44         # Integer
myfloat = 9.445589     # Float

# Just a function to sleep and then clear the LCD
# Saves lines after each example!
def clearLCD():

    time.sleep(3)
    lcd.clear()

##### Example Program Start #####

# This is how you show basic text
lcd.putstr("I am a string")
time.sleep(3)

# This is how you clear the display
# Do this before you send new data to be displayed
lcd.clear()

# This is how you move the cursor
# We moved it to the second row
# 1st number is column (X), 2nd number is row (Y)
# Numbers start at zero
# (0,0) is the 1st column, 1st row
lcd.move_to(0, 1)
lcd.putstr("Second row!")
clearLCD()

# This is how you show a variable
# Variables can be strings, integers or floats
# 'str' converts them to a string
# Here are three examples:
lcd.putstr(str(mystring)) # String
clearLCD()

lcd.putstr(str(myinteger)) # Integer
clearLCD()

lcd.putstr(str(myfloat)) # Float
clearLCD()

# This turns the backlight off
lcd.backlight_off()
clearLCD()

# This turns the backlight on
lcd.backlight_on()
clearLCD()

# This turns the standard cursor on
lcd.show_cursor()
clearLCD()

# This turns the standard cursor off
lcd.hide_cursor()
clearLCD()

# This turns the blinking cursor on
lcd.blink_cursor_on()
clearLCD()

# This turns the blinking cursor on
lcd.blink_cursor_off()
clearLCD()

Как работает библиотека

Приведённый выше код настраивает LCD, а затем проходит через множество наиболее полезных функций, которые вам понадобятся при создании проектов. Давайте разберём каждую из них.

Настройка библиотеки

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

from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd

Нам нужно сообщить нашей программе, какие I2C-выводы (SCL/SDA) мы используем, а также какую шину I2C (обратите внимание на распиновку Pico — синие I2C-выводы — это либо I2C 0, либо I2C 1? Это и есть номер шины):

# Define I2C pins/BUS
SDA = 14
SCL = 15
I2C_BUS = 1

Затем мы сообщаем программе, что такое адрес I2C LCD (мы нашли его за вас — это 0x27), а также количество строк и столбцов нашего дисплея. Это дисплей 16x2 (16 столбцов, 2 строки), поэтому мы используем 16 и 2:

I2C_ADDR = 0x27
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

Затем несколько строк, настраивающих наш I2C LCD с этой информацией:

lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

Функции библиотеки

Давайте разберём, что делает каждая из функций. Все они используются в примере скрипта выше. Первая — самая важная!

Важно! Очистка дисплея

Когда вы используете одну из функций ниже для отображения чего-либо на LCD, обычно сначала нужно очистить дисплей с помощью:

lcd.clear()

Если вы не используете lcd.clear() перед отправкой следующей информации на дисплей — она отобразится поверх старых данных, и в некоторых сценариях старые символы могут остаться и всё испортить.

Отправка строки

Чтобы отправить строку (текст) на LCD, используем:

lcd.putstr("I am a string")

Отправка переменной

Чтобы отправить переменную на дисплей, используем следующую строку. Переменные могут быть строками, целыми числами или числами с плавающей точкой, потому что эта строка сначала конвертирует их в строку с помощью str:

lcd.putstr(str(variable_name))

Отправка строки вместе с переменной

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

Просто используем оператор + для объединения двух элементов.

Вот строка, а затем переменная:

lcd.putstr("String" + str(variable))

Вот переменная, а затем строка:

lcd.putstr(str(variable) + "String")

Перемещение курсора

По умолчанию любые данные, отправленные на дисплей, отображаются в позиции 0,0 (первый столбец, первая строка). Иногда вам нужно будет показывать данные в других позициях.

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

Наш дисплей — 16x2 LCD, поэтому у нас 16 столбцов и 2 строки. Нумерация начинается с нуля: например, первый столбец — 0, последний — 15. Аналогично, первая строка — 0, вторая — 1.

Помните, эту строку нужно использовать перед строкой отправки данных, вот так:

lcd.move_to(0, 1)
lcd.putstr("Second row")

Выключение/включение подсветки

Чтобы выключить подсветку, используйте:

lcd.backlight_off()

Включить снова:

lcd.backlight_on()

Показать стандартный курсор

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

Включить его:

lcd.show_cursor()

Выключить его:

lcd.hide_cursor()

Показать мигающий курсор

Вы также можете настроить программу так, чтобы отображался мигающий курсор, указывающий текущую позицию.

Включить его:

lcd.blink_cursor_on()

Выключить его:

lcd.blink_cursor_off()

Задание #3: Отображение простых данных

Теперь, когда мы понимаем библиотеку, перейдём к некоторым примерам.

Простой счётчик

Начнём с простого — создадим базовый проект счётчика. Мы запустим простой цикл for с диапазоном 500 (цикл 500 раз).

Наш цикл for начнётся с 0 и на каждой итерации будет увеличиваться на 1. На каждой итерации мы вставляем это число (используя i) в наш код LCD, объединяя строку «Count: « с числом (i).

Мы очищаем LCD каждый раз, чтобы освободить место для новых данных на следующей итерации.

from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
import time

# Define LCD I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

for i in range(500):

    lcd.putstr("Count: " + str(i))
    time.sleep(1)
    lcd.clear()

Улучшенный счётчик

Приведённый выше пример работает, но вы замечаете лёгкое мерцание строки «Count:» на каждой итерации?

Это происходит потому, что мы очищаем весь дисплей каждый раз… но строка находится в статической позиции, так зачем постоянно её очищать и отображать заново?

В этом сценарии мы можем написать «Count: « только один раз, до начала цикла, а затем просто передавать новое число на дисплей при каждой итерации.

Мы делаем это, устанавливая позицию курсора на столбец 7 и записывая данные оттуда. Это означает, что «Count: « остаётся нетронутым, а число обновляется в нужном месте каждый раз. Кроме того, нам не нужно очищать дисплей при каждой итерации, поскольку число перезаписывает предыдущее (в той же позиции):

from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
import time

# Define LCD I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

lcd.putstr("Count: ")

for i in range(500):

    lcd.move_to(7, 0)
    lcd.putstr(str(i))
    time.sleep(1)

Задание #4: Отображение текущих данных окружающей среды

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

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

Мы используем lcd.putstr(str(round(measurements[„t“],1))) для всего в одной строке. Это берёт показание температуры, округляет его до 1 знака после запятой, конвертирует в строку (с помощью str) и передаёт на LCD. Мы видели подобное в день #9, когда впервые познакомились с нашим датчиком, так что это не должно быть слишком запутанным… правда?!

Прежде чем попробовать, нам нужно упомянуть одно ограничение нашего кода…

Холод может сломать наш код!

Наш код предполагает, что температура никогда не опустится до однозначных чисел, потому что если это произойдёт, останутся символы от предыдущего значения.

Например, если температура была 10.1 (четыре символа) и опустилась до 9.9 (три символа), новое значение 9.9 не перезапишет последнюю 1 в 10.**1**…и вы увидите 9.9**1**. Мы хотели показать вам это как рабочий пример темы «очистки LCD» — чтобы показать, почему это так важно.

Если температура в вашей среде может так понизиться, вам, возможно, придётся изменить код. Простым способом было бы просто очистить дисплей и перезаписать статические строки каждый раз внутри цикла и смириться с мерцанием. Есть и много других способов решить это.

Два I2C-устройства одновременно — это нормально?!

Вы можете заметить, что мы теперь используем два I2C-устройства (датчик температуры и LCD) на одних и тех же выводах SDA/SCL. Разве это не вызывает проблем?

Нет — в этом проекте всё в порядке! Оба наших устройства используют одинаковые SDA и SCL, но поскольку у них разные адреса, они могут общаться по отдельности.

Когда устройства имеют одинаковый адрес по умолчанию (который нельзя изменить) — вот тогда возникают проблемы. Но это тема другого урока…

Вот код:

from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
from dht20 import DHT20
import time

# Define LCD/sensor I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27
TEMP_ADDR = 0x38

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

# Set up LCD I2C
lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

# Set up temperature sensor I2C
tempi2c = I2C(I2C_BUS, sda=SDA, scl=SCL)
dht20 = DHT20(TEMP_ADDR, tempi2c)

# Write static text
lcd.putstr("Temp:")

lcd.move_to(0, 1) # Move to second row
lcd.putstr("Humidity:")

while True:

    # Grab data from the sensor dictionary
    measurements = dht20.measurements

    # Show temp data on the first row
    lcd.move_to(12, 0) # 12th column, 1st row
    lcd.putstr(str(round(measurements['t'],1)))

    # Show humidity data on the second row
    lcd.move_to(12, 1) # 12th column, 2nd row
    lcd.putstr(str(round(measurements['rh'],1)))

    time.sleep(5)

Задание #5: Дисплей минимальной/максимальной температуры

Приведённый выше пример отлично подходит для отображения текущей температуры, но что если мы хотим отслеживать самую низкую/высокую температуру, которую наша программа зафиксировала с момента запуска?

Это просто! Нам нужно только создать и поддерживать/обновлять переменные, сохраняющие самые низкое и высокое значения температуры.

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

Если они ниже/выше — мы обновляем переменную наименьшей/наибольшей с новым значением, затем записываем его во вторую строку дисплея.

Попробуйте (то же предупреждение применяется, если в вашей среде может быть температура ниже десяти градусов — возможно, вам стоит очищать LCD и записывать статический текст при каждом обновлении):

from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
from dht20 import DHT20
import time

# Define LCD/sensor I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27
TEMP_ADDR = 0x38

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

# Set up LCD I2C
lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

# Set up temperature sensor I2C
tempi2c = I2C(I2C_BUS, sda=SDA, scl=SCL)
dht20 = DHT20(TEMP_ADDR, tempi2c)

# Write  static LCD text
lcd.putstr("Current:")
lcd.move_to(0, 1) # Move to second row
lcd.putstr("L:       H:")

#Take initial reading
measurements = dht20.measurements

# Create temp and humidity variables
# From initial readings
lowtemp = round(measurements['t'],1)
hightemp = round(measurements['t'],1)

# Write initial low temp value to LCD
lcd.move_to(3, 1)
lcd.putstr(str(lowtemp))

# Write initial high temp value to LCD
lcd.move_to(12, 1) # 12th column, 2nd row
lcd.putstr(str(hightemp))

while True:

    # Grab data from the sensor dictionary
    measurements = dht20.measurements

    # Create variable for current temp
    tempnow = round(measurements['t'],1)

    # Update current temp on display
    lcd.move_to(12, 0)
    lcd.putstr(str(tempnow))

    # If the lowest temp is HIGHER than current temp
    if tempnow < lowtemp:

        # Update the lowest recorded temp
        lowtemp = tempnow

        # Update the LCD data
        lcd.move_to(3, 1) # 3rd column, 2nd row
        lcd.putstr(str(lowtemp))

    # If the highest temp is LOWER than current temp
    if tempnow > hightemp:

        # Update the highest recorded temp
        hightemp = tempnow

        # Update the LCD data
        lcd.move_to(12, 1) # 12th column, 2nd row
        lcd.putstr(str(hightemp))

    # Check every 5 seconds
    time.sleep(5)

Задание #6: Отображение цвета ленты

Вернём нашу светодиодную ленту в код. Запустим пример, который показывает текущий RGB-цвет на дисплее, изменяющийся при каждой смене цвета LED.

Для этого мы воспользуемся словарём цветов, поскольку сможем использовать ключ и как RGB-цвет, и как название цвета (текст/строку) для дисплея. Удобно!

Вот наш словарь mycolours (смело добавляйте ещё):

mycolours ={
    "Red":(255,0,0),
    "Green":(0,255,0),
    "Blue":(0,0,255),
    "White":(255,255,255),
}

Используем цикл for для итерации по словарю:

for i in mycolours:

Затем используем i (это будет ключ цвета, например Red) для цвета заливки светодиодной ленты и текста LCD.

strand.fill обращается к словарю, запрашивая значение, связанное с предоставленным ключом (i):

strand.fill(mycolours[i])

LCD-код может использовать i напрямую, поскольку ключ — это название цвета:

lcd.putstr(str(i))

Есть один нюанс — при итерации по словарю порядок не такой, как в списке. Можно добавить код для упорядочивания, но это, вероятно, немного сложновато для первых двенадцати дней (для любопытных и смелых: поищите метод **sorted()* с использованием lambda!*).

Попробуйте пример ниже обычным способом:

from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
from neopixel import NeoPixel
import time

# LED details
GPIOnumber = 2
LEDcount = 15

# Define the strand pin number and number of LEDs from variables
strand = NeoPixel(Pin(GPIOnumber), LEDcount)

# Define LCD I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

# Set up LCD I2C
lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

mycolours ={
    "Red":(255,0,0),
    "Green":(0,255,0),
    "Blue":(0,0,255),
    "White":(255,255,255),
}

# Turn off all LEDs before program start
strand.fill((0,0,0))
strand.write()
time.sleep(1)

while True:

    for i in mycolours:

        print(i)

        # Clear the LCD
        lcd.clear()

        # Fill the strand with the current colour
        strand.fill(mycolours[i])
        strand.write()

        # Update display with the key of the colour
        lcd.putstr("Strand colour:")
        lcd.move_to(0, 1) # 2nd row
        lcd.putstr(str(i)) # Show the dictionary key name

        time.sleep(2)

Задание #7: Нахождение случайного цвета

Хотите найти красивые RGB-цвета для использования в коде, но не знаете, какие RGB-значения использовать?

Как насчёт небольшой программы, которая показывает случайный цвет на нашей светодиодной ленте и отображает RGB-значения на дисплее, чтобы вы могли их записать? Аккуратная маленькая программа — и ещё один отличный повод использовать random!

Наш код создаёт случайные RGB-значения, передаёт их в светодиодную ленту, затем отображает отдельные RGB-значения на LCD рядом с R, G и B. Просто!

Если вам нужно больше времени, чтобы записать RGB-значения, просто увеличьте задержку в конце цикла.

Попробуйте:

from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
from neopixel import NeoPixel
import time
import random

# LED details
GPIOnumber = 2
LEDcount = 15

# Define the strand pin number and number of LEDs from variables
strand = NeoPixel(Pin(GPIOnumber), LEDcount)

# Define LCD I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

# Set up LCD I2C
lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

# Turn off all LEDs before program start
strand.fill((0,0,0))
strand.write()
time.sleep(1)

while True:

    lcd.clear()

    # Add surrounding text
    lcd.putstr("This colour is:")
    lcd.move_to(0, 1)
    lcd.putstr("R:   G    B")

    # Create random RGB values
    r = random.randint(0,255)
    g = random.randint(0,255)
    b = random.randint(0,255)

    # Fill the strand with the current colour
    strand.fill((r,g,b))
    strand.write()

    # Display the RGB values on the LCD
    lcd.move_to(1, 1)
    lcd.putstr(str(r)) # R value

    lcd.move_to(6, 1)
    lcd.putstr(str(g)) # G value

    lcd.move_to(11, 1)
    lcd.putstr(str(b)) # B value

    time.sleep(5)

Задание #8: Прокрутка текста!

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

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

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

from machine import I2C, Pin
from lcd_api import LcdApi
from pico_i2c_lcd import I2cLcd
import time

# Define LCD I2C pins/BUS/address
SDA = 14
SCL = 15
I2C_BUS = 1
LCD_ADDR = 0x27

# Define LCD rows/columns
LCD_NUM_ROWS = 2
LCD_NUM_COLS = 16

# Set up LCD I2C
lcdi2c = I2C(I2C_BUS, sda=machine.Pin(SDA), scl=machine.Pin(SCL), freq=400000)
lcd = I2cLcd(lcdi2c, LCD_ADDR, LCD_NUM_ROWS, LCD_NUM_COLS)

def ScrollLeft(text):

    # Adds 16 blank spaces after our string
    text = text + (16 * " ")

    while True:

        # Always start each loop at 0,0
        lcd.move_to(0, 0)

        # Show the first 16 characters of the string
        lcd.putstr(text[:16])

        # Scroll speed delay
        time.sleep(0.5)

        # Updates 'text' before the next loop
        # text[1:] removes the first character
        # + text[0] adds the first character to the end
        text = text[1:] + text[0]

        print(text)

# Run our function
ScrollLeft("We have scrolling text!")

Краткое описание прокрутки текста

Вот как работает наша функция прокрутки:

  • Мы передаём функции строку в качестве аргумента

  • Мы добавляем 16 пробелов (» «) к концу строки

  • Затем запускаем цикл while: - Каждый цикл мы принудительно возвращаем дисплей на позицию 0,0 - Отображаем первые 16 символов строки на дисплее - Добавляем задержку - Убираем первый символ строки и добавляем его в конец с помощью оператора среза (новая штука, смотрите ниже!)

В самом конце нашего примера кода, вне функции, мы вызываем эту функцию и добавляем строку, которую хотим отобразить, в скобках (как аргумент для функции).

Что такое оператор среза?

Мы используем оператор среза для манипуляции строкой, которую передаём в нашу функцию, но что это такое?

Операторы среза помогают нам нарезать строки, «вырезая» из них части и делая с ними разные вещи. Мы используем имя переменной строки, за которым следуют квадратные скобки [ ], и добавляем внутрь разные вещи в зависимости от того, что хотим сделать.

Вот несколько примеров. Создадим строку с именем mystring и напечатаем её с некоторыми операторами среза.

Пример 1

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

mystring = "Hello"

print(mystring[:1])
print(mystring[:2])
print(mystring[:3])

Вывод будет:

H
He
Hel

Пример 2

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

mystring = "Hello"

print(mystring[1:])
print(mystring[2:])
print(mystring[3:])

Вывод будет:

ello
llo
lo

Пример 3

Если мы просто введём число внутри квадратных скобок, оно вернёт символ из этого индекса строки. Попробуйте:

mystring = "Hello"

print(mystring[0])
print(mystring[1])
print(mystring[2])

Вывод будет:

H
e
l

Наш оператор среза

Мы используем операторы среза в этой строке нашего примера кода:

text = text[1:] + text[0]

Это берёт существующую переменную text, убирает первый символ с помощью text[1:] и затем добавляет первый символ в конец, используя + text[0].

Вопросы, которые могут возникнуть:

  • Зачем мы добавляем 16 пробелов? - Если бы мы этого не делали, когда строка прокручивается до последнего символа, он оказывался бы рядом с первым символом - Следите за строками печати во время работы программы, чтобы увидеть «пробел», который это создаёт - Закомментируйте эту строку и снова понаблюдайте за строками печати и LCD

  • Зачем мы каждый цикл вручную возвращаем LCD в позицию 0,0? - Мы позволяем символам перезаписывать предыдущие без очистки LCD. - Если мы не устанавливаем позицию каждый раз, программа попытается записать символы следующего цикла рядом с последними - Закомментируйте строку lcd.move_to, чтобы убедиться в этом самостоятельно

  • Зачем мы убираем первый символ и добавляем его в конец? - Мы убираем первый символ, а затем показываем обновлённую строку на следующем цикле. Это создаёт впечатление прокрутки текста. - Мы продолжаем добавлять этот удалённый символ в конец строки, чтобы строка отображала тот же текст бесконечно - Следите за строками печати во время работы программы, чтобы увидеть это в действии

  • Что делает звёздочка (*) в text = text + (16 * « «)? - Это просто оператор умножения - Это 16 x « « — пробел длиной 16 символов - Можно было написать « « , но вариант с оператором чище

День #12 завершён!

LCD — это весело, правда?! Мы показали вам основы использования LCD с библиотекой вместе с несколькими маленькими трюками, а теперь ваша очередь придумать крутые проекты, отображающие данные интересными способами.

Итоги! Что мы изучили в день #12:

  • Как подключить LCD (с I2C-платой расширения)

  • Использование нескольких I2C-устройств с разными адресами

  • Как использовать библиотеку LCD

  • Когда очищать LCD, а когда не нужно

  • Отображение данных датчика на LCD-дисплеях

  • Оператор среза

  • Прокрутка текста на LCD

  • Повторное использование полученных знаний (привет, **random*!*)

Что дальше…

Вот и всё, это конец *Let it Glow* :( Мы знаем, что многие из вас хотели, чтобы это продолжалось традиционные 25 дней, однако, как и в прошлом году, мы хотели сохранить это максимально доступным и недорогим для как можно большего числа людей.

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

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

Дополнительные ресурсы

Интернет переполнен проектами и примерами с использованием Raspberry Pi Pico и MicroPython, и 99,9% этой информации бесплатна — так что не останавливайтесь! Поисковые системы — ваш лучший друг в создании и программировании. Форумы, блоги, обучающие материалы, группы в социальных сетях и многое другое всегда под рукой и содержат бесчисленное количество примеров, фрагментов кода, предыдущих обсуждений и полезной информации.

Хорошим отправным пунктом для вдохновения и отличным сообществом является форум Raspberry Pi, раздел Pico. Только не забывайте всегда искать ответ перед тем, как задавать вопрос!

Весёлого Blinkmas и Мигающего Нового Года!

Для создания изображений схем подключения на макетной плате использовался Fritzing.