Разработка самой маленькой в мире книги на e-ink дисплее

Автор текста: the_bat

Привет Пикачу!

Хоть название и резонансное, здесь нет никакого преувеличения. Моя разработка предназначена не для повседневного использования, а скорее для развлечения, но тем не менее это весело. Например, Владимир Анискин из Новосибирска создал книгу о пленке Рафсан 70х90 микрон, а почему бы и нет? Я не претендую на то, чтобы попасть в Книгу рекордов Гиннеса, но если вам все еще интересно, найдите кота. Я не хочу разбивать эту статью на части, так что наберитесь терпения.

идея использовать электронные чернила для создания небольшой книги существует уже довольно давно. Я даже купил для этой цели прямоугольный E-ink дисплей диагональю 2,13 дюйма, но по незнанию заказал версию, не поддерживающую частичное обновление экрана. Изменить страницу можно только путем ее полного стирания, что занимает много времени и выглядит некрасиво. Корабль долгое время был заброшен, но каким-то образом мое внимание привлек 1,54-дюймовый дисплей (200х200 пикселей), и идея снова вдохновила меня. После долгих раздумий нужный монитор был заказан. Хотя упаковка заняла три недели, я решил сделать макет и выкатить старый монитор, уже не выдержав.

картина. 1. Дисплей E-ink на макетной плате 2.13

На плате есть ошибки. Я выбрал неправильные контакты SPI для дисплея и допустил небольшую ошибку со схемой подключения CP2102 в режиме автопрограммирования. Несколько перерезанных реек и три проволоки решили почти все мои проблемы.

Почему стоит выбрать ESP32.
Во-первых, файлы можно загружать через Wi-Fi. Во-вторых, в модуле имеется множество примеров эскизов, в том числе с использованием электронных чернил. Еще у меня есть нереализованная идея, требующая беспроводного соединения, но ее нет в этой статье (заговор). На самом деле я долго метался между микроконтроллерами и модулями, но последние все равно победили.

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

Рисунок 2. Схема включения CD4013

Потребление тока микросхемой в состоянии покоя составляет около микроампер, что меня вполне устраивает. IO4 ESP32 позволит вам программно выключить устройство, например, в течение длительных периодов бездействия. U/D – Кнопка перелистывания страниц. Сигнал OUT_KEY включает основное питание, как показано в следующем разделе схемы.

изображение 3. Схема зарядки и питания

Здесь все просто. Питание контролируется CD4013 с помощью полевого переключателя до тех пор, пока устройство не будет выключено. Сигнал ADC_EN от ESP32 обеспечивает напряжение для измерения заряда аккумулятора. Само зарядное устройство LTC4054. Возможно, стоит установить что-то более сложное с измерением заряда и шиной I2C, типа BQ25895, но на этом всё. Я использовал источник питания постоянного/постоянного тока вместо LDO (как было в прототипе). Зарядная ножка необходима для отслеживания окончания зарядки.

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

Рисунок 4. Схема подключения экрана E-ink

Как видно из таблицы на рисунке, можно подключать различные дисплеи. Я удалил DIP-переключатели и оставил резисторы включенными. Это сделано для экономии места, поскольку в этом проекте размер важен. По этой же причине не вставил SD-карту. На внутренней SPIFlash должно быть достаточно памяти для хранения книги в формате txt.

Батарея, которую я буду использовать, LiPo 582728, 3,7 В, 400 мАч. Этого достаточно для кратковременной работы в режиме Wi-Fi (проверено), но на крайний случай есть разъем microUSB. Вот, пожалуй, и все самое интересное в схемах.

Как только мой никнейм появился на дисплее на латинице, я решил попробовать кириллицу. Библиотеки, использующие электронные чернила, используют «E-Paper_code», поэтому их можно найти на github. Да, русских букв там нет. Мои руки почти упали. Особая благодарность Алексею a3x за помощь в расчете смещений букв в таблице ASCII (и не только) для корректной интерпретации.

if (char_offset > 1140) char_offset -= 780 // Функция DrawCharAt в epdpaint.h

