1. 项目概述与核心思路几年前我在一个极客社区里第一次看到单词时钟的概念当时就被它那种用文字“拼”出时间的优雅方式迷住了。传统的数字时钟告诉你“几点几分”而单词时钟则用一句完整的英文短句告诉你“现在是差五分到十点”这种表达方式多了一丝人情味和诗意。不过大多数单词时钟都是横向排列的方形设计虽然经典但总觉得少了点新意。直到我看到了Dano Wall提出的垂直单词时钟概念它把时间单词从上到下排成一列像一首垂直流淌的时间诗瞬间就击中了我。这不只是一个时钟更是一个融合了嵌入式编程、硬件交互和激光切割工艺的桌面艺术品。今天我就把自己从零开始复刻这个项目的完整过程、踩过的坑以及一些优化心得记录下来希望能给同样喜欢动手的你带来一些启发。这个项目的核心很简单用一块微控制器我选用的是Adafruit Feather M4 Express读取高精度实时时钟模块DS3231 RTC的时间然后通过编程控制一条垂直排列的NeoPixel LED灯带点亮对应的单词来显示当前时间。比如下午3点25分它会从上到下点亮“IT IS”、“TWENTY”、“FIVE”、“PAST”、“THREE”这几行字。整个系统由5V电源供电并且通过一个拨动开关实现了夏令时的一键切换功能。外壳则采用激光切割的亚克力面板和手工制作的瓦楞纸板支架成本低廉且效果出众。2. 硬件选型与电路设计解析2.1 核心控制器为什么是Feather M4 Express在开始动手前硬件的选择决定了项目的上限和难易程度。我选择了Adafruit的Feather M4 Express作为大脑主要基于以下几点考量首先性能足够且生态友好。Feather M4 Express采用了ATSAMD51 Cortex-M4内核运行频率高达120MHz对于驱动一条21颗LED的灯带和运行时间逻辑绰绰有余甚至为未来增加网络对时、亮度自动调节等高级功能留足了空间。更重要的是它原生支持CircuitPython。对于这类创意项目快速迭代比极致性能更重要。CircuitPython让你可以像操作U盘一样更新代码无需复杂的编译和烧录环境极大地降低了开发门槛。其次Feather生态系统的优势。Feather系列定义了统一的引脚排列和外形尺寸其配套的“FeatherWing”扩展板可以像积木一样堆叠。这正好完美匹配本项目需要的DS3231 Precision RTC FeatherWing。你不需要飞线只需将两块板子物理堆叠引脚就自动对齐并连通了既美观又可靠避免了面包板上一团乱麻的窘境。注意市面上也有更便宜的开发板比如某些ESP8266/ESP32开发板。它们功能更强大自带Wi-Fi但在CircuitPython下的RTC支持、引脚定义统一性上可能不如Feather生态完善。对于初次尝试、追求稳定复现的项目我强烈建议使用官方推荐的硬件组合能避开很多驱动和兼容性的“暗坑”。2.2 时间的守护者DS3231 RTC模块详解任何脱离网络的时钟精度都是灵魂。我们不可能每次断电后都重新手动对时。DS3231是一款超高精度的I2C接口实时时钟芯片它有几个关键特性支撑起了这个项目内置温度补偿晶体振荡器TCXO这是它高精度的秘密。普通的32.768kHz晶振会受温度影响产生漂移而DS3231能监测环境温度并动态补偿晶振频率年误差可控制在±2分钟以内远超普通DS1307等模块。内置电池备份模块上有一个CR1220纽扣电池座。当主电源断开时芯片自动由电池供电持续计时。这意味着你的时钟即使拔电搬家再插上时依然分秒不差。完整的日历和时钟功能提供秒、分、时、日、月、年信息甚至能自动处理闰年直到2100年都不需要调整。在电路中它通过I2CSCL和SDA两根线与Feather M4通信。在Feather标准引脚排列中SCL和SDA是固定引脚堆叠后自动连接无需我们操心。2.3 光影的画笔NeoPixel LED灯带选择显示部分我们使用的是Adafruit NeoPixel Side Light LED Strip。这里有几个细节需要特别注意侧发光Side Light设计这是本项目成功的关键。传统的NeoPixel灯带是正面发光光线直接射向观察者。而侧发光灯带的LED芯片位于灯带侧面光线是平行于亚克力面板方向照射的。当我们将灯带垂直紧贴亚克力背面时光线会沿着亚克力板内部传导并通过表面雕刻的单词缝隙均匀地散射出来形成柔和、无光斑的单词亮光。如果使用正面发光灯带你会看到一个个刺眼的光点而不是一个完整的发光单词。灯珠密度与间距项目使用的是60颗/米的规格灯珠中心间距约为16.7mm0.656英寸。这个间距直接决定了激光切割文件中单词行与行之间的距离。务必核对你购买的灯带型号是否与此间距匹配。如果间距不同你需要根据新间距重新设计亚克力板的雕刻文件否则单词和灯珠会对不齐。电源考量单颗NeoPixel全白最亮时约消耗60mA电流。21颗灯珠理论上最大电流为1.26A。我们为其配备了5V 2A的开关电源留有充足余量避免因供电不足导致灯带颜色失真或控制器重启。2.4 电路连接与供电方案整个系统的电路图逻辑清晰但焊接时需要一丝不苟。核心是建立一个灵活的供电系统电源输入一个5.5x2.1mm的DC插座连接到终端块接入5V 2A电源。同时从该终端块引出正极5V和负极GND线。主控供电将5V和GND线分别焊接到Feather M4 Express的USB引脚和GND引脚。这里有个关键点Feather M4的USB引脚内部与Micro USB口的5V相连。这样做的妙处在于当你通过Micro USB线连接电脑或充电宝时电源会通过USB引脚给整个系统供电当你使用DC插座接入5V适配器时电源同样通过USB引脚输入。两种方式可以无缝切换且DC插座的优先级通常更高插入后自动断开USB供电。灯带供电NeoPixel灯带的正极5V和负极GND同样直接连接到电源终端块上而不是接到Feather的引脚上。这是因为大电流负载如果经过开发板可能引起压降或损坏板载稳压器。灯带的数据线Din则连接到Feather M4的D5引脚。夏令时开关这是一个单刀双掷SPDT拨动开关。其中间引脚接地GND一侧引脚根据安装方向决定是左还是右连接到Feather M4的D6引脚该引脚在代码中被配置为上拉输入。当开关拨向该侧时D6引脚与GND接通变为低电平触发代码中的夏令时加一小时逻辑。实操心得焊接Feather和FeatherWing的排针时可以先将排针插入面包板固定再将电路板扣在上面焊接这样能保证所有针脚高度一致且垂直。焊接电源线到终端块时线头先镀锡再拧紧螺丝最后可以用万用表通断档检查一下避免虚接。3. 软件环境搭建与核心代码剖析3.1 CircuitPython初体验与库文件部署CircuitPython的魅力在于其极简的部署方式。首先访问Adafruit官网为你的Feather M4 Express下载最新的CircuitPython固件.uf2文件。按住板子上的复位按钮用USB线连接电脑此时电脑会识别出一个名为FEATHERBOOT的U盘。将下载的.uf2文件拖入该U盘板子会自动重启之后U盘名会变为CIRCUITPY。至此你的开发板就变成了一个Python解释器。接下来是关键一步安装必要的库文件。你需要从Adafruit的CircuitPython库包中将以下四个库文件夹复制到CIRCUITPY磁盘下的/lib目录中adafruit_bus_deviceI2C通信的基础支持。adafruit_ds3231DS3231 RTC芯片的专用驱动库。adafruit_register用于操作芯片内部寄存器。neopixel控制NeoPixel灯带的核心库。如果/lib目录不存在就新建一个。这一步经常被忽略导致代码运行时提示“No module named ‘adafruit_ds3231‘”。请务必确保库文件完整。3.2 时间设定让RTC记住此刻在运行主时钟程序前我们必须先给DS3231模块校准时间。这里需要单独运行一个“一次性”的设置程序。# SPDX-FileCopyrightText: 2019 Limor Fried for Adafruit Industries # SPDX-License-Identifier: MIT # 设置DS3231 RTC时间 import time import board import busio as io import digitalio import adafruit_ds3231 i2c io.I2C(board.SCL, board.SDA) rtc adafruit_ds3231.DS3231(i2c) LED13 digitalio.DigitalInOut(board.D13) LED13.direction digitalio.Direction.OUTPUT # 修改下面这行填入当前时间 # 格式(年, 月, 日, 时, 分, 秒, 周几, 一年中的第几天, 夏令时) # 周几0是周一6是周日。不知道可以填0。 # 一年中的第几天和夏令时填-1即可。 t time.struct_time((2024, 6, 3, 14, 30, 0, 0, -1, -1)) print(Setting time to:, t) rtc.datetime t # 将时间写入RTC芯片 print(Done!) LED13.value True # 点亮板载LED表示设置完成将上述代码保存为code.py并复制到CIRCUITPY磁盘。板子会自动运行串口会打印设置信息同时板载LED常亮。设置完成后务必删除或重命名这个文件然后我们上传主时钟代码。否则每次上电它都会重置时间。3.3 垂直单词时钟核心算法解析主程序的逻辑是项目的灵魂它完成了“时间数字”到“单词点亮”的映射。理解这段代码你就能掌握其精髓。1. 初始化与引脚配置 程序开头初始化了I2C通信、RTC对象、板载LEDD13用于状态指示以及连接夏令时开关的D6引脚配置为上拉输入默认高电平按下接地变为低电平。NeoPixel对象被初始化在D5引脚共21个像素对应21行单词亮度设为1.0最大auto_writeFalse意味着我们批量设置好所有灯的颜色后需要手动调用pixels.show()来统一更新显示这样更高效。2. 单词的“位图”编码 这是代码中最巧妙的设计之一。为了高效地表示哪一行单词该亮程序为每个单词分配了一个唯一的“位”bit。例如THREE 1二进制的0000 0000 0000 0000 0001EIGHT 1 1即0000 0000 0000 0000 0010。21个单词对应21个位可以组合在一个整数value中表示。如果THREE和PAST需要亮起那么value就是THREE | PAST按位或操作。这种方法的优点是判断和设置极其快速只需进行位运算。3. 核心函数writetime(the_hr, the_min) 这个函数接收24小时制的小时和分钟返回一个代表点亮单词的位图整数。分钟处理将0-59分钟划分为12个5分钟区间。例如if (the_min 3) and (the_min 8): value value | FIVEMIN表示时间在“4分到7分”之间点亮“FIVE”单词。后续逻辑依次点亮“TEN”、“QUARTER”、“TWENTY”等。对于“25分”the_min 22 and 28则需要同时点亮“TWENTY”和“FIVE”value value | TWENTY | FIVEMIN。“PAST”与“TO”的逻辑分钟数小于等于32时用“PAST”过大于等于33时用“TO”差并且需要将小时数加1。例如8:35会显示为“TWENTY FIVE TO NINE”差二十五分钟到九点。小时处理将24小时制转换为12小时制然后根据转换后的小时数点亮对应的单词位。0点午夜和12点中午有特殊的MIDNIGHT和NOON位。4. 主循环与显示更新 主循环每秒读取一次RTC时间。如果检测到夏令时开关被按下Slide_Switch.value为False则将小时数加1处理24点边界。当秒数为59或时间需要更新时程序先调用pixels.fill((0,0,0))熄灭所有灯然后调用writetime函数计算当前时间对应的位图。接着用一个循环遍历0到20的每一位如果位图中该位为1if the_time 1 i:则点亮对应的NeoPixelpixels[i1] COLOR。这里有个细节i1是因为代码中第0个像素被预留或隐藏了可能位于底座内部我们从索引1开始对应第一个单词。避坑指南代码中COLOR (0, 200, 0)定义的是绿色。NeoPixel使用RGB元组每个值范围是0-255。如果你想改成暖白色可以尝试(255, 150, 100)。切勿将亮度值brightness和颜色值混淆。brightness1.0是全局亮度调节乘以颜色值。如果你发现灯带颜色怪异或某些灯不亮首先检查数据线D5是否接好其次检查/lib目录下的neopixel库是否正确安装。4. 机械结构制作与组装实战4.1 激光切割文件解读与制作要点项目的“脸面”是一块激光切割的亚克力板。原作者提供了Adobe Illustrator (.ai)格式的文件。文件的核心是两大部分雕刻Engrave所有的时间单词如“IT IS”、“FIVE”、“PAST”、“TEN”等是采用激光雕刻的雕刻区域会让亚克力内部产生微小的散射点从而使透过的光线变得柔和均匀形成发光的文字。切割Cut外轮廓以及可能用于固定的卡槽是通过激光切割彻底切透的。如果你没有激光切割机可以去淘宝、京东等平台搜索“激光切割定制”服务将文件发给商家。沟通时务必说明材料推荐使用3mm厚度的白色透光亚克力。白色能更好地散射光线让单词发光更均匀遮挡住灯珠的颗粒感。工艺文字部分为雕刻轮廓部分为切割。文件处理确认商家能处理.ai文件并询问是否需要你提供DXF或PDF格式。通常专业的激光切割服务商都能直接处理.ai文件。4.2 瓦楞纸板支架的精细制作用瓦楞纸板做支架是个低成本且效果出色的方案但制作精度直接影响最终显示效果。1. LED灯带通道的制作裁剪三条宽度略大于NeoPixel灯带宽度约10mm、长度与亚克力板高度相当的瓦楞纸板条。将三条纸板叠起来用热熔胶粘合成一个“U”型槽。这个槽的深度要确保灯带能完全嵌入且LED发光面侧面与槽口齐平。关键测试粘合前务必用灯带实物放入槽中比划确保灯带能平整放入且每一颗LED都能对准未来亚克力板上对应的单词行中心。你可以用尺子测量灯珠间距应为16.7mm并在纸板上做标记作为粘合时的参考线。2. 时钟底座的制作裁剪三块矩形纸板作为底座的主体。其中两块需要挖一个方形小孔用于穿过灯带的导线和FPC软排线如果你用的是带连接器的灯带。第三块则需要切割出一个与灯带通道宽度匹配的细长通道。将这三块纸板堆叠粘合形成一个有内部通道和穿线孔的稳固底座。穿线孔的位置要规划好确保灯带电源线和数据线能顺利引出连接到后方的控制器上。3. 总装与对位在底座前方粘上两条窄纸板形成一个卡槽用于固定亚克力板的下边缘。将LED灯带通道垂直粘在底座上方确保其中心线与底座上的穿线通道对齐。这是最重要的对位步骤你需要将亚克力板暂时插入底座的卡槽然后透过亚克力板观察调整灯带通道的位置使得每一颗LED都精确地位于对应单词行的背后中心。可以用手电筒从侧面打光辅助观察。将NeoPixel灯带从底座下方穿入沿着通道向上铺展用一点点热熔胶在背面固定几个点。注意代码中忽略了第一个像素pixels[0]所以最底部那颗藏在底座里的LED不发光是正常的。最后将亚克力板插入卡槽在顶部轻轻点一滴热熔胶使其紧贴灯带通道。胶点要小避免影响美观。安全与技巧使用热熔胶枪时小心烫伤。粘合纸板时胶要涂在接触面边缘形成“胶线”而不是在面中心点一下这样强度更高。组装过程中随时用USB供电测试确保每行单词都能正确点亮便于及时调整灯带位置。5. 调试、优化与扩展思路5.1 常见问题排查速查表即使按照步骤操作也可能会遇到一些小问题。下面是我在制作和帮助他人时总结的常见故障及解决方法问题现象可能原因排查步骤与解决方案上电后无任何反应LED不亮1. 电源未接通2. 5V电源损坏3. 主板损坏1. 检查DC插座或USB线连接是否牢固开关电源指示灯是否亮起。2. 用万用表测量电源终端块是否有5V输出。3. 尝试单独给Feather M4插USB线看板载LEDD13是否闪烁CircuitPython运行指示。灯带只亮起第一颗或几颗后面不亮1. 数据线DIN连接错误或虚焊2. 电源功率不足3. 灯带损坏1. 检查Feather M4的D5引脚到灯带DIN的连线。确保是DIN不是DOUT。2. 尝试用手机充电器5V/2A通过USB口直接供电排除DC电源问题。3. 用一根导线短暂地将第一颗不亮的LED的DI引脚与前一颗LED的DO引脚短路如果它能亮了说明前一颗到它的信号线断了。单词显示的时间不正确1. RTC时间未正确设置2. 夏令时开关逻辑反了3. 时区概念混淆1. 重新运行一次时间设置程序并确认code.py文件已替换为主时钟程序。2. 检查开关焊接拨动开关用万用表通断档测量D6引脚是否对地导通。在代码中开关按下接地是加一小时。3. RTC存储的是UTC时间代码直接读取显示。如果你在中国需要手动在设置时间时加上8小时例如北京时间14:30应设置为(2024,6,3,6,30,0,...)。单词发光不均匀有光斑1. 灯带与亚克力板距离不当2. 未使用侧发光灯带3. 亚克力板材质问题1. 确保灯带紧密、平行地贴在亚克力板背面且发光侧朝向板材。2.必须使用侧发光Side Light灯带正面发光灯带无法实现此效果。3. 使用白色雾状亚克力不要用透明或彩色透明的。代码上传后不运行1. 文件未命名为code.py2. 库文件缺失3. 代码语法错误1. 确认CIRCUITPY磁盘根目录下的主程序文件名为code.py注意扩展名。2. 检查/lib目录下是否有neopixel等四个必需的库文件夹。3. 使用Mu编辑器打开代码查看下方是否有语法错误提示红色波浪线。5.2 个性化优化与功能扩展基础功能实现后你可以根据自己的喜好进行改造色彩与动态效果修改COLOR变量可以改变静态颜色。你还可以让颜色随时间渐变。在主循环中可以根据t.tm_hour小时来动态计算HSV或RGB值实现清晨淡蓝、中午纯白、傍晚暖黄的效果。自动亮度调节增加一个光敏电阻连接到模拟输入引脚读取环境光强度然后动态调整pixels.brightness的值范围0.0-1.0让时钟在夜晚自动变暗。网络对时NTP如果你换用带有Wi-Fi功能的开发板如ESP32-S2/S3的Feather可以编写代码让时钟每隔一段时间连接Wi-Fi从网络时间服务器NTP获取精确时间并同步到DS3231实现永远精准。外壳升级将瓦楞纸板升级为3D打印或激光切割的木质外壳质感会提升好几个档次。设计时注意留出散热孔和走线空间。多语言支持核心在于修改writetime函数中的单词映射逻辑和位定义。你可以创建中文、西班牙语等不同语言的单词位图数组并通过另一个开关或按键进行切换。这个垂直单词时钟项目从构思到点亮融合了硬件、软件和手工的乐趣。它摆在桌上不仅是一个实用的时间工具更是一个不断提醒你“时间以文字的形式流淌”的创意作品。我最享受的时刻就是在深夜看着它发出柔和的微光静静地拼写出“IT IS HALF PAST TEN”那一刻仿佛时间都有了形状和温度。希望你的制作过程也一样充满惊喜。