Первый OLED-экран в моей жизни — модуль 0.96 дюйма за двести с небольшим рублей — молчал ровно час. Четыре провода по распиновке, скетч из первого попавшегося туториала, компиляция без единой ошибки — и тёмное стекло. Я перепроверил провода дважды, перезалил скетч трижды и уже примерялся списывать модуль в брак, когда заметил в комментарии туториала мелким шрифтом: адрес по умолчанию 0x3D. У моего экземпляра был 0x3C.
Сменил константу — экран ожил. А неделей позже я поймал вторую классику: print() печатает в буфер, и без display.display() буфер так и остаётся в ОЗУ — экран снова тёмный, хотя код «исправный». Обе ошибки стоят минуты, но найти их без понимания, как устроен дисплей, — час и немного нервов.
В этом уроке подключим SSD1306 128×64 к Arduino по I2C: четыре провода, адрес 0x3C, арифметика буфера — почему 8192 пикселя это 1024 байта и какая это доля от 2 КБ ОЗУ Uno. Потом выведем первый текст через setTextSize и setCursor и соберём счётчик секунд, который сразу заработает в симуляторе.
SSD1306: 8192 пикселя и 1024 байта
SSD1306 — монохромный контроллер OLED-дисплеев, на нём собраны самые ходовые экраны для Arduino: 0.96 и 1.3 дюйма, разрешение 128×64 пикселя. Светится каждый пиксель сам по себе — подсветка не нужна, поэтому чёрный здесь буквально выключенная точка, а контраст по меркам LCD бесконечный. Питается модуль от 3.3 до 5 В и ест копейки: я вешал такой экран на батарейный узел, и он был не главной статьёй расхода заряда — главной был Wi-Fi, но это другая история.
Теперь арифметика, которую полезно знать до подключения. Каждый пиксель — один бит: горит или нет. Считаем: 128 × 64 = 8192 пикселя, делим на восемь бит в байте — получаем ровно 1024 байта на весь кадр. Библиотека держит этот буфер в ОЗУ платы и перекачивает его на контроллер целиком, одной передачей. У Arduino Uno — 2 КБ SRAM: один буфер занимает половину памяти платы. На ESP32 с 520 КБ и на Mega с 8 КБ это не имеет значения, а вот на Uno каждый следующий байт уже считается: добавьте DHT, EEPROM и пару строк лога — и свободная память начнёт таять на глазах.
OLED против привычного LCD1602 — не только размер. ЖК держит подсветку постоянно и ест миллиамперы; OLED светится только там, где есть точки, а тёмный экран почти ничего не потребляет — я измерял: кадр с парой строк добавляет к потреблению платы порядка 5–10 мА, потушенный дисплей — единицы миллиампер. Минус тоже есть, и о нём честно: пиксели выгорают, если держать одну и ту же картинку месяцами; ресурс производители указывают в десятки тысяч часов, поэтому статичную заставку на год не оставляют. Лечится экраном, гаснущим по таймеру бездействия.
И приятная механика света: OLED-пиксель включается и гаснет за микросекунды, поэтому на быстрых анимациях экран не смазывается — качество, которое станет важным в уроке про графику, где кадры будут гоняться по десять раз в секунду.
Четыре провода и адрес 0x3C
Экран висит на шине I2C — две линии, SDA и SCL, к которым цепляется несколько устройств сразу; на Uno это пины A4 и A5. У каждого устройства на шине свой адрес, у SSD1306 их два: 0x3C — подавляющее большинство модулей — и 0x3D. Если экран молчит, первым делом прогоните I2C-сканер из раздела про Wire: он напечатает реальный адрес вашего модуля за минуту и снимет главный вопрос.
Инициализация занимает две строки. Adafruit_SSD1306 display(128, 64, &Wire, -1); создаёт объект под экран 128×64: первые два аргумента — размеры в пикселях (для модуля 128×32 их придётся поменять, иначе нижняя половина кадра окажется за краем), &Wire — шина, через которую идёт общение, -1 — пин Reset, общий с платой. display.begin(SSD1306_SWITCHCAPVCC, 0x3C); запускает контроллер от внутренней накачки заряда — и заодно сам вызывает Wire.begin(), отдельно шину стартовать не нужно. Именно внутри begin библиотека выделяет те самые 1024 байта буфера и заливает на экран стартовую заставку: если заставка мелькнула — физика в порядке, дело в вашем коде.
Один коварный случай из моей коробки: модуль 1.3 дюйма выглядит как старший брат 0.96, адрес на шине тот же — 0x3C, скетч запускается, но текст уезжает на пару пикселей, а нижние строки пропадают. Внутри такого модуля контроллер SH1106 — родственный, но с чуть другой организацией видеопамяти, и Adafruit_SSD1306 работает с ним не идеально; правильный ход — U8g2 или Adafruit_SH110X. Урок простой: сканер адреса говорит, где устройство, но не что это за устройство — маркировку читайте до подключения.
Пара слов о скорости. Шина I2C по умолчанию работает на 100 кГц, и кадр из 1024 байт идёт по ней небыстро — сколько именно, посчитаем в следующем уроке. Пока запомните: если дисплей на шине один и других устройств нет, скорость не критична; как только на те же A4/A5 сядут RTC и датчик, пауза на передачу кадра станет заметна. После begin можно поднять частоту командой Wire.setClock(400000) — двукратное ускорение передачи кадра, которое бесплатно, пока провода короче полуметра.
#include <Wire.h>#include <Adafruit_GFX.h>#include <Adafruit_SSD1306.h> Adafruit_SSD1306 display(128, 64, &Wire, -1); void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.println("Hello, OLED!"); display.display();} void loop() {}Текст: размер шрифта, курсор, сетка символов
Текст живёт в пиксельной сетке. setTextSize(1) — базовый шрифт 6×8 пикселей: в строку помещается 128 / 6 = 21 символ, по вертикали 64 / 8 = 8 строк. setTextSize(2) увеличивает всё вдвое — 12×16 — и оставляет 10 символов и 4 строки: крупный шрифт тратит и место, и количество информации на экране. setTextSize(3) — 18×24, восемь символов в строке: только самое главное, например температура крупно.
setCursor(x, y) ставит курсор в пиксельных координатах, точка (0, 0) — левый верхний угол, ось y растёт вниз. Напечатали один блок — передвинули курсор — напечатали следующий: так собираются зоны будущего интерфейса. setTextColor(SSD1306_WHITE) включает пиксели; SSD1306_BLACK гасит — на OLED это ластик, и он пригодится нам в уроке про графику.
Ещё две мелочи, которые сбивают новичков. Первая: println() переносит курсор на следующую строку сетки и к началу строки, а print() оставляет курсор сразу после напечатанного — смешивая их, вы случайно склеиваете слова. Вторая: печать «за край» не вызывает ошибки — символы просто не рисуются; если надпись обрезана, значит, текст длиннее 21 символа или курсор стоит ниже последней строки. Обе проблемы видны на экране сразу, но лучше их предвидеть: перед каждой печатью ставьте курсор явно.
И нюанс, который узнают не из рекламы: стандартный шрифт Adafruit GFX содержит только ASCII — кириллицы в нём нет. На экране пишем латиницей — Temp, Time, ON — либо подключаем свой шрифт, это разговор отдельного урока. В симуляторе всё то же самое: заголовки и подписи латиницей, и это не прихоть эмуляции, а честное зеркало реального дисплея со штатным шрифтом.
- Считалка символов. Поставьте setTextSize(2) для заголовка и пересчитайте: 128 / 12 — сколько символов влезает в строку крупным шрифтом, а сколько остаётся для данных.
- Миллисекунды. Уберите деление на 1000: счётчик пойдёт в миллисекундах, и при обновлении раз в 100 мс будет видно, как далеко убегает число за один кадр.
- Две зоны. Разделите экран: setCursor(0, 0) — шапка, setCursor(0, 32) — данные. Прикиньте, сколько строк size(1) помещается в каждую зону.
- На настоящей плате. Прогоните I2C-сканер и запишите адрес своего модуля: 0x3C или 0x3D. Наклейка на корпусе экономит час при следующем подключении.
- SSD1306 — монохромный OLED 128×64; кадр — 1024 байта буфера в ОЗУ, половина SRAM Uno (2 КБ)
- I2C: SDA на A4, SCL на A5; адреса 0x3C и 0x3D — проверять сканером из раздела про Wire
- Adafruit_SSD1306 display(128, 64, &Wire, -1); begin(SSD1306_SWITCHCAPVCC, 0x3C) — и шина, и буфер инициализируются внутри begin
- Конвейер кадра: clearDisplay → setTextSize → setCursor → print → display()
- setTextSize(1): 21 символ × 8 строк; setTextSize(2): 10 × 4; setCursor — пиксельные координаты от левого верхнего угла
- Стандартный шрифт Adafruit GFX — только ASCII: кириллицы нет, пишем латиницей
- 1. Сколько байт занимает буфер кадра OLED 128×64?
- 2. Какую долю SRAM Arduino Uno занимает этот буфер?
- 3. Какой адрес I2C у большинства модулей SSD1306?
- 4. Что произойдёт, если забыть display.display()?
Текст выведен, счётчик тикает — фундамент есть. В следующем уроке включим графику Adafruit GFX: линии, прямоугольники и круги, посчитаем, сколько по I2C идёт один кадр и почему экран «мерцает», а в симуляторе соберём дашборд теплицы — шапка, крупные данные, обновление раз в 100 мс. Скелет из этого урока сохраните: он станет каркасом всех будущих экранов.
Домашнее задание на реальной плате: подключите модуль по четырём проводам из урока, залейте скелет, добейтесь надписи — затем поменяйте адрес на 0x3D и посмотрите, что случится: экран молчит. Верните 0x3C. Последний шаг — прогоните I2C-сканер: адрес, который он напечатал, это паспорт вашего модуля. На этом первая встреча с OLED считается состоявшейся.
Частые вопросы по теме урока
- Сколько байт занимает буфер кадра OLED 128×64?
- 8192 пикселя по одному биту: 128 × 64 / 8 = 1024 байта. Именно столько библиотека держит в ОЗУ. 1024
- Какую долю SRAM Arduino Uno занимает этот буфер?
- Буфер держит библиотека в ОЗУ платы: 1024 байта из 2 КБ — половина. На Mega с 8 КБ и ESP32 с 520 КБ это не проблема. Около половины
- Какой адрес I2C у большинства модулей SSD1306?
- 0x3C — типовой адрес OLED; 0x27 — символьные LCD на PCF8574, 0x68 — часы DS3231, 0x3D — редкая версия модуля. 0x3C
- Что произойдёт, если забыть display.display()?
- print() пишет в буфер в памяти; display() — единственная функция, которая отправляет кадр на экран. Буфер останется в ОЗУ — экран не изменится