Пишем свою библиотеку для I2C OLED микродисплея 128х32px

fjxhzvm9ms608zg5wnm-dot-7bc.png


Недавно я обратил свой взгляд на небольшой, но весьма привлекательный OLED микродисплей, который можно вполне успешно применять в своих самоделках, одна беда — известные библиотеки для него поддерживают только латинский шрифт. Ситуация усугубляется тем, что я использую его для подключения к esp32, а не Arduino. Поэтому arduino-библиотеки можно сразу отбросить. Что с этим делать и как дальше жить, об этом мы и поговорим в этой статье.

Честно скажу, этот экранчик мне понравился сразу, как я взял его в руки — особенно когда я увидел его цену, ещё до всяких повышений: цельных 60 р. в розницу (непозволительная роскошь, на грани разорения)!

Сейчас конечно, ситуация несколько грустнее, но не сильно: в пределах «непосильных» 90 с небольшим рублей — он вполне доступен :-)

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

s24hdem00zgoax-yxx2h_pj7xm0.jpeg

Что касается его разрешения, то оно — 128×32 пикселя. Благодаря своему типу, он обладает весьма высокой яркостью, как я уже говорил, впрочем, как и всё OLED дисплеи — яркость позволяет вполне спокойно смотреть на него и чётко различать индикацию даже в яркий солнечный день (только смартфон никак не может сфокусироваться на такую яркость — что и видно на ролике ниже).

Благодаря интерфейсу I2C, для подключения дисплея к esp32 достаточно только двух пинов (не считая двух пинов питания): Serial Clock (SCL) и Serial Data (SDA).

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

Автор разработал решение, которое запустилась у меня на моей esp32 без всяких «танцев с бубном», что и подкупило.

Кроме того, в правовой информации сам автор пишет, что разрешает все возможные модификации и допиливания своей библиотеки, разумеется, если всё это производится не в коммерческих целях (само собой :-)).

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

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

setBrightness(value);


Но, опять же, ложка дёгтя в бочке мёда всё же присутствует: поддержки русского языка не предусмотрено. А что если мы её добавим?!

Сразу скажу, что до этого с дисплеями подобного рода мне работать не приходилось, и поэтому это был даже своего рода для меня вызов — смогу или не смогу осилить подобную работу?!
Изучая документацию по функциям этой библиотеки, меня особенно привлекли 2 из них: для рисования линий и для включения какого-либо конкретного пикселя по координатам X Y:

drawLine(x1,y1,x2,y2);
setPixel(x,y);


И я подумал, что зачем выдумывать — будем базироваться на них, в процессе построения своего решения!

То есть, наше решение будет заключаться в том, что мы будем отрисовывать каждую букву для русского шрифта!

Сразу, после того как созрело подобное решение, передо мной в полный рост встала задача раздобыть некий шрифт.

Сначала, в порыве горячки, я даже подумал, что, может быть, имеет смысл нарисовать его самому? Но быстро отбросил это решение, так как представил гигантский объём работы, который на меня свалится в этом случае. Кроме того, не факт, что получится хорошо.

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

Окей, шрифт скачан, что дальше? Теперь этот шрифт нужно каким-то образом наложить на матрицу, чтобы представить, как он будет выглядеть в реале. Кроме того, такое наложение позволит нам узнать точные координаты каждого пикселя по X Y, чтобы понимать, какие пиксели необходимо включить для отображения той или иной буквы.

Для этих целей мы запускаем свой любимый векторный редактор Corel Draw (лицензионный, кстати! — куплен давным-давно, для одной затеи), в котором отрисовываем из квадратиков матрицу 128×32.

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

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

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

В целом, после того как алфавит и символы были набросаны на матрицу, это стало выглядеть как-то примерно так (кликабельно):

hz7hxyxpqcg8lxwjuz6zg82ucts.jpeg

Сразу скажу, что не все символы, которые поддерживают библиотека, показаны на картинке. Полный список поддерживаемых символов вы можете найти в pdf-файле по ссылке.

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

Как я уже говорил, мы будем использовать для своей работы две изначальные функции — для включения отдельного пикселя и для рисования линии. Функция для рисования линии требует координаты X Y начальной точки и конечной точки линии.

После внесения всех символов в вордовский файл это стало выглядеть примерно как-то так:

w13zwu3qkboxjnxd_lcvnfjymrq.jpeg

Здесь данные для передачи в функцию линии показаны чёрным цветом, а для передачи в функцию включения пикселя — выделены красным цветом, просто чтобы не путать.

Ну, а дальше начинаем кодить!

Так как каждая буква должна отображаться на новом месте, то необходимо после отображения буквы смещать оффсет — стартовую позицию каждой новой буквы. Так как ширина букв имеет значение в 5 пикселей, то после отображения каждой буквы мы значение смещения увеличиваем на 7 — на ширину буквы + 2 пикселя между ними (это не касается узких символов типа:».,:|» — там смещение идёт на 3 единицы).

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

k-cjc2ty5txqst-zctipstwzhma.jpeg

1v9e_oziblztebthex31vievzdc.jpeg

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

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

Так как я изначально представлял себе этот «отрисовщик русского шрифта» в виде некой вызываемой компактной функции, именно так я и сделал:


