1. 从一块屏幕到两块屏幕:这个掌机项目到底在折腾什么
第一次看到"双屏树莓派赛博掌机"这个概念的时候,我脑子里蹦出来的第一个念头是——这不就是把NDS和树莓派塞进同一个壳子里吗?但真正动手做下来才发现,事情远没有想象中那么简单。双屏带来的不只是多一块显示面板,而是整套交互逻辑、供电方案、散热路径和结构设计的全面重构。
这个项目的核心目标很明确:用树莓派作为主控,驱动两块独立显示屏,做成一台可以握在手里的双屏掌上设备。它解决的问题其实挺实际的——单屏掌机在运行某些模拟器或者多任务场景时,屏幕空间永远不够用。比如你想一边看攻略一边玩游戏,或者一边监控系统状态一边跑主程序,单屏就得来回切换,体验很割裂。双屏方案直接把这个问题从根上解决了。
适合谁来参考这个项目?我觉得有三类人。第一类是已经玩过单屏树莓派掌机、想进一步折腾的硬核玩家;第二类是对嵌入式Linux和显示驱动有一定了解、想找个综合项目练手的开发者;第三类是做交互设备原型的设计师,想看看双屏在小型手持设备上到底能玩出什么花样。如果你连树莓派都没点亮过,建议先拿单屏方案练练手再回来,不然这个项目的复杂度会让你在第一步就卡住。
我前后折腾了大概三周时间,中间翻车无数次,从屏幕不亮到系统崩溃到电池鼓包预警,基本上能踩的坑都踩了一遍。下面我把整个过程中的关键决策、技术细节和血泪教训全部摊开来讲,希望能帮你少走点弯路。
2. 双屏方案的核心架构:为什么这样选而不是那样选
2.1 主控选型的纠结:从Zero到4B再到CM4
树莓派家族里能选的型号不少,但真正适合双屏掌机的其实就那么几个。我一开始想用Zero 2 W,理由是功耗低、体积小,塞进掌机壳子里不占地方。但实际测试下来发现,Zero 2 W驱动两块屏幕的时候,GPU负载直接拉满,帧率掉得厉害,尤其是跑RetroPie的时候,主界面都卡顿。
后来换到4B,性能是够了,但功耗和发热又成了新问题。4B满载的时候功耗能到6W以上,加上两块屏幕的功耗,整机续航直接崩到一小时以内。而且4B的尺寸对于掌机来说还是偏大,布局起来很局促。
最终我选了CM4(Compute Module 4),搭配一块自定义的载板。CM4的好处是核心板可以单独更换,载板可以根据双屏需求定制接口布局。我用的是一块带双HDMI输出的载板,虽然CM4原生只支持一个HDMI加一个DSI,但通过载板上的转换芯片,可以把DSI转成HDMI,实现双HDMI输出。这个方案的好处是两块屏幕可以用同一套驱动逻辑,不用分别处理DSI和HDMI的差异。
注意:CM4的DSI转HDMI需要额外的桥接芯片,常见的有TC358743和TC358870,前者支持1080p输入,后者支持4K。我用的TC358870,实测下来延迟在可接受范围内,但配置起来比较麻烦,需要单独写设备树覆盖文件。
2.2 屏幕组合的取舍:尺寸、分辨率与接口的三角博弈
双屏掌机的屏幕选择是个典型的三角博弈——尺寸要合适、分辨率要够用、接口要能接上。我试过三种组合:
| 组合方案 | 上屏 | 下屏 | 优点 | 缺点 |
|---|---|---|---|---|
| 方案A | 5寸HDMI 800x480 | 3.5寸SPI 480x320 | 成本低,接线简单 | 下屏刷新率低,拖影严重 |
| 方案B | 5寸HDMI 800x480 | 5寸HDMI 800x480 | 显示效果一致,驱动统一 | 体积大,功耗高 |
| 方案C | 4寸DSI 800x480 | 3.5寸HDMI 480x320 | 体积紧凑,功耗可控 | DSI配置复杂,需要额外调试 |
最终我选了方案C的变体:上屏用4寸DSI屏,下屏用3.5寸HDMI屏。为什么这么选?因为上屏作为主显示区域,需要更好的色彩和响应速度,DSI接口在这方面有天然优势;下屏主要用来显示辅助信息或者触控操作,对刷新率要求没那么高,HDMI接口的3.5寸屏性价比很高。
这里有个细节要注意:DSI屏的初始化序列和HDMI完全不同,需要在config.txt里单独配置。我用的这块4寸DSI屏需要设置dtoverlay=vc4-kms-dsi-7inch,但实际屏幕尺寸是4寸,所以还得改display_auto_detect=0,手动指定分辨率。这个过程我折腾了整整一个下午,因为屏幕厂商给的文档和树莓派官方文档有出入,最后是抓取内核日志才定位到问题。
2.3 供电架构的设计:双屏带来的额外功耗怎么扛
双屏方案最大的坑之一就是供电。单屏掌机可能5000mAh电池就能跑三四个小时,但双屏加上CM4,功耗直接翻倍。我实测下来,整机满载功耗在8W左右,其中CM4占4W,两块屏幕加起来占3W,剩下的是载板和其他外设。
电池我选的是两块18650并联,总容量6800mAh,标称电压3.7V。通过一个升压模块升到5V给系统供电。这里有个关键点:升压模块的效率直接影响续航。我一开始用的是一个便宜的MT3608模块,效率只有80%左右,后来换成了TPS61088,效率提升到93%,续航直接多了将近一个小时。
提示:双屏掌机的供电设计一定要留足余量。我建议按峰值功耗的1.5倍来选升压模块和电池保护板,不然屏幕亮起的瞬间电流冲击可能会导致系统重启。
另外,两块屏幕的背光供电最好独立控制。我在载板上加了一个MOS管开关电路,可以通过GPIO单独控制每块屏幕的背光。这样在只用一块屏幕的时候,可以把另一块的背光关掉,省电效果很明显。实测下来,关掉一块屏幕的背光,整机功耗能降低1.2W左右。
3. 系统层面的双屏配置:从设备树到显示管理
3.1 config.txt里的双屏参数怎么写才不打架
树莓派的config.txt是双屏配置的第一道关卡。很多人以为只要把两个屏幕的overlay都加上就行了,但实际上HDMI和DSI的配置会互相干扰。我一开始的配置是这样的:
dtoverlay=vc4-kms-v3d dtoverlay=vc4-kms-dsi-7inch hdmi_force_hotplug=1 hdmi_group=2 hdmi_mode=87 hdmi_timings=800 0 40 48 88 480 0 13 3 32 0 0 0 60 0 32000000 6结果开机后只有HDMI屏亮了,DSI屏完全没反应。查了半天才发现,vc4-kms-v3d这个overlay会接管所有显示输出,DSI的overlay必须放在它后面,而且需要额外的参数来指定DSI通道。
正确的配置应该是这样的:
dtoverlay=vc4-kms-v3d dtoverlay=vc4-kms-dsi-7inch,dsi0 hdmi_force_hotplug=1 hdmi_group=2 hdmi_mode=87 hdmi_timings=800 0 40 48 88 480 0 13 3 32 0 0 0 60 0 32000000 6关键在dsi0这个参数,它告诉系统DSI屏接在第一个DSI通道上。如果你的载板用的是第二个DSI通道,就得改成dsi1。这个细节在树莓派官方文档里写得很隐晦,我是翻了内核源码才确认的。
3.2 用xrandr做双屏布局的实操细节
系统启动后,两块屏幕默认会显示相同的内容(镜像模式)。要让它们显示不同的内容,需要用xrandr来配置。首先用xrandr --query查看当前的显示输出:
Screen 0: minimum 320 x 200, current 800 x 960, maximum 8192 x 8192 HDMI-1 connected 800x480+0+0 (normal left inverted right x axis y axis) 0mm x 0mm DSI-1 connected 800x480+0+480 (normal left inverted right x axis y axis) 0mm x 0mm可以看到HDMI-1和DSI-1都被识别到了。接下来设置布局:
xrandr --output HDMI-1 --mode 800x480 --pos 0x0 --rotate normal xrandr --output DSI-1 --mode 800x480 --pos 0x480 --rotate normal这样HDMI屏在上方,DSI屏在下方,形成一个800x960的虚拟桌面。鼠标可以从上屏滑到下屏,窗口也可以跨屏拖动。
但这里有个问题:大部分桌面环境(比如LXDE)默认会把任务栏放在整个虚拟桌面的底部,也就是下屏的最下面。如果你想让任务栏只出现在上屏,需要单独配置面板的位置。我用的LXPanel,在配置文件里把position改成top,然后设置width为800,这样任务栏就只占上屏的宽度了。
注意:xrandr的配置在重启后会丢失,需要把命令写到
~/.config/autostart或者/etc/xdg/autostart里,做成开机自启脚本。我建议写一个简单的shell脚本,加上sleep 5等待显示服务完全启动后再执行。
3.3 双屏下的触控映射怎么调
下屏我选的是带触控的3.5寸HDMI屏,触控接口是USB。系统识别到触控设备后,默认会把触控坐标映射到整个虚拟桌面,也就是说你在下屏上触摸,鼠标可能会跑到上屏去。这个问题需要通过xinput来修正。
首先用xinput list找到触控设备的ID:
⎡ Virtual core pointer id=2 [master pointer (3)] ⎜ ↳ Virtual core XTEST pointer id=4 [slave pointer (2)] ⎜ ↳ ADS7846 Touchscreen id=6 [slave pointer (2)]然后设置坐标变换矩阵,把触控范围限制在下屏区域:
xinput set-prop 6 "Coordinate Transformation Matrix" 1 0 0 0 1 0 0 0 1但这只是重置了默认矩阵,要真正限制到下屏,需要计算变换矩阵。下屏在虚拟桌面中的位置是(0, 480),尺寸是800x480,整个桌面是800x960。变换矩阵的计算公式是:
a = 屏幕宽度 / 桌面宽度 = 800 / 800 = 1 b = 0 c = 屏幕X偏移 / 桌面宽度 = 0 / 800 = 0 d = 0 e = 屏幕高度 / 桌面高度 = 480 / 960 = 0.5 f = 屏幕Y偏移 / 桌面高度 = 480 / 960 = 0.5所以矩阵是:
xinput set-prop 6 "Coordinate Transformation Matrix" 1 0 0 0 0.5 0.5 0 0 1设置完之后,下屏的触控就只在下屏范围内有效了。这个计算过程看起来简单,但实际调试的时候我试了好几次才把偏移量调准,因为屏幕边框和触控区域有细微的偏差,最后是通过在四个角画标记点、反复微调才搞定的。
4. 结构设计与散热:双屏掌机的物理难题
4.1 外壳建模:从纸板原型到3D打印
双屏掌机的结构设计比单屏复杂得多,因为你要同时考虑两块屏幕的固定、排线的走向、电池的放置和散热风道。我一开始用纸板做了个粗略的原型,确认了基本布局之后才用Fusion 360建模。
外壳分三部分:前面板、中框和后盖。前面板负责固定两块屏幕,中框用来放载板和电池,后盖负责保护背面和走线。前面板的设计有个关键点:两块屏幕之间的间距要控制好,太宽了显得笨重,太窄了排线会打架。我最后定的间距是8mm,刚好能容纳DSI排线和HDMI排线并排走。
3D打印我用的是PETG材料,比PLA更耐热,因为掌机内部温度会比较高。打印参数方面,层高0.2mm,填充率20%,壁厚1.2mm。前面板因为要固定屏幕,填充率提到了40%,不然螺丝孔容易滑丝。
提示:打印前面板的时候一定要加支撑,尤其是屏幕开孔的位置。我第一版没加支撑,结果开孔边缘塌陷,屏幕装进去有缝隙。第二版加了树形支撑,效果好很多。
4.2 散热路径:双屏带来的热量怎么排出去
双屏掌机的散热是个容易被忽视的问题。两块屏幕的背光模组都会发热,加上CM4本身的发热,整机内部温度能到50度以上。我一开始没做散热设计,玩了半小时游戏后屏幕开始出现闪烁,拆开一看,DSI排线的接头处已经烫得摸不得了。
后来我在中框上开了两组散热孔,一组在顶部,一组在底部,形成自然对流。同时在CM4上加了一块20x20mm的铝制散热片,用导热胶粘在核心芯片上。屏幕背光模组的背面也贴了石墨散热片,把热量导到中框上。
实测下来,加了散热措施之后,连续游戏一小时,内部温度稳定在42度左右,屏幕不再闪烁,排线接头也只是温热。如果你打算做类似的项目,散热设计一定要提前考虑,不要等出了问题再补救。
4.3 排线管理:双屏走线的避坑经验
双屏掌机内部要走三组排线:DSI排线、HDMI排线和触控USB排线。这三组排线如果走不好,轻则影响信号质量,重则排线折断。我在这上面踩了不少坑。
DSI排线最娇贵,因为它对信号完整性要求高,走线不能有急弯,也不能和电源线靠得太近。我一开始把DSI排线和电池线捆在一起,结果屏幕出现随机噪点。后来把DSI排线单独走一边,远离电源线,噪点就消失了。
HDMI排线相对皮实,但也要注意不要过度弯折。我用的HDMI排线是FPC材质,最小弯曲半径是5mm,超过这个角度就可能断裂。在布局的时候,我特意留了足够的空间让排线自然弯曲,没有强行折成直角。
触控USB排线最粗,但信号要求最低,走线可以随意一些。不过要注意USB排线的屏蔽层要接地,不然触控可能会有漂移。
5. 软件层的双屏交互:让两块屏幕真正协同工作
5.1 用Python写一个双屏状态监控面板
双屏最大的价值在于可以同时显示不同的信息。我写了一个简单的Python脚本,在上屏显示游戏画面,下屏显示系统状态(CPU温度、内存占用、电池电量、网络状态)。这样玩游戏的时候不用切出去就能看到系统状态。
脚本的核心是使用pygame在下屏创建一个独立的窗口,然后通过psutil库读取系统信息。关键代码如下:
import pygame import psutil import os os.environ["DISPLAY"] = ":0" pygame.init() # 下屏的坐标偏移 screen = pygame.display.set_mode((800, 480), pygame.NOFRAME) pygame.display.set_caption("Status Panel") font = pygame.font.Font(None, 36) running = True while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False screen.fill((0, 0, 0)) cpu_temp = os.popen("vcgencmd measure_temp").readline().strip() cpu_usage = psutil.cpu_percent() mem = psutil.virtual_memory() text1 = font.render(f"CPU Temp: {cpu_temp}", True, (0, 255, 0)) text2 = font.render(f"CPU Usage: {cpu_usage}%", True, (0, 255, 0)) text3 = font.render(f"Memory: {mem.percent}%", True, (0, 255, 0)) screen.blit(text1, (20, 20)) screen.blit(text2, (20, 80)) screen.blit(text3, (20, 140)) pygame.display.flip() pygame.time.wait(1000) pygame.quit()这个脚本需要设置DISPLAY环境变量,并且要在xrandr配置好双屏布局之后再运行。我把它做成了systemd服务,开机自动启动。
5.2 双屏下的模拟器配置:让游戏画面和攻略同时显示
双屏掌机最爽的用法之一就是一边玩游戏一边看攻略。我用的RetroArch支持多窗口输出,可以把游戏画面输出到上屏,然后在下屏用浏览器打开攻略页面。
RetroArch的配置需要在retroarch.cfg里设置video_windowed_position_x和video_windowed_position_y,把游戏窗口定位到上屏。然后在下屏用chromium-browser打开攻略页面,设置--window-position=0,480 --window-size=800,480。
但这里有个问题:RetroArch默认会独占音频设备,导致浏览器没有声音。解决办法是在RetroArch的音频设置里把audio_driver改成alsa,然后设置audio_device为hw:0,0,这样浏览器和RetroArch可以共享音频设备。
注意:双屏同时运行两个图形程序对GPU压力很大,建议把RetroArch的视频驱动改成
gl,并且关闭不必要的特效。我实测下来,用gl驱动比dispmanx驱动帧率稳定很多。
5.3 双屏的电源管理:怎么让两块屏幕按需开关
双屏掌机不一定时刻都需要两块屏幕都亮着。比如你在玩全屏游戏的时候,下屏可能只需要显示状态信息,甚至完全可以关掉。我写了一个简单的GPIO控制脚本,可以通过按键来切换下屏的背光。
树莓派的GPIO控制很简单,用RPi.GPIO库就行:
import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) def toggle_backlight(state): GPIO.output(18, state) # 开背光 toggle_backlight(GPIO.HIGH) time.sleep(1) # 关背光 toggle_backlight(GPIO.LOW) GPIO.cleanup()我把这个脚本绑定到了一个物理按键上,按一下切换下屏背光。实测下来,关掉下屏背光能省大约1.2W的功耗,续航能多出将近40分钟。
6. 实测中的翻车记录与修复过程
6.1 屏幕闪烁问题:从电源纹波到排线屏蔽
项目做到一半的时候,上屏开始出现随机闪烁,尤其是在电池电量低于30%的时候特别明显。我一开始以为是屏幕坏了,换了一块新的还是一样。后来用示波器测了一下升压模块的输出,发现纹波高达200mV,远超屏幕要求的50mV以内。
解决办法是在升压模块的输出端并联两个470uF的电解电容和一个100nF的陶瓷电容,把纹波压到了30mV左右。同时把DSI排线换成了带屏蔽层的版本,闪烁问题彻底解决。
这个坑让我意识到,双屏掌机的电源质量比单屏要求高得多,因为两块屏幕对电压波动都很敏感。如果你也遇到类似问题,优先检查电源纹波,而不是怀疑屏幕本身。
6.2 系统随机重启:电池保护板的过流保护太敏感
另一个让我头疼的问题是系统会随机重启,有时候是刚开机就重启,有时候是运行中突然重启。查了内核日志,发现没有任何异常记录,说明是硬件层面的断电。
最后定位到是电池保护板的过流保护阈值设得太低。我用的保护板标称过流保护是3A,但CM4加上双屏的峰值电流能到2.5A,再加上升压模块的转换损耗,实际电流可能超过3A,触发保护板断电。
换了一块过流保护5A的保护板之后,问题解决。这里提醒一下,双屏掌机的电流需求比单屏大很多,选保护板的时候一定要留足余量,建议按峰值电流的2倍来选。
6.3 触控漂移:USB排线屏蔽层没接地
下屏的触控用了一段时间后开始出现漂移,表现为触摸位置和实际光标位置有偏差,而且偏差越来越大。一开始以为是触控校准的问题,重新校准了好几次都没用。
后来用万用表测了一下USB排线的屏蔽层,发现和GND之间没有导通。原来是我在焊接的时候只焊了信号线,屏蔽层悬空了。把屏蔽层焊到GND之后,触控漂移问题消失。
这个坑比较隐蔽,因为触控在刚开机的时候是正常的,要运行一段时间后才会出现漂移。如果你也遇到类似问题,先检查USB排线的屏蔽层是否接地。
7. 这个双屏掌机还能怎么玩
7.1 把下屏做成可编程宏按键面板
下屏是触控的,这意味着它可以变成一块可编程的宏按键面板。我写了一个简单的Python脚本,在下屏上绘制几个按钮,点击后发送对应的键盘按键事件。这样在玩模拟器游戏的时候,可以把一些复杂的组合键映射到下屏上,一键触发。
实现思路是用pygame绘制按钮,然后用evdev库模拟键盘输入。关键是要把下屏的触控事件和按钮区域做碰撞检测,这个用pygame.Rect的collidepoint方法就能搞定。
7.2 双屏异显:上屏跑主系统,下屏跑独立Linux
CM4的性能足够同时跑两个独立的Linux环境。我试过在上屏跑Raspberry Pi OS桌面,下屏用systemd-nspawn跑一个轻量级的Alpine Linux,专门用来做网络监控和日志分析。两块屏幕通过共享内存或者网络套接字通信。
这个玩法比较进阶,需要对Linux容器技术有一定了解。但一旦跑通,双屏掌机就变成了一台真正的双系统设备,可玩性大大提升。
7.3 用下屏做实时视频监控
如果你有USB摄像头,可以把下屏变成实时视频监控窗口。用fswebcam或者motion抓取摄像头画面,然后在下屏显示。我试过用motion把摄像头画面输出到下屏,延迟大概在200ms左右,用来做简单的监控足够了。
这个功能的实用场景是:你可以在上屏玩游戏或者工作,下屏同时监控摄像头画面,不用切窗口就能看到门口或者婴儿房的情况。
8. 一些过来人的经验之谈
做这个双屏掌机的过程中,我最大的体会是:双屏不是简单的1+1,而是引入了全新的复杂度维度。电源、散热、排线、驱动、交互,每一个环节都比单屏方案要复杂得多。如果你打算动手做类似的项目,我有几个建议。
第一,先做单屏版本练手。单屏掌机的所有技术点——电源管理、屏幕驱动、外壳设计——双屏方案都会用到,而且双屏是在这些基础之上做加法。先把单屏跑通,再考虑双屏,能省下大量调试时间。
第二,电源设计要一步到位。我一开始为了省钱用了便宜的升压模块和保护板,结果后面反复更换,反而花了更多钱。双屏掌机的电流需求大,电源部分的预算不要省。
第三,散热设计要提前规划。不要等机器烫手了才想起来加散热片,那时候外壳可能已经变形了。在建模阶段就要预留散热孔和散热片的位置。
第四,排线走线要留足空间。双屏掌机内部空间本来就紧张,排线如果走得太紧,后期维护和更换会非常麻烦。宁可外壳厚一点,也要给排线留出足够的弯曲半径。
最后,软件层面的双屏配置比硬件更磨人。xrandr的配置、触控映射、窗口定位,这些都需要反复调试。建议把所有的配置命令写成脚本,方便随时恢复和修改。我现在的做法是维护一个setup.sh,里面包含了从config.txt到xrandr到触控映射的所有配置,换系统或者重装的时候一键执行,省去了大量重复劳动。
这个项目我前后改了五版外壳,换了三次屏幕组合,烧了一块CM4(电源接反了),最终才达到能稳定使用的状态。但每次解决问题之后的成就感,是买一台现成掌机完全给不了的。如果你也喜欢折腾,这个项目绝对值得一试。