Термостат для гаража я собрал за вечер, а экран на нём не зажёгся вообще: скетч шился, Serial жил, а на месте дисплея оставалось чёрное стекло. Я действовал по худшим своим привычкам: переписал скетч с нуля, снёс и поставил другую библиотеку, переставил платы местами — и только на третий день заметил, что гребёнка штырьков лежала рядом в пакетике. Модуль пришёл с неприпаянными ножками, контакт держался на трении в разъёмах и отваливался от любого чиха. Код был невиновен ни в чём.
С тех пор у меня на столе порядок проверки, который экономит вечера: сначала шина, потом питание, и только в самом конце скетч. Экран — самое честное устройство проекта: если шина жива, он отвечает сканеру за тридцать секунд; если нет, никакая библиотека его не разбудит.
В этом уроке пройдём путь от чёрного стекла до мусора на кадре: пять шагов при тёмном экране, три вида мусора и как по его форме читать диагноз. Схема подключения и выбор питания уже разобраны в уроке про подключение, адреса и сканер — в уроке про 0x3C; здесь собираем всё в один чек-лист.
Чёрный экран: пять шагов от провода к скетчу
Шаг первый — питание, и тут вариантов два. Модуль с чёрным стабилизатором возле VCC ест 5 В с платы, голое стекло без стабилизатора — только 3.3 В: пять вольт на ножках перегревают контроллер, и об этом я писал в уроке про схему. Мультиметр решает спор за минуту: на VCC живой сборки лежит 3.3–5 В в зависимости от конструкции, а сам экран потребляет единицы-десятки миллиампер — если ток вообще не течёт, ищите разорванную рейку макетки или отгоревший провод.
Шаг второй — сигнальные линии. SDA идёт на A4, SCL на A5, но порядок подписей на платах разных партий различается, и перепутанные соседи дают ровно тот же чёрный экран, что и обрыв. Проверка занимает десять секунд: сверить надписи на плате с проводами глазами, а не по памяти.
Шаг третий — сканер шины из урока про сканер адресов: тридцать секунд, и шина сама отвечает, жива ли она. Молчание — причина электрическая, код можно не открывать. Сканер напечатал 0x78 — это тот же OLED в другой записи, в begin пишут 0x3C. Напечатал 0x3D — у модуля перепаяна адресная перемычка SA0. Оба случая из практики, оба лечатся одной цифрой в скетче. Без подтяжек 4.7–10 кОм шина тоже молчит, но у модулей SSD1306 они обычно уже на борту — расклад есть в уроке про подключение.
Шаг четвёртый — инициализация. begin(SSD1306_SWITCHCAPVCC, 0x3C) возвращает bool: true — контроллер ответил, false — шина молчала. Девять скетчей из десяти этот результат выбрасывают, и тогда при тёмном экране вы не отличите молчащий модуль от неверно указанного адреса: библиотека проглотит неудачу и будет рисовать в пустоту. Моё правило: результат begin проверяю всегда, а при false печатаю в Serial план проверки — питание, линии, адрес.
И пятый шаг — самый дешёвый: display() после рисования. print меняет буфер в ОЗУ платы, а на экран картинка попадает только по команде display() — эту модель мы разобрали в уроке про буфер и display(). Забытый display() выглядит как чёрный экран при полностью здоровой шине: сканер видит модуль, Serial молчит, а кадр просто не отправлен. Симптом тот же, что у обрыва, лечение — одна строка.
Обратная ловушка той же пары: print стоит до begin() — рисование в неинициализированный буфер. Порядок в setup всегда один: Serial для лога, begin, clearDisplay, настройки шрифта, рисование, display(). Лестница короткая, но перепутать её ступени проще, чем кажется, когда правишь чужой скетч в три ночи.
#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h> Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { Serial.begin(9600); // мини-сканер: молчащая шина видна раньше любого кода bool found = false; for (byte addr = 8; addr < 120; addr++) { Wire.beginTransmission(addr); if (Wire.endTransmission() == 0) { Serial.print("Na shine: 0x"); Serial.println(addr, HEX); found = true; } } if (!found) { Serial.println("Shina pusta: pitanie, SDA/SCL, payka"); } // результат begin проверяем всегда if (display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("OLED na 0x3C: OK"); } else { Serial.println("OLED molchit: smeni adres na 0x3D"); } display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.println("DIAG start"); display.display();} void loop() {}Мусор на экране: три разных диагноза
Чёрный экран хотя бы честен. Хуже, когда на стекле появляется мусор, — и тут сначала стоит рассмотреть, какой именно мусор, потому что три его вида значат три разные неисправности. Первый: нижняя половина — шум, верхняя показывает текст. Так выглядит конструктор с размерами 128×32 на модуле 128×64 — буфер описывает половину стекла, а вторая половина показывает неочищенную память. Лечение — вернуть 64 на место; ту же ловушку я кладу в домашку урока про буфер.
Второй вид: вся картинка сдвинута на колонку-другую вправо, слева полоска обрезков. Почти всегда это SH1106 вместо SSD1306: чипы близнецы, но у SH1106 видеопамять на 132 колонки, и без поправки кадр едет вбок. Продавцы путают названия, поэтому смотрите не на витрину, а на поведение: сдвиг на 1–2 колонки при идеальном коде — почти всегда SH1106, лечится драйвером под него или библиотекой U8g2.
Третий вид: случайные полосы и снежок, которые прыгают от прикосновения к проводам. Это контакт и помехи, а не софт. Классика — плавающая земля модуля на далёкой рейке макетки: разбор в уроке про общую землю. Плюс длина хвоста: на полуметре шина на 100 кГц живёт спокойно, на метре начинается снежок — укорачивайте провода; разгон до 400 кГц при длинном хвосте только усилит шум.
Четвёртое — уже не мусор из шины, а каша из символов поверх текста: дозапись без очистки зоны. print не стирает клетки, поверх которых пишет; лечится clearDisplay перед каждым кадром или ластиком по зоне — оба приёма разобраны в уроке про буфер. И в этом же ряду дрожь полукадров, если display() вызван посреди сборки кадра: экран успевает показать недорисованное.
| Симптом | Причина | Лечение |
|---|---|---|
| Чёрный экран, сканер молчит | Питание не то, перепутаны SDA/SCL, холодный пай ножек | 3.3 В голому стеклу, сверить подписи, прозвонить и припаять гребёнку |
| Сканер печатает 0x78 | Тот же адрес в другой записи | В begin писать 0x3C; 0x3D значит перепаянный SA0 |
| Картинка есть, текста нет | display() не вызван после рисования | Один display() в конце сборки кадра |
| Нижняя половина — шум | В конструкторе 128×32 на модуле 128×64 | Вернуть 64: размеры описывают буфер, а не стекло |
Мой чек-лист в порядке цены. Сканер — тридцать секунд: он делит мир на электрику и софт. Мультиметр на VCC — минута: 3.3–5 В при живой сборке. Прозвонка SDA, SCL и земли — ещё минута. Сверка подписей и порядка линий — десять секунд, и я делаю их первым делом, если модуль переткнут. Только после всего — код: адрес в begin, результат begin, display() в конце.
Правило одного изменения: за раз правим ровно одну вещь и сразу перепроверяем сканером. Ремонт, в котором провода, адрес и библиотеку меняют одной пачкой, работает как лотерея: даже когда экран ожил, причина неизвестна — и вернётся в самый неподходящий момент.
Домашнее задание: залейте скетч из урока, затем умышленно ломайте сборку — переткните SDA в A5, поставьте в begin 0x3D, закомментируйте display(). По логу Serial и поведению сканера опишите каждый симптом своими словами. Учиться на сломанном безопаснее: за десять минут вы увидите все симптомы, которые в поле приходят по одному.
- 1. Экран чёрный, сборка не менялась. Что я делаю первым делом?
- 2. Сканер напечатал 0x78. Что писать в begin()?
- 3. Нижняя половина экрана в шуме, верхняя показывает текст. Причина?
- 4. Картинка сдвинута на две колонки вправо, код без изменений. Что это чаще всего?
- 5. Что возвращает begin() при молчащем чипе?
- Порядок диагностики: сканер шины, питание на VCC, прозвонка SDA/SCL и земли — и только потом код
- 0x78 в сканере означает 0x3C в begin; 0x3D — перепаянная перемычка SA0 на втором экране
- begin() возвращает bool: false — чип молчал, проверяйте питание, линии и адрес до правок кода
- Мусор читается по форме: пол-экрана — размер буфера, сдвиг на колонки — SH1106, снежок — земля и провода
- Одно изменение за раз: правки пачкой превращают диагностику в лотерею
Частые вопросы по теме урока
- Экран чёрный, сборка не менялась. Что я делаю первым делом?
- Сканер за тридцать секунд делит неисправности на электрику и софт: молчит — причина не в коде, и его можно не открывать. Прогоняю сканер шины
- Сканер напечатал 0x78. Что писать в begin()?
- 0x78 и 0x3C — один адрес с учётом бита чтения-записи. Библиотека ждёт 7-битную запись: 0x3C. 0x3C — тот же адрес в другой записи
- Нижняя половина экрана в шуме, верхняя показывает текст. Причина?
- Буфер описывает половину стекла, вторая половина показывает неочищенную память. Лечится возвратом 64 в конструкторе. В конструкторе 128×32 на модуле 128×64
- Картинка сдвинута на две колонки вправо, код без изменений. Что это чаще всего?
- У SH1106 память на 132 колонки, кадр без поправки уезжает вбок. Лечится драйвером под SH1106 или U8g2. Чип SH1106 вместо SSD1306
- Что возвращает begin() при молчащем чипе?
- begin возвращает bool. Если результат выбросить, молчащий модуль не отличить от неверного адреса: библиотека будет рисовать в пустоту. false — и результат стоит проверять