void RuCharPrinter (String s, int x, int y, OLED &object)
{
  //тут код
}


Как можно видеть по этой функции, она на вход принимает строку (что должно быть отображено на экране), а также начальные координаты по X Y, с которых и начинается отображение.
Кроме того, в функцию передаётся по ссылке и объект класса OLED (чтобы не множить сущности).

Функция содержит достаточно длинную «портянку» кода, который обрабатывает отображение букв, передавая их отдельной подфункции для отрисовки линий или точек. Для примера посмотрим, как выглядит типовая буква, например, буквы а, б, в:

    if (temp==176) //буква а
    {
      lineDrawer (2,1,4,1,x,y, object);
      lineDrawer (1,2,1,7,x,y, object);
      lineDrawer (5,2,5,7,x,y, object);
      lineDrawer (2,4,4,4,x,y, object);
     
      isCharFinded=true;
    }
    else if (temp==177) //буква б
    {
      lineDrawer (2,1,5,1,x,y, object);
      lineDrawer (1,1,1,7,x,y, object);
      lineDrawer (2,3,4,3,x,y, object);
      lineDrawer (5,4,5,6,x,y, object);
      lineDrawer (2,7,4,7,x,y, object);
     
      isCharFinded=true;
    }
    else if (temp==178) //буква в
    {
      lineDrawer (2,1,4,1,x,y, object);
      lineDrawer (1,1,1,7,x,y, object);
      pixelDrawer (5,2,x,y, object);
      lineDrawer (2,3,4,3,x,y, object);
      lineDrawer (5,4,5,6,x,y, object);
      lineDrawer (2,7,4,7,x,y, object);
     
      isCharFinded=true;
    }
//и т.д. — это не конец...


Мы видим, что код получается достаточно компактным и лаконичным.

Что же касается чтения букв из полученной строки, перед их отображением, с этим пришлось поломать голову. Потому что кириллический шрифт для своего хранения использует 2-байтовую кодировку, шрифт идёт в формате UTF-8. То есть, каждая буква кодируется двумя цифрами:

y0s4yqs75irmcgsl2fpy607kmri.jpeg

Полную таблицу по кодам для UTF-8 (и не только) вы можете найти вот по этой ссылке.

Возвращаясь к сказанному ранее, можно сказать, что мы просто фильтруем одну из цифр, которая общая почти для всей подборки: 208 или 209. И берём в качестве цифрового кода (по которому мы делаем вывод, какая буква перед нами) только вторую цифру.

j0-ss_qz08rlu-qw1hnczaraucq.jpeg

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


int temp = int (s[i]);
    if (temp==176) //буква а
    {
      //тут код
    }
     //и т.д.


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

И потом отладка, отладка, отладка…

И… наступает волнующий момент… Оно дышит!!! :-)

8pfmjqjxu0qqpr4db3ps-anskcg.jpeg

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

И на этом можно было бы и закончить наш рассказ… Но, чего-то не хватает!

А именно — вывода:

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

Но можно применить гораздо более элегантное решение:, а давайте-ка, сделаем из этого кода полноценную подключаемую библиотеку!

В случае со средой Arduino IDE для этого нужно не так уж много, всего лишь необходимо:

  • Код функций перенести в файл, который нужно сохранить с расширением .cpp (что расшифровывается как «c plus plus»).
  • Название самих функций — в другой файл, который необходимо сохранить с расширением .h (header — «заголовок»).
  • Потом папку с этими двумя файлами необходимо перенести в вашу директорию, где находятся все библиотеки. Например, вот сюда: C:\Users\USERNAME\Documents\Arduino\libraries


После этого использование кода становится элементарным: мы всего лишь подключаем изначальную библиотеку, подключаем нашу новую библиотеку, и для теста выводим алфавит и символы, которые я набросал в файле Corel Draw:

#include 
#include //наша библиотека

OLED myOLED(18, 19); // OLED display pins (SDA, SCL)

boolean b = false;


void setup()
{
  if(!myOLED.begin(SSD1306_128X32))
   { while(1); }

    Serial.begin (115200);
}

void loop()
{
  if (!b)
  {
    myOLED.clrScr(); //очистка экрана, перед отображением чего то нового
    RuCharPrinter ("абвгдежзийклмнопрс", 0, 0, myOLED);
    RuCharPrinter ("туфхцчшщъыьэюя.:!?", 0, 13, myOLED);
    RuCharPrinter ("0123456789()/#@%|*$", 0, 24, myOLED);
    b=true;
  }
}


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

5msxp_hl3w6z5cuebhx6xuccgza.jpeg

Что и требовалось доказать.

Экранчик приветствует всех читателей :-)

v2fyl7u1zt7s--h-i285kpaeinw.jpeg

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

Успехов в создании собственных самоделок!

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

Теоретически можно было пойти ещё дальше и встроить поддержку русского языка — прямо в изначальную библиотеку. Но я так делать не стал, чтобы несколько разделить своё решение и библиотеку-основу. А вот вы можете попробовать. Заодно и малость прокачаете свой скилл ;-)

oug5kh6sjydt9llengsiebnp40w.png

© Habrahabr.ru