# Подключение OLED дисплея SSD1306 128x64 к Raspberry Pi 5 ## Содержание 1. [Подключение по I2C](#1-подключение-по-i2c) 2. [Настройка I2C на RPi5](#2-настройка-i2c-на-rpi5) 3. [Установка ПО](#3-установка-по) 4. [Проверка дисплея](#4-проверка-дисплея) 5. [Статическое изображение](#5-статическое-изображение) 6. [CPU/Монитор в реальном времени](#6-cpumонитор-в-реальном-времени) 7. [Systemd-сервис для автостарта](#7-systemd-сервис-для-автостарта) 8. [Решение проблем](#8-решение-проблем) 9. [Приложение: полный код скриптов](#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 ```bash sudo sed -i 's/#dtparam=i2c_arm=on/dtparam=i2c_arm=on/' /boot/firmware/config.txt sudo reboot ``` **Проверка после перезагрузки:** ```bash 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 ```bash sudo raspi-config # → Interface Options → I2C → Enable sudo reboot ``` --- ## 3. Установка ПО ### 3.1. Системные пакеты ```bash 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: ```bash python3 -m venv ~/oled-env source ~/oled-env/bin/activate ``` ### 3.3. Установка luma.oled ```bash pip install luma.oled Pillow ``` Проверка установки: ```bash python3 -c "from luma.core.interface.serial import i2c; from luma.oled.device import ssd1306; print('OK')" ``` --- ## 4. Проверка дисплея После включения I2C и установки ПО — быстрый тест сканирует шину и зажигает пиксели: ```bash 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` — выводит статический текст и завершается, оставляя изображение на дисплее. ```python #!/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 и температуру с обновлением раз в секунду. ```python 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` загружается при старте: ```bash echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf ``` Создать сервис: ```bash 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. Активация и управление ```bash sudo systemctl daemon-reload # перечитать конфиги sudo systemctl enable oled-display.service # автостарт при boot sudo systemctl restart oled-display.service # запустить сейчас ``` ### 7.3. Базовые команды ```bash # Статус 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. Смена скрипта Чтобы переключить сервис на другой скрипт (например, со статического на монитор): ```bash 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. Обновление скрипта без простоя Если сервис уже работает: ```bash 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`. **Решение:** добавить ожидание устройства в скрипте: ```python 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` ```bash 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. ```bash 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-сервис падает ```bash 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