чтобы нарисовать русские буквы, вам понадобятся две вещи. Первый — конвертер utf8rus (как пример). Теперь, когда всё понятно, снова заходим на github, чтобы найти скетч. Второй — рисовать буквы в библиотеке. Здесь немного сложно. В примере имеется несколько размеров шрифта: 8, 12, 16, 20, 24. Шрифт 8 имеет размеры 8x5 пикселей. Есть одно существенное преимущество – 25 строк на странице. Выглядит великолепно, но практически нечитабельно. Я думаю, что размер шрифта 12 будет подходящим. Максимальная высота 10 пикселей. На дисплее может отображаться 15 строк по 28 символов каждая.

Какие буквы можно перенести из английского алфавита (я знаю, что подобные многоязычные буквы бывают разными, например «К» и «К»). Потом я попробовал рисовать, но обнаружил, что буквы кривые и их не видно. Я решил воспользоваться программой GLCD Font Creator. Дела идут быстрее.

Рисунок 5. Шрифт «GLCD Font Creator» «Comic Sans MS»

Эта программа позволяет конвертировать шрифты в нужные точки. Поскольку соседние строки требуют отступов, высота этой библиотеки составляет 12 пикселей (12 шрифтов). Многие буквы были обрезаны, а некоторые стали неразборчивыми, но это гораздо проще исправить, чем рисовать их с нуля. В какой-то момент я также вспомнил, что букв было в два раза больше, чем ожидалось (прописные и строчные). Эта программа позволяет загружать шрифты в массив, но структурно у меня она не работает. Я морально не готов писать преобразование из одного массива в другой. Мне приходится все пересчитывать в калькуляторе – это уже другая задача. При внесении корректировок необходимо не забывать оставлять минимум по 1 пикселю слева и справа, чтобы буквы в тексте не слипались. В коде письмо выглядит так:

// @ 'f' (ширина 7 пикселей)
0x00, //
0x00, //
0x38, // ###
0x10, // #
0x7C, // #####
0x92, // # # #
0x92, // # # #
0x7C, // #####
0x10, // #
0x38, // ###
0x00, //
0x00, //

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

рисунок 6. Кириллица 1,54 дюйма, 200х200 пикселей

это первый вариант, который я доработал. Почему я до сих пор пишу твердые и мягкие слова заглавными буквами? Хотя… оказывается, есть, например, слово «бмх» — так назывался советский паровоз. Конечно, можно читать болгарские книги, в которых есть слова, начинающиеся на «Ъ».

С помощью скетча «ESP32_SPIFFS_test» я научился помещать текстовый файл книги на SPIFlash. Когда я впервые написал это, я был расстроен. Как и следовало ожидать, весь текст «разорван» по краям экрана. Прежде чем я смог нормально форматировать текст, мне пришлось воспользоваться онлайн-сервисом.

Рисунок 7. Форматирование текста до 28 символов в строке

Помимо расстановки переносов слов, существует еще и выравнивание текста. Результат очень хороший! Но тут возникает новая проблема. Символы «CR» и «LF» используются для объединения текста. Мой базовый код может искать только «\n» = «LF». Мне нужно вычислить смещение «CR» и заменить его пробелами, иначе в каждой строке справа я получу мусорные символы. Это костыль, но полезный.

Рис. 8. Первый вывод 16 строк текста книги

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

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

