Электронные чернила и Raspberry Pi

Довольно часто возникает необходимость визуально представлять результаты работы устройства в том или ином человеко-понятном виде (текст, картинка, видео). Если это устройство не является абсолютно автономным, то задача решается проще, мы не сильно зависим от источника питания. На просторах Хабра есть ряд публикаций, посвященных различным метеостанциям и другим устройствам с экранами, подключенных к постоянному питанию.

А вот если нам нужно собрать полностью мобильное устройство, работающее от аккумуляторов, то здесь проблема потребления питания может стать достаточно острой. Так, при сборке собственного планшета на базе Raspberry Pi 3 мне пришлось выделить под тачскрин отдельный аккумулятор, так как при использовании общего источника (Li-Po, 6000 мАмпер-часов) питания устройство могло проработать более часа, но при запуске какого-либо ресурсоемкого приложения резко возрастал ток потребления и устройство тупо отрубалось, так как аккумулятор просто не мог выдать такой ток.

Электронные чернила

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

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

Несколько лет назад я собрал вот такое устройство на базе Raspberry Pi Zero и дисплея Adafruit eInk 2.13.

9d2da9ef16c34225d725655389665540.png

Этот микрокомпьютер сканировал Wi-Fi, и пытался подключиться к найденным открытым сетям. В случае, если удавалось подключиться, имя сети, время подключения, настройки IP — все выводилось на маленький дисплей. В силу небольшого форм-фактора устройства, аккумулятор тоже был не слишком мощный, порядка 2000 мАмпер-часов, его вполне хватало на пару часов работы устройства.

Что есть сейчас

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

6145092e77bf4ef59695c863029b6ec0.png

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

Дисплеи в трехцветном исполнении имеют черные и красные чернильные пиксели и белый фон. В линейке Adafruit есть трехцветный дисплей с разрешением 212×104 (более старый экран с более низким разрешением) и трехцветный дисплей с разрешением 250×122 (новый экран с более высоким разрешением). Монохромный (черно-белый) дисплей имеет разрешение 250×122 черных пикселя на белом фоне.

Монохромный гибкий дисплей 2.13 имеет меньшее разрешение — 212×104, но отличается гибкостью. Такой дисплей можно изгибать, хотя насколько это нужно на практике — вопрос открытый.

Ложки дегтя

При работе с этими маленькими экранами на основе электронных чернил следует помнить, что они работают очень медленно по сравнению с OLED, TFT-дисплеями или даже дисплеями с памятью. Полное стирание и замена изображения может занимать более секунды. Также существует рекомендуемое ограничение на обновление — вы не должны обновлять или менять отображение чаще, чем каждые 3 минуты (180 секунд).

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

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

Распиновка

В качестве ядра устройства предлагается использовать Raspberry Pi 2/¾, хотя по заверениям разработчиков, поддерживается также и Arduino. Разработка любого интеллектуального устройства состоит из сборки аппаратной части (в нашем случае подключения дисплея к микрокомпьютеру) и программной части (в нашем случае это будет код на Python).

Начнем со схемы подключения. В соответствии с информацией от вендора, представленной на рисунке ниже, дисплей имеет следующий набор пинов:

c8ed71ff18a84cb44d1aca0c488f4b09.png

Все пины нам не потребуются. На стороне Raspberry мы будем подключаться следующим образом:

  • Raspberry Pi 3.3 to display VIN

  • Raspberry Pi GND to display GND

  • Raspberry Pi SCLK to display SCK

  • Raspberry Pi MOSI to display MOSI

  • Raspberry Pi GPIO CE0 to display ECS

  • Raspberry Pi GPIO 22 to display D/C

  • Raspberry Pi GPIO 27 to display RST

  • Raspberry Pi GPIO 17 to display BUSY

Или на более понятном языке:

6fc678b6037b1f797796f376f90c97ea.png

С аппаратной частью, полагаю все понятно, теперь перейдем к программной.

Готовим софт

Вам нужно будет установить библиотеку Adafruit_Blink, которая обеспечивает поддержку Circuit Python в Python. Для этого выполним следующее:

sudo pip3 install adafruit-circuitpython-epd

