Цифры на экране читают не с ладони, а с расстояния вытянутой руки — и тут размер 1 сдаёт первым. Я собирал сводку теплицы в ноябре: температура, влажность, время до полива — всё красиво влезло шестью строками мелкого шрифта, по двадцать один символ в каждой. Через неделю понял, что пользоваться этим нельзя: чтобы увидеть 21,5, надо подойти, наклониться и разглядывать. Экран остался 0.96 дюйма, а читают его летом, с грязными руками и с двух шагов — на таком расстоянии глиф высотой в семь пикселей глаз просто не разрешает. С тех пор все мои приборы показывают главное крупно, а мелочь прячут в нижние строки.
Стандартный шрифт библиотеки Adafruit_SSD1306 — глиф 5×7 пикселей, которому выдана ячейка 6×8: пять столбцов и семь строк — сама буква, шестой столбец и восьмая строка — зазор, чтобы соседи не слипались. setTextSize(n) не выбирает другой шрифт из таблицы — других шрифтов у этой команды нет. Она растит каждую точку глифа в n раз по горизонтали и вертикали: размер 2 превращает пиксель в квадрат 2×2, ячейка вырастает до 12×16, и вместо аккуратной строки на стекле — плакат. Сглаживания не будет в принципе: точка либо горит, либо нет, поэтому на размерах 3 и 4 скругления букв рассыпаются на заметные лесенки. Гладкие крупные надписи — это уже векторные FreeFonts из Adafruit_GFX, у меня про них отдельный урок; сегодня же речь про масштабирование, которое работает всегда и без дополнительной памяти.
Вся арифметика экрана выводится из одной ячейки, и её полезно посчитать до первого print: сколько символов в строке и сколько строк влезает при каждом размере. Из этих двух чисел следуют и главные грабли — молча уехавшие за правый край заголовки, — и рецепт сводки из одной большой цифры и пары подписей, который я потом переносил из проекта в проект. Дальше — арифметика по размерам, история про перенос, которую я не люблю вспоминать, и сборка сводки по зонам.
Арифметика ячеек: 21, 10, 7
Считается всё в одно действие. Ширина экрана 128 пикселей делится на ширину ячейки: 128 на 6 — 21 символ при размере 1, и один пиксель остаётся в остатке, двадцать второй уже не помещается. Высота 64 на ячейку 8 даёт ровно восемь строк без остатка. Размер 2: ячейка 12×16, в строке 10 символов, строк всего четыре. Размер 3 — ячейка 18×24: семь символов и лишь две полные строки, потому что 64 на 24 — это два и 16 пикселей в остатке; третьей строке не хватает восьми пикселей. Размер 4 оставляет пять символов при двух строках, размер 5 — четыре символа и одну: сорокапиксельная по высоте ячейка второй раз уже не умещается.
Перемножив, получаем ёмкость: 168 символов на единице, 40 на двойке, 14 на тройке. Отсюда моё рабочее правило: макет будущих зон я рисую на бумаге в клетку, где одна клетка — ячейка размера 1, и считаю символы в каждой зоне до того, как писать код. Сколько раз экран резал мне фразу на полуслове — столько раз я радовался, что на этот раз посчитал заранее. Отдельно про потолок: setTextSize формально принимает любое число — 6, 8, хоть 10, — но выше пятого смысла нет, буква уже шире полкадра. Гигантские цифры собирают либо на пятом размере, либо из примитивов drawRect — второй путь дороже по коду и нужен редко.
| setTextSize | Ячейка, px | Символов в строке | Строк на 128×64 |
|---|---|---|---|
| 1 | 6×8 | 21 | 8 |
| 2 | 12×16 | 10 | 4 |
| 3 | 18×24 | 7 | 2 |
| 4 | 24×32 | 5 | 2 |
Первый камень — перенос строк. По умолчанию setTextWrap(true): строка, не влезшая в ширину, продолжается со следующей строки того же размера. На размере 1 это спасение, на размере 2 — мина: одиннадцатый символ уезжает вниз на 16 пикселей и врезается в то, что вы там уже нарисовали. Я однажды полчаса искал, почему подпись датчика наложилась на большую цифру, — а это была шапка из двенадцати букв, тихо перетёкшая на второй этаж. Темы со словами «setTextSize(2) seems too big» на форуме Adafruit — сплошь эта история: длинная строка на двойке превращается в кашу. Визуально всё выглядит как вторая строка, которой вы не планировали: хвост шапки лежит поверх большой цифры, и оба текста читаются через пень. Буфер при этом честно отрисовал ровно то, что ему велели, — ни предупреждения, ни лога.
Если перенос выключить — setTextWrap(false) — символы за правым краем просто исчезают: ни предупреждения, ни скролла, print честно рисует только то, что попало в кадр. Строка-то влезла в память программы — она не влезла на стекло, и компилятор молчит. Мой приём: любую фразу для крупного размера я сначала считаю в уме — 10 символов на двойке, 7 на тройке — и лишь потом печатаю; куда переходит курсор после каждой печати, если хотите управлять зонами вручную, разобрано в уроке про print и setCursor.
Третья мелочь, на которой спотыкаются все: размер действует на всё, что печатается после него, до следующего setTextSize. Курсор задаётся в пикселях, а не в строках, поэтому одна и та же координата Y при разных размерах означает разные строки: 16 пикселей — это вторая строка для единицы и первая для двойки. Собирая кадр из зон разного размера, иду сверху вниз и после каждой смены размера ставлю курсор заново — на глаз это нудно, зато кадр никогда не наползает сам на себя.
#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h> Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { Serial.begin(9600); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // адрес своего модуля знает сканер I2C display.setTextColor(SSD1306_WHITE);} void loop() { // ручка на A0 изображает показания: от -10,0 до +30,0 градуса int raw = analogRead(A0); float t = map(raw, 0, 1023, -100, 300) / 10.0; display.clearDisplay(); // зона 1: подпись, размер 1, строка 0, до 21 символа display.setTextSize(1); display.setCursor(0, 0); display.print("Teplota segodnya"); // зона 2: значение, размер 3, 7 символов в строке display.setTextSize(3); display.setCursor(0, 12); display.print(t, 1); // второй аргумент - десятые доли: "21.5" display.print(" C"); // строка "21.5 C" - 6 символов из 7 доступных // зона 3: статус, размер 1, нижняя строка 56 (64 минус 8) display.setTextSize(1); display.setCursor(0, 56); display.print("A0 = "); display.print(raw); display.display(); // кадр уходит на стекло одной посылкой delay(200);}Обратите внимание на порядок: сначала весь кадр собирается в буфере — подпись, большая цифра, статус, — и только в конце один display() отправляет его на стекло. Никаких промежуточных обновлений между зонами: механика двойной буферизации и цена clearDisplay разобраны в уроке про буфер и display(). Десятые доли печатает print со вторым аргументом: display.print(t, 1) даёт один знак после точки, print(t, 0) — целое число; строка «21.5 C» занимает шесть символов из семи доступных, а минусовая «-10.0 C» — ровно все семь, вплотную. И ещё мелочь, которую замечаешь не сразу: печать идёт поверх того, что уже лежит в буфере, — clearDisplay перед кадром не украшение, а необходимость, иначе старые цифры просвечивают под новыми там, где новая зона короче старой.
delay(200) здесь стоит нарочно: этот скетч про раскладку, а не про таймеры, и при опросе ручки частая перерисовка безвредна. Но как только вместо потенциометра появится настоящий сенсор, опрос и перерисовку придётся развести по времени — об этом весь третий урок этой серии.
Крупные показания: три зоны
Рецепт сводки, который я перетаскиваю из проекта в проект, состоит из трёх зон и двух размеров. Верхняя строка — подпись на размере 1: латиницей, потому что стандартный шрифт кириллицу не умеет, и максимум 21 символ. Середина — значение на размере 3: до семи символов, чего хватает любой температуре от минус сорока до плюс восьмидесяти — строка «-19.5 C» занимает ровно все семь. Низ — второстепенное: влажность, время, статус, всё на единице в одной-двух нижних строках. Глаз выхватывает большую цифру с расстояния, а мелочь читается, когда она действительно нужна. Числа «21 и 7» я запомнил как границу приличия: подпись, не влезающая в 21 символ на размере 1, этому экрану не нужна; значение, не влезающее в 7 на тройке, надо ужимать, а не уменьшать размер — иначе теряется весь смысл крупной зоны.
Крупные цифры со стандартным шрифтом живут и в часах на DS3231 — там размеры 2 и 3 разобраны на живом примере с ведущими нулями и мигающим двоеточием. А когда в сводке появится настоящий сенсор вместо ручки, опрос надо сразу ставить на millis — неблокирующий таймер, к которому мы придём в конце серии: delay в приборе с экраном и кнопками долго не живёт.
- 1. Сколько символов влезает в строку 128 пикселей при setTextSize(2)?
- 2. Что делает setTextSize(3) со стандартным шрифтом 5×7?
- 3. Строка «Teplota 25.5» из 12 символов при размере 2 и включённом переносе по умолчанию:
- 4. Сколько полных строк занимает размер 3 на высоте 64 пикселя?
- 5. display.print(t, 1) при t, равном 9.87, напечатает:
- Стандартный глиф 5×7 живёт в ячейке 6×8; setTextSize(n) растит каждую точку в n раз — других шрифтов внутри этой команды нет.
- Ёмкость 128×64: размер 1 — 21×8 символов, размер 2 — 10×4, размер 3 — 7×2, размер 4 — 5×2.
- Перенос по умолчанию включён: лишний символ крупной строки уезжает на 16 пикселей вниз; setTextWrap(false) просто отрезает хвост.
- Курсор — в пикселях: после смены размера одна и та же координата Y означает другую строку.
- Сводка — три зоны: подпись на 1, значение на 3, статус на 1; десятые доли печатает print(t, 1).
Частые вопросы по теме урока
- Сколько символов влезает в строку 128 пикселей при setTextSize(2)?
- Ячейка растёт до 12×16: 128 на 12 — десять символов и 8 пикселей в остатке. Строк при этом всего четыре. 10
- Что делает setTextSize(3) со стандартным шрифтом 5×7?
- Масштабирование точечное: ячейка 6×8 превращается в 18×24. Других шрифтов внутри setTextSize нет, сглаживания — тоже. Берёт глиф 5×7 и растит каждую точку втрое по обеим осям
- Строка «Teplota 25.5» из 12 символов при размере 2 и включённом переносе по умолчанию:
- setTextWrap(true) стоит по умолчанию: продолжение строки падает на следующую ячейку 12×16 и врезается в нижние зоны кадра. Хвост уедет на строку ниже, на 16 пикселей вниз
- Сколько полных строк занимает размер 3 на высоте 64 пикселя?
- 64 на 24 — две строки и 16 пикселей в остатке. Хвост ровно вмещает две строки размера 1: место для статуса. 2
- display.print(t, 1) при t, равном 9.87, напечатает:
- Второй аргумент print — число знаков после точки: один знак с округлением. Для строки на размере 3 это важный лимит ширины. 9.9