Использование Raspberry Pi для управления светодиодным матричным дисплеем из Python

Это начало серии статей о проекте по созданию матричного RGB-дисплея 17x17 пикселей. В этой части рассматривается взаимодействие с дисплеем Raspberry Pi и создание библиотеки Python для отображения на нем графики. Мы начнем с базового интерфейса для микросхем WS2812B и продолжим с преобразованиями координат и цвета. К концу этой статьи мы сможем отображать файлы изображений на дисплее.

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

Превращение цепочки светодиодов в дисплей

Я использовал микросхемы WS2812B в виде полосок по 60 светодиодов на метр. Каждая микросхема имеет три светодиода красного, зеленого и синего цветов. Микросхема регулирует их яркость с помощью широтно-импульсной модуляции (ШИМ) от выключенного до полного включения за 256 шагов. Это означает, что на каждый пиксель приходится один байт цвета, что в сумме дает более 16 миллионов различных возможных комбинаций. ШИМ означает, что микросхема периодически включает и выключает светодиод, так что в среднем доля времени, в течение которого свет горит, равна установленному значению цвета. Рисунок иллюстрирует концепцию:

Когда период T меньше, чем скорость реакции ваших глаз, это создает иллюзию источника света постоянной яркости. Если бы вы сделали снимок камерой с выдержкой, которая намного короче T, скажем, в 10 раз короче, эта иллюзия исчезнет, ​​и вы увидите изображение пикселей, которые либо включены, либо выключены. Очень интересный вопрос, какой будет скорость затвора человеческого глаза. В поисках ответа можно многому научиться… Более простой вопрос — что нужно сделать, чтобы получить дисплей без мерцания. Эта статья дает некоторую предысторию. Короткий ответ: с частотой обновления 100 Гц или T = 10 мс вы должны быть в безопасности.

Я не совсем уверен, что такое период ШИМ на WS2812B и как именно работает время обновления. В техническом описании указана частота сканирования не менее 400 Гц. Кажется, это частота ШИМ, и это означает, что T = 2,5 мс. См. также это и это обсуждение. Другой фактор, который мы должны учитывать, — это частота обновления значений RGB в цепочке пикселей. При разрешении 17x17 пикселей у нас получается 289 пикселей в одной цепочке. В техническом описании указана скорость передачи данных 800 кбит/с (=килобит в секунду). При 24 битах на пиксель обновление составляет 289 x 24 бита = 6936 бит. Для обновления всех пикселей или частоты кадров 115 Гц потребуется около 8,7 мс. Этого должно быть достаточно даже для движущихся сцен.

WS2812B использует код на основе синхронизации на одном проводе данных для обновления значений пикселей. Подробности можно посмотреть в datasheet. Идея состоит в том, чтобы передать три символа 0, 1, RESET по одному проводу, используя различные соотношения высокого и низкого уровня напряжения. Все микросхемы находятся в цепочке, получая сигнал входных данных и отправляя сигнал выходных данных. После того, как микросхема получила 24 бита, она пересылает все остальное на выход, пока не увидит код сброса. Сброс немедленно пересылается нижестоящим чипам.

Это означает, что нам нужно отправить 6936 бит, используя символы 0 и 1. А затем одиночный RESET для отправки следующего кадра. Поскольку символы требуют точной синхронизации, стандартного интерфейса управления GPIO будет недостаточно. Вместо этого мы будем использовать специализированную библиотеку, которая может использовать ШИМ-выход Pi для связи со светодиодами. Этот выход обычно используется для звука на аналоговом выходе. Так что это нужно отключить, занеся модуль ядра в черный список и отключив звук:

/etc/modprobe.d/snd-blacklist.conf:
blacklist snd_bcm2835
/boot/config.txt:
#dtparam=audio=on

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

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

2D координаты

Для рисования графики на дисплее удобнее обращаться к пикселям в двумерной системе координат. Итак, нам нужна функция для преобразования двух координат в один индекс пикселя:

Код сначала проверяет, что заданные координаты находятся на экране. Затем он вычисляет позицию пикселя в цепочке. И, наконец, он снова проверяет, что вычисленная позиция находится в пределах экрана. В этом нет необходимости, если вычисление позиции правильное. Но удобно отлавливать ошибки в коде. Как вычислить позицию в цепочке, конечно же, зависит от того, как полоски соединены проводами. В моем случае я соединил выходной сигнал одной полосы с входом следующей полосы выше по короткому краю. Это означает, что четные и нечетные полосы идут в противоположных направлениях. К сожалению, я ошибся при наклеивании полосы 14 и положил ее не той стороной. Это объясняет (y ‹ 14) в операторе if. Также я добавил один светодиод, не относящийся к матрице экрана, сразу после выхода из Pi. Таким образом, есть одна позиция пикселя вне экрана, которая просто добавляется к вычисляемому индексу. Кроме того, вычисления выполняются прямолинейно: координата y умножается на ширину экрана, а координата x добавляется к этому числу, чтобы получить смещение в строке (слева направо или наоборот).

Абстракции

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

Он принимает любую двумерную последовательность Python и два преобразования. Один для координат и один для цвета. Мы только что видели одно возможное преобразование координат с использованием координат x и y. В компьютерной графике начало координат довольно часто помещают в верхний левый угол с осями, указывающими вправо и вниз. В математике начало координат находится в левом нижнем углу с осями, указывающими вправо и вверх. Или, может быть, я хочу повернуть дисплей на бок по какой-то причине. Делая систему координат параметром, она абстрагируется от реального графического кода и может быть легко изменена в любой момент времени. Тот же аргумент применим к преобразованию цвета. Загрузчик PNG может возвращать массив байтов. Некоторый другой код может захотеть использовать числа с плавающей запятой от 0 до 1 для представления значений цвета.

