Files
wikipedia/SBC - Hardware/ssd1306-rpi5-guide.md
T

15 KiB
Raw Blame History

Подключение OLED дисплея SSD1306 128x64 к Raspberry Pi 5

Содержание

  1. Подключение по I2C
  2. Настройка I2C на RPi5
  3. Установка ПО
  4. Проверка дисплея
  5. Статическое изображение
  6. CPU/Монитор в реальном времени
  7. Systemd-сервис для автостарта
  8. Решение проблем
  9. Приложение: полный код скриптов

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