OLED SSD1306 128x64: подключение к Arduino по I2C
OLED 128x64 на чипе SSD1306 это контрастный пиксельный дисплей, который подключается так же, как LCD по I2C: четыре провода. Отрисовка идёт через библиотеки Adafruit SSD1306 и Adafruit GFX: сначала рисуем в буфер, потом display() отправляет кадр на экран.
Текст и дашборды разобраны в уроках первый текст и графика на OLED.
Ниже объясняем, как устроен контроллер и почему изображение сначала рисуется в память, разбираем чёрный экран по шагам, честно говорим про память Uno и про кириллицу, и даём список из семи шагов для проверки готового экрана.
Схема и адрес
| OLED I2C | Arduino Uno |
|---|---|
| GND | GND |
| VCC | 5 В (модули со стабилизатором) или 3.3 В |
| SCL | A5 |
| SDA | A4 |
В отличие от LCD, где на экране живут символы, OLED это настоящая графическая матрица: 128 на 64 отдельных пикселя, каждый со своим состоянием. Поэтому библиотека работает как маленький компьютерный экран. Сначала вы рисуете в буфер, буфер это массив размером 128 × 64 бит, то есть ровно 1024 байта, а потом одной командой display() содержимое буфера уезжает в контроллер по I2C. Именно поэтому без вызова display() на экране ничего не появится, даже если все линии и прямоугольники нарисованы.
Сам контроллер SSD1306 умеет только примитивы: поставить точку, провести линию, залить прямоугольник и показать текст. Шрифты берутся из библиотеки Adafruit GFX, и это отдельная важная деталь. Встроенный шрифт там один, растровый, и содержит он только латиницу, цифры и служебные символы. Кириллицы в нём нет, и если вы впишете русский текст, на экране получится мусор. Для русских надписей нужен либо свой шрифт, либо другая библиотека, например u8g2, где начертания задаются шрифтовым файлом. Про это стоит знать заранее, до того как проект дойдёт до вывода надписей.
Первый текст
#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("Privet, OLED!"); display.display();} void loop() {}Разберём параметры. Конструктор Adafruit_SSD1306 получает ширину, высоту, ссылку на шину I2C и номер вывода сброса. Минус один означает, что вывода сброса нет и сброс делать не нужно, а если у модуля есть кнопка RESET на плате, вместо минуса ставят номер этого вывода. В begin() первым идёт режим заряда конденсатора: SSD1306_SWITCHCAPVCC подходит большинству модулей, а на части плат с внешним питанием нужно ставить SSD1306_EXTERNALVCC. Второй параметр это адрес, он должен совпадать с тем, что стоит на модуле. Дальше setTextSize задаёт размер шрифта, setTextColor цвет, setCursor позицию, а println печатает строку.
Отдельно про clearDisplay(): он очищает буфер, и вызывать его нужно перед каждой отрисовкой кадра. Если этого не делать, старое изображение остаётся поверх нового и получается каша. Правильный цикл выглядит так: clearDisplay, рисуем всё нужное, display. Частота обновления зависит от скорости шины I2C и от объёма кадра, и для 128 × 64 это десятки миллисекунд, так что обновлять экран чаще нескольких раз в секунду с полной перерисовкой смысла нет.
Тупик: экран не загорается
Первый модуль, который мы подключали, не загорелся вообще. Мы проверили провода, адрес, попробовали библиотеку SH110x, сняли питание и подали его с другого источника. Экран оставался чёрным, и мы решили, что модуль бракованный.
Модуль был в порядке. Дело оказалось в библиотеке: мы вызвали display.begin() и сразу нарисовали, не проверив результат. Вместо этого сначала надо убедиться, что begin() прошёл успешно, а ещё проверить, что на шине вообще есть устройство, например сканером I2C. Второй случай с тем же симптомом: у части модулей вывод RESET не разведён с землёй, и если в конструкторе стоит минус один, а плата при этом требует импульс сброса, контроллер остаётся в неопределённом состоянии. Лечится указанием номера вывода сброса или подачей импульса вручную. Третий случай самый частый: не вызван display.display(), и весь рисунок остался в буфере.
Личный опыт: сколько памяти съедает OLED
Буфер кадра это 1024 байта, и на Uno, где SRAM всего 2 КБ, это половина всей памяти. На практике мы сталкивались с тем, что проект с OLED и логикой переставал собираться или выдавал странные сбои, и причина была в нехватке памяти, а не в коде. Спасают три пути. Первый: библиотека позволяет создать объект с меньшим буфером, например на 128 × 16, тогда это 256 байт, и на экране появляются только верхние строки. Второй: рисовать не всё сразу, а по частям, вызывая display() после каждой порции. Третий и самый честный для Uno: перейти на плату с большей памятью.
Ещё одно наблюдение из практики: OLED это эмиссивный дисплей, он сам светится и потребляет ток, когда на экране что-то нарисовано. Пустой чёрный экран почти не ест энергию, а статичная надпись, которую никто не стирает, горит постоянно. Мы в датчиках выключали экран между обновлениями и включали его по событию, и это заметно продлевало работу от батарейки. Заодно это же помогает сохранить ресурс пикселей: у синих и белых OLED он конечен, и статичная картинка на экране постепенно выгорает, оставляя после себя тёмный след.
Авторская позиция: какой дисплей взять
Мы советуем брать OLED 128x64, если в проекте нужна графика, показания с прибора, логотип или русский текст. Для OLED с этим проще: любой символ рисуется как набор пикселей, ограничения алфавита нет. Плюс сам OLED стоит сопоставимо с LCD-модулем, а выглядит заметно современнее.
Но LCD 16x2 мы не списываем со счетов. У него есть два преимущества: он дешевле и читается на прямом солнце, где OLED с его зеркальным чёрным фоном может «слепнуть». Для уличного табло или для прибора в цехе LCD честно удобнее. И ещё: если проект живёт на Uno и память поджимает, дисплей 16 × 2 не требует буфера в килобайт и оставляет процессору место для логики. Выбор мы сводим к одному вопросу: нужно ли рисовать или достаточно написать текст.