玩转树莓派屏幕之二:自定义屏幕显示
- 树莓派
- 8天前
- 57热度
- 0评论
一、树莓派屏幕显示原理
屏幕驱动
安装执行MHS35-show完成之后,会加载屏幕驱动,使用命令
ll /dev/fb*
将会展示:
crw-rw---- 1 root video 29, 0 Oct 5 00:17 /dev/fb0
crw-rw---- 1 root video 29, 1 Oct 5 01:22 /dev/fb1
其中,fb0为默认HDMI输出,fb1为安装的屏幕,使用fbset命令查看缓存内容。
root@raspberrypi:/home/longtao/LCD-show/usr/fbcp-ili9341# fbset -fb /dev/fb0
mode "480x320"
geometry 480 320 480 320 16
timings 0 0 0 0 0 0 0
nonstd 1
rgba 5/11,6/5,5/0,0/0
endmode
root@raspberrypi:/home/longtao/LCD-show/usr/fbcp-ili9341#
root@raspberrypi:/home/longtao/LCD-show/usr/fbcp-ili9341# fbset -fb /dev/fb1
mode "480x320"
geometry 480 320 480 320 16
timings 0 0 0 0 0 0 0
nonstd 1
rgba 5/11,6/5,5/0,0/0
endmode
因为MHS35-show命令中会执行fbcp命令,将/dev/fb0(HDMI屏幕)中的内容映射到/dev/fb1(3.5寸屏幕),所以显示是一致内容。否则fb0将会显示以下内容
root@raspberrypi:# fbset -fb /dev/fb0
mode "640x480"
geometry 640 480 640 480 32
timings 0 0 0 0 0 0 0
rgba 8/16,8/8,8/0,8/24
endmode
MHS35-show 修改内容
1、/boot/firmware/config.txt文件:
在文件最后新增以下内容:
.....
[all]
hdmi_force_hotplug=1
---- 新增以下内容---
dtparam=i2c_arm=on
dtparam=spi=on
enable_uart=1
dtoverlay=mhs35:rotate=90
hdmi_group=2
hdmi_mode=1
hdmi_mode=87
hdmi_cvt 480 320 60 6 0 0 0
hdmi_drive=2
这些配置的作用为:
# 启用 I2C(用于传感器等)
dtparam=i2c_arm=on
# 启用 SPI(用于 MHS35 屏幕)
dtparam=spi=on
# 启用 UART 串口
enable_uart=1
# 加载 MHS35 屏幕驱动,横屏显示
dtoverlay=mhs35:rotate=90
# HDMI 设置:使用自定义分辨率
hdmi_group=2 # CEA 组(电视)
hdmi_mode=87 # 自定义模式
# hdmi_cvt <width> <height> <fps> <aspect> <margins> <interlace> <rb>
hdmi_cvt=480 320 60 6 0 0 0 # 480x320 @ 60Hz
hdmi_drive=2 # HDMI 模式(支持音频)
2、/etc/rc.local
新增==fbcp &==, 用于映射/dev/fb0 --> /dev/fb1
root@raspberrypi:~# cat /etc/rc.local
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
printf "My IP address is %s\n" "$_IP"
fi
sleep 7
fbcp &
exit 0
3、 /boot/firmware/overlays/中
新增mhs35屏幕中的驱动
-rwxr-xr-x 1 root root 2.6K Oct 4 02:41 mhs35-overlay.dtb
-rwxr-xr-x 1 root root 2.6K Oct 4 02:41 mhs35.dtbo
二、 Linux LCD FrameBuff原理
Linux LCD Frambuffer 基础介绍和使用:https://blog.51cto.com/u_13064014/5079683
Linux应用开发【第一章】Framebuffer应用开发:https://zhuanlan.zhihu.com/p/443120506
Linux Framebuffer 技术:https://zhuanlan.zhihu.com/p/496623603
LCD显示原理:
具体细节可以看上面的介绍,其实简单来说,LCD显示的数据与内存中的数据有映射,通过该段这段内存中数据内容可以刷新屏幕。
Framebuffer 测试命令
为了方便测试 Framebuffer 可用,可以快速通过命令进行简单测试,如下所示:
-
清屏命令
dd if=/dev/zero of=/dev/fb0 dd if=/dev/zero of=/dev/fb0 bs=1024 count=768
-
截屏命令
dd if=/dev/fb0 of=fbfile cp /dev/fb0 fbfile
注意:这里的截屏其实就是拷贝 中的数据,所以只有当framebuffer中有数据存在时才能截屏成功
-
将保存的信息显示传回framebuffer
dd if=fbfile of=/dev/fb0
-
往屏幕的左上角画一个白色的像素点
echo -en '\xFF\xFF\xFF\x00' > /dev/fb0
-
花屏指令
cat /dev/urandom > /dev/fb0
三、显示代码
从上面已经知道,屏幕中显示的内容都是数据,可以通过C代码操作Framebuffer进行操作,有一定的上手难度。
其实换一个角度,屏幕中显示的内容都可以看成一帧图片,可以将显示的内容形成图片,然后将图片传入到/dev/fb0也是能够正常展示的。使用Python形成一帧图片还是比较简单的。
安装python3的依赖
sudo apt update
sudo apt install python3-pip fbi
pip3 install Pillow psutil --break-system-packages
--break-system-packages 表示破坏系统包,树莓派新版操作系统不运行直接pip安装包
编辑文件:
vim system_show.py
# system_monitor.py
from PIL import Image, ImageDraw, ImageFont
import os
import time
import psutil
import socket
import subprocess
# 屏幕尺寸
WIDTH, HEIGHT = 480, 320
def get_ip():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except:
return "N/A"
def get_cpu_usage():
return psutil.cpu_percent(interval=0.5)
def get_memory_usage():
mem = psutil.virtual_memory()
return mem.percent
def get_load():
load1, load5, load15 = os.getloadavg()
return f"{load1:.2f}"
def is_service_running(service_name):
try:
result = subprocess.run(['systemctl', 'is-active', service_name],
capture_output=True, text=True)
return result.stdout.strip() == 'active'
except:
return False
def get_file_count(file_dir):
try:
result = subprocess.run(f"ls -al {file_dir} | wc -l", shell=True, capture_output=True, text=True, check=True)
#print(result)
return str(int(result.stdout.strip()) - 1)
except Exception as e:
print(f"Error counting files in {file_dir}: {e}")
return 'NA'
def is_docker_container_running(container_name):
try:
result = subprocess.run(['docker', 'inspect', '-f', '{{.State.Running}}', container_name],
capture_output=True, text=True)
return 'true' in result.stdout.lower()
except:
return False
def is_mounted(path):
return os.path.ismount(path)
def get_disk_usage(path):
try:
usage = psutil.disk_usage(path)
percent = usage.percent
total_gb = usage.total / (1024**3)
used_gb = usage.used / (1024**3)
return f"{percent:.1f}% ({used_gb:.1f}G/{total_gb:.1f}G)"
except:
return "N/A"
def show_text(text_lines, fontsize=20):
img = Image.new('RGB', (WIDTH, HEIGHT), (0, 0, 0))
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", fontsize)
except:
font = ImageFont.load_default()
y_start = 10
line_height = fontsize + 6
for i, line in enumerate(text_lines):
draw.text((10, y_start + i * line_height), line, fill=(255, 255, 255), font=font)
# 保存临时图片
img.save("/tmp/system_monitor.png")
# 输出到 framebuffer
os.system("fbi -d /dev/fb1 -T 1 -noverbose -a /tmp/system_monitor.png > /dev/null 2>&1")
# === 主循环 ===
if __name__ == "__main__":
while True:
# 收集信息
ip = get_ip()
cpu = get_cpu_usage()
mem = get_memory_usage()
load = get_load()
omv_ok = "✓" if is_service_running("openmediavault-engined") else "✗"
openlist_ok = "✓" if is_docker_container_running("openlist") else "✗"
rclone_task_ok = "✓" if any("rclone" in p.name().lower() or "rsync" in p.name().lower()
for p in psutil.process_iter(['name'])) else "✗"
mount1 = "/srv/dev-disk-by-uuid-0987bf77-ebce-4022-afeb-fc9a56417e54"
mount2 = "/mnt/aliyun"
mount1_ok = "✓" if is_mounted(mount1) else "✗"
mount2_ok = "✓" if is_mounted(mount2) else "✗"
file_count1 = get_file_count("/srv/dev-disk-by-uuid-0987bf77-ebce-4022-afeb-fc9a56417e54/smb_xiaomi_vidoes/xxx")
file_count2 = get_file_count("/mnt/aliyun/xxx/")
usb_usage = get_disk_usage(mount1)
# 构建显示文本
lines = [
"=== System Monitor ===",
f"IP: {ip}",
f"CPU: {cpu:.1f}%",
f"Mem: {mem:.1f}%",
f"Load: {load}",
"",
"=== Services ===",
f"OMV: {omv_ok}",
f"OpenList: {openlist_ok}",
f"Rclone/Rsync: {rclone_task_ok}",
"",
"=== Mounts ===",
f"Data Disk: {mount1_ok} file_count:{file_count1}",
f"AliyunFS: {mount2_ok} file_count:{file_count2} ",
"",
"=== /srv/dev-disk-by-uuid-0987bf77-ebce-4022-afeb-fc9a56417e54 ===",
f"Usage: {usb_usage}"
]
# 显示
show_text(lines, fontsize=12)
# 每秒刷新一次
time.sleep(5)
执行命令:
python3 system_show.py
展示效果
屏幕展示效果:
/tmp/system_monitor.png
真机显示效果(有点模糊):