Модуль 1.3 дюйма приехал с формулировкой «тот же OLED, только больше» и влез в отлаженный скетч без единой правки: адрес 0x3C, конструктор тот же, каркас из первого урока. Сканер увидел устройство, begin() отработал — а экран молчал. Я перепрошил трижды, прогнал сканер ещё дважды и уже готов был списать стекло, когда вспомнил скользкий абзац из первого урока про OLED: большой брат может быть не SSD1306.
Вторая попытка — со специализированной библиотекой — оживила экран, но открыла вторую тайну: весь текст стоял на два пикселя правее положенного, а у правого края вертикальной лентой шёл мусор. Сканер при этом упорно показывал 0x3C: устройство на шине было, отвечало на адрес, но диагноз по шине поставить было невозможно — болезнь жила выше уровня адресов. Два вечера и один замеренный в пустоту отладочный сеанс привели к короткому выводу: внутри был SH1106 — контроллер-близнец с чуть другой памятью.
Здесь разберём пару до винтика: чем различается память, откуда берётся ровно двухпиксельный сдвиг и полоса мусора, как опознать контроллер за две минуты без даташита и какие драйверы ставить — в экосистеме Adafruit и за её пределами. Про то, что сканер доказывает адрес, но не чип, мы говорили в уроке про сканер адреса OLED — сейчас этот урок работает против меня самого.
132 против 128: откуда берётся сдвиг
Оба контроллера монохромные, понимают почти одинаковый набор команд и живут на одном адресе 0x3C — отсюда и путаница: скетч SSD1306 на SH1106 выглядит почти работающим. Разница прячется в видеопамяти: у SSD1306 её 128 столбцов на 64 строки, у SH1106 — 132 столбца на те же 64 строки. Четыре лишних столбца — не брак, а запас инженеров, но именно они ломают несовпадающий кадр.
Кадр от библиотеки под SSD1306 упакован в 128 столбцов и отправляется с нулевого адреса памяти. SH1106 раскладывает его по своей 132-столбцовой разметке: картинка ложится со сдвигом на два пикселя вправо — (132 − 128) / 2, — правые столбцы кадра вылетают за видимый край, а у правой границы проявляется мусор из соседних адресов. Текст, рассчитанный на сетку 21×8, уезжает, рамки съезжают, последний столбец экрана живёт своей жизнью. Страдает и левый край: первые символы строки встают не на свои места, и все зоны, рассчитанные по сетке из урока про текст, сходятся с экраном с той же двойкой погрешности.
Симптомы собираются в три группы. Первая: картинка есть, но вся на два пикселя правее, плюс мусорная полоса справа — классика жанра. Вторая: сканер видит 0x3C, begin() возвращает успех, а экран тёмный — стартовые команды SSD1306 не подошли конкретному клону SH1106. Третья: всё работает, но вертикальные линии дрожат по краям — сдвиг гуляет при смене кадров. Во всех трёх случаях код ни при чём: чужой контроллер. Порядок диагностики у меня такой: сначала линейка-тест — она за минуту отделяет сдвиг от мёртвого стекла, потом сканер — чтобы исключить обрыв шины, и только потом смена драйвера.
Как опознать за две минуты. Правило с условием: 0.96 дюйма — почти всегда SSD1306, 1.3 дюйма — почти всегда SH1106; но партии бывают разные, поэтому доверяй, но проверяй. Проверка — линейка-тест: печатаете строку из двадцати одного символа базовым шрифтом — она занимает 126 пикселей из 128, и на SH1106 с чужим драйвером первый символ встанет правее, а у правого края появится мусор. Отдельная семья — вытянутые модули 0.91 дюйма с разрешением 128×32: внутри обычно SSD1306, но конструктор обязан называть 128 и 32, иначе низ кадра уйдёт за край памяти. Маркировка чипа под лупой закрывает вопрос окончательно.
Чего не делать: компенсировать сдвиг setCursor(-2) или правкой координат. Сдвиг уедет, мусор останется, а каждая новая зона интерфейса потребует своей поправки — вы обложите код минус-двойками по всей площади. Двойка при этом постоянна: сколько ни меняй шрифт и размер текста, кадр из 128 столбцов всегда ляжет на те же два пикселя правее. Лечится сменой драйвера один раз и целиком, чем и займёмся в следующей части урока.
| Симптом | Диагноз | Лечение |
|---|---|---|
| Текст уехал вправо на 2 px, справа мусорная полоса | SH1106 со 132 столбцами работает на драйвере SSD1306 под 128 | Драйвер Adafruit_SH1106G или U8g2 |
| Сканер видит 0x3C, begin проходит, экран тёмный | Стартовые команды SSD1306 не подошли клону SH1106 | Сменить драйвер, провода не трогать |
| 0.96 дюйма — обычно SSD1306, 1.3 — обычно SH1106 | Типовой расклад модулей, партии бывают разными | Линейка-тест из 21 символа и маркировка чипа |
Драйверы под SH1106: Adafruit и U8g2
Путь первый — остаться в Adafruit. Библиотека называется Adafruit SH110X и ставится из менеджера так же, как драйвер SSD1306 из урока про установку — вместе с зависимостями. Класс для 1.3-дюймового модуля: Adafruit_SH1106G display(128, 64, &Wire, -1); запуск — begin(0x3C); шину он поднимает сам, отдельный Wire.begin не нужен — как и у SSD1306. Константы меняют приставку: SH110X_WHITE вместо SSD1306_WHITE. Буфер — те же 1024 байта, сетка 21×8 — та же, поэтому весь текстовый код раздела из урока про print и setCursor переезжает заменой двух строк и приставки в константах.
Путь второй — U8g2: самостоятельная экосистема со своими шрифтами и конструктором под каждую модель. Для текстового режима — класс U8X8_SH1106_128X64_NONAME_HW_I2C: он пишет строки прямо в контроллер без буфера кадра и экономит ОЗУ. Для графики — U8G2_SH1106_128X64_NONAME_F_HW_I2C с полным буфером. API другой: clearBuffer и sendBuffer вместо clearDisplay и display, шрифты выбираются setFont, и их десятки — включая кириллицу, которой в штатном шрифте Adafruit нет. Сотни конструкторов U8g2 — удобство и ловушка разом: в имени класса зашиты и чип, и разрешение, и вариант шины, поэтому строку из примера под свой модуль копируйте целиком, а не собирайте по памяти.
Моё распределение с условием: если проект уже написан в стиле этого раздела — берите Adafruit SH110X, переезд стоит трёх строк, и код остаётся единым по всему разделу. U8g2 я открываю, когда проекту нужны шрифты — кириллица, разные кегли, тонкая типографика — или когда ОЗУ Uno впритык и текстовый режим без буфера спасает. По скорости шины близнецы не различаются: кадр те же 1024 байта, темпы из урока про замер FPS действуют в обе стороны. И после переезда прогоните линейку заново: рамка по краям и строка из двадцати одного символа обязаны стоять ровно — это финальный тест переезда.
#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SH110X.h> Adafruit_SH1106G display(128, 64, &Wire, -1); void setup() { display.begin(0x3C); // адрес тот же — 0x3C display.clearDisplay(); display.setTextSize(1); display.setTextColor(SH110X_WHITE); display.setCursor(0, 0); display.println("SH1106 1.3 inch"); // линейка-тест: 21 символ = 126 px из 128 display.setCursor(0, 12); display.println("012345678901234567890"); display.println("012345678901234567890"); display.setCursor(0, 40); display.println("Rovno - driver verno"); display.println("Sdvig +2 - ne tot chip"); display.display();} void loop() {}Практика покупок после двух обожжённых вечеров: в описании модуля я ищу не диагональ и не красивое фото, а имя контроллера; если продавец пишет только «OLED 1.3», спрашиваю чип письмом до заказа. Семьпиновые SPI-модули тоже бывают на SH1106 — сдвиг там тот же, драйвер нужен тот же, отличие только в шине из первого урока пакета; в описаниях SPI-версий ищите перечисление DC, CS, RST.
И привычка для ящика: держите два тестовых скетча — каркас SSD1306 и каркас SH1106G — в одной папке рядом со сканером шины из раздела про Wire. Новый модуль проходит оба за пять минут, и вопрос «что у меня внутри» перестаёт быть загадкой на два вечера.
- SSD1306 — память 128 столбцов, SH1106 — 132; кадр из 128 столбцов ложится со сдвигом на 2 пикселя и мусором справа
- Три симптома: сдвиг с мусорной полосой, тёмный экран при живом сканере, дрожь по краям; код ни при чём — чужой контроллер
- 0.96 дюйма — почти всегда SSD1306, 1.3 — почти всегда SH1106; проверка — линейка из 21 символа (126 px из 128)
- Лечение — смена драйвера, а не setCursor(-2): Adafruit SH110X для переезда за три строки, U8g2 — за шрифтами и кириллицей
- Adafruit_SH1106G display(128, 64, &Wire, -1); begin(0x3C); константы SH110X_WHITE; сетка 21×8 и буфер 1024 байта — те же
- Скорость у близнецов одинаковая: темпы FPS из урока про шины действуют на обоих контроллерах
- 1. Почему кадр от библиотеки SSD1306 уезжает на SH1106?
- 2. Сканер видит 0x3C, но экран молчит. Что показал сканер?
- 3. Какой класс Adafruit нужен для 1.3-дюймового SH1106 128x64?
- 4. На сколько пикселей типовой сдвиг картинки SH1106 с чужим драйвером?
- 5. Что даёт компенсация setCursor(-2) при сдвиге?
Пара SSD1306 и SH1106 — самая частая железная засада OLED-раздела, и теперь у вас есть тест, диагноз и два готовых драйвера. В следующий раз, когда «тот же, но больше» молчит с рабочим скетчем, первые две минуты уйдут на линейку, а не на перепайку схемы, — и раздел продолжит работать без железных сюрпризов.
Домашнее задание: опознайте контроллер своего модуля линейкой из 21 символа и запишите вердикт на бумажке с адресом — той самой, что мы заводили после сканера; бумажку я вклеиваю прямо в футляр макетки, чтобы следующий сеанс с модулем начинался без детектива. Если внутри SH1106, переезжайте на Adafruit_SH1106G по коду из урока и убедитесь, что зоны интерфейса встали ровно: сетка 21×8 общая, и весь код раздела обязан лечь на экран без сдвига. Не лёг — проверяйте, не остался ли где-то в скетче старый конструктор.
Частые вопросы по теме урока
- Почему кадр от библиотеки SSD1306 уезжает на SH1106?
- Четыре лишних столбца памяти смещают раскладку: кадр из 128 столбцов встанет на два пикселя правее, край уйдёт в мусор. Память шире: 132 столбца против 128, кадр ложится со сдвигом
- Сканер видит 0x3C, но экран молчит. Что показал сканер?
- Сканер отвечает на вопрос где, но не что. SSD1306 и SH1106 живут на одном адресе и различаются только драйвером. Только адрес; чип он не различает
- Какой класс Adafruit нужен для 1.3-дюймового SH1106 128x64?
- Adafruit_SH1106G display(128, 64, &Wire, -1) из библиотеки Adafruit SH110X; begin(0x3C) и константы с приставкой SH110X_. Adafruit_SH1106G
- На сколько пикселей типовой сдвиг картинки SH1106 с чужим драйвером?
- (132 − 128) / 2 = 2 пикселя вправо. Ровно эта двойка и полоса мусора справа — визитная карточка SH1106 на драйвере SSD1306. На 2 вправо
- Что даёт компенсация setCursor(-2) при сдвиге?
- Сдвиг уедет, мусорная полоса останется, а каждая зона интерфейса потребует своей поправки. Лечится сменой драйвера один раз. Ничего хорошего: мусор остаётся, поправки расползаются по всему коду