【教程】树莓派驱动 0.96 寸 SSD1315 OLED 屏幕完整指南

转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn]

如果本文帮助到了你,欢迎[点赞、收藏、关注]哦~

0.96 寸 OLED 屏幕是树莓派项目中最常用的显示模块之一。本文中的屏幕采用 SSD1315 驱动芯片,该芯片与常见的 SSD1306 寄存器完全兼容,因此可以直接使用现有的 SSD1306 驱动库。本文将带你从零开始,完成接线、配置到代码运行的全过程。

一、硬件准备

物品 说明
树莓派(任意型号) 3B/3B+/4B/Zero 均可
0.96 寸 OLED 屏幕 SSD1315 驱动,4 针 I2C 接口
杜邦线 母对母 4 根

二、硬件接线(I2C)

OLED 模块通常为 4 针 I2C 接口,接线方式如下:

OLED 引脚 树莓派引脚(BCM 编号) 物理排针号 说明
VCC 3.3V 或 5V Pin 1 / Pin 2 电源正极(多数模块支持 3.3V~5V)
GND GND Pin 6 / Pin 9 / Pin 14 等 电源负极
SCL GPIO 3 Pin 5 I2C 时钟线
SDA GPIO 2 Pin 3 I2C 数据线

⚠️ 注意:如果你的 OLED 是 7 针 SPI 接口,接线方式不同,请参考 SPI 方案。

三、开启树莓派 I2C 功能

在终端中执行以下命令:

bash 复制代码
sudo raspi-config

依次选择:

复制代码
Interface Options → I2C → Yes → Finish

完成后重启树莓派

复制代码
sudo reboot

四、检测 I2C 设备

重启后,安装 i2c-tools(通常已预装):

复制代码
sudo apt install i2c-tools

扫描 I2C 总线上的设备:

bash 复制代码
sudo i2cdetect -y 1

如果接线正确,你会看到类似下面的输出,其中 3c 就是 OLED 屏幕的 I2C 地址:

看到 3c 就说明硬件和通信都正常,可以进行下一步了!


五、安装 Python 驱动库

推荐使用 luma.oled,它对 SSD1306/SSD1315 支持良好,API 简洁易用。

bash 复制代码
sudo apt update
sudo apt install python3-pip python3-dev python3-venv \
    libfreetype6-dev libjpeg-dev libopenjp2-7 libtiff5 -y

pip3 install luma.oled

六、基础显示示例

创建测试文件 oled_test.py

python 复制代码
#!/usr/bin/env python3
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import ImageFont
import time

# 初始化 I2C,地址为 0x3C,分辨率 128x64
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial, width=128, height=64)

# 使用默认字体
font = ImageFont.load_default()

with canvas(device) as draw:
    draw.text((0, 0),  "Raspberry Pi", font=font, fill="white")
    draw.text((0, 16), "SSD1315 OLED", font=font, fill="white")
    draw.text((0, 32), "I2C Addr: 0x3C", font=font, fill="white")
    draw.text((0, 48), "Hello World!", font=font, fill="white")

print("内容已显示,保持 30 秒...")
time.sleep(30)

运行:

复制代码
python3 oled_test.py

如果一切正常,你的 OLED 屏幕上将显示四行文字!

七、进阶:实时系统信息监控

下面这段代码可以实时显示 IP 地址、CPU 温度、日期和时间:

python 复制代码
#!/usr/bin/env python3
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import ImageFont
import subprocess
import time

# 初始化设备
serial = i2c(port=1, address=0x3C)
device = ssd1306(serial, width=128, height=64)
font = ImageFont.load_default()

def get_ip():
    cmd = "hostname -I | cut -d' ' -f1"
    return subprocess.check_output(cmd, shell=True).decode().strip()

def get_cpu_temp():
    with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
        return round(int(f.read()) / 1000, 1)

try:
    while True:
        with canvas(device) as draw:
            draw.text((0, 0),  f"IP: {get_ip()}", font=font, fill="white")
            draw.text((0, 16), f"CPU: {get_cpu_temp()}°C", font=font, fill="white")
            draw.text((0, 32), time.strftime("%Y-%m-%d"), font=font, fill="white")
            draw.text((0, 48), time.strftime("%H:%M:%S"), font=font, fill="white")
        time.sleep(1)
