一、树莓派屏幕显示原理
屏幕驱动
安装执行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 16timings 0 0 0 0 0 0 0nonstd 1rgba 5/11,6/5,5/0,0/0
endmoderoot@raspberrypi:/home/longtao/LCD-show/usr/fbcp-ili9341#
root@raspberrypi:/home/longtao/LCD-show/usr/fbcp-ili9341# fbset -fb /dev/fb1mode "480x320"geometry 480 320 480 320 16timings 0 0 0 0 0 0 0nonstd 1rgba 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" ]; thenprintf "My IP address is %s\n" "$_IP"
fisleep 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, 320def 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 ipexcept:return "N/A"def get_cpu_usage():return psutil.cpu_percent(interval=0.5)def get_memory_usage():mem = psutil.virtual_memory()return mem.percentdef 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 Falsedef 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 Falsedef is_mounted(path):return os.path.ismount(path)def get_disk_usage(path):try:usage = psutil.disk_usage(path)percent = usage.percenttotal_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 = 10line_height = fontsize + 6for 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")# 输出到 framebufferos.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
真机显示效果(有点模糊):
参考文章
- https://worktile.com/kb/ask/320840.html
- https://www.ebaina.com/articles/140000017739
- https://www.cnblogs.com/jzcn/p/16898249.html