树莓派圣诞倒计时时钟:Python Guizero GUI与3D打印外壳全流程
1. 项目概述
作为一个喜欢折腾树莓派和Python的玩家,我每年都会琢磨点新花样来增加节日气氛。去年圣诞季,我决定动手做一个摆在书架上的圣诞倒计时时钟。核心想法很简单:一个能实时显示距离圣诞节还有多少天、多少小时、多少分钟、多少秒的小设备,用看得见的方式把期待感拉满。研究显示,对美好事物的期待本身就能带来快乐,这个项目算是把这种心理体验给实体化了。
整个项目融合了硬件、软件和一点手工。硬件核心是树莓派,搭配一块小尺寸LCD屏幕来显示信息。软件部分,我选择了Python的Guizero库来构建图形界面,因为它对新手极其友好,代码写起来清晰直观。为了让这个小玩意儿能体面地放在书架上,我还用Tinkercad设计了外壳,并用3D打印机把它造了出来。最终效果是一个独立运行、界面喜庆的桌面小摆件,从十一月底开始运行,直到圣诞夜归零,仪式感十足。
这个项目非常适合有一定Python基础、想入门嵌入式GUI开发或树莓派应用的朋友。它不涉及复杂的电路,重点在于软件逻辑的构建和软硬件的整合。通过它,你能掌握如何让树莓派驱动自定义显示屏、编写一个持续运行并自动更新的图形化程序,以及如何为你的创客项目设计并制作一个合适的外壳。下面,我就把从零开始制作的完整过程、踩过的坑和总结的经验,毫无保留地分享给你。
2. 硬件选型与物料清单解析
2.1 核心硬件:树莓派与屏幕的选择考量
树莓派是这个项目的大脑,负责运行Python程序并进行时间计算。我手头有一块闲置的树莓派3B+,所以就用了它。但本质上, 任何能运行Raspberry Pi OS(原Raspbian)并具备视频输出能力的树莓派型号都可以 ,比如树莓派4B、树莓派Zero 2 W等。选择时主要考虑功耗、体积和接口。树莓派Zero系列更省电、体积小,但可能需要额外的转接器;树莓派3B+/4B性能更强、接口齐全,但体积和功耗稍大。对于这个始终通电的桌面时钟项目,功耗不是大问题,因此我选择了接口更方便的3B+。
屏幕的选择是第一个关键决策点,直接影响了后续软件配置的复杂度。 我最初为了节省成本,选用了一块通过GPIO(DPI接口)驱动的5英寸非触摸LCD屏,分辨率是800x480。这种屏幕价格便宜,但需要手动修改树莓派的系统配置文件( /boot/config.txt )来启用和配置,步骤稍显繁琐。在项目过程中我深刻体会到, 对于新手或者希望快速看到效果的朋友,强烈推荐使用通过HDMI接口连接的屏幕 。HDMI屏幕即插即用,无需修改任何系统配置,能省去很多麻烦。市面上有很多5寸或7寸的HDMI IPS屏,显示效果通常也更好。
注意: 如果你和我一样,已经购买或手头只有GPIO屏幕,也不用担心,后文会详细讲解配置方法。但如果你正准备采购,HDMI屏幕是更稳妥、更便捷的选择。
2.2 辅助材料与工具清单
除了核心的计算单元和显示单元,你还需要一些外围设备和制作材料:
-
树莓派基础套件:
- Micro SD卡: 容量8GB或以上,用于安装系统。
- 电源适配器: 确保能提供树莓派所需的稳定5V电压(通常3B+需要5V/2.5A以上)。
- 键盘和鼠标: 仅在初始设置和调试阶段需要。
- HDMI线缆和一台临时显示器: 用于最初的系统设置。一旦小屏幕配置好,就不再需要它们了。
-
外壳制作材料:
- 3D打印部件: 需要打印底壳、中框和面盖三个零件。设计文件可以在后文提供的链接中找到。
- 紧固件: 4颗小号木螺丝(我使用的是沉头、外径3mm、总长11mm的规格),用于固定外壳的三层结构。
- 螺丝刀: 对应螺丝规格的一字或十字螺丝刀。
-
其他:
- 一台用于切片和传输文件的电脑。
- 稳定的网络连接(用于树莓派安装软件包)。
选择3D打印作为外壳方案,主要是因为其灵活性和可定制性。你可以轻松调整设计尺寸来适配不同大小的屏幕或树莓派型号。当然,如果你有激光切割机,用亚克力板制作一个“汉堡包”结构的外壳也是完全可行的,思路类似:底盒放树莓派,中层固定屏幕,上层亚克力板做保护盖。
3. 外壳设计与3D打印实战
3.1 使用Tinkercad进行快速建模
我并非专业3D建模师,因此选择了在线的Tinkercad工具。它界面直观,通过拖拽基本几何体并进行组合(联合或挖空)就能完成设计,非常适合初学者。
我的外壳设计理念是“三层夹心结构”:
- 底壳(Bottom Case): 一个敞口的盒子,用于容纳树莓派主板。底部我设计了一些栅格状的开孔,主要目的有两个:一是让树莓派的电源线可以穿入;二是作为散热风道,帮助主板散热,避免长时间运行积热。
- 中框(Middle Plate): 一个带边框的平板,作用是承托LCD屏幕,并确定屏幕的显示区域(视窗)位置。屏幕将放置在这个框内。
- 面盖(Top Cover): 一个带视窗的盖子,扣在中框上,将屏幕压紧固定,并保护屏幕表面。
三个部件通过四角预留的螺丝孔对齐,最终用四颗长螺丝一次性贯穿拧紧,结构非常稳固。 这种模块化设计的好处是,如果未来要更换不同厚度的屏幕,只需要重新调整中框的厚度即可,底壳和面盖可以复用。
实操心得: 在Tinkercad中设计螺丝孔时,孔径要略小于螺丝的外径。例如,我用的螺丝外径是3mm,那么我设计的孔直径大约是2.8mm。这样螺丝拧进去时会有一定的“咬合力”,结合塑料的弹性,可以达到自攻螺丝的效果,固定得非常牢靠,又省去了预打螺纹的麻烦。
我已经将最终的设计文件公开发布。你可以直接访问Tinkercad项目链接,在线查看、编辑,或者直接下载STL格式的文件进行打印。如果你用的屏幕尺寸和我不同(比如7寸屏),你可以在Tinkercad中轻松调整中框和面盖的视窗尺寸。
3.2 3D打印参数设置与后处理
下载好STL文件后,需要用切片软件(如PrusaSlicer、Cura)将其转换为打印机可识别的G代码。
- 打印机: 我使用的是老款的Prusa i3 MK2.5。
- 耗材: 普通的PLA材料即可,我手头只有白色,所以就用了白色。当然,用红色、绿色这种更有圣诞气氛的 filament 会更好看。
- 关键切片参数:
- 层高(Layer Height): 0.2mm。这是一个兼顾打印质量和速度的常用值。
- 填充密度(Infill Density): 10%。对于这种非承重的外壳,10%的填充率足以保证结构强度,同时节省材料和打印时间。
- 壁厚(Wall Thickness): 通常设置为喷嘴直径的倍数,例如0.4mm喷嘴,设置2层壁厚就是0.8mm。
- 支撑(Support): 这个设计是经过优化的,所有部件的悬垂角度都在打印机可处理的范围之内,因此不需要生成任何支撑材料。 这大大简化了后处理流程,打印完成即可使用,无需费力拆除支撑。
- 打印平台附着(Build Plate Adhesion): 建议开启裙边(Skirt)或 brim(边缘),尤其是底壳这种底面面积较大的部件,可以有效防止打印初期边角翘起。
将切片后的文件发送给打印机,等待几个小时后,三个部件就打印完成了。检查一下打印质量,确保螺丝孔通畅,各部件边缘平整。由于没有支撑,基本上取下来就能用,非常方便。
4. 树莓派系统与屏幕环境配置
4.1 基础系统安装与首次启动
首先,我们需要为树莓派安装操作系统。推荐使用官方的 Raspberry Pi Imager 工具,它可以在Windows、macOS和Linux上运行。
- 下载镜像: 在Imager工具中,选择“Raspberry Pi OS (Legacy, 32-bit)”或“Raspberry Pi OS (Legacy, 64-bit)”。对于这个项目,轻量级的“Legacy”版本(基于Debian Bullseye)完全够用,且资源占用更少。
- 烧录SD卡: 将Micro SD卡插入电脑读卡器,在Imager中选择该SD卡作为存储设备,点击“烧录”。烧录完成后,工具可能会提示“烧录成功”,此时先不要急着拔出SD卡。
- 预配置(可选但推荐): 在烧录完成的SD卡根目录(名为
boot的分区)下,你可以新建一个名为wpa_supplicant.conf的文件来预配置Wi-Fi,以及一个名为ssh的空文件来启用SSH服务。这样树莓派第一次启动时就能自动连接网络,并允许你通过SSH远程登录,后续操作可以脱离键鼠和临时大屏幕。 - 首次启动: 将SD卡插入树莓派,连接HDMI线到大显示器,接上键盘、鼠标和电源。树莓派会首次启动并完成系统初始化设置(如地区、语言、密码等)。按照屏幕提示完成即可。
4.2 GPIO屏幕的专用配置(HDMI用户请跳过)
如果你不幸(或者说有幸挑战)和我一样使用了GPIO屏幕,那么接下来是关键步骤。 HDMI屏幕用户请直接跳到4.3节。
GPIO屏幕(DPI接口)需要告诉树莓派如何驱动它,这通过修改 /boot/config.txt 文件实现。这个文件包含了树莓派启动时的各种硬件参数。
- 在树莓派桌面打开终端(Terminal)。
- 输入以下命令,使用nano编辑器打开配置文件:
sudo nano /boot/config.txt - 使用方向键滚动到文件最底部。
- 将以下配置参数粘贴进去。 请注意,这些参数是针对800x480分辨率的屏幕的,如果你的屏幕分辨率不同(如480x320、1024x600),必须使用屏幕厂商提供的对应参数。
参数解读:# 启用DPI(并行显示)接口 dtoverlay=dpi24 enable_dpi_lcd=1 display_default_lcd=1 # DPI显示组和模式设置 dpi_group=2 dpi_mode=87 # 输出格式(RGB顺序等) dpi_output_format=0x6f005 # 最关键的部分:自定义屏幕时序参数 (针对800x480) hdmi_timings=800 0 40 48 88 480 0 13 3 32 0 0 0 60 0 32000000 6 # 帧缓冲区大小(匹配屏幕分辨率) framebuffer_width=800 framebuffer_height=480hdmi_timings这一长串数字定义了屏幕的详细时序,包括水平像素、前沿、同步、后沿、垂直像素等。这是驱动非标准分辨率屏幕的核心。framebuffer_width/height将系统帧缓冲区设置为屏幕物理分辨率,确保1:1像素映射,显示不模糊。
- 粘贴后,按
Ctrl + X退出,按Y确认保存,再按Enter确认文件名。
4.3 系统优化:禁用屏保与隐藏任务栏
为了让倒计时时钟能够24小时不间断显示,我们需要对系统做两处优化。
1. 禁用屏幕保护(屏幕空白): 树莓派OS默认在一段时间无操作后会自动关闭屏幕背光(屏幕空白)。我们需要关闭这个功能。
- 点击左上角的树莓派菜单,选择
Preferences->Raspberry Pi Configuration。 - 在弹出的窗口中,切换到
Display标签页。 - 找到
Screen Blanking选项,将其设置为Disable。 - 点击
OK,可能需要重启生效。
2. 隐藏顶部任务栏: 我们希望应用程序全屏显示,顶部的任务栏会破坏整体感。
- 将鼠标移动到屏幕顶部的黑色任务栏上,点击右键。
- 选择
Panel Settings。 - 在弹出的设置窗口中,切换到
Advanced标签页。 - 勾选
Minimize panel when not in use。 - 将
Size (pixels)设置为1。这样任务栏在平时就会缩小到几乎看不见的1像素高度。当你需要调用它时,只需将鼠标移动到屏幕最顶部边缘,它就会自动弹出。
完成以上设置后, 请先关闭树莓派 ( sudo shutdown now )。断开HDMI大屏幕,将GPIO屏幕的排线小心地连接到树莓派的GPIO引脚上(注意方向,通常屏幕排线蓝色一面朝向网口方向),并连接屏幕的背光电源(如果有独立供电的话)。然后重新上电启动。如果一切配置正确,你将看到树莓派桌面出现在你的小屏幕上。
5. Guizero图形界面程序开发详解
5.1 环境搭建与Guizero库介绍
系统就绪后,我们开始编写核心的倒计时程序。首先需要通过终端安装必要的Python库。
打开终端,输入以下命令安装Guizero:
pip3 install guizero
pip3 是针对Python3的包管理器。Guizero是一个基于Tkinter但更加简化的Python GUI库,它的API设计非常直观,特别适合初学者快速创建窗口、按钮、文本框等控件。
我使用的开发环境是树莓派自带的 Thonny Python IDE 。它界面简洁,集成了代码编辑和运行功能,对于小型项目非常方便。你可以在开始菜单的“编程”分类下找到它。
5.2 倒计时逻辑的核心代码实现
程序的完整代码如下,我将分段进行详细解读。你可以新建一个文件,例如 christmas_countdown.py ,将代码粘贴进去。
from guizero import App, Text
import time
import datetime
def update():
# 获取当前时间
now = datetime.datetime.now()
# 定义目标时间:2023年圣诞节(可修改为你想要的任何日期)
christmas = datetime.datetime(2023, 12, 25, 0, 0)
# 计算时间差,得到一个 timedelta 对象
diff = christmas - now
# 将 timedelta 转换为字符串,格式如 "34 days, 10:15:30.123456"
diff = str(diff)
# 初始化列表,用于查找字符串中的空格和冒号位置
blank = []
colon = []
for i in range(len(diff)):
if diff[i] == " ":
blank.append(i)
for j in range(len(diff)):
if diff[j] == ":":
colon.append(j)
# 根据空格和冒号的位置,切片提取出天、时、分、秒
days = diff[0:blank[0]]
hours = diff[(blank[1] + 1):colon[0]]
minutes = diff[(colon[0] + 1):colon[1]]
seconds = diff[(colon[1] + 1):(colon[1] + 3)]
# 格式化:去除小时、分钟、秒前面的“0”(例如“09”变成“9”)
if hours[0] == "0":
k = hours[1]
hours = k
if minutes[0] == "0":
l = minutes[1]
minutes = l
if seconds[0] == "0":
m = seconds[1]
seconds = m
# 更新GUI上对应的文本控件
text_days.value = days + " days"
# 处理英文单复数:1 hour vs 2 hours
if hours == "1":
text_hours.value = hours + " hour"
else:
text_hours.value = hours + " hours"
if minutes == "1":
text_minutes.value = minutes + " minute"
else:
text_minutes.value = minutes + " minutes"
if seconds == "1":
text_seconds.value = seconds + " second"
else:
text_seconds.value = seconds + " seconds"
# 500毫秒后再次调用自己,实现持续更新
text_seconds.after(500, update)
# 创建主应用窗口,标题和尺寸(应匹配你的屏幕分辨率)
app = App(title="Christmas Countdown", width=810, height=500)
# 设置背景色为圣诞绿 (RGB: 20, 107, 58)
app.bg = (20, 107, 58)
# 创建文本控件
# 空文本用于占位或布局调整
text_0 = Text(app, text="")
# 固定提示文本
text_1 = Text(app, text="It is currently", size=70, color="red")
# 动态更新的时间文本,初始为空
text_days = Text(app, text="", size=40, color="red")
text_hours = Text(app, text="", size=40, color="red")
text_minutes = Text(app, text="", size=40, color="red")
text_seconds = Text(app, text="", size=40, color="red")
text_2 = Text(app, text="until Christmas!", size=70, color="red")
# 启动第一次更新
text_seconds.after(500, update)
# 进入GUI主循环,显示窗口
app.display()
代码逻辑深度解析:
-
时间计算核心 (
datetime模块):datetime.datetime.now()获取当前精确到微秒的本地时间。datetime.datetime(2023, 12, 25, 0, 0)构造一个代表目标时间(圣诞节零点)的对象。- 两者相减得到一个
timedelta对象,它自动包含了天数 (days) 和秒数 (seconds) 的差值。但为了统一格式化,我们将其转换为字符串进行处理。
-
字符串解析逻辑:
str(timedelta)的默认格式是[days] days, [hours]:[minutes]:[seconds].[microseconds]。- 通过遍历字符串,找到第一个空格(分隔天数和时间部分)和两个冒号(分隔时、分、秒)的位置索引。
- 利用这些索引,使用字符串切片
[start:end]精准地提取出“天”、“时”、“分”、“秒”的子字符串。
-
格式化与单复数处理:
- 提取出的“时”、“分”、“秒”可能是两位数,如“09”。为了显示美观,通过判断首位是否为“0”来去除前导零。
- 在英文中,数量为1时单位是单数(hour),大于1时是复数(hours)。代码通过判断字符串是否等于
"1"来动态添加正确的单位。
-
GUI更新机制 (
after方法):text_seconds.after(500, update)是核心中的核心。它并不是一个循环,而是告诉Tkinter/Guizero:“在500毫秒后,调用update函数”。update函数在执行结束时,又会再次调用after方法,安排下一次更新。这样就形成了一个“链式回调”,实现了周期性(每0.5秒)更新界面,而不会阻塞GUI的主事件循环。这是一种在GUI编程中常见的定时刷新技术。
-
GUI布局与样式:
App创建了主窗口,width和height我设置为比屏幕分辨率(800x480)稍大一点(810x500),以确保窗口边框不会遮挡内容,并让背景色充满屏幕。Text控件用于显示文字。控件的创建顺序决定了它们在窗口上的默认堆叠顺序(从上到下)。你可以通过grid或更复杂的布局管理器来精确定位,但这里简单的顺序排列已经足够。app.bg和color="red"设置了圣诞主题的颜色。
5.3 程序调试与自启动设置
在Thonny中点击运行按钮,如果一切正常,你应该能看到一个充满圣诞绿背景的窗口,上面用红色大字显示着倒计时。
调试常见问题:
- 窗口位置或大小不对: 调整
App(width, height)的参数,使其与你的屏幕分辨率匹配。也可以使用app.full_screen = True尝试全屏模式。 - 文字显示不全或太小: 调整
Text控件中的size参数。注意Guizero的size是文本高度的大致像素值,并非字号。 - 时间不准: 确保树莓派已连接网络,并正确同步了网络时间(通常默认已开启)。可以在终端输入
date命令查看。
设置开机自启动: 我们希望树莓派一上电就自动运行这个倒计时程序,无需手动操作。
- 将你的Python脚本(如
christmas_countdown.py)移动到一个固定的目录,例如/home/pi/。 - 打开终端,编辑自动启动的桌面配置文件:
sudo nano /etc/xdg/autostart/christmas_countdown.desktop - 在该文件中输入以下内容:
[Desktop Entry] Type=Application Name=Christmas Countdown Exec=python3 /home/pi/christmas_countdown.py - 按
Ctrl+X,再按Y,最后按Enter保存退出。 - 重启树莓派 (
sudo reboot)。重启后,图形界面加载完成时,你的倒计时时钟程序就会自动启动了。
6. 整机组装与最终调试
6.1 硬件组装步骤与注意事项
当软件在临时大屏幕上测试无误后,就可以进行最终的硬件组装了。
- 安全关机: 首先,在树莓派桌面上正确关机 (
sudo shutdown now),等待所有指示灯熄灭后再拔掉电源。 - 连接小屏幕: 将小屏幕的排线穿过中框的预留空间,然后 仔细地 连接到树莓派的GPIO引脚上。务必对准方向,均匀用力按下。连接屏幕的背光电源(如果有的话)。
- 通电测试: 先不要急着装进外壳!连接鼠标(此时键盘可以不用了)和电源,临时通电。观察小屏幕是否正常点亮并显示树莓派桌面。这是验证所有硬件连接和屏幕配置是否成功的最后机会。
- 运行程序: 在小屏幕上打开Thonny,运行你的倒计时脚本。调整窗口位置和大小,使其在屏幕上完美居中显示。确认无误后,关闭程序。
- 装入外壳:
- 将树莓派放入底壳,注意将USB电源线从底壳的预留孔洞中穿出。
- 将屏幕排线轻轻折好,塞入底壳内,避免过度弯折。
- 盖上中框,确保屏幕的显示区域对准中框的视窗。
- 最后盖上顶盖,四角对齐。
- 使用四颗木螺丝,从顶盖的四个孔穿入,依次穿过中框,最终拧紧在底壳的支柱上。螺丝不要拧得过紧,以免压裂塑料部件。
重要提示: 在最终固定螺丝之前,务必考虑好设备的摆放位置和电源线的长度。一旦拧紧螺丝封箱,再想调整电源线位置就比较麻烦了。建议在桌面上模拟摆放一下,确保电源线能舒适地连接到插座。
6.2 最终效果与项目扩展思路
组装完成后,接通电源,你的圣诞倒计时时钟就开始正式工作了!将它放在书桌、书架或壁炉上,红色的数字在绿色背景上跳动,节日氛围瞬间拉满。
这个项目作为一个起点,有非常多的扩展可能性:
- 更换主题: 修改
app.bg和color参数,可以轻松适配元旦、春节、生日等任何倒计时场景。 - 增加功能: 使用Guizero的
Picture控件在背景上添加一张节日图片;或者用Text控件增加一句随机显示的节日祝福语。 - 网络对时与多时区: 可以编写代码从网络获取更精确的时间,甚至显示距离全球不同时区圣诞节的时间。
- 硬件升级: 换用触摸屏,增加按钮来切换不同的倒计时目标(如新年、假期结束);添加一个光线传感器,让屏幕亮度在夜晚自动调暗。
- 外壳美化: 使用不同颜色的PLA打印,或者在打印完成后进行喷涂、粘贴装饰物。
这个项目的魅力在于,它完整地走完了一个嵌入式创意项目的全流程:从想法、设计、编程到实体制作。它用到的技术栈(Python、基础GUI、3D设计)都是非常通用且易学的技能。希望这个详细的教程能帮助你成功制作出自己的倒计时时钟,并激发你更多的创作灵感。如果在制作过程中遇到任何问题,回顾一下每个步骤的细节,或者检查一下代码的拼写和缩进,大多数问题都能迎刃而解。祝你制作愉快,并提前祝你节日快乐!
更多推荐


所有评论(0)