1. 项目概述:打造你的专属复古像素动画挂坠
如果你对八九十年代的电脑屏保还有印象,比如那个著名的“飞行烤面包机”,或者红白机上《超级马里奥》里缓缓飘过的云朵,那么你大概能理解那种由简单像素构成的、充满复古趣味的图形魅力。如今,借助像CircuitPython这样易上手的嵌入式编程语言和Adafruit丰富的硬件生态,我们完全可以把这些经典的像素动画从电脑屏幕上“摘”下来,变成一个可以随身佩戴、独一无二的电子挂坠。这不仅仅是复刻一段数字记忆,更是一次深入理解嵌入式图形系统、微控制器编程和硬件集成的绝佳实践。
这个项目的核心,是使用Adafruit ItsyBitsy M4 Express这款高性能微控制器,驱动一块小巧但色彩鲜艳的IPS显示屏,通过CircuitPython的DisplayIO图形库来播放动画。DisplayIO是CircuitPython中用于管理图形显示的框架,它抽象了底层硬件的复杂性,让我们可以用相对高级的概念(如图像组、平铺网格)来组织屏幕上的元素,这对于实现精灵动画来说非常高效。整个系统会被封装进一个3D打印的定制外壳里,配合锂电池和充电管理模块,最终变成一个可以独立运行、随时开关的便携式可穿戴设备。
无论你是想学习嵌入式图形编程的开发者,还是热爱制作个性化电子饰品的创客,这个项目都能提供一条清晰的学习路径。它涵盖了从软件环境搭建、图形编程原理、代码解析,到硬件焊接、3D打印组装的全流程。你会发现,让硬件“动”起来,并把它变成一件可以拿在手里、戴在身上的实物,所带来的成就感远超在屏幕上看到一串运行日志。接下来,我们就从零开始,一步步拆解这个充满趣味的制作过程。
2. 核心硬件选型与电路设计解析
一个稳定可靠的硬件平台是项目成功的基础。在这个复古挂坠项目中,每一个元器件的选择都经过了权衡,旨在体积、功耗、性能和易用性之间找到最佳平衡点。理解这些选择背后的逻辑,不仅能帮你顺利完成本项目,更能为你未来的自定义项目积累宝贵的选型经验。
2.1 主控与显示模块:性能与视觉的平衡
项目的“大脑”是Adafruit ItsyBitsy M4 Express。选择它而非更常见的Arduino Nano或ESP32,主要基于几个关键考量。首先,ItsyBitsy M4搭载了ARM Cortex-M4内核的ATSAMD51芯片,运行频率高达120MHz,并拥有192KB RAM。对于需要实时解码位图、操作显示缓冲区并维持流畅动画的应用来说,充足的内存和处理速度至关重要。其次,ItsyBitsy M4原生支持CircuitPython,其内置的USB大容量存储设备(USB Mass Storage)模式,使得上传代码和资源文件就像在U盘间拖拽一样简单,极大地简化了开发流程。最后,其紧凑的尺寸(约36mm x 18mm)非常适合嵌入到小型挂坠外壳中。
显示部分,我们有两种选择:1.3英寸或1.54英寸的240x240 IPS TFT显示屏。这两款屏幕都具备170度的广视角和高亮度,确保从任何角度观看都有不错的视觉效果。240x240的分辨率对于像素风动画来说恰到好处:既能呈现足够的细节,又不会给微控制器带来过大的渲染压力(整屏缓冲区约115KB)。IPS面板相比传统的TN屏,在色彩还原和可视角度上优势明显,这对于一个需要被多角度观赏的挂坠来说是个加分项。两者引脚兼容,你可以根据对外壳尺寸和显示面积的偏好自由选择。
注意:在采购屏幕时,请务必确认其驱动芯片为ST7789,并且支持SPI接口。市面上有些外观类似的屏幕可能使用其他驱动或接口,不兼容本项目提供的代码和库。
2.2 供电与结构模块:确保便携与安全
供电系统由三部分组成:锂电池、充电管理模块和电源开关。我们选用一块3.7V、150mAh的锂聚合物电池。这个容量是一个经过计算的折衷:它足以支持屏幕和主控全速运行数小时,同时其物理尺寸(通常约35mm x 20mm x 5mm)又能轻松塞进设计好的外壳内。更大的电池会延长续航,但也会增加体积和重量,影响佩戴体验。
Adafruit LiPoly Backpack充电模块是关键的安全组件。它直接插在ItsyBitsy M4的电池接口上,负责三件事:1. 通过ItsyBitsy的USB口为电池安全充电(无需单独充电器);2. 提供稳定的3.3V输出给整个系统;3. 内置保护电路,防止电池过充、过放和短路。对于任何使用锂电池的项目,这样一个专业的充电/保护电路是绝对必要的,它能极大降低因误操作导致电池损坏甚至发生危险的风险。
SPDT滑动开关用于物理切断电池与系统的连接。即使在软件休眠后,微控制器和屏幕可能仍有微小的待机电流。一个物理开关可以确保在长期不使用时实现真正的零功耗,避免电池在不知不觉中耗尽。我们将它连接在充电模块的电池输出端,这样开关控制的是整个系统的总电源,最为彻底。
2.3 电路连接原理与布线技巧
整个系统的电路连接并不复杂,核心是SPI总线。下图清晰地展示了各部件间的连接关系:
| 从 | 到 | 连接线 | 功能说明 |
|---|---|---|---|
| 显示屏 (VIN) | ItsyBitsy M4 (Vhi) | 红色 | 提供3.3V电源。注意接Vhi而非USB,以确保由稳压后的电池供电。 |
| 显示屏 (GND) | ItsyBitsy M4 (GND) | 黑色 | 共地,所有电路的参考零点。 |
| 显示屏 (SCK) | ItsyBitsy M4 (SCK) | 蓝色/绿色 | SPI时钟线,主控通过它同步数据传输时序。 |
| 显示屏 (SI/MOSI) | ItsyBitsy M4 (MO) | 紫色/黄色 | SPI数据线(主出从入),图像数据由此发送到屏幕。 |
| 显示屏 (D/C) | ItsyBitsy M4 (D7) | 白色/橙色 | 数据/命令选择线。高电平时SI上传的是像素数据,低电平时是控制命令。 |
| ItsyBitsy M4 (BAT) | LiPoly Backpack (BAT) | 红色 | 将电池正极引入主控的电池电压监测引脚。 |
| ItsyBitsy M4 (G) | LiPoly Backpack (G) | 黑色 | 共地。 |
| ItsyBitsy M4 (USB) | LiPoly Backpack (5V) | 白色 | 将主控USB口的5V电源引至充电模块,用于为电池充电。 |
| 滑动开关 (中间引脚) | LiPoly Backpack (BAT输出端) | 红色 | 开关的公共端接电池正极输出。 |
| 滑动开关 (一侧引脚) | ItsyBitsy M4/显示屏 VIN | 红色 | 开关的输出端接系统电源输入。 |
在实际焊接时,我强烈建议使用硅胶被覆排线。这种线材柔软、耐弯折,且多根线并排在一起,便于整理和捆绑,能有效避免机壳内部线材杂乱纠缠,也更能承受佩戴时可能产生的轻微晃动。焊接顺序上,我习惯先焊接屏幕引脚(因其焊盘较小),给每根线预留足够到达ItsyBitsy的长度并做好标记,然后再集中焊接至ItsyBitsy。焊接充电模块前,务必记得用美工刀切断模块上标记为电池输出的那两个过孔之间的铜箔,否则开关将无法切断电路。
3. CircuitPython环境搭建与核心库剖析
硬件准备就绪后,我们需要为其注入“灵魂”——软件。CircuitPython以其极低的上手门槛而闻名,但为了充分发挥其图形能力,正确配置环境和理解核心库的工作原理是必不可少的一步。
3.1 固件烧录与开发环境配置
首先,确保你的ItsyBitsy M4运行的是CircuitPython 4.0或更高版本。用数据线连接电脑,如果电脑出现一个名为CIRCUITPY的U盘盘符,打开其中的boot_out.txt文件即可查看版本。如果版本过低或只出现ITSYM4BOOT盘符,则需要更新固件。
去Adafruit官网下载对应ItsyBitsy M4的最新版CircuitPython UF2文件。按住ItsyBitsy上的复位按钮(或短接复位触点),然后插入USB线,此时电脑会识别出一个名为ITSYM4BOOT的驱动器。将下载好的.uf2文件直接拖入该驱动器,等待几秒,驱动器会自动弹出并重新识别为CIRCUITPY,固件更新即告完成。
接下来是代码编辑器的选择。虽然任何文本编辑器都能编写.py文件,但我强烈推荐Mu Editor。它是一个专为教育和小型项目设计的Python编辑器,对CircuitPython有原生支持。其内置的串行监视器(Serial Console)能直接打印板子的调试信息,当代码出现错误时,错误提示会清晰地显示在这里,这对于排查问题至关重要。安装Mu后,确保在模式选择中切换到“CircuitPython”模式。
3.2 必备库文件:驱动与图像解码
CircuitPython通过“库”来扩展功能。我们需要两个核心库来驱动屏幕和加载图片:
adafruit_st7789:这是ST7789显示驱动芯片的CircuitPython驱动程序。它负责将DisplayIO发出的高级图形指令,翻译成屏幕能理解的底层SPI命令和数据流。没有它,屏幕只是一块黑色的玻璃。adafruit_imageload:这是图像加载库。我们的动画素材是BMP位图文件,这个库能解析BMP文件格式,将其转换为CircuitPython内部可用的位图(Bitmap)和调色板(Palette)对象,供DisplayIO使用。
获取这些库最简单的方法是下载完整的Adafruit CircuitPython Library Bundle。解压后,在lib文件夹中找到上述两个库的文件夹(例如adafruit_st7789和adafruit_imageload),将它们整体复制到CIRCUITPY驱动器根目录下的lib文件夹中(如果没有就新建一个)。千万不要只复制.mpy文件而遗漏了同名的文件夹或其他必要文件。
3.3 DisplayIO图形框架深度解析
DisplayIO是本项目动画得以实现的核心框架。理解它的几个基本概念,对于读懂代码乃至未来创作自己的动画至关重要。
1. 位图(Bitmap)与调色板(Palette):我们的动画素材(云朵、烤面包机)是以索引色位图的形式存储的。这意味着图片文件(如tilesheet-2x.bmp)本身不直接存储每个像素的RGB颜色,而是存储一个“颜色索引号”。另一个独立的“调色板”定义了每个索引号对应什么实际颜色。例如,索引0代表透明(或背景色),索引1代表白色,索引2代表浅灰色。这样做的好处是极大地节省了存储空间和内存占用。adafruit_imageload.load()函数的工作就是读取BMP文件,并分离出位图对象(存储索引号矩阵)和调色板对象(存储索引到颜色的映射)。
2. 平铺网格(TileGrid):这是实现精灵动画的魔法所在。你可以把TileGrid想象成一个覆盖在屏幕上的、由许多相同大小“单元格”组成的网格。每个单元格可以显示位图中的一小块区域,这块区域被称为一个“图块”(Tile)。我们的精灵表(Sprite Sheet)就是一张包含了所有动画帧的大位图。通过设置TileGrid中每个单元格引用精灵表中的不同图块,并快速切换这些引用,就能实现动画效果。在代码中,displayio.TileGrid()创建了这个网格,参数tile_width和tile_height定义了每个图块的大小,而width和height则定义了网格有多少行、多少列单元格。
3. 组(Group)与显示根(root_group):DisplayIO采用层级结构来管理显示对象。Group是一个容器,可以包含多个TileGrid、其他Group甚至矢量图形。我们将创建好的TileGrid添加到一个Group中,最后将这个Group赋值给display.root_group。屏幕就会从根组开始,逐层渲染其所有子项。这种结构使得管理复杂的UI(如叠加层、多个动画层)变得非常清晰。
4. 刷新机制:默认情况下,DisplayIO会在每次图形操作后自动刷新屏幕。对于动画,我们更希望控制刷新的时机以获得更流畅的效果。代码中通过display.auto_refresh = True(云朵项目)或手动调用display.refresh()(烤面包机项目)来控制。target_frames_per_second参数可以设定目标帧率,系统会尝试以此速率刷新,但实际帧率受代码复杂度和微控制器性能限制。
4. 代码实现与动画逻辑详解
有了硬件和库的基础,我们来深入剖析两个动画示例的代码。虽然它们最终效果不同,但核心逻辑一脉相承,理解其中一个,另一个也就触类旁通。
4.1 “马里奥云朵”动画:无限横向卷轴
“云朵”动画模拟了经典横版游戏中背景无限循环滚动的效果。其核心思想是:屏幕显示区域只是整个世界地图的一个“视窗”,通过不断移动视窗或移动地图,就能产生滚动的错觉。
1. 精灵表与网格设计:tilesheet-2x.bmp这张精灵表包含了绘制一朵云所需的所有“零件”:左端(LEFT)、中段(MIDDLE)、右端(RIGHT),以及一个空白(EMPTY)图块。代码中定义了一个9列x5行的TileGrid,每个图块大小为32x48像素。这意味着整个TileGrid代表了屏幕外一个更大的逻辑地图(288像素宽,240像素高),而屏幕只显示其中240x240的区域。
2. 核心循环四步法:动画主循环while True内,按顺序执行四个函数,构成了云朵生成、移动的逻辑:
slide_tiles(): 将整个TileGrid向左移动1像素。这是产生平滑滚动感的关键,它通过逐步改变TileGrid的x坐标偏移来实现。shift_tiles(): 当TileGrid向左移动了整整一个图块的宽度(32像素)后,需要“重置”逻辑。这个函数将网格中每一行的所有图块索引向左移动一列(即tilegrid[col, row] = tilegrid[col+1, row]),并将最右边新空出的列全部设为EMPTY。然后将TileGrid的x坐标归零。这个过程就像把地图最左边一列剪掉,然后在最右边补上一列空白,为生成新云朵做准备。extend_clouds(): 检查当前地图最右侧(第7列,因为第8列刚被清空)是否有未完成的云朵(即LEFT或MIDDLE)。如果有,根据随机概率决定是继续延伸(在第8列放入MIDDLE)还是结束这朵云(放入RIGHT)。add_cloud(): 同样根据随机概率,尝试在最右侧的空白位置(第7、8列均为空)开始一朵新的云,即放入一个LEFT图块。
3. 随机性与自然感:通过CHANCE_OF_NEW_CLOUD和CHANCE_OF_EXTENDING_A_CLOUD这两个概率常数,控制了云朵出现的频率和长度。随机数randint的引入,使得每次运行产生的云朵序列都不同,避免了机械重复感。seed_clouds(5)函数在开始时生成5朵初始云,避免了屏幕启动时一片空白的尴尬期。
实操心得:调整
CHANCE_OF_EXTENDING_A_CLOUD的值可以显著改变云朵的形态。降低这个值,云朵更容易被“终结”,会变得短而碎;提高这个值,云朵会更容易延伸,形成长条状的云带。你可以通过修改这个常数来创造不同天气效果的云层。
4.2 “飞行烤面包机”动画:斜向运动的精灵
“烤面包机”动画更接近传统的精灵动画。每个烤面包机(或吐司)是一个独立的精灵,它们从屏幕右上角“出生”,沿45度斜线向左下角飞行,直至飞出屏幕。
1. 精灵表与动画帧:spritesheet-2x.bmp包含了烤面包机扇动翅膀的4个动画帧(CELL_1到CELL_4),一个静态的吐司图块(TOAST),以及空白(EMPTY)。每个图块大小为64x64像素。TileGrid设置为5x5,但初始位置y=-64,意味着网格整体向上偏移了64像素(一个图块的高度),这样新的精灵可以从“屏幕上方”进入。
2. 动画与移动分离:这个项目的逻辑将“精灵自身动画”和“精灵位置移动”分开了,这是更标准的游戏编程思路。
advance_animation(): 遍历TileGrid中的每一个单元格,如果该单元格的图块属于动画序列(CELL_1到CELL_4),就将其替换为序列中的下一帧。吐司(TOAST)是静态的,不受此函数影响。这就实现了每个烤面包机都在原地扇动翅膀。slide_tiles(): 同时将整个TileGrid的x坐标减1,y坐标加1。这产生了所有精灵整体向左下角移动的效果。shift_tiles(): 同样,在移动了足够距离后(这里是64像素),需要重置逻辑地图。这个函数比云朵的复杂,因为它处理的是斜向移动。它把地图中每个元素向左下角方向“搬运”一格,然后清空顶部一行和最右边一列,为新生腾出空间。最后重置TileGrid的坐标。add_toaster_or_toast(): 在重置后的地图最右侧列(第4列)和顶部行(第0行)的随机空白位置,根据概率放入一个新的烤面包机动画帧或一个吐司。
3. 性能优化技巧:注意烤面包机代码中display.refresh()的调用方式。它在主循环开始前调用一次进行初始渲染,然后在每次slide_tiles()(移动1像素)后并不立即刷新,而是累积移动64次后,通过一个循环以80fps的目标帧率连续刷新64次,快速播放完累积的动画帧,最后再以120fps刷新一次确保画面稳定。这种“批量更新,集中渲染”的策略,可以减少频繁刷新带来的开销,在某些情况下能让动画更流畅。
5. 从代码到实物:3D打印与组装全流程
当代码在屏幕上完美运行后,最后一步就是将它“封装”起来,变成一个坚固、美观、可佩戴的实物。3D打印和精细的组装是实现这一目标的关键。
5.1 外壳设计与打印要点
项目提供了分别适配1.3英寸和1.54英寸屏幕的外壳STL文件。外壳通常由底壳和顶盖两部分组成,底壳用于固定屏幕和电路板,顶盖则留有按钮孔和挂绳孔。
打印设置建议(以通用FDM打印机为例):
- 层高:0.2mm。这是一个在打印质量和时间之间的良好平衡点,能保证外壳表面足够光滑,同时不会耗时过长。
- 壁厚/线宽:至少2条轮廓线(约0.8mm),确保结构强度。填充密度10%-15%即可,因为外壳本身是受力件,不需要太高填充。
- 支撑:不需要。外壳设计通常都是自上而下打印,所有悬空部分的角度都在45度以内,良好的打印机自身桥接能力可以应对。
- 打印平台附着:建议使用裙边(Skirt),3圈即可。它可以帮助挤出机稳定出丝,并检查平台调平,又不会像底垫(Raft)那样难以清理且影响底面光洁度。
- 材料选择:PLA是最佳选择。它易于打印、无异味、强度足够,且后处理简单。如果追求更好的韧性和光泽,可以考虑PETG,但打印温度需要更高。
避坑指南:打印完成后,务必不要立即将电路板装入。先用屏幕和电路板进行试装配,检查所有卡槽、支柱和螺丝孔的位置是否精准。特别是屏幕开口与显示区域的对齐、USB口和开关开口的位置。如果发现过紧,可以使用小锉刀或砂纸进行微调;如果过松,可以考虑在接合处涂抹少量胶水加固。这一步的耐心测试能避免后续的返工。
5.2 分步焊接与组装工艺
组装顺序至关重要,合理的顺序能让操作更顺手,并避免损坏已焊好的脆弱部分。
第一步:焊接屏幕引线。将屏幕固定在工作台上,使用细芯焊锡丝(建议0.6mm)和尖头烙铁(温度约320°C),为VIN、GND、SCK、SI、D/C这五个引脚分别上好锡(“搪锡”)。线材预留长度应比屏幕到ItsyBitsy M4的预估距离长1-2厘米,以备调整。
第二步:焊接主控板。将ItsyBitsy M4用辅助夹具固定在上方。根据电路图,将屏幕引线的另一端对应焊接至ItsyBitsy的相应引脚。特别注意:ItsyBitsy上的MO引脚对应屏幕的SI(MOSI),Vhi引脚对应屏幕的VIN。焊接完成后,用万用表通断档检查所有连接,确保无虚焊、短路。
第三步:改装充电模块。这是保证开关功能正常的关键。找到LiPoly Backpack上标记为电池输出(通常有“BAT”丝印)的两个相邻过孔,用锋利的美工刀彻底切断它们之间的细小铜箔。然后,焊接两根导线到这两个过孔上,另一端接至滑动开关的两端(中间引脚接电池正极输入,一侧引脚接系统电源输出)。
第四步:集成供电系统。将充电模块通过排线焊接到ItsyBitsy的BAT、G、USB引脚。然后将改装好的开关线接入系统。在连接电池前,再次确认开关处于“关”状态。
第五步:绝缘与总装。用一小块电工胶布或Kapton胶带,贴在ItsyBitsy和充电模块背面有焊点和元器件的区域,防止它们与金属外壳或彼此之间短路。先将屏幕模块放入底壳卡槽,再将ItsyBitsy和充电模块小心放入,理顺线材,确保USB口和开关从外壳开口处露出。最后扣上顶盖,如果设计是卡扣式,听到“咔哒”声即可;如果较松,可以在边缘点少量胶水固定。
第六步:功能测试与封盖。装入电池,打开开关。屏幕应点亮并开始播放动画。如果无反应,立即关闭开关,按顺序检查:电池是否有电?开关接线是否正确?屏幕排线是否接触良好?主控板是否正常供电(USB口旁LED是否亮)?确认一切正常后,再进行最终封盖。
6. 自定义进阶:创造属于你的动画
项目的乐趣远不止于复现。掌握了基本原理后,你可以轻松地替换图形、修改逻辑,甚至从头创作全新的动画。
6.1 制作自定义精灵表
这是最直观的修改。你可以使用任何喜欢的图像编辑软件(如Aseprite、Photoshop、GIMP,甚至是在线的Piskel)来绘制精灵表。需要遵循以下规则:
- 格式:保存为索引色模式的BMP文件。在保存时,选择“颜色表”或“索引颜色”,并将颜色数限制在256色以内(本项目实际只用几种颜色)。
- 结构:将所有动画帧或图块排列在一个图片文件中。例如,对于烤面包机,四个动画帧可以水平排列。确保每个帧的尺寸一致,并且是最终显示尺寸的整数倍(代码中的
tile_width和tile_height必须与此匹配)。 - 透明色:在调色板中,将索引0的颜色定义为透明色(或你希望的背景色)。在绘制时,背景部分就填充这个颜色。
- 命名与上传:将制作好的BMP文件重命名为代码中指定的文件名(如
myspritesheet.bmp),并上传到CIRCUITPY驱动器的根目录。在代码中修改adafruit_imageload.load()函数的文件路径参数即可加载你的新素材。
6.2 修改动画行为与逻辑
代码中的常数和函数是你发挥创意的杠杆。
- 控制密度与速度:修改
CHANCE_OF_NEW_CLOUD或CHANCE_OF_NEW_TOAST可以改变新精灵出现的频率。调整slide_tiles()函数中每次移动的像素数(如tilegrid.x -= 2)可以改变滚动或飞行速度,但要注意与shift_tiles()的触发条件(移动一个图块宽度)相匹配。 - 改变运动轨迹:烤面包机是斜向运动,这是由
slide_tiles()中同时修改x和y坐标实现的。你可以尝试只修改x(水平飞行)、只修改y(垂直下落),或者使用正弦函数让精灵做波浪形运动(这需要引入math库并更复杂的计算)。 - 增加交互:ItsyBitsy M4上有富余的GPIO引脚。你可以焊接一个轻触开关或倾斜开关到某个引脚和GND之间,然后在代码中
import digitalio来读取开关状态。例如,当检测到按钮按下时,可以切换动画主题,或者改变精灵的移动速度。
6.3 优化性能与功耗
如果你发现动画有卡顿,或者希望进一步延长续航,可以考虑以下优化:
- 减少颜色深度:你的自定义精灵表可能不需要256色。在图像编辑软件中将颜色数减少到最低必要值(如16色、4色),可以减小文件体积,加快加载速度。
- 精简刷新区域:DisplayIO支持局部刷新。如果动画只发生在屏幕的一部分,可以创建多个TileGrid或Group,只刷新变化的部分,而不是整个屏幕。但这会显著增加代码复杂度。
- 利用睡眠模式:CircuitPython的
alarm模块可以让微控制器进入深度睡眠。你可以设计一个功能:当一段时间没有操作(或通过加速度计检测到静止)后,自动关闭屏幕并让MCU进入睡眠,仅通过一个外部中断(如按键)来唤醒。这能极大延长电池寿命。
从复现一个有趣的项目,到理解其每一行代码的原理,再到亲手修改和创造,这正是嵌入式开发与创客精神的魅力所在。这个复古动画挂坠不仅是一个佩戴在身上的装饰品,更是一个随时可以拿在手里把玩、修改和展示的编程学习平台。希望你在完成它的过程中,不仅收获了一件酷炫的作品,更点燃了对硬件编程和图形创意的持续热情。