except KeyboardInterrupt:
    print("\n已退出")

八、显示中文

默认字体不支持中文,需要加载中文字体。以霞鹜文楷字体为例,开源免费,显示效果很好:

bash 复制代码
mkdir -p ~/.fonts && wget -q -O ~/.fonts/chinese.ttf "https://pcdn.xfxuezhang.cn/https://github.com/lxgw/LxgwWenKai/releases/download/v1.510/LXGWWenKai-Regular.ttf"

对于缺少字体,可以参考以下两种方式:

方案一:安装其他 apt 字体包

复制代码
# 方案 A:思源黑体(Noto CJK,现代且完整)
sudo apt install -y fonts-noto-cjk

# 方案 B:文泉驿微米黑(更轻量)
sudo apt install -y fonts-wqy-microhei

装完后在 Python 中使用:

复制代码
# 思源黑体路径
font = ImageFont.truetype("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", 12)

# 或文泉驿微米黑路径
font = ImageFont.truetype("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", 12)

方案二:直接下载字体文件

如果 apt 里都没有,直接下载一个开源中文字体到本地:

复制代码
# 创建字体目录
mkdir -p ~/.fonts

# 下载思源黑体(Google 官方源,7MB 左右)
wget -O ~/.fonts/NotoSansSC-Regular.otf \
  "https://github.com/googlefonts/noto-cjk/raw/main/Sans/OTF/SimplifiedChinese/NotoSansSC-Regular.otf"

然后在代码里直接用:

复制代码
font = ImageFont.truetype("/home/pi/.fonts/NotoSansSC-Regular.otf", 12)

在代码中加载:

python 复制代码
from PIL import ImageFont

# 加载中文字体,字号 12
font = ImageFont.truetype("/home/pi/.fonts/chinese.ttf", 12)

with canvas(device) as draw:
    draw.text((0, 0), "你好,树莓派!", font=font, fill="white")
    draw.text((0, 20), "SSD1315 屏幕测试", font=font, fill="white")

九、完整版!树莓派系统监控面板

下面是一个功能完整的 树莓派 OLED 系统监控面板,可以实时显示 CPU、内存、磁盘、网络、IP 和温度等关键信息。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
树莓派 OLED 系统监控面板 - 适配双色 OLED 版