Блиттинг, сглаживание, гамма-коррекция…

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

Передача битового блока

Наша функция изображения сверху переносит двумерную матрицу цветных точек на отдельные светодиодные ИС. Вы можете думать об этой матрице как о кадровом буфере экрана. Он содержит полное содержание того, что показано. Что нам нужно, так это функции для изменения этого буфера кадра, чтобы отрисовать желаемое изображение. Типичная операция, которая нам также понадобится, — это объединение нескольких изображений в буфер кадра. Это операция, известная как разметка битов, восходящая к семидесятым годам. Битовое блицирование обычно передает блоки памяти, применяя операцию на пиксель для формирования результирующего изображения. По сути, это причудливая операция копирования. В первые дни операции были булевыми операциями, такими как И или ИЛИ. Этого достаточно, чтобы выбрать, должен ли пиксель T исходить от изображения A или B, учитывая бинарную маску M:

T = (NOT M AND A) OR (M AND B)

Это поместит пиксель из A в целевой буфер, если маска равна нулю в этой координате, и из B в противном случае. Сегодня вы также ожидаете альфа-смешивания: объедините значения цвета из обоих источников с помощью взвешенной суммы, где вес определяет уровень прозрачности. Кроме того, вы хотели бы указать координаты места назначения и исходный прямоугольник, чтобы контролировать, какая часть изображений используется.

Пространственное сглаживание

Сглаживание пытается уменьшить ступенчатость изображения, отображаемого на экране с разрешением менее бесконечного. Представьте себе пейзаж, на который вы смотрите с холма. Теперь вы уменьшаете это представление до конечного числа значений цвета пикселей. Для каждого пикселя вы должны выбрать один цвет, который представляет эту часть реального изображения. Участок травы на расстоянии будет уменьшен до одного среднего зеленого пикселя. Этот процесс получения значений пикселей называется выборкой. Когда вы позже посмотрите на это изображение на экране, у вас возникнет похожая проблема: разрешение вашего экрана, вероятно, отличается от разрешения изображения. Таким образом, ваш экран должен снова взять образец из изображения, чтобы определить значения отдельных пикселей. То, как выполняется эта выборка, может иметь огромное влияние на то, как человек воспринимает полученное изображение, особенно на то, насколько близко оно соответствует исходному виду. Название псевдонима происходит из области обработки сигналов: пространственные частоты выше предела Найквиста отсекаются при дискретизации и поэтому отображаются как более низкие частоты в дискретизированном изображении. Звучит сложно? Вы когда-нибудь замечали, как ротор вертолета меняет направление во время вращения? Тот же эффект, но теперь это временной псевдоним. Эффекты сглаживания появляются во многих местах. По-настоящему странным является эффект Umklapp.

В нашем случае мы хотим нарисовать угловые линии для стрелок часов. Мы можем вычислить эти линии как векторную графику произвольной точности, а затем сэмплировать их на 289 пикселей, которые у нас есть. В таком случае распространенным способом борьбы с алиасингом является суперсэмплинг: для каждого реального пикселя мы вычисляем цвета для нескольких субпикселей и берем среднее значение. Это не обязательно лучший подход — иметь обычную прямоугольную сетку субпикселей. Их положение может быть даже случайным, чтобы избежать резкого отсечки Найквиста. Поскольку мы хотим рисовать только отдельные линии, мы можем разместить субпиксели точно на линии и смешать цвет линии с фоном, взвешенным по расстоянию от субпикселя до центра пикселя.

Вот код:

Гамма-коррекция

В предыдущем разделе я описал, как светодиоды на нашем дисплее используют ШИМ для управления отображаемым цветом. Значение цвета от 0 до 255 определяет, как долго горит светодиод, что приводит к линейной зависимости между интенсивностью и запрограммированным значением цвета. К сожалению, это не очень хорошо согласуется с тем, как наши глаза воспринимают свет. Человеческая чувствительность ближе к степенному закону, что означает, что вы можете воспринимать меньшие абсолютные изменения яркости для темных областей, чем для ярких. Между прочим, люди могут видеть так же мало света, как один фотон. Из-за этой зависимости от степенного закона обычно выполняется гамма-коррекция от измеренной интенсивности I к сохраненному значению пикселя E (кодирование гаммы) и обратно к отображаемой интенсивности D (декодирование гаммы). Простейшая форма - экспоненциальная gamma > 1:

E = I ^ (1/gamma)
D = E ^ (gamma)

Обычно используемое цветовое пространство sRGB определяет несколько более сложную гамма-коррекцию. Если мы хотим отображать изображения, закодированные в цветовом пространстве sRGB, мы должны сделать эту коррекцию, чтобы вычислить значения ШИМ для наших светодиодов.

Если вы продолжите мыслить в том же духе, вы можете спросить, должны ли мы также делать поправку на относительную чувствительность глаза к красному, зеленому и синему цветам. Мы обязательно должны! Люди гораздо более чувствительны к зеленому, чем к синему. Цветовое пространство sRGB учитывает это, чтобы избежать потери битов. К счастью, светодиоды в WS2812B уже имеют относительную интенсивность, соответствующую реакции глаз. Нам не нужно делать никаких дополнительных исправлений, чтобы получить достаточно хорошие результаты.

Вот код для гамма-коррекции sRGB при загрузке изображения PNG:

Конец первой части

Теперь у нас есть все необходимое для рисования часов и отображения изображений, загруженных из файла. Вы можете найти полный код на https://github.com/five-elephants/hello-ursula. Если у вас есть вопросы или предложения, вы можете связаться со мной через Twitter.

В следующей части серии мы создадим веб-приложение для взаимодействия с дисплеем.