设备树 Overlay(Device Tree Overlays, DTO),它在嵌入式Linux系统(尤其是基于ARM的设备,比如树莓派、NanoPi等)中非常常见。它主要用于动态修改设备树,以适配硬件的变化或扩展外设支持。
1. 设备树 (Device Tree) 基础
- 设备树是Linux内核用来描述硬件的一个数据结构(通常是
.dts
源文件,编译成.dtb
二进制文件)。 - 它告诉内核“硬件是什么”,“有什么设备”,以及“设备如何连接”的信息。
- 这样,内核就不需要硬编码特定硬件,增强了移植性。
2. 设备树 Overlay(DTO)是什么?
- Overlay 就是对设备树的“增量补丁”,它定义了一段额外的设备树节点,能够在运行时或者启动时“叠加”到主设备树上。
- 目的:实现设备树的动态扩展,比如给板子添加新的外设(GPIO扩展板、摄像头模块、触摸屏等),而不用修改主设备树文件。
- Overlay通常是一个独立的
.dtbo
文件(编译后的Overlay),由主设备树加载时或内核模块加载时合并。
3. 使用场景
典型场景:
-
外设模块动态添加
比如你的主板没有集成摄像头,需要外接摄像头模块,就可以用Overlay来描述摄像头设备节点和相关GPIO、中断配置。 -
GPIO复用和引脚配置
一块板子多个设备共用引脚,Overlay能配置引脚复用,打开或关闭某些设备接口。 -
设备树不方便修改的环境
有些固件或内核只能加载固定的主设备树,Overlay给了动态配置的能力。
4. Overlay 如何使用?
-
制作Overlay文件
- 编写
.dts
文件,继承主设备树的内容,只写你想“覆盖/新增”的部分。 - 使用设备树编译器(
dtc
)编译成.dtbo
文件。
- 编写
-
加载Overlay
- 在启动时加载(很多板子在bootloader配置文件中支持Overlay加载,比如
config.txt
里的dtoverlay=
) - 运行时加载(通过
configfs
接口,动态插入Overlay)
- 在启动时加载(很多板子在bootloader配置文件中支持Overlay加载,比如
举例(树莓派):
# /boot/config.txt
dtoverlay=your-overlay-name
5. Overlay 和 Initramfs、systemd 对比
特性 | 设备树 Overlay | initramfs | systemd |
---|---|---|---|
作用 | 动态修改硬件描述,加载新硬件节点 | 早期用户空间临时根文件系统加载 | Linux启动初始化系统及服务管理 |
关注层面 | 硬件抽象层 | 系统启动早期阶段用户空间环境 | 系统服务管理及并发启动 |
运行时机 | 启动阶段或运行时内核设备树修改 | 内核启动时挂载,用于系统启动 | 内核启动后启动管理服务 |
主要功能 | 硬件描述扩展、配置引脚、设备绑定 | 提供最初的文件系统环境 | 启动服务、管理依赖、日志、资源等 |
适用场景 | 硬件变动、模块化硬件设计 | 内核启动时根文件系统不可用场景 | 系统服务启动和管理 |
是否直接涉及硬件 | 是 | 否 | 否 |
动态修改硬件描述能力 | 支持,动态加载/卸载Overlay | 不支持 | 不支持 |
6. 简单总结
- 设备树 Overlay:专注于硬件描述和配置,特别适合动态加载硬件节点。
- initramfs:内核启动的第一个用户空间,负责挂载根文件系统和初始化环境。
- systemd:现代Linux系统的初始化和服务管理器,负责启动和维护系统服务。
7. 你实际使用设备树Overlay时要注意
- 设备树版本兼容性
- Overlay文件的正确编译
- 确保Overlay描述的硬件节点匹配实际硬件
- 确保bootloader或内核支持Overlay机制
- 运行时加载Overlay需内核开启
CONFIG_OF_OVERLAY
支持
很好,你正在使用的是 NanoPi NEO Core 这类基于 Allwinner H3 SoC 的板子,跑的是 Armbian,这类系统通常是 Debian 或 Ubuntu 的轻量定制版本,广泛用于嵌入式设备和 DIY 项目。
✅ 背景确认
你的平台:
- 硬件:NanoPi NEO Core(基于 Allwinner H3)
- 系统:Armbian(可能基于 Debian/Ubuntu)
- 用途:可能用于音频服务(你提到 Navidrome)或其他嵌入式用途
- 需求:动态控制硬件(如 I2C、SPI、UART、GPIO、LED、外接模块等)
📌 在 Armbian 上使用设备树 Overlay(DTO)
Armbian 提供 标准化支持 DTO 的机制,适用于大多数基于 mainline kernel 的 Allwinner 平台。
📘 Armbian Overlay 使用方法
1. 查看当前启用的 Overlays
armbian-config
进入 → System
→ Hardware
→ 你可以通过界面启用常见的设备树 Overlays(I2C, SPI, UART, PWM, etc)
或手动查看:
cat /boot/armbianEnv.txt
你可能会看到类似:
overlays=spi-spidev i2c0 uart1
param_spidev_spi_bus=0
2. 启用已有 Overlay
编辑 /boot/armbianEnv.txt
:
sudo nano /boot/armbianEnv.txt
添加或修改:
overlays=spi-spidev i2c0 uart1
保存后重启:
sudo reboot
你可以用 dmesg | grep spi
或 ls /dev/spidev*
来确认是否生效。
3. Overlay 文件存放位置
Armbian 默认加载 /boot/dtb/overlay/
中的 .dtbo
文件。你也可以:
ls /boot/dtb/allwinner/overlay/
4. 自定义 Overlay 示例
举个例子:你要启用一个额外的 LED 灯连接到 GPIO PH7
创建文件:my-led-overlay.dts
/dts-v1/;
/plugin/;&{/} {fragment@0 {target-path = "/";__overlay__ {my_led: gpio-leds {compatible = "gpio-leds";status_led {label = "status_led";gpios = <&pio 7 7 GPIO_ACTIVE_HIGH>; // PH7 = port 7, pin 7default-state = "on";};};};};
};
然后编译:
dtc -@ -I dts -O dtb -o my-led-overlay.dtbo my-led-overlay.dts
拷贝到 overlay 文件夹:
sudo cp my-led-overlay.dtbo /boot/dtb/allwinner/overlay/
修改 /boot/armbianEnv.txt
添加:
user_overlays=my-led-overlay
保存并重启:
sudo reboot
🧠 Overlay 与 systemd/initramfs 区别(再次归纳)
项目 | 设备树 Overlay (DTO) | initramfs | systemd |
---|---|---|---|
含义 | 动态叠加修改设备树(硬件配置) | 临时根文件系统(启动用) | 启动初始化系统管理器 |
使用时机 | 启动时或运行时加载 | 启动时使用 | 内核启动后立即运行 |
作用层面 | 硬件层(GPIO/I2C/UART等) | 用户空间临时运行环境 | 管理服务、守护进程、挂载等 |
是否与硬件直接相关 | ✅ 是 | ❌ 否 | ❌ 否 |
🧪 常用场景举例(在你设备上)
目标 | 使用方式 |
---|---|
启用 SPI | overlays=spi-spidev |
启用 UART | overlays=uart1 uart2 |
自定义 LED GPIO | 自写 DTO + user_overlays |
音频模块支持(I2S DAC) | DTO + 手动加载驱动 |
外接 OLED 屏(I2C) | overlays=i2c0 + dto 添加地址和驱动绑定 |
🔧 小贴士
- 用
dtc -I fs /proc/device-tree
可以查看当前设备树的实际内容 - 确保
CONFIG_OF_OVERLAY=y
在内核中开启(Armbian默认是开的) - 你可以使用
fdtput
工具直接修改.dtb
文件(高级用法)
如果你遇到的问题是:
- DTO加载无效 ➜ 看
/boot/armbianEnv.txt
里拼写是否正确 - 驱动不生效 ➜ 用
dmesg
、lsmod
、modinfo
等调试 - 不知道引脚编号 ➜ 查找对应的 sunxi pin map(Allwinner特有)
你想在 NanoPi NEO Core(Armbian) 上通过 I2C 接 OLED 显示屏(SSD1306 控制器),下面是完整详解,包括:
- ✅ 硬件连接
- ✅ 内核模块/驱动配置
- ✅ 设备树 Overlay 编写与使用
- ✅ 软件层显示内容(Python 示例)
✅ 1. 硬件接线(基于 I2C)
OLED Label | NanoPi NEO Pin | Notes |
---|---|---|
VCC | 3.3V / 5V | OLED 供电 |
GND | GND | 地线 |
SDA | I2C0_SDA (Pin 3, GPIO PA11) | 数据线 |
SCL | I2C0_SCL (Pin 5, GPIO PA12) | 时钟线 |
确认你接在了 I2C0(默认引出)的 SDA/SCL 上。
✅ 2. 启用 I2C0 接口(Armbian Overlay)
编辑 /boot/armbianEnv.txt
:
overlays=i2c0
保存后重启:
sudo reboot
确认 /dev/i2c-0
是否出现:
ls /dev/i2c*
还可以确认 OLED 是否连上(地址通常为 0x3c
):
sudo apt install -y i2c-tools
sudo i2cdetect -y 0
看到类似:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- 3c
...
✅ 3. 添加设备树 Overlay(挂载 SSD1306)
Armbian 自带的 overlay 目录通常 没有直接包含 SSD1306 的 overlay。所以我们需要自定义:
✏️ 创建文件 ssd1306-i2c.dts
/dts-v1/;
/plugin/;/ {compatible = "allwinner,sun8i-h3";fragment@0 {target-path = "/";__overlay__ {i2c@01c2ac00 {status = "okay";ssd1306: ssd1306@3c {compatible = "solomon,ssd1306fb-i2c";reg = <0x3c>;pinctrl-names = "default";reset-gpios = <0>; /* if no reset pin */width = <128>;height = <64>;page-offset = <0>;seg-remap;com-invdir;contrast = <127>;};};};};
};
说明:
- 如果你接的是 128x32 OLED,请将
height = <32>
。- 若有
reset
引脚可设置为<&pio X Y GPIO_ACTIVE_LOW>
,比如<&pio 0 6 0>
是 PA6。
🛠️ 编译 & 安装
dtc -@ -I dts -O dtb -o ssd1306-i2c.dtbo ssd1306-i2c.dts
sudo cp ssd1306-i2c.dtbo /boot/dtb/allwinner/overlay/
修改 /boot/armbianEnv.txt
添加:
user_overlays=ssd1306-i2c
然后重启:
sudo reboot
✅ 4. 驱动检查
确认驱动模块是否加载:
lsmod | grep ssd1306
如果没有:
sudo modprobe ssd1306
也可以添加到 /etc/modules
里让其开机加载:
echo ssd1306 | sudo tee -a /etc/modules
✅ 5. 显示内容(Python)
安装 Python 库
sudo apt install python3-pip
pip3 install luma.oled
示例 Python 脚本
from luma.core.interface.serial import i2c
from luma.oled.device import ssd1306
from PIL import ImageDraw, ImageFont, Imageserial = i2c(port=0, address=0x3C)
device = ssd1306(serial)draw = ImageDraw.Draw(Image.new("1", device.size))
font = ImageFont.load_default()
draw.text((0, 0), "Hello NanoPi!", font=font, fill=255)device.display(draw.im)
✅ 6. 常见问题排查
问题 | 原因 |
---|---|
/dev/i2c-0 不存在 | overlay 没启用 i2c0 |
i2cdetect 无响应或空 | 线路错误 / 接线问题 |
OLED 无反应 | 地址错误(0x3c vs 0x3d),接错线,驱动未加载 |
无法加载驱动 | 没有开启内核模块 CONFIG_FB_SSD1306 |
Python 报错 | 未装 luma.oled 或设备没有正确挂载 |
✅ 小结
你只需要:
- 确保 I2C 通了 →
overlays=i2c0
- 加载自定义 Overlay →
ssd1306@3c
- 加载驱动 →
ssd1306fb
(内核驱动或 Python 用户空间库) - 选定显示方式 → 控制台 framebuffer 或 Python 图形库
很好!你想通过 framebuffer 控制 SSD1306
OLED(I2C 接口),这样你可以直接使用命令行工具、C 程序或 Shell 脚本将图像、文字显示到屏幕上,而不需要 Python 库。
🧩 目标实现:
让 SSD1306 显示屏出现在 /dev/fb1
,通过 cat
、fbi
、dd
等 framebuffer 工具控制它显示内容。
✅ 1. 检查内核模块
确保内核已包含以下模块(Armbian 通常自带):
lsmod | grep ssd1306
如果没有,手动加载:
sudo modprobe ssd1306
sudo modprobe ssd1306fb
⚠️ 如果提示模块不存在,可能你需要启用模块支持并重新编译内核(我可指导你做)。
✅ 2. 设备树 Overlay(带 framebuffer 绑定)
使用下面的 .dts
文件创建 overlay,挂载 SSD1306,并启用 framebuffer 输出。
✏️ 文件:ssd1306-fb-i2c.dts
/dts-v1/;
/plugin/;/ {compatible = "allwinner,sun8i-h3";fragment@0 {target = <&i2c0>;__overlay__ {status = "okay";ssd1306fb: ssd1306fb@3c {compatible = "solomon,ssd1306fb-i2c";reg = <0x3c>;width = <128>;height = <64>;page-offset = <0>;seg-remap;com-invdir;contrast = <127>;rotate = <0>;};};};
};
✅ 3. 编译并启用 overlay
dtc -@ -I dts -O dtb -o ssd1306-fb-i2c.dtbo ssd1306-fb-i2c.dts
sudo cp ssd1306-fb-i2c.dtbo /boot/dtb/overlay/
编辑 /boot/armbianEnv.txt
,添加:
user_overlays=ssd1306-fb-i2c
保存后重启:
sudo reboot
✅ 4. 验证 framebuffer
重启后检查:
ls /dev/fb*
你应该看到 /dev/fb1
(主 framebuffer 通常是 fb0)
确认信息:
cat /sys/class/graphics/fb1/name
应返回:ssd1306fb
查看分辨率:
cat /sys/class/graphics/fb1/virtual_size
✅ 5. 使用 framebuffer 工具显示内容
🖼️ 显示图片(需安装 fbi 工具)
sudo apt install fbi
sudo fbi -d /dev/fb1 -T 1 --noverbose -a your-image.png
注意:
- 图片必须为 单色位图(1bit bmp/png),大小 128×64。
- 你也可以用
imagemagick
转图:
convert input.png -resize 128x64\! -monochrome out.bmp
🧪 输出简单图像(命令行写 framebuffer)
sudo bash -c 'dd if=/dev/zero of=/dev/fb1 bs=1024 count=1'
这会清空屏幕。
你可以写一个程序像素级控制 /dev/fb1
,例如写字符、画图。
✅ 6. 示例:C 程序写 framebuffer
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main() {int fb = open("/dev/fb1", O_RDWR);if (fb < 0) return 1;// 简单填充图案char buffer[1024]; // 128 x 64 = 1024 bits = 128 bytes x 8 linesmemset(buffer, 0xAA, sizeof(buffer)); // 交错黑白条纹write(fb, buffer, sizeof(buffer));close(fb);return 0;
}
✅ 小结
步骤 | 动作 |
---|---|
1️⃣ | 确保 OLED 连到 I2C(地址 0x3c) |
2️⃣ | 使用 ssd1306fb-i2c overlay |
3️⃣ | 检查 /dev/fb1 是否出现 |
4️⃣ | 用 fbi 、dd 、程序操作 framebuffer 显示内容 |