vault backup: 2026-10-09 21:27:26
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
# Подключение 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
|
||||
Reference in New Issue
Block a user