Void DrawPage (интервал страницы) {
если (!SPIFFS.begin(истина)) {
Serial.println("Произошла ошибка при установке SPIFFS");
возвращаться;
}
если(EEPROM.read(3) == 0) {
Файл = SPIFFS.open("/book.txt", "r");
если(!файл) {
Serial.println("Не удалось открыть файл для чтения");
возвращаться;
}
символ temp_qs[56];
в то время как (file.available()) {
file.readBytesUntil('\n', temp_qs, sizeof(temp_qs));
количество_строка++;
}
количество_страниц = (количество_строка/15);
если (количество_страниц> 255) {
EEPROM.write(2, Quantity_page/100);
EEPROM.write(3,Quantity_page%100);
} другой {
ЭСППЗУ.запись(2, 0);
EEPROM.write(3, Quantity_page);
}
ЭСППЗУ.коммит();
Файл.Закрыть();
Serial.println(Количество_страниц);
} другой {
Число_страниц = (EEPROM.read(2)*100)+EEPROM.read(3);
Serial.println(Количество_страниц);
}
Файл = SPIFFS.open("/book.txt", "r");
если(!файл) {
Serial.println("Не удалось открыть файл для чтения");
возвращаться;
}
если (number_pages == 0) {
Paint.SetWidth(200);
краска.setHeight(24);
краска прозрачный (бесцветный);
paint.DrawStringAt(2, 2, utf8rus("Файл пуст...").c_str(), &Font12, COLORED);
epd.SetFrameMemoryPartial(paint.GetImage(), 50, 20, Paint.GetWidth(), Paint.GetHeight());
epd.DisplayPartFrame();
} другой {
Serial.print("Номер страницы:");
Serial.println(страница);
Serial.println("Содержимое файла:");
for (int i = 0; i
int l = file.readBytesUntil('\n', one_string, sizeof(one_string));
}
Paint.SetWidth(200);
краска.setHeight(36);
//номер страницы
краска прозрачный (бесцветный);
paint.DrawStringAt(2, 2, utf8rus(String(page_count + 1)).c_str(), &Font12, ЦВЕТНОЙ);
paint.DrawStringAt(30, 2, utf8rus("from").c_str(), &Font12, ЦВЕТНОЙ);
paint.DrawStringAt(52, 2, utf8rus(String(Quantity_page + 1)).c_str(), &Font12, ЦВЕТНОЙ);
Paint.DrawRectangle(167, 5, 194, 15, цвет);
paint.DrawStringAt(168, 5, "'''''", &Font8, цвет);
epd.SetFrameMemoryPartial(paint.GetImage(), 2, 183, Paint.GetWidth(), Paint.GetHeight());
//Страница
интервал k_line = 2;
для (int я = 0; я <5; я++) {
краска прозрачный (бесцветный);
for (int s = 0; s < 3; s++) {
int l = file.readBytesUntil('\n', one_string, sizeof(one_string));
одна_строка[л] = 0;
//Serial.println(one_string);
paint.DrawStringAt(0, s * 12, utf8rus(one_string).c_str(), &Font12, ЦВЕТНОЙ);
}
epd.SetFrameMemoryPartial(paint.GetImage(),2,k_line,paint.GetWidth(),paint.GetHeight());
к_линия += 36;
}
Файл.Закрыть();
Страницы++;
}
}


Здесь мы читаем текстовый файл из памяти ESP32. В переменную "page" я передаю номер страницы из (псевдо)EEPROM - это своего рода закладка. Для перемещения указателя в файле необходим первый цикл for. Нам еще нужно усердно работать над этим.

Далее я решил сформировать итоговую строку, чтобы показать общее количество прочитанных страниц. Чтобы показать уровень заряда батареи, я еще нарисовал в рамке заглушку шрифтом 8 размера (чтобы не было пересечений при выводе). То есть для вывода текста осталось 15 строк. Далее сама страница обретает форму. Это требует отдельного пояснения.

интервал k_line = 2;
для (int я = 0; я <5; я++) {
краска прозрачный (бесцветный);
for (int s = 0; s < 3; s++) {
int l = file.readBytesUntil('\n', one_string, sizeof(one_string));
одна_строка[л] = 0;
//Serial.println(one_string);
paint.DrawStringAt(0, s * 12, utf8rus(one_string).c_str(), &Font12, ЦВЕТНОЙ);
}
epd.SetFrameMemoryPartial(paint.GetImage(),2,k_line,paint.GetWidth(),paint.GetHeight());
к_линия += 36;
}
Файл.Закрыть();
Страницы++;


как я писал ранее, этот монитор поддерживает частичные покадровые обновления. Максимальный размер кадра в памяти — 8192 пикселя. Заполнение кадра занимает примерно 0,8 секунды. Сначала я делал это построчно, это заняло около 15 секунд. После некоторых расчетов становится ясно, что можно сформировать сразу 3 линии: 200х36=7200 (каждая линия — 200х12). Тогда это занимает около 5 секунд - что намного лучше (4 секунды для текста, 1 секунда для строки состояния). Таким образом, внутренний цикл формирует три строки, а внешний — всю страницу. По какой-то причине здесь нет функции «epd.DisplayPartFrame()» (вывод вывода). Открыв книгу, пользователь видит, что читается последняя страница (запускаю в "void setup()"), а я готовлю следующую страницу (мне еще нужно подумать о процессе чтения предыдущей страницы).

