Заказчик метеостанции попросил подписи по-русски: Температура, Влажность, Давление. Я обрадовался — дело на десять минут — написал строку через print и получил на экране вереницу мусорных знаков: по два посторонних символа вместо каждой буквы. Полчаса я переустанавливал библиотеки и перешивал плату, пока не открыл таблицу кодов: русский текст в UTF-8 занимает по два байта на букву, а шрифт знает только девяносто пять знаков латиницы. Библиотека честно отрисовала каждый байт тем, что нашла в таблице, — это не сбой, а точное поведение.
Adafruit GFX не умеет UTF-8 в принципе: и классический шрифт 5x7, и Free-семейство из урока про шрифты GFX содержат только диапазон 0x20-0x7E. Кириллица в готовом виде живёт в U8g2, и здесь собираем весь путь: как читаются имена шрифтов, какие из них реально несут русский, куда писать drawUTF8, чем грозит кодировка файла и что делать, когда тащить вторую библиотеку не хочется. Сравнение двух библиотек по памяти и скорости уже сделано в уроке про U8g2 против Adafruit — здесь только кириллица.
Почему print выдаёт мусор
Механика мусора — арифметика одной строчки. В UTF-8 каждая русская буква кодируется двумя байтами: Т — это 0xD0 0xA2, и так по всему алфавиту. print прогоняет строку побайтово и для каждого байта ищет глиф в шрифте: латиница и цифры совпадут, а байты 0xD0 и 0xD1 в диапазон шрифта не попадают — вместо них рисуются пустые клетки или мусор из хвоста таблицы. Слово из шести букв превращается в двенадцать байтов и примерно столько же посторонних знаков на экране.
U8g2 решает это на входе: внутри библиотеки строки декодируются из UTF-8, двухбайтовые последовательности собираются обратно в буквы, и рисуется нужный глиф. Печатать можно явным drawUTF8(x, y, строка) либо print — декодирование по умолчанию включено (флаг enableUTF8Print выставлен с завода). Я беру drawUTF8 с координатами: после привычек setCursor проще один раз запомнить, что у drawStr и drawUTF8 координата y указывает на базовую линию буквы, — та самая грабля, о которой писал урок сравнения библиотек.
Рядом по кодам живут соседи: кириллический диапазон юникода сплошной, и украинские с белорусскими буквы сидят в тех же шестнадцати битах, что русские — на форуме U8g2 подтверждали работу и русского, и украинского на unifont. Отсюда практический вывод для мультиязычных панелей: один кириллический шрифт закрывает все славянские подписи сразу, отдельный шрифт на язык не нужен. А вот в текстовом тайловом режиме U8x8 кириллицы по-прежнему нет — там только латиница, это ограничение самого режима без буфера.
| Путь | Как выглядит в коде | Цена и граница |
|---|---|---|
| Adafruit print, латиница | Temp: 24 C — как раньше | Ноль лишних килобайтов; русского нет и не будет |
| U8g2 целиком | setFont + drawUTF8, clearBuffer/sendBuffer | Готовые кириллические шрифты; переезд вызовов и другая модель буфера |
| Мост U8g2_for_Adafruit_GFX | setFont моста + drawUTF8 в буфер Adafruit | Кириллица без переезда; базовая линия и вес шрифта те же |
| Свои глифы битмапами | Буквы 8х8 как drawBitmap с шагом 9 | Копейки флеша, ручная работа; пара слов на весь проект |
Имена шрифтов: где спрятана кириллица
Имя шрифта U8g2 читается по частям: u8g2_font_<семейство>_<прозрачность><набор>. Буква прозрачности: t рисует только чёрные точки глифа, m добавляет непрозрачный фон под буквой. Хвост имени — набор символов: f значит полный юникод всего, что в шрифт заложили, r — урезанный до 256 знаков. Ловушка, о которую я спотыкался: u8g2_font_6x12_tf несёт полный юникод, но заложенной в него латиницы — кириллицы в мелких x11-шрифтах нет, и счётчик полного юникода буквы задним числом не добавляет.
Реально русские шрифты классического комплекта — семейство unifont: u8g2_font_unifont_t_cyrillic и соседние варианты с пометкой cyrillic, плюс версии с номерами страниц в имени. Все они высотой 16 пикселей: мелких кириллических в библиотеке долго не было вовсе — жалобы на форумах тянутся с 2016 года; в свежих версиях набор расширился, и проверка одна — компилятор. Имени нет в вашей версии, значит, шрифта в ней нет: обновляйте библиотеку из менеджера библиотек или берите unifont.
Цена вопроса — флеш. Глиф высотой 16 пикселей — до двух байтов на строку плюс служебные, порядка тридцати пяти байтов на букву; кириллический unifont с латиницей и знаками тянет на два-три килобайта. Для Uno с тридцатью двумя это ощутимо, но терпимо; пять шрифтов в одном скетче я не держу — один cyrillic для русских подписей, один мелкий латинский для цифр. Вес проверяется компилятором: соберите скетч до и после добавления setFont — разница в байтах прошивки и есть честная цена шрифта. Приёмы экономии — из урока про PROGMEM и память.
#include <U8g2lib.h>#include <Wire.h> // F — полный буфер 1024 байта; на голодной Uno берите вариант _1_U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); void setup() { u8g2.begin();} void loop() { u8g2.clearBuffer(); u8g2.setFont(u8g2_font_unifont_t_cyrillic); u8g2.drawUTF8(0, 16, "Температура: 24 C"); u8g2.drawUTF8(0, 36, "Влажность: 40%"); u8g2.setFont(u8g2_font_6x12_tf); u8g2.drawUTF8(0, 56, "update: 1 s"); u8g2.sendBuffer(); delay(1000);}Свои шрифты: когда много и второй библиотеки
Иногда кириллица нужна в двух словах: РУЧН, АВТО, СТОП на кнопках режима. Тащить из-за них U8g2 и пару килобайтов глифов — стрельба из пушки по воробьям. Мой путь в таких скетчах: буквы как битмапы из урока про иконки и маски, по восемь байтов на знак 8х8; слово из четырёх букв — тридцать два байта, вывод обычным drawBitmap с шагом девять пикселей, как у строки текста. Буква рисуется на клетчатом листке восемью строками и переводится в байты той же считалкой, что и любая иконка; в цикле её выводит drawBitmap со сдвигом x на девять за знак. Смешивать в одной строке латиницу print и русское слово битмапами можно без конфликтов: сначала текст с его шириной, потом битмапы с x плюс шесть на символ. Флеша уходит копейки, но каждая новая буква — ручная работа: для связного текста этот путь умирает, для трёх слов — то, что нужно.
Раскладываю подходы по полкам от первого лица: остаюсь на чистом Adafruit, если русский на экране не нужен, — ноль лишних килобайтов; беру мост U8g2_for_Adafruit_GFX для готовых скетчей, где переезд страшнее килобайтов; перехожу на U8g2 целиком, когда экран с нуля и подписи по-русски — там и страничный режим для голодной Uno рядом; свои битмапы — для пары слов, и дальше они не тянут. Часы с крупными цифрами и русскими подписями собираются из этой таблицы без остатка — пример крупного шрифта и часов в уроке про RTC на OLED.
- print в Adafruit не декодирует UTF-8: русская буква — два байта, шрифт знает только ASCII — на экране мусор
- Кириллица в U8g2 живёт в unifont-шрифтах высотой 16 пикселей; суффикс _tf у мелких x11-шрифтов кириллицу не добавляет
- Печать через drawUTF8 с координатой базовой линии; файл скетча должен быть сохранён в UTF-8
- Цена: глиф 16 пикселей — около 35 байтов, кириллический unifont — порядка 2-3 КБ флеша
- Мост U8g2_for_Adafruit_GFX рисует шрифтами U8g2 в буфер Adafruit; пара слов проще как битмапы 8х8
- 1. Почему print("Привет") в Adafruit выдаёт мусор?
- 2. Что означает u8g2_font_6x12_tf и есть ли в нём кириллица?
- 3. Какой высоты классические кириллические шрифты U8g2?
- 4. Что делает мост U8g2_for_Adafruit_GFX?
- 5. Скетч сохранён в Windows-1251, шрифт кириллический выбран верно. Что на экране?
Домашнее задание: подключите U8g2, выведите drawUTF8 свою фамилию и соберите в Serial два массива байтов одной и той же строки — слово из шести букв даст двенадцать байтов, и двойная кодировка перестанет быть теорией. Потом попробуйте мост U8g2_for_Adafruit_GFX на любом готовом скетче раздела: русские подписи появятся, а старый код останется нетронутым.
На этом нижние слои графики и текста в разделе собраны: спрайты, пиксели, шрифты. Дальше по плану — собирать из этого интерфейсы целиком: итоговый дашборд с меню и графиком крутится ровно на тех приёмах, которые вы прошли в трёх уроках выше.
Частые вопросы по теме урока
- Почему print("Привет") в Adafruit выдаёт мусор?
- Шесть букв превращаются в двенадцать байтов; байты 0xD0 и 0xD1 вне диапазона шрифта рисуются как посторонние знаки. Библиотека работает точно, шрифт просто не знает букв. UTF-8 кодирует каждую русскую букву двумя байтами, шрифт знает только ASCII
- Что означает u8g2_font_6x12_tf и есть ли в нём кириллица?
- t — прозрачный, f — полный юникод того, что в шрифт заложили. В x11-шрифтах вроде 6x12 заложена латиница, поэтому русский там брать неоткуда. Прозрачный шрифт с полным набором заложенных глифов; заложена латиница, кириллицы нет
- Какой высоты классические кириллические шрифты U8g2?
- Классический русский набор — unifont высотой 16 пикселей; мелких кириллических в библиотеке долго не было. Битмапные шрифты не масштабируются. 16 пикселей (семейство unifont)
- Что делает мост U8g2_for_Adafruit_GFX?
- Мост пишет глифы U8g2 в тот же буфер, дальше обычный display(). Русские строки печатаются drawUTF8 моста, остальной код на print и примитивах не меняется. Рисует шрифтами U8g2 в буфер Adafruit — кириллица без переезда кода
- Скетч сохранён в Windows-1251, шрифт кириллический выбран верно. Что на экране?
- Декодер ждёт двухбайтовые UTF-8-последовательности, а получает одиночные байты CP1251. Пересохраните файл в UTF-8 — в Arduino IDE 2.x это кодировка по умолчанию. Мусор: drawUTF8 ждёт UTF-8, а получает однобайтовые коды