Программирование графики на MicroPython для дисплеев Raspberry Pi Pico
В одном из предыдущих руководств мы объяснили, как управлять цветом на дисплеях Waveshare Raspberry Pi Pico LCD.
В этом руководстве Тони Гудхью объясняет, как использовать базовые графические процедуры, включённые в драйвер дисплея, а для более амбициозных мейкеров он также приводит примеры для продвинутых фигур и графики!
Это очень удобно для создания собственных интерфейсов, отображения данных и других интересных проектов на множестве LCD-дисплеев Waveshare Pico.
Что вам понадобится
Raspberry Pi Pico с припаянными контактами
Кабель Micro-USB — для питания и программирования Pico
Дисплей Waveshare Pico LCD и программное обеспечение драйвера
Установленный на компьютере Thonny
Умение вводить, редактировать, сохранять и выполнять код MicroPython на Pico через Thonny
Дополнительно: потенциометры 10 кОм, макетная плата, соединительные провода, Pico Decker/Duo и тактильные кнопки.
Мы будем использовать дисплей Waveshare 1.44» 128x128 Pico, но любой другой из этой серии будет работать точно так же.
Начало работы
Для построения графической системы достаточно одной простой инструкции — установить определённый пиксель (x, y) в заданный цвет.
Все остальные графические и текстовые объекты, которые мы хотим отображать, строятся из этой единственной пиксельной инструкции: линии, окружности, прямоугольники, треугольники и текстовые строки разных размеров.
Всё это реализуется кодом. Производители дисплеев обычно поставляют часть таких процедур/методов, а остальное предоставляется пользователю для самостоятельной реализации.
Импорт библиотек
В начале нашей программы-драйвера мы всегда импортируем минимальный набор библиотек с помощью следующего блока в начале MicroPython-скрипта (позже мы добавляем ещё библиотеки, когда пишем расширенные программы):
from machine import Pin,SPI,PWM
import machine
import framebuf
import utime
import gc
Третья строка импортирует библиотеку Framebuffer, которая содержит несколько очень полезных процедур для рисования объектов на дисплее. Также была импортирована библиотека сборщика мусора gc, чтобы можно было проверять доступный объём памяти.
Базовые примеры
Начнём с нескольких простых примеров кода, который следует за блоком импортов.
Следующая строка помещает 16-битный код цвета в буфер в позиции пикселя (x, y):
lcd.pixel(x, y, c)
…а горизонтальную красную линию можно нарисовать так:
for x in range(25):
lcd.pixel(x, 5, colour(255,0,0))
lcd.show()
Подсказка: если нарисовать серию линий одинаковой длины, расположенных одна под другой вплотную, получится закрашенный прямоугольник. Две горизонтальные и две вертикальные линии образуют контур прямоугольника.
Рисование примитивных фигур
Следующие методы рисуют фигуры (как показано выше) в FrameBuffer. Они становятся видимы пользователю только после выполнения инструкции lcd.show().
Ниже приведено описание из документации MicroPython:
lcd.fill(c)
Заполнить весь дисплей указанным цветом.
lcd.pixel(x, y[, c])
Если c не указан — получить значение цвета указанного пикселя (полезно для обнаружения столкновений в игре). Если c указан — установить указанный пиксель в заданный цвет.
Например:
lcd.pixel(25, 30, colour(0,255,0)) # Окрашивает пиксель (25, 30) зелёным
pc = lcd.pixel(45, 50) # Получает цвет пикселя в позиции (45, 50)
lcd.vline(x, y, h, c)
lcd.hline(x, y, w, c)
lcd.line(x1, y1, x2, y2, c)
Нарисовать линию из заданных координат с указанным цветом и толщиной 1 пиксель. Метод line рисует линию до второго набора координат, тогда как методы hline и vline рисуют горизонтальные и вертикальные линии соответственно до заданной длины.
lcd.rect(x, y, w, h, c)
lcd.fill_rect(x, y, w, h, c)
Нарисовать прямоугольник в заданном месте, с указанным размером и цветом. Метод rect рисует только контур толщиной 1 пиксель, тогда как fill_rect рисует и контур, и заливку.
lcd.text(s, x, y[, c])
Вывести текст в FrameBuffer, используя координаты как верхний левый угол текста. Цвет текста можно задать необязательным аргументом, иначе используется значение по умолчанию 1. Все символы имеют размер 8x8 пикселей, и в настоящее время нет возможности изменить шрифт.
lcd.scroll(xstep, ystep)
Сдвинуть содержимое FrameBuffer на заданный вектор. При этом в lcd могут остаться следы предыдущих цветов.
FrameBuffer.blit(fbuf, x, y, key=- 1, palette=None)
Нарисовать другой FrameBuffer поверх текущего в заданных координатах. Если задан key, он должен быть целочисленным значением цвета, и соответствующий цвет будет считаться прозрачным: все пиксели с этим значением цвета не будут нарисованы.
Примеры программ с фигурами
Мы предоставляем несколько базовых примеров программ для некоторых популярных плат (переименуйте файлы, если при скачивании к имени добавились странные символы):
Каждая программа содержит код драйвера экрана, настраивает кнопки/джойстик (если применимо), задаёт переменные ширины и высоты, загружает необходимые библиотеки, определяет процедуры colour (R, G, B) и clear (c), а затем отображает проверочный текст цветов следующим образом:
Задания для самостоятельной практики
С помощью lcd.vline и lcd.hline нарисуйте синюю рамку по крайним пикселям экрана. Подождите 3 секунды и поставьте одиночный белый пиксель в каждом углу.
С помощью lcd.rect измените контур на красный с жёлтыми угловыми точками.
С помощью lcd.fill_rect залейте весь экран зелёным, а затем закрасьте центр чёрным, оставив рамку в 10 пикселей. Поставьте красные квадраты 10x10 пикселей в каждом углу.
Нарисуйте диагональные синие линии по экрану из противоположных углов и жёлтый закрашенный квадрат шириной 21 пиксель ближе к центру. Нарисуйте оранжевый ромб-контур, касающийся центральных точек сторон квадрата.
Напишите своё имя в центре экрана и прокрутите его вверх, влево, вниз, вправо и по диагонали на 15 пикселей, вернувшись в исходное положение.
Нарисуйте тёмно-серый прямоугольник в центре экрана. Нарисуйте 500 белых пикселей внутри квадрата, ни один не касается края. (Случайные числа были рассмотрены в предыдущем руководстве по дисплеям.)
Расширенные графические процедуры
Теперь давайте рассмотрим более _продвинутые_ графические процедуры!
Мы предоставляем оптимизированные примеры программ с расширенной графикой для некоторых популярных плат. Скачайте файл для своей платы и продолжайте чтение (переименуйте файлы, если при скачивании к имени добавились странные символы):
Эти программы вводят расширенные процедуры для рисования закрашенных и контурных треугольников, окружностей и колец. Процедуры достаточно сложные, но вы можете использовать их, не понимая до конца, как они работают (хотя мы призываем вас экспериментировать с ними, ломать их и учиться!)
Код закрашенного треугольника занимает очень много памяти, но он очень полезен, поскольку любой закрашенный многоугольник можно разбить на серию треугольников.
Разбор расширенных графических функций
Вот объяснение некоторых ключевых строк кода, которые вы увидите в приведённых выше файлах.
def triangle(x1,y1,x2,y2,x3,y3,c): # Рисует контур треугольника
Эта функция простая — она просто рисует три линии:
def tri_filled(x1,y1,x2,y2,x3,y3,c): # Рисует закрашенный треугольник
Эта процедура очень сложная. Она разбивает исходный треугольник на два горизонтальной линией, а затем закрашивает их. Если раскомментировать все строки с # lcd.show() и инструкциями sleep, выполнение сильно замедлится и можно будет наблюдать процесс (к сожалению, дисплею 2» требуется такой большой буфер, что памяти для кода закрашенных треугольников не хватает):
def circle(x,y,r,c):
Рисует закрашенную окружность с центром (x, y) и радиусом r, используя теорему Пифагора:
def ring(x,y,r,c):
Рисует пустую окружность с центром (x, y) и радиусом r:
Пример расширенной графики для дисплея 1.44»
Вот видео программы, запущенной на дисплее 1.44»:
Структура программы с расширенной графикой
Импорт
Для импортов мы добавили библиотеку math, так как она нужна для Sin и Cos при построении графиков. Также была импортирована библиотека random для генерации случайных треугольников. За ними следует базовая настройка LCD-платы, которую мы рассматривали ранее.
from machine import Pin,SPI,PWM
import framebuf
import utime
import gc
import math
import random
Заголовочный экран
Здесь мы создаём заголовочный экран. Обратите внимание, что синие звёздочки добавляются позже:
# Title screen
clear(0)
lcd.show()
lcd.text("Graphics",5,10,colour(200,0,0))
cc = colour(200,200,0)
lcd.text("Triangles",24,30,cc)
lcd.text("Circles",24,40,cc)
lcd.text("Rings",24,50,cc)
lcd.text("Rectangles",24,60,cc)
lcd.text("and Lines",24,70,cc)
for y in range(5):
lcd.text("*",13,y*10 + 30,colour(0,0,255))
lcd.show()
utime.sleep(2)
lcd.fill(0)
Графика и фигуры
Следующая секция рисует прямоугольники и надпись «Graphics»:
# Built into framebuf library with the basic font
lcd.rect(0,80,128,47,colour(255,0,0))
lcd.show()
utime.sleep(0.6)
for x in range(4,125,5):
lcd.vline(x,82,40,colour(0,0,255))
for y in range(82,125,5):
lcd.hline(4,y,121,colour(0,0,255))
lcd.fill_rect(20,88,89,29,colour(50,50,50))
lcd.text("Graphics",32,98,colour(0,255,255))
lcd.show()
utime.sleep(1)
Следующей загружается секция треугольников:
# Triangle
x1 = 2
y1=5
x2=15
y2=45
x3=120
y3=75
c = colour(90,90,90)
triangle(x1,y1,x2,y2,x3,y3,c)
lcd.show()
utime.sleep(1)
tri_filled(x1,y1,x2,y2,x3,y3,c)
lcd.show()
utime.sleep(1)
triangle(10,30,50,2,70,70,colour(255,255,255))
lcd.show()
utime.sleep(1)
Окружности и кольца
Далее отображается секция окружностей и колец:
# Circle & Ring
c = colour(0,0,255)
ring(90,30,25,c)
lcd.show()
utime.sleep(1)
circle(90,30,25,c)
lcd.show()
utime.sleep(1)
c = colour(0,255,0)
ring(90,30,25,c)
lcd.show()
utime.sleep(1)
c=colour(255,0,0)
ring(90,30,30,c)
lcd.show()
utime.sleep(1)
c = colour(255,0,0)
circle(90,30,15,c)
lcd.text(chr(227),85,25,colour(225,0,0))
lcd.show()
…за которой следует жёлтое «нитяное искусство», демонстрирующее линии, нарисованные под углом — медленнее, чем vline и hline:
# Thread art
for i in range(0,61,4):
lcd.line(0,10+i,i,70,colour(255,255,0))
lcd.show()
utime.sleep(1)
Синусоида и косинусоида
Построение кривых синуса и косинуса требует довольно сложной математики с использованием библиотеки math, но демонстрирует работу с отдельными пикселями:
# Graphs - Sine and Cosine
clear(0)
lcd.show()
c = colour(80,80,80)
factor = 361 / width
lcd.hline(0,40,160,c)
lcd.show()
c = colour(255,0,0)
for x in range(0,width):
y = int ((math.sin(math.radians(x * factor)))* -30) + 40
lcd.pixel(x,y,c)
lcd.show()
lcd.text("Sine", 5, 65, colour(255,0,0))
lcd.show()
utime.sleep(1)
lcd.show()
c = colour(80,80,80)
lcd.hline(0,40,160,c)
lcd.show()
c = colour(0,255,0)
for x in range(0,width):
y = int((math.cos(math.radians(x * factor)))* -30) + 40
lcd.pixel(x,y,c)
lcd.text("Cosine",60,10,colour(0,255,0))
lcd.show()
utime.sleep(5)
Простая прокрутка
Здесь демонстрируется простая прокрутка:
clear(0)
lcd.text("Scrolling",2,2,colour(255,255,0))
lcd.show()
utime.sleep(0.8)
for i in range(15):
lcd.scroll(2 * i, i)
lcd.show()
utime.sleep(0.3)
Наконец, случайные треугольники и последовательность «Завершение и уборка»:
# 30 random triangles
c = colour(200,0,0)
for i in range(30):
clear(0)
c=colour(200,0,0)
x1 = random.randint(2,width-3)
x2 = random.randint(2,width-3)
x3 = random.randint(2,width-3)
y1 = random.randint(2,height-3)
y2 = random.randint(2,height-3)
y3 = random.randint(2,height-3)
tri_filled(x1,y1,x2,y2,x3,y3,c)
c=colour(0,200,0)
triangle(x1,y1,x2,y2,x3,y3,c)
c=colour(0,0,200)
lcd.text(str(i),110,5,c)
lcd.show()
utime.sleep(0.6)
clear(0)
lcd.text("Done",45,40,colour(255,0,0))
lcd.show()
utime.sleep(3)
clear(0)
lcd.show()
Задания для самостоятельной практики
Отобразите синее «нитяное искусство» в верхнем левом и нижнем правом углах экрана.
В центре экрана отобразите круговую мишень «яблочко» с золотым центром, 4 другими цветами и очками 10, 8, 6, 4 и 2, написанными в соответствующих позициях.
Нарисуйте 50 случайных треугольников случайных цветов на экране, быстро, без очистки экрана.
Нарисуйте 50 случайных окружностей случайных цветов на экране, быстро, без очистки экрана.
Нарисуйте в левой части экрана зелёную стрелку, указывающую вправо, и прокрутите её за правый край экрана.
Нарисуйте четыре пурпурные стрелки, указывающие в четыре угла экрана (каждая стрелка требует 3 закрашенных треугольника — один для наконечника и два для древка).
Проект «Динамическая гистограмма»
Для этого нужны кнопки на трёх контактах. Если на вашей плате нет встроенных кнопок — подключите их на макетной плате.
Начните с программы MIN, сохранённой ранее. Сохраните её под именем Bar.py. Удалите основную часть программы и вставьте этот код. Он позволяет управлять переменной v в диапазоне от 0 до 100 двумя кнопками. Третьей кнопкой можно остановить программу.
# ==== Board now setup ========== MAIN BELOW====================
lcd.text("Bar Graph",5,5,colour(255,0,0)) # Title
lcd.vline(9,16,30,colour(200,0,0)) # Base line
lcd.vline(8,16,30,colour(200,0,0))
v = 50 # Initial value
running = True # Loop control
while running:
# Update v if button pressed
if key3.value() == 0:
v = v + 1
if v > 100:
v = 100
if key2.value() == 0:
v = v - 1
if v < 0:
v = 0
lcd.fill_rect(10,20,100,60,0) # Rub out Bar and percentage with background
lcd.fill_rect(10,20,v,20,colour(200,200,200)) # Draw new Bar
lcd.text(str(v) + " %",35,50,colour(200,2000,0)) # Update percentage
lcd.show()
utime.sleep(0.01)
# Halt now?
if key0.value() == 0:
running = False # Stop looping
# Tidy Up ============
pwm.duty_u16(32768)# HALF BRIGHTNESS
clear(0)
lcd.show()
На что обратить внимание
Заголовок и базовая линия рисуются только один раз — до начала цикла.
В цикле мы не очищаем весь экран и не перерисовываем всё заново.
Мы перекрываем полосу и процентное значение прямоугольником цвета фона перед обновлением. Меняются только они.
В цикле есть только одно lcd.show().
Цикл управляется булевой переменной running. Изменение её значения с True на False останавливает цикл.
Можно добавить дополнительную проверку — обновлять ли дисплей. Обновление нужно только в том случае, если значение v изменилось при нажатии кнопки.
Следующая версия содержит эту проверку:
# ==== Board now setup ========== MAIN BELOW====================
lcd.text("Bar Graph",5,5,colour(255,0,0)) # Title
lcd.vline(9,16,30,colour(200,0,0)) # Base line
lcd.vline(8,16,30,colour(200,0,0))
v = 50 # Initial value
oldv = 999
running = True # Loop control
while running:
# Update v if button pressed
if key3.value() == 0:
v = v + 1
if v > 100:
v = 100
if key2.value() == 0:
v = v - 1
if v < 0:
v = 0
if v != oldv:
lcd.fill_rect(10,20,100,60,0) # Rub out Bar and percentage with background
lcd.fill_rect(10,20,v,20,colour(200,200,200)) # Draw new Bar
lcd.text(str(v) + " %",35,50,colour(200,2000,0)) # Update percentage
lcd.show()
oldv = v
utime.sleep(0.01)
# Halt now?
if key0.value() == 0:
running = False # Stop looping
# Tidy Up ============
pwm.duty_u16(32768)# HALF BRIGHTNESS
clear(0)
lcd.show()
Примечание: oldv инициализируется «нестандартным» значением, чтобы принудительно выполнить lcd.show() при первом прогоне цикла.
Задания для самостоятельной практики
Измените гистограмму с горизонтальной на вертикальную.
Замените ввод с кнопок на UP и DOWN джойстика, если он доступен.
Добавьте шкалу к гистограмме — отметку для каждых 10 пикселей вдоль верхнего края полосы.
Замените ввод на потенциометр с диапазоном от 0 до 100.
Обновите программу смешивания цветов из предыдущего руководства, чтобы отображать значения красного, зелёного и синего в виде цветных гистограмм.
Используйте джойстик для перемещения круглого шарика по экрану.
Заставьте небольшой квадрат или окружность отскакивать от границ экрана. Можно также добавить рамку по краям.
Вы могли заметить, что на некоторых экранах текст очень маленький и его трудно читать. В следующем руководстве мы добавим дополнительный шрифт с большим набором символов, который можно будет отображать в разных размерах.
Об авторе
Эта статья написана Тони Гудхью. Тони — педагог на пенсии, преподававший информатику, который начал писать код в 1968 году, когда это ещё называлось программированием — он начинал с FORTRAN IV на IBM 1130! Активный участник сообщества Raspberry Pi, его основные интересы сейчас — программирование на MicroPython, путешествия и фотография.