1. 项目概述与核心思路如果你手头有一块闲置的TFT显示屏想把它接到树莓派上做个信息面板、迷你相框或者简单的游戏机界面但又不想去折腾复杂的内核驱动和编译那今天聊的这个方案可能正合你意。我最近在几个物联网仪表盘项目里反复用到了CircuitPython配合Pillow库来驱动各种SPI接口的TFT屏实测下来这套组合拳在灵活性和开发效率上比传统的方案要舒服不少。简单来说这个方案的核心是“用户空间驱动”。它不依赖fbtft这类内核帧缓冲驱动意味着你的系统桌面控制台不会抢占这块屏幕。相反你通过纯Python代码直接通过SPI总线向显示屏发送绘图指令和数据。这样做的好处显而易见环境依赖少跨平台兼容性好调试和修改代码就像运行普通Python脚本一样简单。无论是Adafruit的ILI9341屏还是市面上更常见的ST7789、ST7735甚至是OLED屏如SSD1331都能用同一套逻辑搞定。整个流程可以拆解为几个关键步骤首先是硬件连接把屏幕的SPI引脚CLK, MOSI和几个控制引脚CS, DC, RST正确接到树莓派的GPIO上然后是软件环境搭建主要是安装Adafruit-Blinka它让CircuitPython库能在普通Linux/Python3上运行和Pillow图像库最后就是写Python脚本初始化屏幕、创建画布、绘制内容并刷新显示。下面我就结合自己踩过的坑和总结的技巧把这套流程掰开揉碎了讲清楚。2. 硬件连接详解与避坑指南硬件连接是第一步也是容易出错的一步。不同的屏幕虽然控制器可能相同但引脚排列和功能可能略有差异。这里我以最常见的2.4寸ILI9341和1.54寸ST7789两款SPI屏为例详细说明接线逻辑和注意事项。2.1 树莓派SPI引脚定义首先必须清楚树莓派上硬件SPI0的引脚定义以40针GPIO接口的树莓派3B/4B为例SCLK (SPI时钟):GPIO 11(物理引脚23)MOSI (主设备输出从设备输入):GPIO 10(物理引脚19)MISO (主设备输入从设备输出):GPIO 9(物理引脚21) -注意对于纯输出的显示屏此引脚通常不需要连接。CE0 (片选0):GPIO 8(物理引脚24)CE1 (片选1):GPIO 7(物理引脚26)此外我们还需要两个通用的GPIO引脚来充当屏幕的数据/命令选择线(D/C或DC)和复位线(RST)。2.2 ILI9341屏幕接线方案对于大多数2.2寸、2.4寸、2.8寸、3.2寸的ILI9341屏幕其引脚通常包括VCC、GND、CS、RST、DC、MOSI、SCLK有时还有MISO和背光控制BL。推荐接线方式如下屏幕引脚树莓派GPIO物理引脚说明VCC3.3V引脚1或17绝对不要接5V会烧屏。GNDGND引脚6, 9, 14, 20, 25, 30, 34, 39等任选一个接地引脚。CS (片选)CE0 (GPIO 8)引脚24使用SPI0的默认片选0。RST (复位)GPIO 24引脚18可自定义代码中需对应修改。DC (数据/命令)GPIO 25引脚22可自定义代码中需对应修改。MOSI (数据输入)MOSI (GPIO 10)引脚19主输出从输入。SCLK (时钟)SCLK (GPIO 11)引脚23时钟信号。BL (背光)3.3V或GPIO引脚1或17接3.3V常亮接GPIO则可编程控制。重要提示有些大尺寸屏幕如某些3.5寸屏默认是8位并行接口。你需要检查屏幕背面通常会有焊盘或跳线帽标记为IM0,IM1,IM2,IM3。必须将其配置为SPI模式。具体方法请查阅屏幕手册通常需要将IM0接高电平3.3VIM1、IM2、IM3接低电平GND。2.3 ST7789屏幕接线方案ST7789常用于1.3寸、1.54寸、2.0寸的IPS屏接线与ILI9341类似但需要注意一些屏幕特别是高分辨率或带偏移的在初始化时需要特定的width、height、x_offset、y_offset参数。接线参考表屏幕引脚树莓派GPIO物理引脚说明VCC3.3V引脚1GNDGND引脚6CSCE0 (GPIO 8)引脚24RSTGPIO 24引脚18DCGPIO 25引脚22MOSIMOSI (GPIO 10)引脚19SCLKSCLK (GPIO 11)引脚23BL3.3V或GPIO 18引脚1或12建议接GPIO以便软件控制亮度。2.4 接线实操心得与避坑点电源是头等大事绝大多数3.3V逻辑的TFT屏VCC必须接树莓派的3.3V引脚。接5V瞬间冒烟不是开玩笑。如果你需要驱动大尺寸屏或背光电流很大可以考虑使用外部3.3V稳压模块单独供电但信号线仍需连接树莓派。GPIO编号与物理引脚代码里用的是BCM编号如board.D25对应的是GPIO 25而不是物理引脚号。接线时务必对照GPIO引脚图我习惯用pinout命令在终端查看。SPI使能默认情况下树莓派的SPI接口可能是关闭的。需要通过sudo raspi-config-Interface Options-SPI-Yes来启用。启用后可以在/dev/下看到spidev0.0和spidev0.1设备。避免引脚冲突如果你之前为这块屏幕安装过fbtft内核驱动务必先卸载或禁用否则SPI总线会被内核驱动占用导致我们的Python程序无法访问。通常可以编辑/boot/config.txt注释掉相关的dtoverlay行并重启。背光控制如果屏幕背光单独引出了BL或LED引脚强烈建议将其连接到一个可用的GPIO如GPIO 26而不是直接接3.3V。这样你可以在代码中控制背光开关实现息屏节能体验好很多。3. 软件环境搭建与库安装详解硬件连好后我们就进入软件环节。这套方案的核心是三个Python库Adafruit-Blinka、Adafruit_CircuitPython_RGB_Display和Pillow。3.1 系统准备与SPI验证首先确保你的树莓派系统是最新的并且启用了SPI接口如上文所述。可以通过以下命令检查SPI是否已启用lsmod | grep spi如果看到spi_bcm2835等相关模块说明SPI已加载。也可以检查设备文件ls -l /dev/spi*应该能看到/dev/spidev0.0和/dev/spidev0.1。3.2 安装Adafruit BlinkaAdafruit-Blinka是一个兼容层它让为CircuitPython设计的硬件控制库比如我们的RGB Display库能够在运行CPython的Linux计算机如树莓派上工作。它本质上是board和busio等模块在Linux上的实现。安装非常简单使用pip3即可sudo pip3 install adafruit-blinka如果你的系统没有pip3需要先安装sudo apt update sudo apt install python3-pip3.3 安装RGB Display驱动库和Pillow接下来安装显示屏驱动库和图像处理库sudo pip3 install adafruit-circuitpython-rgb-display这个库包含了ILI9341、ST7789、ST7735、HX8357、SSD1351、SSD1331等常见控制器的驱动。然后安装PillowPIL的友好分支它是我们处理图像、绘制图形和文字的核心sudo apt-get install python3-pil有时通过pip安装的Pillow可能缺少一些底层图像库支持如果运行时报错关于libopenjp2或libtiff可以一并安装这些系统依赖sudo apt-get install libopenjp2-7 libtiff5 libatlas-base-dev3.4 安装字体可选但推荐示例代码中使用了DejaVu字体树莓派系统通常已预装。如果未安装可以手动安装sudo apt-get install fonts-dejavu3.5 环境验证与常见问题安装完成后可以写一个最简单的测试脚本test_spi.py来验证基础SPI通信是否正常不涉及具体屏幕import board import busio spi busio.SPI(board.SCK, board.MOSI, board.MISO) print(“SPI对象创建成功”, spi)运行python3 test_spi.py如果没有报错说明Adafruit-Blinka和SPI基础访问是正常的。踩坑记录最常遇到的问题是权限不足。普通用户可能无法访问/dev/spidev0.0。解决方法有两种一是使用sudo运行你的Python脚本不推荐长期使用二是将你的用户加入spi组sudo usermod -a -G spi $USER然后注销并重新登录使组生效。推荐第二种方法。4. 核心代码解析与图像显示实战环境就绪我们来深入看看代码。第一个例子是显示一张图片并实现自适应缩放和居中裁剪这是很多项目如电子相册的必备功能。4.1 代码结构与初始化我们以ILI9341为例完整代码如下我将逐段解析# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries # SPDX-License-Identifier: MIT import digitalio import board from PIL import Image, ImageDraw from adafruit_rgb_display import ili9341 # 其他控制器导入已省略 # 1. 引脚配置 cs_pin digitalio.DigitalInOut(board.CE0) # 片选接GPIO8 dc_pin digitalio.DigitalInOut(board.D25) # 数据/命令接GPIO25 reset_pin digitalio.DigitalInOut(board.D24) # 复位接GPIO24 # 2. SPI总线与显示屏参数配置 BAUDRATE 24000000 # SPI通信速率24MHz是许多屏的极限不稳定可降至16MHz spi board.SPI() # 使用硬件SPI0 # 3. 创建显示屏驱动对象 disp ili9341.ILI9341( spi, rotation90, # 屏幕旋转90度 cscs_pin, dcdc_pin, rstreset_pin, baudrateBAUDRATE, )关键点解析引脚对象digitalio.DigitalInOut用于将GPIO引脚对象化并交由库管理方向。SPI对象board.SPI()会自动选择系统默认的SPI总线通常是/dev/spidev0.0。你也可以使用busio.SPI指定具体的SCK和MOSI引脚但硬件SPI性能更好。驱动初始化ili9341.ILI9341()是核心。rotation参数非常实用可以设置0、90、180、270度物理上不变动屏幕方向通过软件旋转显示内容。更换屏幕如果你用的是ST7789屏只需注释掉ILI9341那行取消注释对应的ST7789初始化行并可能需要调整width、height、x_offset、y_offset等参数。这些参数用于告诉驱动屏幕的实际可绘制区域对于有“黑边”的屏幕尤其重要。4.2 创建画布与处理旋转接下来我们需要创建一个与屏幕当前方向匹配的内存画布Image对象。# 根据旋转角度确定画布的宽高。 # 因为驱动对象的.width/.height属性是物理宽高旋转后逻辑宽高要对调。 if disp.rotation % 180 90: height disp.width # 旋转90或270度逻辑高度物理宽度 width disp.height # 逻辑宽度物理高度 else: width disp.width height disp.height # 创建RGB模式的画布 image Image.new(RGB, (width, height)) # 创建绘图对象所有绘制操作都基于这个draw对象 draw ImageDraw.Draw(image)这里有个容易混淆的点disp.width和disp.height返回的是屏幕物理像素尺寸。当设置rotation90后驱动内部会处理像素数据的排列但disp.width和disp.height的值不会变。因此我们需要在应用层Pillow画布手动对调宽高以确保我们绘制的坐标体系与最终显示方向一致。4.3 图像缩放与居中裁剪算法这是显示任意图片的核心技巧目的是让图片在不失真的情况下填满屏幕。# 打开图片文件 image_original Image.open(blinka.jpg) # 计算图片和屏幕的宽高比 image_ratio image_original.width / image_original.height screen_ratio width / height # 决定缩放策略以高度为基准还是以宽度为基准 if screen_ratio image_ratio: # 屏幕更“胖”或图片更“瘦”。以屏幕高度为基准缩放图片宽度会超出。 scaled_width image_original.width * height // image_original.height scaled_height height else: # 屏幕更“瘦”或图片更“胖”。以屏幕宽度为基准缩放图片高度会超出。 scaled_width width scaled_height image_original.height * width // image_original.width # 执行缩放使用BICUBIC插值算法保证质量 image_scaled image_original.resize((scaled_width, scaled_height), Image.BICUBIC) # 计算裁剪区域使图片居中 x scaled_width // 2 - width // 2 y scaled_height // 2 - height // 2 image_cropped image_scaled.crop((x, y, x width, y height)) # 最终显示 disp.image(image_cropped)算法逻辑解读比较宽高比目的是判断是图片的宽度过剩还是高度过剩。等比缩放让图片的短边刚好等于屏幕的对应边。例如屏幕是240x320竖屏ratio0.75图片是800x600ratio≈1.33。由于screen_ratio (0.75) image_ratio (1.33)我们选择以屏幕高度320为基准缩放图片。缩放后图片尺寸为(800*320/600, 320) ≈ (426, 320)。此时图片宽度(426)大于屏幕宽度(240)。居中裁剪从缩放后的图片中间裁剪出一个和屏幕一样大(240x320)的区域。x (426-240)//2 93y0。这样就得到了完美居中填充屏幕的图片且无黑边。实操心得Image.BICUBIC在质量和速度间取得了很好的平衡。对于小尺寸屏幕Image.NEAREST最近邻最快但可能有锯齿Image.LANCZOS质量最好但最慢。根据你的刷新需求选择。另外如果图片路径错误Image.open会抛出异常建议用try...except包裹或先检查文件是否存在。5. 图形绘制与文字渲染进阶仅仅显示图片还不够我们经常需要绘制UI元素、图表或显示文字。第二个例子展示了如何使用Pillow的ImageDraw模块进行基本绘图。5.1 绘制基础图形import digitalio import board from PIL import Image, ImageDraw, ImageFont # 注意导入了ImageFont from adafruit_rgb_display import ili9341 # ... 引脚、SPI、显示屏初始化与之前相同 ... BORDER 20 FONTSIZE 24 # ... 创建画布draw对象 ... # 1. 绘制绿色背景全屏矩形 draw.rectangle((0, 0, width, height), fill(0, 255, 0)) disp.image(image) # 可以分步刷新但通常最后统一刷新 # 2. 绘制一个内嵌的紫色矩形 draw.rectangle( (BORDER, BORDER, width - BORDER - 1, height - BORDER - 1), fill(170, 0, 136) # RGB颜色值 )draw.rectangle的参数是一个四元组(x0, y0, x1, y1)代表矩形左上角和右下角的坐标。fill参数指定填充色。outline参数可以指定边框颜色。5.2 加载字体与渲染文字显示文字是GUI的基础Pillow支持TrueType字体。# 加载系统字体指定大小 font ImageFont.truetype(/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf, FONTSIZE) text Hello World! # 获取文字将要占据的像素尺寸这对居中计算至关重要 (font_width, font_height) font.getsize(text) # 计算文字左上角的起始坐标使其在屏幕上居中 text_x width // 2 - font_width // 2 text_y height // 2 - font_height // 2 # 绘制黄色文字 draw.text((text_x, text_y), text, fontfont, fill(255, 255, 0)) # 最终刷新显示 disp.image(image)字体使用要点字体路径示例中使用的是系统字体。你可以使用任何.ttf字体文件只需提供完整路径。将字体文件放在项目目录下用“./myfont.ttf”引用更便于移植。getsize()的替代注意Pillow 9.2.0及以上版本已弃用font.getsize()。推荐使用font.getbbox()或font.getlength()。例如获取包围盒bbox font.getbbox(text)然后text_width bbox[2] - bbox[0],text_height bbox[3] - bbox[1]。性能考虑频繁创建ImageFont对象会影响性能。如果程序中多次使用同一种字体和大小应该将其创建为全局对象。5.3 更多绘图功能ImageDraw模块功能丰富你还可以画线draw.line([(x0, y0), (x1, y1), ...], fillcolor, widthwidth)画圆/椭圆draw.ellipse([(x0, y0), (x1, y1)], fillcolor, outlinecolor)指定外接矩形。画多边形draw.polygon([(x0,y0), (x1,y1), ...], fillcolor, outlinecolor)画点draw.point((x, y), fillcolor)结合这些基础图形你已经可以绘制出简单的图表、进度条和按钮等UI元素了。6. 动态信息显示系统监控仪表盘第三个例子将前面所学结合起来创建一个动态更新的系统信息仪表盘。这非常实用比如可以做一个树莓派资源监视器。6.1 获取系统信息我们使用Python的subprocess模块调用shell命令来获取系统数据。import time import subprocess def get_ip_address(): 获取树莓派的IP地址 cmd hostname -I | cut -d -f1 try: ip subprocess.check_output(cmd, shellTrue).decode(utf-8).strip() return fIP: {ip} if ip else IP: N/A except: return IP: Error def get_cpu_load(): 获取CPU负载1分钟平均 cmd top -bn1 | grep load | awk {printf \CPU Load: %.2f\, $(NF-2)} try: return subprocess.check_output(cmd, shellTrue).decode(utf-8).strip() except: return CPU Load: N/A def get_memory_usage(): 获取内存使用情况 cmd free -m | awk NR2{printf \Mem: %s/%sMB %.1f%%\, $3,$2,$3*100/$2 } try: return subprocess.check_output(cmd, shellTrue).decode(utf-8).strip() except: return Mem: N/A def get_disk_usage(): 获取根分区磁盘使用情况 cmd df -h | awk $NF\/\{printf \Disk: %s/%s %s\, $3,$2,$5} try: return subprocess.check_output(cmd, shellTrue).decode(utf-8).strip() except: return Disk: N/A def get_cpu_temp(): 获取CPU温度 cmd cat /sys/class/thermal/thermal_zone0/temp | awk {printf \CPU Temp: %.1fC\, $1/1000} try: return subprocess.check_output(cmd, shellTrue).decode(utf-8).strip() except: return CPU Temp: N/A命令解析top -bn1: 以批处理模式运行一次top命令。grep load: 筛选出包含“load”的行。awk ‘{printf “CPU Load: %.2f”, $(NF-2)}’:NF是字段数$(NF-2)就是倒数第三个字段即1分钟平均负载。free -m: 以MB为单位显示内存信息。NR2处理第二行Mem行。df -h: 显示磁盘空间使用情况。$NF”/”筛选出挂载点为根目录/的行。安全与效率提示使用shellTrue存在潜在安全风险如果命令字符串来自不可信输入应避免使用。对于固定命令风险可控。此外频繁调用subprocess开销较大。对于高刷新率的需求可以考虑使用psutil这样的Python库来获取系统信息效率更高。6.2 主循环与动态刷新有了获取数据的函数主循环就清晰了# ... 初始化显示屏、画布、字体等 ... padding 2 # 文字起始的顶部边距 x 0 # 文字起始的左边界 while True: # 1. 清屏绘制黑色背景 draw.rectangle((0, 0, width, height), fill(0, 0, 0)) # 2. 获取所有系统信息 lines [] lines.append(get_ip_address()) lines.append(get_cpu_load()) lines.append(get_memory_usage()) lines.append(get_disk_usage()) lines.append(get_cpu_temp()) # 3. 逐行绘制文字 y padding colors [#FFFFFF, #FFFF00, #00FF00, #0000FF, #FF00FF] # 为每行定义不同颜色 for i, line in enumerate(lines): draw.text((x, y), line, fontfont, fillcolors[i % len(colors)]) # 更新y坐标为下一行预留位置文字高度 行间距 y font.getsize(line)[1] 2 # 4. 刷新到屏幕 disp.image(image) # 5. 控制刷新频率避免CPU占用过高 time.sleep(1.0) # 每秒刷新一次关键优化点局部刷新上述代码每次循环都重绘了整个屏幕。对于只有文字变化的场景这是低效的。更优的方案是只刷新变化的部分如数字但这需要更复杂的逻辑来判断和计算脏区域。对于小型屏幕和简单应用全屏刷新通常可以接受。刷新率与功耗time.sleep(1)让循环每秒运行一次对于系统监控足够了。如果用于动画可能需要更短的间隔如0.1秒但要考虑SPI写入速度和CPU负载。过高的刷新率可能导致屏幕闪烁或系统卡顿。错误处理getsize()在Pillow新版本中已废弃建议改用font.getbbox()。同时subprocess调用可能因命令不存在而失败try...except包裹是必要的。7. 性能优化与高级技巧当项目变得复杂时性能就成了问题。这里分享几个我实践中总结的优化技巧。7.1 减少SPI数据传输量屏幕刷新的本质是将内存中的图像数据Image对象通过SPI总线发送到屏幕的GRAM中。数据量宽度 x 高度 x 颜色深度如16位色为2字节。对于320x240的16位色屏幕一帧数据约150KB。24MHz的SPI总线理论上每秒可传输约3MB但算上协议开销实际帧率可能在15-30FPS。优化策略降低颜色深度如果你的项目不需要全彩可以尝试使用“P”模式调色板或“1”模式单色创建图像。但adafruit_rgb_display库通常期望“RGB”模式你可能需要转换。有些屏幕控制器支持16位RGB565格式库内部可能已做优化。局部刷新只更新屏幕上变化的部分。你需要维护一个“脏矩形”列表每次只将这些区域的数据发送到屏幕。这需要驱动库支持部分刷新或者你自己计算并发送特定矩形区域的数据。对于disp.image()它是全屏更新。你可以研究驱动库的底层方法看是否有blit或block操作。双缓冲与直接操作创建两个Image对象一个用于绘制后台缓冲区完成后一次性调用disp.image()切换到前台。这可以避免绘制过程中的闪烁。更激进的做法是直接操作图像的像素数据数组image.tobytes()或image.load()但这对编程要求较高。7.2 使用硬件加速如果可用树莓派的GPUVideoCore能力强大。虽然我们这个方案是用户空间的但可以通过其他途径间接利用使用pygame或SDL2它们可能利用硬件加速进行渲染然后将最终帧缓冲区转换为Image对象再通过我们的SPI库发送。这增加了复杂性但对于复杂2D图形或简单游戏可能值得。使用numpy加速图像处理Pillow的许多操作底层是C实现的已经很快。但对于像素级的批量操作如颜色变换、阿尔法混合将图像转换为numpy数组进行处理再转回Image对象速度可能有数量级的提升。7.3 背光控制与低功耗如果你的屏幕背光连接到了GPIO如GPIO 26可以在代码中轻松控制import digitalio backlight_pin digitalio.DigitalInOut(board.D26) backlight_pin.direction digitalio.Direction.OUTPUT def turn_on_backlight(): backlight_pin.value True def turn_off_backlight(): backlight_pin.value False # 在需要时调用例如屏幕休眠时关背光 turn_off_backlight() time.sleep(5) turn_on_backlight()这对于电池供电的项目至关重要可以显著延长续航时间。7.4 多屏幕支持与SPI总线共享树莓派的硬件SPI0有两个片选CE0和CE1理论上可以连接两个SPI从设备。你需要为每个屏幕分配独立的CS、DC、RST引脚并在代码中初始化两个独立的disp对象。关键是要确保同一时间只有一个设备被选中CS拉低。库会通过你传入的cs_pin对象自动管理片选。# 屏幕1 (使用CE0) cs_pin1 digitalio.DigitalInOut(board.CE0) dc_pin1 digitalio.DigitalInOut(board.D22) rst_pin1 digitalio.DigitalInOut(board.D27) disp1 ili9341.ILI9341(spi, cscs_pin1, dcdc_pin1, rstrst_pin1, ...) # 屏幕2 (使用CE1) cs_pin2 digitalio.DigitalInOut(board.CE1) dc_pin2 digitalio.DigitalInOut(board.D23) rst_pin2 digitalio.DigitalInOut(board.D24) disp2 st7789.ST7789(spi, cscs_pin2, dcdc_pin2, rstrst_pin2, ...) # 使用时库会自动控制对应的CS引脚 disp1.image(image1) disp2.image(image2)注意GPIO资源的分配避免冲突。如果还需要连接其他SPI设备如SD卡、传感器需要考虑总线负载和时序。8. 常见问题排查与调试记录即使按照教程操作也难免会遇到问题。这里汇总了我遇到的一些典型问题及解决方法。8.1 屏幕无显示或花屏这是最常见的问题可能的原因和排查步骤电源与连接首要检查用万用表测量屏幕VCC和GND之间电压是否为稳定的3.3V背光引脚是否有电压接线顺序确保每根线都连接牢固特别是CLK和MOSI接触不良会导致数据错乱表现为花屏或随机条纹。引脚冲突确认你使用的GPIO如D24, D25没有被系统其他功能占用如串口、I2C。可以暂时在代码中更换其他GPIO引脚试试。软件配置SPI是否启用运行ls /dev/spi*确认设备存在。用户权限运行groups命令查看当前用户是否在spi组中。如果不是用sudo运行脚本看是否正常以确定是权限问题。驱动型号选择错误这是花屏的常见原因。ILI9341和ST7789的初始化序列不同用错驱动会导致乱码。仔细核对屏幕规格书或卖家提供的资料。初始化参数错误特别是width,height,x_offset,y_offset。对于非标准分辨率的屏幕如240x240的ST7789必须正确设置。一个错误的偏移会导致显示区域错位看起来像花屏或显示不全。尝试注释掉rotation参数或将其设为0看是否有改善。速率问题降低波特率将代码中的BAUDRATE 24000000改为BAUDRATE 16000000甚至8000000。过高的SPI速率可能导致信号质量差在长导线或劣质屏上尤其明显。检查逻辑电平确保树莓派的3.3V电平与屏幕的IO电平匹配。有些5V容忍的屏幕在3.3V下工作可能不稳定。8.2 显示内容错位或旋转不正确症状文字只显示在一半屏幕或者方向不对。排查检查代码中disp.rotation的设置是否与你物理摆放屏幕的方向一致。检查画布创建部分的逻辑if disp.rotation % 180 90:那段。确保width和height变量在旋转后正确交换。对于有偏移的屏幕仔细查看驱动库源码或示例中对应你屏幕尺寸的注释行确保x_offset和y_offset参数正确。这些偏移值通常是屏幕驱动IC周围的“死区”像素。8.3 程序报错“OSError: [Errno 13] Permission denied” 或 “OSError: [Errno 16] Device or resource busy”权限被拒绝用户不在spi组。解决sudo usermod -a -G spi $USER然后重新登录。设备忙SPI总线被其他进程占用。可能的原因之前运行的程序未正常退出GPIO未释放。重启树莓派是最快的方法。内核驱动如fbtft占用了设备。运行dtoverlay -l查看已加载的overlay或在/boot/config.txt中检查并注释掉类似dtoverlayfb-ili9341的行然后重启。其他软件如pigpio守护进程可能使用了SPI。尝试停止相关服务。8.4 图像显示速度慢刷新卡顿原因SPI数据传输是瓶颈或者Python绘图操作太慢。优化降低分辨率如果允许在代码中创建小尺寸画布如160x120然后让驱动库缩放如果支持或者直接使用屏幕的原始小分辨率模式。简化图像减少颜色数量避免复杂的抗锯齿绘图操作。预渲染静态内容将不变化的背景、边框等预先绘制到一个Image对象中每次循环只更新变化的文字或图形然后与背景合成。使用image.paste()更新局部区域时用paste操作比通过draw重新绘制可能更快。检查循环中的阻塞操作如网络请求、复杂的文件读写等它们会拖慢整个刷新循环。8.5 Pillow字体相关错误IOError: cannot open resource找不到字体文件。检查ImageFont.truetype()中的路径是否正确。可以使用绝对路径或将字体文件复制到脚本所在目录使用相对路径“./DejaVuSans.ttf”。AttributeError: ‘ImageFont’ object has no attribute ‘getsize’Pillow版本过高。解决方法# 新版本Pillow获取文字尺寸的方法 try: # Pillow 10.x bbox font.getbbox(text) text_width bbox[2] - bbox[0] text_height bbox[3] - bbox[1] except AttributeError: # Pillow 10.x (或使用getlength) # 注意getlength只返回宽度高度需用font.getmetrics() from PIL import ImageFont left, top, right, bottom font.getbbox(text) text_width right - left text_height bottom - top调试时一个非常有效的方法是在关键步骤后添加print语句输出变量状态如图像尺寸、颜色值或者先在不连接屏幕的情况下运行确保没有Python语法和逻辑错误。也可以先用一个极简的测试脚本只初始化屏幕并显示纯色来隔离问题。