功能说明:
1. 在 128x64 OLED 屏幕上显示系统运行状态。
2. 顶部黄色区域显示当前时间和实时网速。
3. 下方蓝色区域显示 IP 地址、CPU 温度、CPU 使用率、内存占用率和磁盘占用率。
4. 启动时显示居中的启动页文字。
5. 支持配置屏幕旋转方向和屏幕对比度。
"""

import os
import time
import subprocess

from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import ssd1306
from PIL import ImageFont

# =========================
# OLED 基础配置
# =========================

# 树莓派默认 I2C 总线通常为 1
I2C_PORT = 1

# SSD1306 OLED 常见 I2C 地址为 0x3C
I2C_ADDRESS = 0x3C

# 屏幕刷新间隔,单位为秒
REFRESH_INTERVAL = 1.0

# 屏幕旋转方向
# 0 表示不旋转
# 1 表示顺时针旋转 90°
# 2 表示旋转 180°
# 3 表示顺时针旋转 270°
SCREEN_ROTATE = 0

# 屏幕对比度,范围 0-255
# 数值越大越亮,越小越暗
# OLED 没有传统 LCD 背光,这里调节的是 SSD1306 对比度
SCREEN_CONTRAST = 160

# 初始化 I2C 通信接口
serial = i2c(port=I2C_PORT, address=I2C_ADDRESS)

# 初始化 OLED 设备
device = ssd1306(
    serial,
    width=128,
    height=64,
    rotate=SCREEN_ROTATE
)

# 设置屏幕对比度
device.contrast(SCREEN_CONTRAST)

# =========================
# 字体配置
# =========================

# 主界面字体,支持中文、箭头和温度符号
FONT_MAIN = ImageFont.truetype("/home/sxf/.fonts/chinese.ttf", 12)

# 启动页标题字体
FONT_TITLE = ImageFont.truetype("/home/sxf/.fonts/chinese.ttf", 12)

# 默认字体,后续绘制函数默认使用这个字体
FONT = FONT_MAIN

# 双色屏分界线大约在第 15-16 行
# 0-15 行为黄色区域
# 16-63 行为蓝色区域


def set_screen_contrast(level):
    """
    设置 OLED 屏幕对比度。

    参数:
    level 范围为 0-255。
    数值越大,显示越亮。
    数值越小,显示越暗。
    """
    level = max(0, min(255, int(level)))
    device.contrast(level)


def screen_off():
    """
    关闭 OLED 显示。

    适合夜间或暂时不需要显示时使用。
    """
    device.hide()


def screen_on():
    """
    打开 OLED 显示。

    与 screen_off 配合使用。
    """
    device.show()


def get_cpu_temp():
    """
    获取 CPU 温度。

    树莓派 CPU 温度通常存放在:
    /sys/class/thermal/thermal_zone0/temp

    文件中的数值单位是毫摄氏度,因此需要除以 1000。
    返回值单位为摄氏度。
    """
    try:
        with open("/sys/class/thermal/thermal_zone0/temp", "r") as f:
            return round(int(f.read().strip()) / 1000, 1)
    except:
        return 0.0


def get_cpu_usage():
    """
    获取 CPU 使用率百分比。

    计算方式:
    1. 从 /proc/stat 读取 CPU 累计时间。
    2. 记录上一次的 idle 和 total。
    3. 当前值减去上一次的值,得到间隔时间内的 CPU 使用情况。
    4. 使用率 = 1 - idle_delta / total_delta。

    首次调用时没有历史数据,因此返回 0.0。
    """
    global _last_cpu

    try:
        with open("/proc/stat", "r") as f:
            parts = f.readline().split()

        values = list(map(int, parts[1:]))

        # idle 是空闲时间,iowait 也计入等待时间
        idle = values[3] + values[4]

        # total 是所有 CPU 状态时间的总和
        total = sum(values)

        if "_last_cpu" not in globals():
            _last_cpu = (idle, total)
            return 0.0

        last_idle, last_total = _last_cpu
        idle_delta = idle - last_idle
        total_delta = total - last_total

        _last_cpu = (idle, total)

        if total_delta <= 0:
            return 0.0

        usage = (1.0 - idle_delta / total_delta) * 100
        return round(usage, 1)

    except:
        return 0.0


def get_memory_info():
    """
    获取内存信息。

    返回值:
    total    总内存,单位 KB
    used     已用内存,单位 KB
    mem_pct  已用内存百分比
    """
    try:
        with open("/proc/meminfo", "r") as f:
            lines = f.readlines()

        total = int(lines[0].split()[1])
        available = int(lines[2].split()[1])
        used = total - available

        return total, used, round(used / total * 100, 1)

    except:
        return 1, 0, 0.0


def get_disk_usage():
    """
    获取根目录磁盘使用率。

    os.statvfs("/") 返回文件系统统计信息。
    f_blocks 表示总块数。
    f_bavail 表示普通用户可用块数。
    f_frsize 表示块大小。
    """
    try:
        stat = os.statvfs("/")

        total = stat.f_blocks * stat.f_frsize
        free = stat.f_bavail * stat.f_frsize

        return round((total - free) / total * 100, 1)

    except:
        return 0.0


def get_ip_address():
    """
    获取当前设备的 IPv4 地址。

    优先检查 wlan0。
    如果 wlan0 没有 IP,再检查 eth0。
    """
    try:
        for iface in ["wlan0", "eth0"]:
            result = subprocess.run(
                ["ip", "-4", "addr", "show", iface],
                capture_output=True,
                text=True
            )

            if result.returncode == 0:
                for line in result.stdout.split("\n"):
                    if "inet " in line:
                        ip = line.strip().split()[1].split("/")[0]
                        return ip

        return "No IP"

    except:
        return "No IP"


def get_network_speed():
    """
    获取实时网络上传和下载速度。

    数据来源:
    /proc/net/dev

    计算方式:
    1. 读取 wlan0 或 eth0 的累计接收字节数和发送字节数。
    2. 与上一次读取的字节数做差。
    3. 除以时间间隔,得到每秒字节数。
    4. 再除以 1024 * 1024,换算为 MB/s。

    返回值:
    rx_speed 下载速度,单位 MB/s
    tx_speed 上传速度,单位 MB/s
    """
    global _last_net

    try:
        with open("/proc/net/dev", "r") as f:
            for line in f:
                if "wlan0:" in line or "eth0:" in line:
                    data = line.split()

                    if len(data) >= 10:
                        rx_bytes = int(data[1])
                        tx_bytes = int(data[9])
                        break
            else:
                return 0.0, 0.0

    except:
        return 0.0, 0.0

    now = time.time()

    if "_last_net" not in globals():
        _last_net = (now, rx_bytes, tx_bytes)
        return 0.0, 0.0

    last_time, last_rx, last_tx = _last_net
    elapsed = now - last_time

    if elapsed <= 0:
        return 0.0, 0.0

    rx_speed = round((rx_bytes - last_rx) / elapsed / 1024 / 1024, 1)
    tx_speed = round((tx_bytes - last_tx) / elapsed / 1024 / 1024, 1)

    _last_net = (now, rx_bytes, tx_bytes)

    return rx_speed, tx_speed


def get_text_size(draw, text, font=FONT):
    """
    获取指定文字的宽度和高度。

    新版本 PIL 支持 textbbox。
    老版本 PIL 可能只支持 textsize。
    """
    try:
        bbox = draw.textbbox((0, 0), text, font=font)
        return bbox[2] - bbox[0], bbox[3] - bbox[1]
    except AttributeError:
        return draw.textsize(text, font=font)


def draw_center_text(draw, y, text, font=FONT):
    """
    在指定 y 坐标绘制水平居中的文字。
    """
    text_w, _ = get_text_size(draw, text, font)
    x = max(0, (device.width - text_w) // 2)
    draw.text((x, y), text, font=font, fill="white")


def draw_right_text(draw, y, text, font=FONT, right=127):
    """
    在指定 y 坐标绘制右对齐文字。

    right 默认为 127,对应 128 像素宽屏幕的最右侧。
    """
    text_w, _ = get_text_size(draw, text, font)
    x = max(0, right - text_w)
    draw.text((x, y), text, font=font, fill="white")


def draw_screen(draw):
    """
    绘制主监控界面。

    顶部黄色区域:
    左侧显示当前时间。
    右侧显示下载和上传速度。

    下方蓝色区域:
    第 17 行显示 IP 地址。
    第 31 行显示 CPU 温度和 CPU 使用率。
    第 45 行显示 RAM 和 DSK 使用率。
    """
    temp = get_cpu_temp()
    cpu_pct = get_cpu_usage()
    _, _, mem_pct = get_memory_info()
    disk_pct = get_disk_usage()
    ip = get_ip_address()
    rx_speed, tx_speed = get_network_speed()

    now = time.strftime("%H:%M:%S")

    # ↓ 表示下载速度,↑ 表示上传速度
    # 这里省略单位 MB/s,节省屏幕空间
    net_text = f"↓{rx_speed:.1f} ↑{tx_speed:.1f}"

    # 黄色区域,0-15 行
    draw.text((0, 0), now, font=FONT, fill="white")
    draw_right_text(draw, 0, net_text, font=FONT)

    # 顶部区域下方画一条横线,作为视觉分隔
    draw.line((0, 15, 127, 15), fill="white")

    # 蓝色区域,16-63 行
    draw.text((0, 17), f"IP: {ip}", font=FONT, fill="white")
    draw.text((0, 31), f"CPU: {temp}°C ({int(cpu_pct)}%)", font=FONT, fill="white")

    draw.text(
        (0, 45),
        f"RAM: {int(mem_pct)}%  DSK: {int(disk_pct)}%",
        font=FONT,
        fill="white"
    )


def main():
    """
    程序入口。

    启动流程:
    1. 打印启动提示。
    2. 应用屏幕对比度设置。
    3. 显示启动页。
    4. 进入循环,每隔 REFRESH_INTERVAL 秒刷新一次 OLED。
    5. 按 Ctrl+C 退出时清空屏幕。
    """
    print("OLED 监控已启动(双色屏适配版),按 Ctrl+C 退出")

    # 启动时应用一次对比度设置
    set_screen_contrast(SCREEN_CONTRAST)

    # 启动页显示一次
    with canvas(device) as draw:
        draw_center_text(draw, 15, "System Monitor", font=FONT_TITLE)
        draw_center_text(draw, 49, "小锋学长生活大爆炸", font=FONT)

    time.sleep(1.5)

    try:
        while True:
            with canvas(device) as draw:
                draw_screen(draw)

            time.sleep(REFRESH_INTERVAL)

    except KeyboardInterrupt:
        print("\n已退出")

        # 清空 OLED 屏幕
        with canvas(device) as draw:
            pass


if __name__ == "__main__":
    main()

设置开机自启动

如果你希望树莓派开机后自动运行这个监控:

bash 复制代码
sudo nano /etc/systemd/system/oled-monitor.service

粘贴以下内容:

bash 复制代码
[Unit]
Description=OLED System Monitor
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi
ExecStart=/usr/bin/python3 /home/pi/system_monitor.py
Restart=always

[Install]
WantedBy=multi-user.target

启用并启动:

bash 复制代码
sudo systemctl daemon-reload
sudo systemctl enable oled-monitor
sudo systemctl start oled-monitor

# 查看状态
sudo systemctl status oled-monitor

这样每次开机,OLED 就会自动显示系统状态了。

十、常见问题排查

现象 可能原因 解决方案
屏幕完全不亮 VCC 供电不足或接错 尝试换到 5V 引脚;检查接线
i2cdetect 无地址 SDA/SCL 接反;I2C 未开启 检查接线;重新运行 raspi-config 开启 I2C
显示花屏/偏移 驱动芯片兼容性问题 SSD1315 基本兼容 SSD1306,若异常可尝试 sh1106 驱动
中文显示为方块 未加载中文字体 安装 fonts-wqy-zenhei 并在代码中指定字体路径
屏幕有残影 OLED 特性导致 属于正常现象,可通过定期清屏或显示反色缓解

结语

SSD1315 与 SSD1306 的高度兼容性,使得我们可以直接使用成熟的 luma.oled 库来驱动这块屏幕。通过本文的步骤,你应该已经成功点亮了屏幕。在此基础上,你可以进一步开发:

  • 温湿度传感器数据显示

  • 网络状态监控面板

  • 小型游戏或动画效果

  • 与 MQTT、Home Assistant 联动的智能家居信息屏

希望这篇教程对你有所帮助,欢迎在评论区交流遇到的问题!

相关推荐
ye150127774552 小时前
12V-24V升110V升压转换WT3207
单片机·嵌入式硬件·其他·硬件工程
yong99903 小时前
基于 STM32 的数字控制实现双向 DC-DC 电源
stm32·单片机·嵌入式硬件
12.=0.3 小时前
【stm32_9】RTOS的概念、种类对比,FressRTOS的概述、FressRTOS的源码结构、FressRTOS的源码移植
stm32·单片机·嵌入式硬件
Yeats_Liao4 小时前
智能感知低功耗设计:MCU上的AI异常检测与能效优化
人工智能·单片机·物联网·neo4j
Y多了个想法4 小时前
RK3576 android14 I2C总线,硬件I2C 与 GPIO模拟I2C 比对
经验分享·嵌入式硬件·i2c·rk·rk3576
blevoice4 小时前
JL杰理AC696N开发板上调试蓝牙音质优化:开启AAC高清音频支持
单片机·ffmpeg·音视频·aac·ac6966b蓝牙音响方案·杰理智能音箱开发·杰理ac6965e蓝牙音频开发
小+不通文墨4 小时前
树莓派4b-wiringpi库的安装和使用
驱动开发·经验分享·笔记·嵌入式硬件·学习
小麦嵌入式4 小时前
FPGA入门(三):3-8 译码器 仿真波形解读
stm32·单片机·嵌入式硬件·mcu·fpga开发·硬件工程
其实防守也摸鱼5 小时前
upload-labs靶场的pass-2~12的解题步骤及原理讲解
笔记·安全·web安全·网络安全·教程·web·工具