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工具。它界面直观,通过拖拽基本几何体并进行组合(联合或挖空)就能完成设计,非常适合初学者。

我的外壳设计理念是“三层夹心结构”:

  1. 底壳(Bottom Case): 一个敞口的盒子,用于容纳树莓派主板。底部我设计了一些栅格状的开孔,主要目的有两个:一是让树莓派的电源线可以穿入;二是作为散热风道,帮助主板散热,避免长时间运行积热。
  2. 中框(Middle Plate): 一个带边框的平板,作用是承托LCD屏幕,并确定屏幕的显示区域(视窗)位置。屏幕将放置在这个框内。
  3. 面盖(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上运行。

  1. 下载镜像: 在Imager工具中,选择“Raspberry Pi OS (Legacy, 32-bit)”或“Raspberry Pi OS (Legacy, 64-bit)”。对于这个项目,轻量级的“Legacy”版本(基于Debian Bullseye)完全够用,且资源占用更少。
  2. 烧录SD卡: 将Micro SD卡插入电脑读卡器,在Imager中选择该SD卡作为存储设备,点击“烧录”。烧录完成后,工具可能会提示“烧录成功”,此时先不要急着拔出SD卡。
  3. 预配置(可选但推荐): 在烧录完成的SD卡根目录(名为 boot 的分区)下,你可以新建一个名为 wpa_supplicant.conf 的文件来预配置Wi-Fi,以及一个名为 ssh 的空文件来启用SSH服务。这样树莓派第一次启动时就能自动连接网络,并允许你通过SSH远程登录,后续操作可以脱离键鼠和临时大屏幕。
  4. 首次启动: 将SD卡插入树莓派,连接HDMI线到大显示器,接上键盘、鼠标和电源。树莓派会首次启动并完成系统初始化设置(如地区、语言、密码等)。按照屏幕提示完成即可。

4.2 GPIO屏幕的专用配置(HDMI用户请跳过)

如果你不幸(或者说有幸挑战)和我一样使用了GPIO屏幕,那么接下来是关键步骤。 HDMI屏幕用户请直接跳到4.3节。

GPIO屏幕(DPI接口)需要告诉树莓派如何驱动它,这通过修改 /boot/config.txt 文件实现。这个文件包含了树莓派启动时的各种硬件参数。

  1. 在树莓派桌面打开终端(Terminal)。
  2. 输入以下命令,使用nano编辑器打开配置文件:
    sudo nano /boot/config.txt
    
  3. 使用方向键滚动到文件最底部。
  4. 将以下配置参数粘贴进去。 请注意,这些参数是针对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=480
    
    参数解读:
    • hdmi_timings 这一长串数字定义了屏幕的详细时序,包括水平像素、前沿、同步、后沿、垂直像素等。这是驱动非标准分辨率屏幕的核心。
    • framebuffer_width/height 将系统帧缓冲区设置为屏幕物理分辨率,确保1:1像素映射,显示不模糊。
  5. 粘贴后,按 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()

代码逻辑深度解析:

  1. 时间计算核心 ( datetime 模块):

    • datetime.datetime.now() 获取当前精确到微秒的本地时间。
    • datetime.datetime(2023, 12, 25, 0, 0) 构造一个代表目标时间(圣诞节零点)的对象。
    • 两者相减得到一个 timedelta 对象,它自动包含了天数 ( days ) 和秒数 ( seconds ) 的差值。但为了统一格式化,我们将其转换为字符串进行处理。
  2. 字符串解析逻辑:

    • str(timedelta) 的默认格式是 [days] days, [hours]:[minutes]:[seconds].[microseconds]
    • 通过遍历字符串,找到第一个空格(分隔天数和时间部分)和两个冒号(分隔时、分、秒)的位置索引。
    • 利用这些索引,使用字符串切片 [start:end] 精准地提取出“天”、“时”、“分”、“秒”的子字符串。
  3. 格式化与单复数处理:

    • 提取出的“时”、“分”、“秒”可能是两位数,如“09”。为了显示美观,通过判断首位是否为“0”来去除前导零。
    • 在英文中,数量为1时单位是单数(hour),大于1时是复数(hours)。代码通过判断字符串是否等于 "1" 来动态添加正确的单位。
  4. GUI更新机制 ( after 方法):

    • text_seconds.after(500, update) 是核心中的核心。它并不是一个循环,而是告诉Tkinter/Guizero:“在500毫秒后,调用 update 函数”。
    • update 函数在执行结束时,又会再次调用 after 方法,安排下一次更新。这样就形成了一个“链式回调”,实现了周期性(每0.5秒)更新界面,而不会阻塞GUI的主事件循环。这是一种在GUI编程中常见的定时刷新技术。
  5. 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 命令查看。

设置开机自启动: 我们希望树莓派一上电就自动运行这个倒计时程序,无需手动操作。

  1. 将你的Python脚本(如 christmas_countdown.py )移动到一个固定的目录,例如 /home/pi/
  2. 打开终端,编辑自动启动的桌面配置文件:
    sudo nano /etc/xdg/autostart/christmas_countdown.desktop
    
  3. 在该文件中输入以下内容:
    [Desktop Entry]
    Type=Application
    Name=Christmas Countdown
    Exec=python3 /home/pi/christmas_countdown.py
    
  4. Ctrl+X ,再按 Y ,最后按 Enter 保存退出。
  5. 重启树莓派 ( sudo reboot )。重启后,图形界面加载完成时,你的倒计时时钟程序就会自动启动了。

6. 整机组装与最终调试

6.1 硬件组装步骤与注意事项

当软件在临时大屏幕上测试无误后,就可以进行最终的硬件组装了。

  1. 安全关机: 首先,在树莓派桌面上正确关机 ( sudo shutdown now ),等待所有指示灯熄灭后再拔掉电源。
  2. 连接小屏幕: 将小屏幕的排线穿过中框的预留空间,然后 仔细地 连接到树莓派的GPIO引脚上。务必对准方向,均匀用力按下。连接屏幕的背光电源(如果有的话)。
  3. 通电测试: 先不要急着装进外壳!连接鼠标(此时键盘可以不用了)和电源,临时通电。观察小屏幕是否正常点亮并显示树莓派桌面。这是验证所有硬件连接和屏幕配置是否成功的最后机会。
  4. 运行程序: 在小屏幕上打开Thonny,运行你的倒计时脚本。调整窗口位置和大小,使其在屏幕上完美居中显示。确认无误后,关闭程序。
  5. 装入外壳:
    • 将树莓派放入底壳,注意将USB电源线从底壳的预留孔洞中穿出。
    • 将屏幕排线轻轻折好,塞入底壳内,避免过度弯折。
    • 盖上中框,确保屏幕的显示区域对准中框的视窗。
    • 最后盖上顶盖,四角对齐。
    • 使用四颗木螺丝,从顶盖的四个孔穿入,依次穿过中框,最终拧紧在底壳的支柱上。螺丝不要拧得过紧,以免压裂塑料部件。

重要提示: 在最终固定螺丝之前,务必考虑好设备的摆放位置和电源线的长度。一旦拧紧螺丝封箱,再想调整电源线位置就比较麻烦了。建议在桌面上模拟摆放一下,确保电源线能舒适地连接到插座。

6.2 最终效果与项目扩展思路

组装完成后,接通电源,你的圣诞倒计时时钟就开始正式工作了!将它放在书桌、书架或壁炉上,红色的数字在绿色背景上跳动,节日氛围瞬间拉满。

这个项目作为一个起点,有非常多的扩展可能性:

  • 更换主题: 修改 app.bg color 参数,可以轻松适配元旦、春节、生日等任何倒计时场景。
  • 增加功能: 使用Guizero的 Picture 控件在背景上添加一张节日图片;或者用 Text 控件增加一句随机显示的节日祝福语。
  • 网络对时与多时区: 可以编写代码从网络获取更精确的时间,甚至显示距离全球不同时区圣诞节的时间。
  • 硬件升级: 换用触摸屏,增加按钮来切换不同的倒计时目标(如新年、假期结束);添加一个光线传感器,让屏幕亮度在夜晚自动调暗。
  • 外壳美化: 使用不同颜色的PLA打印,或者在打印完成后进行喷涂、粘贴装饰物。

这个项目的魅力在于,它完整地走完了一个嵌入式创意项目的全流程:从想法、设计、编程到实体制作。它用到的技术栈(Python、基础GUI、3D设计)都是非常通用且易学的技能。希望这个详细的教程能帮助你成功制作出自己的倒计时时钟,并激发你更多的创作灵感。如果在制作过程中遇到任何问题,回顾一下每个步骤的细节,或者检查一下代码的拼写和缩进,大多数问题都能迎刃而解。祝你制作愉快,并提前祝你节日快乐!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