Если pip3 не установлен, то сначала выполним:

sudo apt-get install python3-pip

Также, для работы с библиотекой потребуется файл шрифта. Вы можете загрузить с помощью команды:

wget https://github.com/adafruit/Adafruit_CircuitPython_framebuf/raw/main/examples/font5x8.bin

Прежде чем продолжить, убедитесь, что в папке, из которой вы запускаете скрипты, содержится файл font 5×8.bin.

Также может потребоваться PIL (Python Imaging Library) — библиотека изображений Python, позволяющая использовать графику и текст с пользовательскими шрифтами. Существует несколько системных библиотек, на которые опирается PIL, поэтому установка через менеджер пакетов — это самый простой способ установки:

sudo apt-get install python3-pil

Теперь мы можем рассмотреть несколько примеров. Давайте попробуем очистить экранный буфер и нарисовать несколько фигур.

В начале идет блок инициализации:

import digitalio
import busio
import board
from adafruit_epd.epd import Adafruit_EPD
from adafruit_epd.il0373 import Adafruit_IL0373
from adafruit_epd.il91874 import Adafruit_IL91874  # pylint: disable=unused-import
from adafruit_epd.il0398 import Adafruit_IL0398  # pylint: disable=unused-import
from adafruit_epd.ssd1608 import Adafruit_SSD1608  # pylint: disable=unused-import
from adafruit_epd.ssd1675 import Adafruit_SSD1675  # pylint: disable=unused-import
from adafruit_epd.ssd1680 import Adafruit_SSD1680  # pylint: disable=unused-import
from adafruit_epd.ssd1681 import Adafruit_SSD1681  # pylint: disable=unused-import
from adafruit_epd.uc8151d import Adafruit_UC8151D  # pylint: disable=unused-import
from adafruit_epd.ek79686 import Adafruit_EK79686  # pylint: disable=unused-import

# здесь мы прописываем нужные пины:
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
ecs = digitalio.DigitalInOut(board.D12)
dc = digitalio.DigitalInOut(board.D11)
srcs = digitalio.DigitalInOut(board.D10)  # can be None to use internal memory
rst = digitalio.DigitalInOut(board.D9)  # can be None to not use this pin
busy = digitalio.DigitalInOut(board.D5)  # can be None to not use this pin


# указываем нужный дисплей
print("Creating display")
# display = Adafruit_SSD1608(200, 200,        # 1.54" HD mono display
# display = Adafruit_SSD1675(122, 250,        # 2.13" HD mono display
# display = Adafruit_SSD1680(122, 250,        # 2.13" HD Tri-color display
# display = Adafruit_SSD1681(200, 200,        # 1.54" HD Tri-color display
# display = Adafruit_IL91874(176, 264,        # 2.7" Tri-color display
# display = Adafruit_EK79686(176, 264,        # 2.7" Tri-color display
# display = Adafruit_IL0373(152, 152,         # 1.54" Tri-color display
# display = Adafruit_UC8151D(128, 296,        # 2.9" mono flexible display
# display = Adafruit_IL0373(128, 296,         # 2.9" Tri-color display
# display = Adafruit_IL0398(400, 300,         # 4.2" Tri-color display

display = Adafruit_IL0373(
    104,
    212,  # 2.13" Tri-color display
    spi,
    cs_pin=ecs,
    dc_pin=dc,
    sramcs_pin=srcs,
    rst_pin=rst,
    busy_pin=busy,
)

 
# IF YOU HAVE A 2.13" FLEXIBLE DISPLAY uncomment these lines!
# display.set_black_buffer(1, False)
# display.set_color_buffer(1, False)

# IF YOU HAVE A 2.9" FLEXIBLE DISPLAY uncomment these lines!
# display.set_black_buffer(1, True)
# display.set_color_buffer(1, True)

display.rotation = 1
 
# очищаем буфер
print("Clear buffer")
display.fill(Adafruit_EPD.WHITE)
display.pixel(10, 100, Adafruit_EPD.BLACK)

