当前位置: 首页 > news >正文

玩转树莓派屏幕之二:自定义屏幕显示

一、树莓派屏幕显示原理

屏幕驱动

安装执行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显示原理:
image

具体细节可以看上面的介绍,其实简单来说,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
system_monitor

真机显示效果(有点模糊):
image

参考文章

  • https://worktile.com/kb/ask/320840.html
  • https://www.ebaina.com/articles/140000017739
  • https://www.cnblogs.com/jzcn/p/16898249.html
http://www.hskmm.com/?act=detail&tid=24596

相关文章:

  • INFINI Labs 产品更新 - Coco AI v0.8 与 Easysearch v1.15 全新功能上线,AI 搜索体验再进化!
  • 玩转树莓派屏幕之一:LCD屏幕显示
  • Python离群值检测实战:使用distfit库实现基于分布拟合的异常检测
  • 10.4 闲话
  • 神秘专题训练之老题补做
  • 全球 whk 水平下降 998244353 倍,而你不变
  • 202510做题记录
  • 全球 wkh 水平下降 998244353 倍,而你不变
  • python 基础问题汇总
  • 球球大作战
  • 全球 OI 水平下降 998244353 倍,而我不变
  • VulnHub-Raven2 靶场 wp
  • javaScript的构造函数和java的构造函数区别
  • 天一生水 地六成之
  • P14041 [PAIO 2025] Towers
  • 根号分治简单解说
  • 哈希简单解说
  • Say 题选记(9.28 - 10.4)
  • Excel表设置为细框线
  • 前端学习教程-VIte整合ECharts
  • const不可改变解释
  • macOS Sequoia 15.7.1安全更新:修复字体解析器内存损坏漏洞
  • AtCoder Beginner Contest 426 ABCDEF 题目解析
  • 数学
  • 前端学习教程-ElementPlus 教程
  • AI训练的悖论:为什么越追求准确率越会产生幻觉?
  • 信奥大联赛周赛(提高组)#2516-S 赛后盘点
  • PSRAM 是什么
  • Debian 13 eza 安装与常用参数
  • Syncthing 2.0 版本开机自启