创客 Tim Alex Jacobs 制作了一款电池供电的茶蜡烛,它实际上是一个 3D 立体显示器,旋转速度足够快,可以利用视觉暂留 (POV) 效果来创建悬停图像。
项目来自:POV Candle
作者: Tim Alex Jacobs
注:全文采用Google翻译,因此部分语句中会存在着不通顺或不正确的专业用语,不过不影响对该项目的介绍。
创意:
最近,我很幸运地发现自己和一些非常有创意和才华横溢的人一起在酒吧里讨论转向电子蜡烛,以及如何创造出从任何角度看起来都像闪烁蜡烛的东西。 我建议使用视觉暂留显示器,但普遍的共识是 - 这些显示器需要太多支撑机械比如轴承,可能还有滑环等才能使其工作。
后来我想了想,如果电机和电池足够小,整个东西就可以旋转。 第二天我在网上订购了一些电路板,包括一个简单的LED矩阵板。
不久前,我接触到了一台贴片机(Charmhigh CHM-T36VA),可以一直租用它。 使用它给我的感受就是:机器人是未来。 我一生中已经花了很多时间手动组装电路板,拥有一台可以在几秒钟内构建电路板的机器是一件非常幸福的事情。
它的一个缺点是装载卷轴需要很长时间,对于每个单独的器件,都必须繁琐地加载卷轴,有时可能需要 20 分钟。 我当初租用这个机器来加工的电路总计有26个不同的器件,但此加载卷轴需要一整天的工作。
然而,这个LED矩阵只有一个器件(LED),因此装载卷轴的时间在技术上尽可能短。 然后我就可以以极快的速度生产出板子来了!
我没有得到合适的模板,我只是用醋酸纤维激光蚀刻了一个。 这个项目很大程度上还处于最低投资阶段,我只是闲着无聊地提出想法。 但通用的微型 LED 矩阵似乎是值得拥有的东西,它几乎肯定会派上用场。
我用0603做了一些,也用0805做了一些,因为我已经加载了其中一些。
如果电路板设计不是很仓促的话,我也会制作一个圆形PCB来以直角支撑它,底部的焊盘直接焊接在板之间。 我打算在下一个版本时这样做。
现在我只是在验证想法。 我知道我想要一个带有大量闪存的微控制器,因为我们可能需要大量的体积视频数据。 我很倾向于选用树莓派Pico,它是双核 125MHz(或更高)和高达16MB 闪存(重要的是,它很便宜)。 Pico的主要缺点之一是单独使用裸露的RP2040 芯片很痛苦。 它没有板载闪存,因此至少您需要连接一个QSPI闪存芯片,并且几乎总是需要一个外部晶体和相当数量的支持电容。 Pico 板本身对于我们的情况来说太大了。
然而,很多人已经生产了更小的RP2040板来满足这种情况。 其中大多数都不合适,要么太大,要么没有足够的GPIO。 我发现了一款看起来很有前途的产品,名为 Waveshare RP2040-tiny。 在这里,他们基本上将pico板切成两半,将最少的部分放在主板上,并有一个辅助板,通过扁平柔性电缆连接,带有USB端口、复位和启动按键。
这对于我们的原型来说似乎是完美的。 它仍然有点太大,并且仍然没有引出所有GPIO,但应该能够满足我们的要求。
至于电池,我弄了一块LIR2450,它是一颗锂离子充电电池,可提供超过100mA的电流。 也有更小的锂离子电池,但它们的容量和电流能力会大大降低。 我还对LIR2032电池闲置感到有点紧张,因为我觉得我会不小心将一块电池放入 CR2032(相同物理尺寸)的东西中,并可能损坏它(LIR 电池充满电为4.2V)。 此外,RP2040 板的对角线长度约为29毫米,因此使用较小的电池并不会让最终结果变得更小。
我用 PETG 3D 打印了一个很小的的电池支架。
我这里确实太节俭了,打印厚度为0.5mm。 它分为两部分打印,顶部粘在上面。 我认为将所有部件加厚并以90度一次打印会更有意义,后面我就采用这种方式。 这个 3D 打印部件绝对是败笔,每次我的样机掉地上的时候它都会破裂,我必须将其重新粘在一起。
无论如何,我们已经完成了第一个模型:
这是我焊接的 TCRT5000 红外传感器,带有微小的表面贴装电阻。 传感器有点大,但这就是我所需要的。 输出是模拟的,检测器只是一个光电二极管,但我们可以使用上拉电阻并将其直接连接到GPIO引脚。 RP2040的输入端有施密特触发器(可以通过软件禁用),因此我们基本上免费获得了一个比较器。
板上有一个 WS2812 LED,连接到GPIO16。 我宁愿为我的LED矩阵增加一个GPIO 引脚,所以我折断了LED并在其上焊接了一些漆包线。 我不确定是否需要这样做,但现在是我们这样做的唯一机会。
然后我开始焊接LED矩阵板。 矩阵底部的标签的想法是在我连接它的板上有插槽,这将使所有东西保持对齐。 相反,它们似乎可以很好地作为小腿,将电路板保持在组件上方。 我将实心线焊接到焊盘上并将其连接起来,以便最终所有东西都非常牢固。 可以纠正木板之间的角度,但需要足够的力,不会意外发生。
这已经完成了一半的连接,背面还有另外十根电线要焊接,但在我们这样做之前,我想连接电机。
我有一些尺寸大致合适的电机。 我选择的型号是 RF-410CA。 CD和DVD驱动器中的大多数类似电机的直径和轴长度都略有不同。 我也考虑过RPM,大多数这些电机的空载转速为5000至 10000RPM,这太快了。 我们可以通过脉宽调制来降低速度,但这也说明了它们的启动扭矩。 为了达到 30FPS,我需要 1800的 RPM。这里很难做出决定,因为一旦它开始旋转,就会产生空气阻力,并且它可能会达到某种平衡。 呃,如果电机不好的话我们可以稍后再把它关掉。
我在我们的作品上安装了一个小型 sot-23 MOSFET和一个反激二极管。
IR LED 直接连接至电源线。 理想情况下,我们可以对其进行软件控制,以在它不旋转时节省电量,但对于这个原型,我不想在其上浪费GPIO。 LED矩阵为8x10,即 18 个 GPIO,加上1个用于传感器输入和1个用于电机控制,我还想保留1个用于监控电池电压。 我认为可以在 RP2040小板上连接更多的GPIO,这些电路板已被分解为“用户模式选择板”,但当前的思考过程是不要担心,尽快将其变成可能的工作原型。
请注意,我将LED矩阵直接连接到GPIO引脚。 没有电流限制或驱动晶体管。 RP2040的所有GPIO总共可输出/吸收约50mA的电流。 我了解到从微控制器驱动LED的危险,但非常吸引人的方法是跳过限流电阻并使用PWM驱动它们。 GPIO引脚固有的导通电阻足以限制电流——可能达不到连续照明的安全水平,但可以预见, 只要限制占空比就可以了。 在这里,我们将非常快速地闪烁所有矩阵,并对其进行大量仔细的计时。 我认为我们不太可能过度驱动LED,除非芯片崩溃并且矩阵冻结。
我用漆包线连接了矩阵的其余部分,将其穿在下面。 我们不需要任何进一步的刚性,这样看起来很酷。
裸露的电线有一种令人惊讶的吸引力。 它有一种赛博朋克的氛围。 当我第一次使用棋盘测试模式为LED矩阵供电时,这种氛围才得到增强。
我将 3D打印的电池座粘在电机上,并用柔软的3M胶带将原型的其余部分固定起来。 然后我必须缩短电机的电线并连接电池端子。
我将电池的正极端子直接连接到电路板的VBUS。 RP2040位于3.3V稳压器后面,但在连接电池的情况下,这意味着连接USB电缆将使电池端子上产生5V电压。 我们可以稍后再担心。 现在我只想知道这个 hacky 原型是否有工作的希望。
有趣的是,一旦我将其配置为响应红外光电二极管而点亮矩阵,只要相机闪光灯闪光,它就会点亮矩阵。
拆下电池后,您可以看到它的连接,只是将一些漆包线剥去并镀锡并穿过几个小孔。 此时电池已积极工作, 只有在它跌落几次并且塑料破裂后,我才需要使用橡皮筋将其固定到位。
我在背面钻了一个小孔,以便能够弹出电池,只需用尖头的东西将其戳出来即可。
软件
我们监控红外传感器并使用触发之间的时间来设置矩阵显示的速度。 除了另一个维度外,这些都是视觉暂留显示的常见内容。
我喜欢 RP2040的一个原因是可以在同一时钟周期内设置(或输入)每个GPIO引脚。 STM32 芯片尽管具有32位处理器,但仍将IO分组到16 位寄存器中,如果您尝试一次更改所有寄存器,这些寄存器会遭受总线争用。
在这里,我们可以预处理需要发送到 GPIO 的所有内容,并以与测量的旋转成正比的速度逐步执行它。
处理器是双核ARM Cortex-M0。 需要注意的一件事是,两个内核都有独立的 SysTick 硬件。 我们可以在忙等待循环中使用两个内核,而不是通过中断来实现此目的。 第一个核心监视红外传感器,并使用其控制杆测量触发之间的确切周期数。 第二个核心等待信号点亮,一旦收到信号,就会使用自己的系统定时器逐步遍历体积缓冲区,以实现循环精度。
在电机控制方面,我相当有信心我们不需要做任何太复杂的事情。 设备以恒定速度旋转以获得一致的帧速率很重要,但它应该具有相当的自我调节能力。 桨轮经常被用作旧机械设备中的调节器。 我制作了最简单的速度控制逻辑:如果 RPM 低于 1200,则将电机设置为 90% 功率,否则将其设置为 60% 功率。
稍后我可能会将其升级为适当的 PID 控制,但到目前为止,有足够的惯性和空气阻力,简单的控制看起来完全没问题。
当我第一次让这个东西旋转时,我兴奋得头晕目眩。 不一会儿,我就让它画出了一个简单的体积棋盘图案。 这是最初的测试之一:
计划是为该设备加工一个底座,并在上面贴上一个小路标,以便红外传感器能够看到。 我意识到只要用一根手指靠近它就可以很好地工作,所以从来没有抽出时间来讨论这部分。 电机附有一个非常小的滑轮,足以让它旋转而不会摔倒。 后来我激光切割了聚甲醛树脂圆盘,只是在轴上推入配合,这只是我在车床上做东西之前的权宜之计。
该代码以按列的方式逐步遍历矩阵。 从上到下观察该设备,每条径向线都有轻微的螺旋状,但这比将整个东西变成螺旋线更容易纠正(如果我们关心的话)。 与外围相比,中心 LED 的占空比按比例减少。
我很快就得到了这个显示静态测试体积的东西。 我让它掉到地板上只是时间问题。 3D 打印件沿着胶线破裂。
不用担心,我们可以继续把它粘在一起。 真是无敌了!
电池电量
让我担心的一件事是我们没有电池保护电路。 如果电压降至 3V 以下,则会永久损坏。 (实际上,极限值更像是 2.7V,但这取决于电池。我通过这个过程损坏了一些电池。)大多数锂电池都有内置保护电路,当电压达到危险的低电平时,该电路会断开连接。 裸电池则不然。 至少我想监控电池电压,这样如果我们认为电压太低,我们可以发出警报并无法运行。
监控电源电压电平的正常方法是添加一个分压器,使其进入 ADC 的可读范围。 我认为Pico板通常将其连接到其GPIO之一,但不是 RP2040-tiny。 我添加了两个 100K 电阻来为我们最后一个可用的GPIO供电和接地。
问题是,Pico上没有参考电压。 有一个外部引脚(RP2040-tiny 上未断开)可以使用,但如果ADC参考只是其电源电压,那么当电源电压下降时我们将无法检测到。 至少,不太好。
3.3V LDO 稳压器的部件号为 RT9193-33,300mA 时的压差为 220mV。 这意味着当电池电压达到 3.52V 时,我们的RP2040电源电压也将开始下降。 作为电池电压函数的 ADC 读数最终将如下所示:
当然,压差是电流负载的函数,因此它甚至没有那么可预测。 对于这个原型,我让设备在电压达到略低于3.6V时显示警告。 这是我们在这里唯一能做的事情。 对于下一个版本,我们将添加参考电压。 甚至可以将ADC参考连接到Pico的内部1.8V稳压器。 这就足够了。
充电器
这个概念是在 LIR2450 电量低时弹出并将其插入独立充电器中。 我买了一个独立充电器,又3D打印了另一个电池座。 从技术上讲,弹簧片在最弱的方向上弯曲,但它们足够大,而且壁厚现在都是 1 毫米。
我不打算将其用于显示器,这只是我的新电池充电器。 我将台式电源设置为电流限制为 50mA,恒定电压为 4.2V。 这足以为单个锂离子电池充电。 恒流偏保守,我不确定这是120mAh电池还是60mAh电池。 除非电池另有说明,否则充电速度最好不要超过1C 左右。 但充电速度低于1C一般都不会出现问题。
这让我再次兴奋起来,体积之旅继续进行。 但在我们继续叙述之前,让我补充一点,取出电池并连接到电源是为设备充电最不方便的方法,尤其是在原型破裂之后,我不得不添加橡皮筋以防止电池飞出 。
RP2040微型USB适配器板仍然被用来将代码加载到芯片上。 如果我们构建一种USB拦截板,我们可以引出 5V 线,并暴露电池的引脚。 它位于从PC 到RP2040微型编程板的USB电缆之间。
现在我们可以将电源连接到电池,而无需将其从原型上拆下。 数据线仍然连接在下面,因此我们也可以在电池就位的情况下对其进行编程。
我意识到焊接起来很浪费时间,因为我可以简单地将电线放在 RP2040 微型适配器板上。
然而,在工作体积显示引起的疯狂中,以及因库存中只有一台 LIR2450 而感到懊恼的情况下,我发现这对于快速开发来说仍然太不方便。 在某个抽屉里,我有一些锂离子充电IC。 我花了一点时间才找到他们。 这些都是质量很好的,每个售价大约90便士。 部件号为 BQ21040DBVR。 我回到那个USB拦截板,把充电IC放到了它的中间。
这样,我们就可以保持编程电缆的连接,当我们思考的时候它就会给电池充电。
当然,它永远不会以这种方式给电池充满电,因为原型永远不会关闭。 仅 IR LED 就持续消耗约 9mA 的电流。 RP2040 微型适配器板上有一个非常亮的电源 LED,我将电阻更换为 20K,以防止其浪费电力。 但我仍然认为,即使原型没有运行,它总体消耗的电流约为15mA。 充电IC处于恒压阶段,会等待充电电流降至0.1C。 由于我们的充电电流设置为54mA,因此这种情况永远不会发生。 此外,随着电缆上的电压下降,电池电压可能不会超过 4.1V。 这些都不重要,这只是下一个版本需要记住的事情。
最后我们可以开始进行流体模拟了!
生成体积数据
我们需要在 3D 极坐标(即 r、theta 和 z)中创建体积数据。
我从一个线框立方体开始,它至少应该在某种程度上是可识别的。 我故意将它旋转到一端,以最大程度地减少显示它的尴尬。 我真的认为我们应该为设备编写一个矢量显示例程,它将执行某种 3D 极坐标 Bresenham 插值。 有很多关于将 Bresenham 应用于 3D 以及绘制圆的信息,但我们想在极坐标中绘制直线。 这听起来是一件有趣的事情,但现在让我们专注于从 Blender 导出极坐标体素数据。
这是带有线框修改器的默认立方体。 要将立方体旋转到一端,使一个角朝上,我们需要首先将 x 旋转 45 度,然后将 y 旋转 atan(1/sqrt(2))。 Blender 的一大优点是您可以在任何地方输入公式。
为了获得这个线框立方体的切片,我添加了另一个立方体,将其重塑为有点像切片,并在它们之间做了布尔修改器。 然后,我将相机和该切片都设置为空的父级,并为空的 Z 旋转设置动画。
我将相机投影配置为正交,其分辨率为我们的微型 8x10。 我将背景设置为黑色,将立方体的材质设置为自发光。 在合成器中,我们可以使用颜色渐变来设置阈值。 目前体积显示的位深度仅为 1,因此每个体素只是打开或关闭。 在这里设置阈值可以让我们直观地选择最佳的截止值。
“渲染动画”现在生成 24 个图像、24 个线框立方体切片。 我使用了一个快速的 python 脚本将它们分解成一个可以包含在代码中的头文件。
在 Blender 中,不仅任何输入都可以接受公式,而且几乎所有输入都可以设置关键帧,您还可以设置驱动程序。 它不会在您输入公式时解析公式,而是重新计算每一帧的结果。 因此,我没有对相机的旋转设置关键帧并手动将其设置为线性插值,而是输入 (frame/24)*2*pi ,它将无限循环。 对于立方体的 y 旋转,我现在输入了 Floor(frame/24)*pi/24,这样它将为相机的每个完整循环旋转一小部分。
老实说,连续旋转就很好,但我希望每一帧数据都是离散的,以防万一我们想根据电机转速调整播放速度。
您只需相信我的话,显示器在现实生活中看起来更像是三维的。 看看图片和视频,它看起来确实就像一堆随机点亮的点。 亲爱的读者,如果您有一个体积显示器来体验它就好了!
流体模拟
在 Blender 中运行流体模拟既简单又困难。 上手容易,做好很难。 涉及的参数非常多。
液体模拟稍微容易移植到体积显示,因为将流体粒子转换为网格很简单。 理论上,我们应该能够以 1/24 速度运行流体模拟,并使用与上述相同的技术来提取极坐标体积数据。
不幸的是,使用极端参数(例如非常慢的时间尺度)会导致不稳定。 似乎没有一种简单的方法可以以较慢的速度回放模拟,并且非常慢的模拟速度在加速时看起来完全错误。 我确实摆弄了这个问题一段时间但没有运气。 从好的方面来说,我们的目标是非常低的空间分辨率,因此流体模拟的速度足够快,可以在我的台式机上实时运行。
我研究了渲染体积数据的其他方法。 有一个称为多视图或立体视觉的功能,旨在渲染 3D 视频。 这使您可以添加两个摄像机并同时从两个角度渲染场景。 可以添加任意数量的摄像机,并根据它们的命名后缀输出所有摄像机。 我不确定是否有一种快速方法来添加 24 个摄像机并均匀旋转它们(遗憾的是,您无法将数组修改器应用于摄像机,我不确定以前是否有人要求过),但还有一个问题 使用这种策略的原因是我们还需要同时渲染切片的布尔修饰符。
我们可以稍微作弊并使用内置的剪切距离,而不是布尔修饰符和切片。 通过将相机设置为仅渲染场景的 0.1 切片,我们几乎获得了正确的输出。 问题是只绘制了表面,而不是对剪切对象进行实体填充。 我认为也许对物体应用体积材质可以使它们至少部分填充,但玩了一段时间后我没有运气。
相反,让我们采用更通用(但更复杂)的方法:只需编写一个 python 脚本。 在搅拌机的脚本选项卡中,我编写了以下内容:
import bpy
import os
from math import pi
obj = bpy.data.objects['Empty']
output_path = bpy.context.scene.render.filepath
for i in range(24):
obj.rotation_euler[2] = (i/24)*2*pi
bpy.context.scene.render.filepath = os.path.join(output_path, f"###_{i:02}.png")
bpy.ops.render.render(animation=True)
bpy.context.scene.render.filepath = output_path
通过这种方式,我们可以实时运行流体模拟,并通过 Empty(相机和切片的父级)的不同旋转来简单地重新渲染整个动画 24 次。
这个概念得到验证后,我继续进行火灾模拟。 渲染的方法基本相同,但多了几个步骤。 我们设置火灾模拟,然后以 OpenVDB 格式烘焙它。 我在这里点燃了一个小立方体。
然后重新开始,并将 OpenVDB 数据导入回 Blender。 然后,我们可以创建一个新的网格,并在其上应用“体积到网格”修改器,这使我们可以对体积数据进行阈值处理。 最后,用我们的相机切片另一个布尔修饰符,并重新运行上面的脚本。
再说一次,我觉得这张照片并没有真正捕捉到这里的感觉,但希望你能明白我的意思。
我突然想到,如果可以预测,LED 对准可以通过软件进行校正。 我们可以将布尔切片偏移到离相机更近或更远的位置,这样它就不会绕着确切的中心旋转。 如果它与真实显示器的运动相匹配,我们应该是金色的。 同样,我们可以将其做成稍微弯曲的形状,以代替拉伸的立方体,以补偿板旋转时的矩阵扫描图案。 然而,在这种分辨率水平下,我认为这些改进都不会是可见的。
唯一真正重要的是,照亮周边附近的单个体素应该看起来像一个点,而不是从某些角度看的双点。 您可以在下图中看到它,其中最靠近相机的体素被拉长,因为矩阵旋转时的两个照明没有完全对齐:
中间的字母“m”非常清楚,因为我在那里故意作弊。 为了使文本从各个方向都可读,文本体素的渲染方式有所不同。 我这样做是为了让文本以可读方向滚动,无论您是看显示器的正面还是背面。 无论如何,这种体素差异只能在显示器的外围才能真正注意到,在那里两种照明同时可见。
结论,暂时
我的火灾模拟还有很多工作要做,但也许我会稍微推迟一下,直到我制作出下一个原型,该原型可能会更好一点,分辨率更高一点。
如果我有一个微型滑动开关库存,我会把它添加到这个原型中,以断开电池而不将其移除。 我开始在盒子里寻找合适的东西,然后意识到我可以简单地在电池和触点之间插入一小块醋酸盐,就像红外遥控器等的纽扣电池一样。 效果很好。
说到红外遥控器,如果有一个遥控器那就太好了。 我们已经有一个红外传感器,尽管它不是解调类型。 如视频中所示,我只是在超时无活动后前进到下一个模式。
这是该设备的一些虚荣照片。
当然,肉眼完全看不到红外线,但数码相机将其捕捉到微弱的紫色光芒。
我像往常一样将源代码贴在github上。