# рисуем различные геометрические фигуры
print("Draw Rectangles")
display.fill_rect(5, 5, 10, 10, Adafruit_EPD.RED)
display.rect(0, 0, 20, 30, Adafruit_EPD.BLACK)

print("Draw lines")

display.line(0, 0, display.width - 1, display.height - 1, Adafruit_EPD.BLACK)

display.line(0, display.height - 1, display.width - 1, 0, Adafruit_EPD.RED)
display.display()

10e9ac35fea68b76ca6974e2714e0946.png

Для того, чтобы вывести текст используем следующий код:

print("Draw text")
display.text("hello world", 25, 10, Adafruit_EPD.BLACK)
display.display()

Рисуем картинку

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

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

…

display.rotation = 0

FILENAME = "blinka.bmp"
 

def read_le(s):
    # as of this writting, int.from_bytes does not have LE support, DIY!
    result = 0
    shift = 0
    for byte in bytearray(s):
        result += byte << shift
        shift += 8
    return result
 

class BMPError(Exception):
    pass


def display_bitmap(epd, filename):  # pylint: disable=too-many-locals, too-many-branches
    try:
        f = open(filename, "rb")  # pylint: disable=consider-using-with
    except OSError:
        print("Couldn't open file")
        return

    print("File opened")
    try:
        if f.read(2) != b"BM":  # check signature
            raise BMPError("Not BitMap file")

        bmpFileSize = read_le(f.read(4))
        f.read(4)  # Read & ignore creator bytes

        bmpImageoffset = read_le(f.read(4))  # Start of image data
        headerSize = read_le(f.read(4))
        bmpWidth = read_le(f.read(4))
        bmpHeight = read_le(f.read(4))
        flip = True

        print(
            "Size: %d\nImage offset: %d\nHeader size: %d"
            % (bmpFileSize, bmpImageoffset, headerSize)
        )
        print("Width: %d\nHeight: %d" % (bmpWidth, bmpHeight))

        if read_le(f.read(2)) != 1:
            raise BMPError("Not singleplane")
        bmpDepth = read_le(f.read(2))  # bits per pixel
        print("Bit depth: %d" % (bmpDepth))
        if bmpDepth != 24:
            raise BMPError("Not 24-bit")
        if read_le(f.read(2)) != 0:
            raise BMPError("Compressed file")

        print("Image OK! Drawing...")

        rowSize = (bmpWidth * 3 + 3) & ~3  # 32-bit line boundary

        for row in range(bmpHeight):  # For each scanline...
            if flip:  # Bitmap is stored bottom-to-top order (normal BMP)
               pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize
            else:  # Bitmap is stored top-to-bottom
                pos = bmpImageoffset + row * rowSize

            # print ("seek to %d" % pos)
            f.seek(pos)
            rowdata = f.read(3 * bmpWidth)
            for col in range(bmpWidth):
                b, g, r = rowdata[3 * col : 3 * col + 3]  # BMP files store RGB in BGR
                if r < 0x80 and g < 0x80 and b < 0x80:
                    epd.pixel(col, row, Adafruit_EPD.BLACK)
                elif r >= 0x80 and g >= 0x80 and b >= 0x80:
                    pass  # epd.pixel(row, col, Adafruit_EPD.WHITE)
                elif r >= 0x80:
                    epd.pixel(col, row, Adafruit_EPD.RED)

    except OSError:
        print("Couldn't read file")
    except BMPError as e:
        print("Failed to parse BMP: " + e.args[0])
    finally:
        f.close()
    print("Finished drawing")


# clear the buffer
display.fill(Adafruit_EPD.WHITE)
display_bitmap(display, FILENAME)
display.display()

В результате на дисплее появится что-то подобное:

abb4517e24bf151ccef8e2dc6afa5559.png

Заключение

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

В заключение расскажу про открытый урок «Фильтрация радиосигналов», который пройдет в рамках курса Otus «Электроника и электротехника» 19 июня. На нём вы освоите теоретические основы и практические аспекты разработки и использования радиочастотных фильтров. Полученные знания помогут вам в моделировании и создании эффективных фильтрационных схем для улучшения производительности радиосистем. Записывайтесь по ссылке.

Habrahabr.ru прочитано 2176 раз