15 KiB
Подключение OLED дисплея SSD1306 128x64 к Raspberry Pi 5
Содержание
- Подключение по I2C
- Настройка I2C на RPi5
- Установка ПО
- Проверка дисплея
- Статическое изображение
- CPU/Монитор в реальном времени
- Systemd-сервис для автостарта
- Решение проблем
- Приложение: полный код скриптов
1. Подключение по I2C
SSD1306 подключается по I2C-шине. Пины I2C на Raspberry Pi 5:
| Пин | Название | GPIO | Назначение |
|---|---|---|---|
| 3 | SDA | GPIO2 | I2C Data |
| 5 | SCL | GPIO3 | I2C Clock |
| 1 | 3.3V | — | Питание |
| 6 | GND | — | Земля |
Схема подключения:
RPi5 OLED SSD1306
GPIO2 (Pin 3) ─────── SDA
GPIO3 (Pin 5) ─────── SCL
3.3V (Pin 1) ─────── VCC
GND (Pin 6) ─────── GND
Важно: Для I2C нужны подтягивающие резисторы (pull-up) 4.7 кОм на линиях SDA и SCL к 3.3V. На большинстве готовых OLED-модулей они уже впаяны — дополнительно ставить не нужно.
2. Настройка I2C на RPi5
На Raspberry Pi 5 I2C по умолчанию отключён в /boot/firmware/config.txt.
2.1. Включение I2C
sudo sed -i 's/#dtparam=i2c_arm=on/dtparam=i2c_arm=on/' /boot/firmware/config.txt
sudo reboot
Проверка после перезагрузки:
sudo /usr/sbin/i2cdetect -y 1
Вывод (пример — если дисплей подключён):
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
...
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
...
Адрес 0x3C — стандартный для SSD1306 (может быть 0x3D, проверяйте по даташиту).
Заметка: i2cdetect лежит в /usr/sbin/ — этого каталога нет в PATH обычного пользователя. Используйте полный путь или sudo.
2.2. Альтернатива: raspi-config
sudo raspi-config
# → Interface Options → I2C → Enable
sudo reboot
3. Установка ПО
3.1. Системные пакеты
sudo apt-get install -y i2c-tools python3-pip python3-smbus python3-pil
3.2. Виртуальное окружение Python
На Raspberry Pi OS Bookworm используется external-managed-environment — pip install напрямую не работает. Обязательно создаём venv:
python3 -m venv ~/oled-env
source ~/oled-env/bin/activate
3.3. Установка luma.oled
pip install luma.oled Pillow
Проверка установки:
python3 -c "from luma.core.interface.serial import i2c; from luma.oled.device import ssd1306; print('OK')"
4. Проверка дисплея
После включения I2C и установки ПО — быстрый тест сканирует шину и зажигает пиксели:
source ~/oled-env/bin/activate
python3 -c "
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from PIL import Image, ImageDraw
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial)
img = Image.new('1', device.size, 'black')
draw = ImageDraw.Draw(img)
draw.rectangle((0, 0, 127, 63), outline='white', fill='black')
draw.text((20, 25), 'Hello!', fill='white')
device.display(img)
device.cleanup = lambda: None
"
Если дисплей показывает рамку и текст — всё работает.
5. Статическое изображение
Скрипт /home/emb/oled_show.py — выводит статический текст и завершается, оставляя изображение на дисплее.
#!/home/emb/oled-env/bin/python3
import sys, os, signal
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from PIL import Image, ImageDraw, ImageFont
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial)
img = Image.new('1', device.size, 'black')
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 11)
draw.rectangle((0, 0, 127, 63), outline='white', fill='black')
draw.text((2, 2), 'RPi5 Ready!', font=font, fill='white')
draw.text((2, 16), 'I2C 128x64', font=font, fill='white')
draw.text((2, 30), '192.168.1.46', font=font, fill='white')
draw.text((2, 50), 'by Hermes', font=font, fill='white')
device.display(img)
device.cleanup = lambda: None # <-- КЛЮЧЕВОЙ МОМЕНТ
print('OLED ready')
Почему device.cleanup = lambda: None?
Библиотека luma.oled в деструкторе объекта device вызывает команду 0xAE (DISPLAY OFF), что гасит дисплей при завершении скрипта. Замена cleanup на пустую функцию предотвращает это.
Если вам нужно, чтобы скрипт работал постоянно (и дисплей не гас никогда), добавьте signal.pause() в конец — процесс останется висеть.
6. CPU/Монитор в реальном времени
Скрипт /home/emb/oled_cpu_mon.py — отображает загрузку CPU, RAM и температуру с обновлением раз в секунду.
import time, os
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from PIL import Image, ImageDraw, ImageFont
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial)
font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 10)
font_big = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', 16)
device.cleanup = lambda: None
def get_cpu():
with open('/proc/loadavg') as f:
parts = f.read().split()
load = float(parts[0])
cores = os.cpu_count() or 4
percent = (load / cores) * 100
return min(percent, 100), load
def get_ram():
with open('/proc/meminfo') as f:
lines = f.readlines()
mem_total = int([l for l in lines if 'MemTotal' in l][0].split()[1])
mem_avail = int([l for l in lines if 'MemAvailable' in l][0].split()[1])
used = mem_total - mem_avail
return used / mem_total * 100, used // 1024, mem_total // 1024
def get_temp():
try:
with open('/sys/class/thermal/thermal_zone0/temp') as f:
return int(f.read()) / 1000
except:
return 0
while True:
cpu_pct, load = get_cpu()
ram_pct, used_mb, total_mb = get_ram()
temp = get_temp()
img = Image.new('1', device.size, 'black')
draw = ImageDraw.Draw(img)
draw.rectangle((0, 0, 127, 63), outline='white', fill='black')
# CPU bar
bar_w = 90
bar_h = 12
draw.rectangle((32, 4, 32 + bar_w, 4 + bar_h), outline='white', fill='black')
fill_w = int(bar_w * cpu_pct / 100)
if fill_w > 0:
draw.rectangle((32 + 1, 4 + 1, 32 + fill_w, 4 + bar_h - 1), fill='white')
draw.text((2, 5), 'CPU', font=font, fill='white')
draw.text((32 + bar_w + 4, 3), f'{cpu_pct:.0f}%', font=font_big, fill='white')
draw.text((2, 20), f'Load: {load:.2f}', font=font, fill='white')
# RAM bar
draw.rectangle((32, 36, 32 + bar_w, 36 + bar_h), outline='white', fill='black')
fill_w2 = int(bar_w * ram_pct / 100)
if fill_w2 > 0:
draw.rectangle((32 + 1, 36 + 1, 32 + fill_w2, 36 + bar_h - 1), fill='white')
draw.text((2, 38), 'RAM', font=font, fill='white')
draw.text((32 + bar_w + 4, 36), f'{ram_pct:.0f}%', font=font_big, fill='white')
# Temp and PID at bottom
draw.text((2, 52), f'Temp: {temp:.1f}C', font=font, fill='white')
draw.text((72, 52), f'PID: {os.getpid()}', font=font, fill='white')
device.display(img)
time.sleep(1)
Визуальная схема расположения на дисплее:
┌─────────────────────────────────────────┐
│ CPU ████████████████░░░░ 45% │
│ Load: 1.80 │
│ RAM ██████████░░░░░░░░ 32% │
│ Temp: 52.3C PID: 2535 │
└─────────────────────────────────────────┘
7. Systemd-сервис для автостарта
Чтобы скрипт запускался автоматически при включении RPi5 и перезапускался при падении — используем systemd.
7.1. Создание сервиса
Предварительно: убедитесь, что модуль i2c-dev загружается при старте:
echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf
Создать сервис:
sudo tee /etc/systemd/system/oled-display.service > /dev/null << 'EOF'
[Unit]
Description=OLED Display Service
After=multi-user.target
[Service]
Type=simple
User=emb
ExecStart=/home/emb/oled-env/bin/python3 /home/emb/oled_cpu_mon.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
| Параметр | Значение | Пояснение |
|---|---|---|
User=emb |
emb | Запуск от вашего пользователя |
ExecStart |
venv/bin/python3 + скрипт | Полный путь к интерпретатору |
Restart=always |
— | Перезапуск при любом завершении |
RestartSec=5 |
5 | Пауза между перезапусками |
7.2. Активация и управление
sudo systemctl daemon-reload # перечитать конфиги
sudo systemctl enable oled-display.service # автостарт при boot
sudo systemctl restart oled-display.service # запустить сейчас
7.3. Базовые команды
# Статус
sudo systemctl status oled-display.service
# Остановка
sudo systemctl stop oled-display.service
# Запуск
sudo systemctl start oled-display.service
# Отключение автостарта
sudo systemctl disable oled-display.service
# Логи
sudo journalctl -u oled-display.service -n 20
7.4. Смена скрипта
Чтобы переключить сервис на другой скрипт (например, со статического на монитор):
sudo sed -i 's|oled_show.py|oled_cpu_mon.py|' /etc/systemd/system/oled-display.service
sudo systemctl daemon-reload
sudo systemctl restart oled-display.service
7.5. Обновление скрипта без простоя
Если сервис уже работает:
sudo systemctl stop oled-display.service
# редактируем /home/emb/oled_cpu_mon.py
sudo systemctl restart oled-display.service
8. Решение проблем
8.1. Дисплей пустой после запуска
Причина: деструктор luma.oled отключает дисплей.
Решение: device.cleanup = lambda: None перед выходом из скрипта.
8.2. I2C device not found после перезагрузки (стартовая гонка)
Симптом: сервис падает в логах с I2C device not found: /dev/i2c-1, хотя после ручного запуска всё работает.
Причина: systemd запускает сервис до загрузки модуля i2c-dev.
Решение: добавить ожидание устройства в скрипте:
import os, time
for attempt in range(30):
if os.path.exists('/dev/i2c-1'):
break
time.sleep(1)
else:
raise RuntimeError('I2C device /dev/i2c-1 not found after 30s')
Также можно загружать модуль принудительно: echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf
sudo /usr/sbin/i2cdetect -y 1
Если адрес не отображается:
- Проверьте физическое подключение пинов SDA/SCL/3.3V/GND
- Включите I2C в
/boot/firmware/config.txtи перезагрузите - Проверьте
ls /dev/i2c*— если шины нет, загрузите модуль:sudo modprobe i2c-dev
8.3. pip install не работает
Ошибка: error: externally-managed-environment
Причина: Raspberry Pi OS Bookworm включает PEP 668.
Решение: всегда используйте venv.
python3 -m venv ~/oled-env
source ~/oled-env/bin/activate
pip install luma.oled Pillow
8.4. i2cdetect: command not found
Причина: /usr/sbin/ не в PATH обычного пользователя.
Решение: sudo /usr/sbin/i2cdetect -y 1
8.5. systemd-сервис падает
sudo journalctl -u oled-display.service -n 20
Типичные ошибки:
SyntaxError— проверьте скрипт на синтаксисModuleNotFoundError— проверьте путь вExecStart(должен вести к python из venv)PermissionError— файл скрипта должен быть читаемым для пользователя
8.6. Дисплей показывает мусор/рябь
- Сделайте полную инициализацию контроллера (последовательность 0xAE ... 0xAF)
- Проверьте питание — 3.3V стабильно?
- Убедитесь что нет конфликта адресов (другие I2C устройства на той же шине)
9. Приложение: полный код скриптов
9.1. static_text.py — статический текст
Полный код доступен в файле /home/emb/oled_show.py на целевой системе.
9.2. cpu_mon.py — монитор CPU/RAM/Температура
Полный код доступен в файле /home/emb/oled_cpu_mon.py на целевой системе.
Создано: Hermes Agent · Raspberry Pi 5 · Debian Bookworm · SSD1306 128×64 I2C