неверный цикл()
{
if (((digitalRead(KEY_UP) == LOW) && (page_count <= Quantity_page + 2))) {
Число_страниц = (EEPROM.read(2)*100)+EEPROM.read(3);
//Сохранить страницу
Serial.println(page_count);
if (количество страниц > 255) {
EEPROM.write(0, количество страниц/100);
EEPROM.write(1, page_count%100);
} другой {
ЭСППЗУ.запись(0, 0);
EEPROM.write(1, количество_страниц);
}
ЭСППЗУ.коммит();
//Загрузить страницу
epd.DisplayPartFrame();
//Предварительная загрузка следующей страницы
drawpage(количество страниц);
задержка(50);
//Легкий сон
esp_light_sleep_start();
}
}


В итоге получается, что к тому моменту, когда мы наслаждались загрузкой логотипа и чтением первой страницы (5 секунд + 5 секунд), я уже был готов к следующей странице. Нажатием кнопки я моментально делаю вывод и начинаю формировать следующий, который готов примерно через 5 секунд. Запись закладки на тот же адрес EEPROM ни к чему хорошему не приведет, но 10 000 страниц я прочитаю. Вопрос в том, сколько страниц можно прочитать с аккумулятором емкостью 400 мАч? Вы все еще придерживаетесь графика сна, но оказывается, что это довольно просто. Добавьте «void setup()» в конце и добавьте функцию «esp_light_sleep_start()» при нажатии кнопки. Чтобы проснуться, установите RTC_GPIO «esp_sleep_enable_ext0_wakeup(GPIO_NUM_14, 0)». Итого: В момент "перелистывания страницы" потребляет около 38мА, затем 5,0мА. Мой мультиметр (UNI-T UT70A) показывает 0 в режиме измерения мкА, когда выключаю его кнопкой (думаю врет).

Рисунок 9. Вывод 15 строк текста + служебная строка

К плате добавляется еще один провод для кнопки. Оказывается, не все контакты ESP32 можно использовать как GPIO.

пока я «писал код», в производстве находилась вторая итерация платы. В результате размер получается 40х57мм.

Рис. 10. 3D-модель платы v0.2

Уменьшить размер я особо не пытался, так как решающую роль здесь играет аккумулятор и сам модуль ESP32. Кроме того, чем меньше размер, тем нелепее выглядит толщина устройства (батарея 5,8 мм). Я удалил светодиодные индикаторы с помощью жестов смахивания (оставив только индикаторы процесса зарядки/окончания), хотя они все еще имеют свое место и полезны для отладки.

Рисунок 11. Сборка платы

Рисунок 12. Устройство в сборе

Из нереализованного:

  1. Перезапишите записи EEPROM.

  2. Позволяет отправлять книги через Wi-Fi.

  3. Переверните страницу назад.

  4. Мониторинг и отображение уровня заряда батареи.

  5. Поддержка электронных книг.

  6. Научитесь писать код.


Я не люблю использовать видео в статьях, но здесь без него не могу. Он наиболее полно отражает работу оборудования.

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

PS: В первой книге «Саги о Ведьмаке» 1654 страницы (24810 строк).

Чтение – это движение к мудрости.
Благодарим за внимание и желаем удачи!


Написано специально для читателей Timeweb Cloud и Pikabu. Еще больше интересных статей смотрите в нашем блоге и телеграм-канале о Хабре.

Если вы хотите стать писателем (или уже знамениты) и хотите поделиться чем-то интересным в нашем блоге, напишите об этом здесь.

Облачный сервис Timeweb Cloud — рекомендуемая ссылка, которая может помочь поддержать проект автора.

[My] TimewebDIY Мастер сборки гаджетов с электронными чернилами на электронных чернилах Техническое шоу Домашнее видео Длинный пост на YouTube 27

Больше интересных статей здесь: Гаджеты.

Источник статьи: Разработка самой маленькой в мире книги на e-ink дисплее.