IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Series: Lập trình Raspberry Pi – Bài 10: OLED/LCD hiển thị trạng thái (SSD1306 I2C) — nhiệt độ, IP, service status

Series: Lập trình Raspberry Pi & Ứng dụng thực tế Phần 2 — Lập trình GPIO & phần cứng Bài 10: OLED/LCD hiển thị trạng thái (SSD1306 I2C) — nhiệt độ, IP, service status


1) Mục tiêu bài học

Sau bài này bạn sẽ:

  • Kết nối và hiển thị lên OLED SSD1306 I2C (128×64).
  • Hiển thị 3 thứ “thực chiến”:
    • IP hiện tại
    • Nhiệt độ/độ ẩm (từ BME280 hoặc giá trị demo)
    • Trạng thái service (systemd: active/inactive)
  • Có code Python chạy được ngay.

2) Phần cứng & wiring (SSD1306 I2C)

OLED SSD1306 thường có 4 chân: VCC, GND, SDA, SCL

  • VCC → 3.3V (Pin 1) (nhiều module chịu 3.3V tốt)
  • GND → GND (Pin 6)
  • SDA → GPIO2 (Pin 3)
  • SCL → GPIO3 (Pin 5)

Kiểm tra OLED có lên I2C không:

sudo i2cdetect -y 1

Thường OLED là 0x3C hoặc 0x3D.

3) Cài thư viện cần thiết

sudo apt update
sudo apt -y install python3-pip python3-pil i2c-tools
pip3 install adafruit-circuitpython-ssd1306

4) Code: OLED dashboard mini

Tạo thư mục:

mkdir -p ~/apps/oled-status
cd ~/apps/oled-status
nano oled_status.py

Dán code:

import time
import socket
import subprocess
from datetime import datetime

import board
import busio
from PIL import Image, ImageDraw, ImageFont
import adafruit_ssd1306

OLED_ADDR = 0x3C
SERVICE_NAME = "pi-agent"   # đổi thành service bạn đang dùng
REFRESH_SEC = 2

def get_ip():
    try:
        # cách lấy IP “thực tế” hay dùng
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(("1.1.1.1", 80))
        ip = s.getsockname()[0]
        s.close()
        return ip
    except Exception:
        return "no-ip"

def get_service_status(name: str) -> str:
    try:
        r = subprocess.run(
            ["systemctl", "is-active", name],
            capture_output=True, text=True, check=False
        )
        return r.stdout.strip() or "unknown"
    except Exception:
        return "unknown"

def main():
    i2c = busio.I2C(board.SCL, board.SDA)
    disp = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=OLED_ADDR)

    disp.fill(0)
    disp.show()

    # canvas
    width = disp.width
    height = disp.height
    image = Image.new("1", (width, height))
    draw = ImageDraw.Draw(image)

    # font
    font = ImageFont.load_default()

    while True:
        draw.rectangle((0, 0, width, height), outline=0, fill=0)

        now = datetime.now().strftime("%H:%M:%S")
        ip = get_ip()
        svc = get_service_status(SERVICE_NAME)

        # demo sensor (bạn có thể thay bằng BME280 từ bài 8)
        temp = 26.5
        hum = 70.2

        draw.text((0, 0),  f"IoTLabs Pi  {now}", font=font, fill=255)
        draw.text((0, 16), f"IP: {ip}", font=font, fill=255)
        draw.text((0, 32), f"T: {temp:.1f}C  H: {hum:.1f}%", font=font, fill=255)
        draw.text((0, 48), f"svc {SERVICE_NAME}: {svc}", font=font, fill=255)

        disp.image(image)
        disp.show()

        time.sleep(REFRESH_SEC)

if __name__ == "__main__":
    main()

Chạy:

python3 oled_status.py

5) (Tuỳ chọn) Kết hợp dữ liệu BME280 thật (từ Bài 8)

Nếu bạn đã có BME280, bạn chỉ cần thay phần demo:

Cài:

pip3 install adafruit-circuitpython-bme280

Thêm vào code:

import adafruit_bme280
bme = adafruit_bme280.Adafruit_BME280_I2C(i2c)
temp = bme.temperature
hum = bme.humidity

6) Lỗi thường gặp & cách fix

  • i2cdetect không thấy 0x3C:
    • chưa enable I2C (Bài 7)
    • sai SDA/SCL
    • module OLED cấp nhầm 5V (nguy hiểm)
  • Màn hình trắng/đen:
    • sai địa chỉ (0x3C vs 0x3D)
  • Text bị “cắt”:
    • OLED 128×64 rất nhỏ → chia dòng hợp lý, viết ngắn

7) Bài tập nâng cấp

  1. Hiển thị thêm: CPU temp (đọc từ vcgencmd measure_temp).
  2. Hiển thị thêm: disk usage (df -h).
  3. Chạy script OLED bằng systemd service để tự chạy khi boot.