基于透射全息与ESP32的全息时钟:从光学原理到工程实现
1. 项目概述与核心思路
我一直对全息技术着迷,它不仅仅是科幻电影里的炫酷特效,更是一门严谨的光学工程。市面上的“伪全息”方案,比如佩珀尔幻象或者基于视觉暂留的旋转LED,虽然效果不错,但总感觉少了点“真东西”的灵魂。所以,当决定动手做一个全息时钟时,我的目标很明确:做一个基于真实透射全息图的、能显示数字的玩意儿,让每个数字段都像真的悬浮在空中一样。
这个项目的核心,是把传统的7段数码管显示,用全息技术重新“翻译”一遍。传统数码管是物理发光,而这里,每个数字段(a到g)都是一个独立的透射全息图。这些全息图被记录在特殊的感光板上,当用特定角度的白光LED从背后照射时,就能再现出记录时“物体”(一个发光的灯罩薄膜)的光场,看起来就像光从板子后面凭空“长”了出来。整个系统的大脑是一块ESP32开发板,它负责从网络获取精确时间,并控制28个LED(4位数,每段1个)的亮灭,从而驱动这些全息“像素”显示出当前的时分。
听起来复杂,但拆解开来,无非是光学、机械、电子和固件四个部分的精密耦合。光学部分负责“造像”,机械结构负责“固定”,电子部分负责“驱动”,固件负责“指挥”。下面,我就把这几个月从原理摸索到实践落地的全过程,包括踩过的坑和总结的经验,毫无保留地分享出来。
2. 全息原理与Litiholo套件初探
2.1 透射全息图的核心原理
在动手之前,必须搞清楚我们到底在做什么。全息术(Holography)的本质是记录并再现物体的完整光波前,包括振幅和相位信息。这不同于普通照相,后者只记录强度(振幅)。
我们制作的是透射全息图。其基本原理分为两步:记录和再现。
记录过程:需要两束相干的激光(通常来自同一激光器)。一束叫物光,它照射到物体上,经物体反射或透射后,携带着物体的信息到达全息干板。另一束叫参考光,它直接照射到干板上。这两束光在干板处相遇,发生干涉,形成极其精细的、明暗相间的干涉条纹图案。这个图案被干板上的感光材料记录了下来。它记录的不仅是光的强弱,更是两束光相遇时的相位差,这个相位差编码了物体的三维信息。
再现过程:用一束与参考光相同波长和角度的光(称为再现光)照射制作好的全息图。全息图上记录的干涉条纹相当于一个复杂的光栅,当再现光通过时,会发生衍射。其中一级衍射光波前,恰好与当初的物光波前一模一样。当我们的眼睛接收到这个重建的波前时,就会“看到”原来物体所在的位置有一个立体的虚像,仿佛物体就在那里。
在这个时钟项目中,我们的“物体”是一个被均匀照亮的、特定形状(7段数码管的段形状)的漫射体(灯罩薄膜)。我们记录下它的光波前。再现时,我们用普通的白光LED去照射全息图,由于LED光谱较宽,我们看到的再现像会带有彩虹色,这正是白光再现透射全息图的特征之一。
注意:全息记录对稳定性要求极高。任何微小的振动(甚至声波)导致光路变化超过半个波长(对于红光约316纳米),干涉条纹就会模糊,记录就会失败。这就是为什么实验常在深夜、光学平台上进行。
2.2 Litiholo套件实战与避坑指南
对于没有光学实验背景的爱好者,Litiholo的套件是一个绝佳的入门选择。它把复杂的激光器、光学元件和化学药水都打包好了,让你能专注于体验全息记录的过程。
我拿到套件后,严格按照说明书做了第一次测试。套件里包含一个玩具小车作为物体。过程大致是:在暗室环境(套件配了暗袋),将激光器和干板架设好,保持绝对安静和静止约几分钟进行曝光,然后用配套的药水进行显影、定影。
第一次尝试就成功了,这让我有点意外。因为套件里的激光器只是个简单的二极管激光,用金属弹簧散热,支架也是亚克力激光切割的,非常“业余”。但这恰恰证明,在严格控制振动和环境光的前提下,入门级设备也能做出真正的全息图。
关键操作心得:
- 预热激光器:即使是这种简易激光器,开机后亮度也会有一个短暂的稳定过程。我的习惯是提前打开至少5-10分钟,再进行光路调整和曝光。
- 绝对黑暗:任何杂散光都是敌人。确保暗袋拉链完全闭合,或者在一个真正的暗房里操作。曝光过程中,连手机屏幕的光都不能有。
- “一动不如一静”:调整好光路、放好干板后,最好的姿势就是保持不动,甚至屏住呼吸几秒钟,直到曝光完成。地面震动是最大的威胁,远离走动的人和马路。
- 药水温度:显影和定影药水对温度敏感。室温(20-25°C)下操作效果最好。太冷反应慢,太热可能使药水失效或损伤乳剂。
这次成功给了我极大的信心,但也让我意识到套件的局限性:它的支架是为标准物体设计的,无法方便地录制我们需要的、多个特定形状且位置精确的“段”。因此,自定义光路和机械结构势在必行。
3. 定制化全息记录系统搭建
3.1 光路设计与机械结构
为了录制7段数码管形状的全息图,我需要一个可重复、可精确定位的光学系统。套件的水平布局不适合,我设计了一个垂直光路。
整个系统核心是一个3D打印的黑色支架。激光器从底部以45度角向上照射。在光路中,自上而下依次是:
- 全息干板:感光面朝下,面向激光。
- 物体玻璃板:位于干板正下方,上面贴有切成段形状的白色自粘灯罩薄膜。这层薄膜的作用是作为一个理想的漫射体,将激光均匀地散射开,照亮整个“段”的形状。
- 激光束:从下方45度角入射,同时照亮干板和物体。
为什么选择垂直布局和45度角?垂直布局使得放置和更换干板、物体板更加方便,也更容易实现旋转。45度角是透射全息中一个常见的参考光角度,能在记录效率和再现像亮度之间取得较好的平衡。更重要的是,后续用LED再现时,我们也需要从类似的角度照射,垂直布局为LED的安装提供了天然的空间。
机械设计要点:
- 材料:全部使用哑光黑色PLA打印。黑色是为了最大限度地吸收杂散光,防止其在内部反射形成噪声;哑光表面进一步防止镜面反射。
- 稳定性:结构件设计得足够厚重,连接处采用卡扣和螺丝双重固定,确保在长时间曝光中不会因自身应力产生形变或晃动。
- 旋转机构:干板架和物体板架必须能作为一个整体,在水平面内进行精确的旋转。我设计了带刻度的旋转盘,每次旋转51.4度(360/7),以确保7个段能均匀地记录在一张干板上。旋转的中心必须与光路中心对齐,否则再现时各段的位置会错乱。
3.2 分段记录流程与实战细节
一张全息干板上要记录7个独立的段,这需要用到空间复用技术。简单说,就是每次只让干板的一小部分感光,记录一个段,其他部分用遮光板(光圈)挡住。
详细记录步骤(一次深呼吸般的操作):
- 激光预热:打开激光器,稳定至少5分钟。
- 预对齐:在不放入干板的情况下,打开室内微弱的蓝光LED(全息干板对蓝光不敏感),调整激光光斑,确保它能均匀覆盖物体板上的“段”形状区域。
- 黑暗准备:关闭所有灯,只留那盏对着墙的微弱蓝光。用黑色卡纸挡住激光束。
- 放置干板:在暗袋或全黑环境下,取出全新的全息干板(注意不要触碰感光面),以正确方向放入支架。干板有玻璃的一面通常朝外(背对激光),感光乳剂面朝内(对着物体)。
- 绝对黑暗:关闭蓝光LED。此时应伸手不见五指。
- 开始曝光:迅速而平稳地移开遮挡激光的卡纸。开始计时(我的设置是5分钟)。这5分钟内,你必须像雕塑一样保持静止。任何咳嗽、脚步声都可能导致前功尽弃。
- 结束曝光:时间到,迅速用卡纸挡回激光束。打开蓝光LED。
- 旋转准备:按照预定顺序(例如从段“a”开始),将干板架和物体板架整体旋转到下一个位置。调整光圈,只让下一个“段”对应的干板区域暴露。
- 重复:重复步骤5-8,直到7个段全部记录完毕。
- 化学处理:将曝光后的干板在完全黑暗中取出,立即进行显影、漂白、定影、水洗和干燥。Litiholo的套件药水是一步到位的,简化了流程。
核心难点与技巧:
- 振动隔离:我选择在凌晨后操作,并将整个装置放在厚重的花岗岩板上,石板下垫了海绵。这能有效隔离大部分建筑和环境振动。
- 激光稳定性:我后来换用了工作用的单频红色激光二极管,并加了恒流驱动和更大的散热片,光强和波长稳定性远好于套件激光器。但对于套件激光,确保供电电压稳定是关键。
- 光圈制作:我用黑色卡纸手工切割了精确的开口。更优的方案是使用光阑,但卡纸成本低,易于调整。关键是边缘要整齐,不能有毛边衍射。
4. 时钟的机械与电子系统实现
4.1 结构设计与3D打印
时钟的外壳和内部支架全部由哑光黑色PLA通过FDM 3D打印机完成。设计上分为左右两个完全对称的“半钟”,每个半钟容纳两位数字。这样设计便于打印、组装和维修。
主要结构部件:
- 底座板:承载所有其他部件的基板。两个半钟的底座板通过燕尾榫结构咬合,无需螺丝即可牢固连接,且保证了拼接的精度。
- 全息板支架:用于固定四片录制好的全息干板。设计有卡槽,确保干板以精确的角度和位置插入,这是再现像能否对齐的关键。
- LED支架/PCB背板:用于安装定制PCB和LED。每个数字对应一块背板,上面有精确的孔位,确保28个LED的每一个都能对准其对应的全息“段”的中心。背板通过卡扣和螺丝固定在底座上。
- 顶盖:覆盖住ESP32主控板和驱动板,起保护和美观作用。
- 冒号支架:位于两半钟中间,用于安装两个作为时间分隔冒号“:”的LED。这个部件我用双色打印完成,主体黑色,光导部分使用透明PLA,让光更柔和。
打印与后处理经验:
- 层高与填充:使用0.15mm层高以获得更光滑的表面,尤其是与全息板接触的斜面。填充率25%足够保证强度,同时节省材料和时间。
- 防止翘曲:打印大面积底板时,一定要使用热床并确保粘贴牢固(我用的美纹纸胶带加胶水),否则边缘翘曲会导致整个结构不平。
- 哑光黑的选择:普通的黑色PLA打印出来也有光泽,会反射杂散光干扰全息像。务必选择真正的哑光(Matte)黑色PLA,或者对成品进行哑光喷漆处理。
4.2 定制PCB设计与LED驱动逻辑
用28根飞线去控制28个LED是一场噩梦,不仅混乱,而且可靠性极差。设计定制PCB是让项目从“实验原型”走向“成品”的关键一步。
PCB设计思路: 我的目标是复现商用4位7段数码管的电气连接方式,这样就能直接使用现成的驱动芯片和库。商用数码管内部采用动态扫描(多路复用)来减少引脚使用。对于共阴极数码管,所有段的阴极连接在一起(位选),而每个段的阳极是独立的。
我将这个逻辑移植到PCB上:
- 每块PCB对应一个数字位,上面焊接7个LED(对应a-g段)。
- 这7个LED的阴极(负极)全部连接在一起,引出一个“位选”引脚。
- 每个LED的阳极(正极)单独引出,连接到“段选”引脚。
- PCB边缘设计成排母接口,这样两块PCB可以像积木一样堆叠插接,位选和段选信号通过排针/排母自动级联,完全省去了连接两个数字位之间的导线。
设计踩坑实录: 第一版PCB犯了一个低级错误:我把LED的封装画反了!导致LED安装后极性颠倒。无奈之下,只能用刀在PCB上割线,再用飞线修补,非常狼狈。教训:在投板生产前,务必用实物LED在打印出来的1:1图纸上比对,或者使用EDA软件的3D预览功能反复检查封装方向。
4.3 主控与驱动电路集成
主控选择了Adafruit的Feather ESP32 V2。选择它有三个原因:1) 体积小巧,集成度高;2) ESP32自带Wi-Fi,便于实现NTP网络授时;3) Adafruit为其提供了完美的配套组件——4-Digit 7-Segment LED Matrix Display FeatherWing。
这个“翅膀板”本质上是一个已经焊好HT16K33驱动芯片的转接板。HT16K33正是专门驱动LED点阵/数码管的I2C芯片,它内部集成了扫描逻辑和显存,我们只需要通过I2C告诉它哪个LED该亮,它就会自动处理多路复用的繁琐细节,大大减轻了MCU的负担。
连接方式:
- 将FeatherWing插到Feather ESP32主板上。
- FeatherWing上引出了标准的14引脚7段数码管接口(尽管我们只用到其中一部分)。
- 我们自制的、模仿商用数码管接口的PCB,通过杜邦线连接到这个FeatherWing的对应引脚上。
这样一来,从软件角度看,我们的“全息数码管”和一块普通的4位红色7段数码管没有任何区别!所有复杂的LED控制逻辑都被硬件抽象掉了。
电源考虑:28个白光LED全亮时电流不小。ESP32的USB口或3.3V引脚可能无法提供足够电流。我的方案是,PCB的VCC直接从外部5V电源接入,而位选/段选信号来自FeatherWing(3.3V电平)。HT16K33是开漏输出,可以承受5V,因此用3.3V信号控制5V供电的LED完全没有问题。只需在PCB的VCC和GND之间加一个滤波电容即可。
5. 软件实现与网络授时
5.1 开发环境与库依赖
代码在Arduino IDE中开发,主要依赖以下库,这些库都可以通过Arduino的库管理器轻松安装:
- Adafruit_LEDBackpack:用于控制HT16K33驱动芯片,这是显示的核心。
- Adafruit_GFX:LEDBackpack库的图形基础依赖。
- WiFiManager:一个极其好用的库。它让设备在首次启动时进入AP模式,你可以用手机连接到一个特定的Wi-Fi热点,然后通过网页配置它连接到你家的Wi-Fi。无需在代码里硬编码SSID和密码。
- NTPClient:用于从网络时间协议服务器获取精确的UTC时间。
5.2 核心代码逻辑剖析
代码结构清晰,主要包含初始化、Wi-Fi连接、时间获取和显示刷新几个部分。
#include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_LEDBackpack.h> #include <WiFiManager.h> #include <NTPClient.h> #include <WiFiUdp.h> Adafruit_7segment matrix = Adafruit_7segment(); // 创建显示对象 WiFiUDP ntpUDP; // 设置NTP服务器和时区偏移(例如,东八区为8*3600秒) NTPClient timeClient(ntpUDP, "pool.ntp.org", 8*3600, 60000); void setup() { Serial.begin(115200); matrix.begin(0x70); // HT16K33的I2C地址通常是0x70 // 使用WiFiManager WiFiManager wm; // 如果配置失败(比如第一次启动),它会自动进入AP模式 bool res = wm.autoConnect("HoloClockAP", "password"); if(!res) { Serial.println("Failed to connect"); // 这里可以增加一个错误显示,比如显示“----” ESP.restart(); } timeClient.begin(); // 启动NTP客户端 matrix.println("INIT"); // 显示初始化完成提示 matrix.writeDisplay(); delay(1000); } void loop() { timeClient.update(); // 更新NTP时间 int currentHour = timeClient.getHours(); int currentMinute = timeClient.getMinutes(); // 将时分组合成一个四位数,例如14:35 -> 1435 int displayNumber = (currentHour * 100) + currentMinute; // 打印到数码管,drawColon参数控制中间的冒号点亮 matrix.print(displayNumber, DEC); matrix.drawColon(true); // 点亮冒号 matrix.writeDisplay(); // 将缓存写入驱动芯片,真正点亮LED delay(1000); // 每秒更新一次 }关键点解析:
- 时区处理:
NTPClient的第三个参数是时区偏移,以秒为单位。8*3600即代表UTC+8(北京时间)。这是需要根据你所在地修改的唯一参数。 - 显示格式:
matrix.print()函数会将一个整数显示在4位数码管上。我们将小时和分钟合并成一个四位数,非常巧妙。例如下午2点35分,就是1435。 - 冒号控制:
drawColon(true/false)是Adafruit_7segment库提供的特有函数,用于控制中间冒号的亮灭。我们可以让它每秒闪烁一次,增加生动性。 - 低功耗考虑:在实际代码中,我添加了在整点后的一段时间内调低LED亮度(通过
matrix.setBrightness())的逻辑,以减少夜间光污染。
5.3 调试与测试模式
在组装完成但尚未连接网络时,一个测试模式非常有用。我写了一个简单的计数器循环,让数字从0000滚动到9999,这样可以快速验证所有28个LED及其对应的全息段是否工作正常,有无连接错误。
void testAllSegments() { for (int i = 0; i < 10000; i++) { matrix.print(i); matrix.writeDisplay(); delay(50); } }6. 总装、调试与效果优化
6.1 系统集成与布线
这是最需要耐心的一步,顺序很重要:
- 焊接LED与PCB:先将LED插入3D打印的支架孔中,注意正负极方向(通常LED长脚为正)。然后将定制PCB对准引脚扣上,从背面焊接。焊完一个数字位后,立刻通电测试这7个段是否正常。
- 连接两个数字位:通过排针排母将两块代表十位和个位的PCB连接起来。确保连接牢固。
- 安装机械结构:将两个半钟的底座通过燕尾榫合体。依次插入全息板支架、已焊接LED的PCB背板。
- 安装主控:将Feather ESP32与FeatherWing组合体放入底座的预留位置,盖上顶盖。
- 飞线连接:这是最繁琐的。需要用杜邦线将4块PCB(代表4个数字位)的14个引脚(7段选+4位选+电源+地)连接到FeatherWing的对应插口。强烈建议:
- 使用不同颜色的线区分功能(如红色VCC,黑色GND,其他颜色对应段)。
- 先根据原理图制作一个连接表,每接一根线划掉一项。
- 线缆留出合适余量,用细扎带分组捆扎,固定在底座内侧,避免杂乱。
6.2 效果评估与问题排查
组装完成后通电,你可能会遇到以下典型问题及解决方案:
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 某个数字位的所有段都不亮 | 该位PCB的位选(共阴极)信号未接通或短路 | 检查连接该位“位选”引脚到FeatherWing的导线。用万用表通断档测量。 |
| 所有数字的同一段都不亮(如所有“a”段) | 该段的段选信号线断路 | 检查连接该“段选”引脚到FeatherWing的导线。 |
| 某个LED常亮或不受控 | LED正负极接反;或PCB上该LED的驱动三极管/线路短路 | 断电检查焊接点是否有桥接。确认LED方向。 |
| 显示的数字乱码或部分段异常亮 | I2C地址冲突或通信不良;导线接触不良导致信号串扰 | 确认HT16K33地址正确(通常0x70)。检查I2C(SDA, SCL)连接是否牢固。重新插拔所有杜邦线。 |
| 全息像暗淡、模糊或有重影 | LED与记录时激光的角度/位置不匹配;环境光太强 | 微调LED支架的角度,确保LED光斑能均匀覆盖全息图对应区域。在较暗环境下观看。 |
| 再现像有彩虹色,但边缘发虚 | 记录时物体(灯罩薄膜)与干板距离或角度有误差;LED不是点光源 | 这是系统固有局限。尝试使用更小尺寸、发光角度更集中的LED。确保记录时光路严格垂直。 |
关于LED颜色的选择:我最初使用了白光LED,因为它能产生彩虹色的全息像,看起来很“科幻”。但实测下来,红色LED的亮度和对比度远超白光LED。因为全息干板对记录时使用的红色激光最敏感,用同色系的红色LED再现,衍射效率最高,像最亮。白光LED光谱宽,只有其中红色成分被有效衍射,其他颜色成分成了背景杂光,降低了对比度。所以,如果追求最佳显示效果,强烈推荐使用红色LED。
6.3 光学像质提升的思考
在最终成品中,垂直的段看起来有些倾斜或变形。这暴露了本项目的一个根本性挑战:再现光源与记录光源的匹配问题。
- 记录时:使用的是高度准直、方向单一的激光。
- 再现时:使用的是发散角较大、尺寸非理想的LED。
LED发出的光不是从一个理想的点发出,而是从一个面发出,且光线发散。这导致再现光波前与参考光波前不完全一致,从而引起像差,表现为图像模糊、变形或位置偏移。
理论上的改进方向:
- 使用更小的LED:选择芯片尺寸更小的LED,使其更接近点光源。
- 添加准直透镜:在每个LED前增加一个微型准直透镜,使发出的光更接近平行光。这会大幅增加硬件复杂度和调校难度。
- 在记录光路中引入透镜:这是原作者在文末提到的进阶想法。如果在记录时,让物光先通过一个透镜(例如,让物体位于透镜焦点,产生平行光;或位于两倍焦距,产生倒立实像),那么再现时,像的位置和清晰度会对光源的位置和大小不那么敏感,容错率更高。但这需要更精密的光学设计和调试。
尽管存在这些光学上的不完美,但这个项目成功地验证了将真实全息技术应用于动态显示的可能性。当你在昏暗的房间里,看到几个发着微光、仿佛悬浮在玻璃板后的数字静静跳动时,那种融合了精密工程与奇妙光学的成就感,是任何普通显示器都无法给予的。
整个项目从光学原理学习开始,历经机械设计、电路板绘制、嵌入式编程,到最后的总装调试,是一次完整的跨学科工程实践。它教会我的不仅是技术,更是一种解决问题的方法论:将复杂目标分解为可实现的模块,尊重每一门学科(光学、机械、电子)的基本原理,在妥协中寻找最优解。
