1. 项目概述与设计思路

几年前,看到乐高马里奥玩具时,我就被那种实体玩具与数字内容互动的魅力深深吸引了。作为一个喜欢鼓捣硬件和软件的创客,我一直在想,能不能自己动手做一个既有“灵魂”又能“说话”的玩具?这个想法在去年万圣节终于落地,变成了一个会变脸、会播放经典恐怖片音效的南瓜灯骑士玩具。它不仅仅是一个节日装饰,更是一个融合了3D打印、嵌入式开发和交互设计的微型工程。

这个项目的核心,是打造一个具备完整交互逻辑的电子玩具。用户通过旋转南瓜头上的编码器,可以在内置的TFT屏幕上切换不同的万圣节角色脸谱,比如《惊魂记》里的诺曼·贝茨,或者《十三号星期五》里的杰森。每切换到一个特定脸谱,玩具会通过微型MP3模块播放对应的标志性音乐片段。更有趣的是,这个南瓜灯骑士还“骑”着一匹发光的小马,当触发特定模式时,小马的眼睛和嘴巴会亮起LED灯,并伴随音效,形成一个完整的互动叙事。

整个制作流程清晰地分成了几个大块:首先是外观的创意与实现,这涉及到3D建模和打印;其次是“大脑”和“神经系统”的搭建,即基于ESP32的电路设计与PCB制作;最后是为其注入“灵魂”,也就是用CircuitPython编写控制程序。这种从外观到内核,从硬件到软件的完整闭环,正是创客项目的精髓所在。它适合有一定电子基础和编程兴趣的朋友,无论你是想深入学习ESP32开发,还是想体验从零打造一个智能硬件的成就感,这个项目都能提供一条清晰的路径。

2. 核心硬件选型与设计解析

2.1 主控与核心模块选型

项目的“大脑”我选择了 ESP32 Mini模块 。原因很简单,它功能强大且性价比极高。ESP32自带Wi-Fi和蓝牙,虽然在这个玩具里暂时没用上,但为未来增加无线控制或联机功能留足了空间。其双核处理器和充足的GPIO引脚,驱动一个小屏幕、处理编码器输入和MP3播放指令绰绰有余。更重要的是,它对CircuitPython的支持非常友好,这对于想用Python快速开发嵌入式应用的人来说是个福音。

显示部分,我选用了一块 1.9英寸的RGB TFT屏幕 。这个尺寸经过精心计算,需要刚好能放入南瓜头的面部空间,同时分辨率(160x128)足以清晰显示各种精心绘制的像素风脸谱。屏幕通过SPI接口与ESP32通信,这是一种在嵌入式领域非常常见的高速串行通信协议,接线简单,驱动库成熟。

为了实现声音互动, Mini DFPlayer模块 是不二之选。这个小模块可以直接读取SD卡中的MP3文件,并通过简单的串口指令控制播放、暂停、选曲,大大简化了音频播放功能的开发。我只需要将经典的电影音效片段转换成MP3格式存入SD卡,程序里发送对应的曲目编号即可。

旋转编码器 是这个人机交互的关键。与简单的按钮不同,编码器可以无限旋转,并感知旋转方向和步数,非常适合用来在多个选项(如不同脸谱)之间流畅切换。我选择的是带按键功能的编码器,这样旋转选择、按下确认,一个器件就能完成核心交互。

2.2 电源系统与PCB设计考量

玩具的移动性要求它必须使用电池供电。我选择了一块 900mAh的3.7V锂聚合物电池 ,这个容量经过实测,可以支持玩具断续玩耍数小时。这里的一个关键点是电压转换。ESP32和TFT屏幕通常需要3.3V供电,而DFPlayer模块和某些LED的工作电压可能是5V。因此,一个高效的 DC-DC升压/降压模块 必不可少。我使用的模块可以将电池的3.7V(满电约4.2V,欠压约3.3V)稳定地转换为5V和3.3V输出,同时集成了锂电池充电管理功能,可以通过Micro USB接口直接充电,极大方便了日常使用。

当所有模块在面包板上验证功能后,为了追求极致的集成度和可靠性,设计一块 定制PCB 就成了必然选择。用万用板飞线不仅体积大,而且在后续组装到狭小的3D打印壳体内容易松动,带来无穷无尽的调试噩梦。自己设计PCB可以将所有模块、电阻、接口插座合理地布局在一块板子上,通过印刷线路连接,稳定性是质的飞跃。

注意:在设计PCB时,务必为电池连接器、屏幕排线座、编码器和扬声器等外设预留接插件或焊盘。同时,仔细核对每个元件的封装尺寸(尤其是贴片元件),原理图上的引脚连接一定要反复检查,特别是电源和地线,任何错误都会导致整批板子报废。

我的PCB设计采用双层板,顶层主要放置主要的集成模块(ESP32、DFPlayer、电源模块)和较大的接插件,底层则布置贴片电阻电容以及走线。布局的核心原则是:信号流清晰,减少交叉;电源路径尽量短而粗;模拟部分(音频)和数字部分适当隔离。设计完成后,我将Gerber文件发给PCB打样厂商,大约一周就能收到成品。

3. 3D建模与结构设计实战

3.1 利用Tinkercad进行创意改造

对于不擅长复杂三维建模的创客来说, Tinkercad 是一个神器。它完全在线、免费,而且操作直观。我的设计策略是“站在巨人的肩膀上”。我首先在Printables和Thingiverse上找到了一个基础南瓜模型和一匹小马模型作为起点。直接使用这些模型可以节省大量从头建模的时间。

在Tinkercad中导入这两个基础模型后,真正的设计工作才开始。我需要将完整的南瓜模型**“切割”**成多个部件:身体、头部、脸部(用于嵌入屏幕)、手臂、腿等。这样做的目的是为了方便打印,也为了后续安装电子元件。使用Tinkercad的“组合”和“镂空”工具,可以轻松地从实体模型中挖出放置电池、PCB主板和扬声器的空腔。

例如,为了在头部容纳扬声器,我创建了一个与扬声器外形匹配的圆柱体,将其设置为“孔”,然后与头部模型进行组合剪切,就得到了一个完美的扬声器卡槽。对于脸部,我需要挖出一个与TFT屏幕外框尺寸精确匹配的方形孔,确保屏幕能严丝合缝地嵌入,并从正面完美显示。

3.2 3D打印参数与后处理心得

所有部件我均使用 PLA材料 打印,因为PLA易于打印、气味小、强度对于此类玩具也足够。打印层高设置为 0.2mm ,这是一个在打印质量和时间之间取得良好平衡的参数。对于这种展示性模型,我选择了较高的填充密度(约25%),以确保部件有足够的强度来承受组装和把玩。

实操心得:打印带有精密卡扣或插槽的部件时,务必进行 公差补偿 。3D打印存在一定的收缩和误差,理论上严丝合缝的配合,打印出来可能会装不上。我的经验是,对于需要紧配合的孔位,在模型设计时将其尺寸放大0.2-0.3mm;对于需要插入的轴,则将其直径缩小0.2-0.3mm。打印一个小测试件来验证配合度,是避免返工的最佳方法。

打印完成后,支撑结构的清除需要耐心。对于内部腔室的支撑,可以使用尖头镊子小心拆除。所有部件在组装前,最好用砂纸(例如600目)对结合面进行轻微打磨,去除毛刺,使拼接更平整。对于像南瓜这样的装饰性部件,打印完成后还可以进行喷涂上色,让成品更具质感。

4. 电路焊接与系统集成步骤

4.1 PCB焊接的顺序与技巧

收到PCB后,焊接顺序非常重要,应遵循“先贴片,后插装;先矮件,后高件”的原则。

  1. 焊接贴片电阻 :首先焊接那两个0603封装的贴片电阻。使用细尖烙铁头,少量焊锡,利用焊盘的焊锡先将元件一端固定,再焊接另一端。可以使用放大镜台灯来辅助观察。
  2. 安装排针 :将一排排的排针(用于连接屏幕、编码器等)插入PCB,从背面焊接。这里的关键是确保所有排针与PCB板保持垂直。可以将排针插在一个面包板上,再将PCB扣上去焊接,这样能保证所有排针高度一致且垂直。
  3. 焊接TFT屏幕 :将屏幕的排针剪短并焊平,然后将其对准PCB上的插座焊盘。在焊接前,我用 高温胶带 将屏幕背面的金属部分和裸露的引脚贴了起来,这是为了防止后续与PCB上其他元件短路。焊接时要快而准,避免热量长时间传递到屏幕本身。
  4. 焊接ESP32模块 :ESP32 Mini模块的引脚较长,我先用剪钳将其剪到合适的长度。焊接时,确保模块的Micro USB口与PCB板边缘对齐,方便日后插线。同样要注意防短路,检查引脚间有无焊锡桥连。
  5. 焊接电源与MP3模块 :DC-DC模块和DFPlayer模块是“叠罗汉”式安装。先将DC-DC模块以一定角度焊接,使其紧贴PCB。 特别注意 :在焊接前,我用绝缘胶带或绿油胶覆盖了PCB上可能与模块背面接触的焊点,杜绝任何短路风险。然后,再将DFPlayer模块焊接在DC-DC模块上方,它们之间用胶带绝缘。

4.2 机械与电子总装流程

组装是让玩具从一堆零件“活”过来的过程,需要耐心和细致。

  1. 头部内部组装 :首先在南瓜头内部固定三个元件。微型扬声器放入预留的卡槽,必要时用一点热熔胶固定周边。旋转编码器穿过头部侧面的孔,从内部用螺母锁紧,并在PCB板一侧用胶水辅助固定,防止旋转时松动。锂电池则放入专门设计的电池盒中。
  2. 身体部分布线 :腿部壳体内部需要布置三组线:电源开关线、Micro USB充电口延长线、以及连接到底座(小马)的多芯线。每组线都尽量剪到合适长度,用扎带或胶带固定,避免内部杂乱。
  3. 主体合体 :将焊接好的PCB主板安装到“脸部”背面,屏幕从正面嵌入。接着,将身体部分与脸部部分对齐扣合。此时需要连接穿过身体的那些线缆到主板的对应接口上:开关线接到主板上的电源开关焊盘,USB线接到充电输入,底座连接线接到指定的排针。
  4. 最后连接与封闭 :这是最需小心的一步。将头部(已装有电池、编码器、扬声器)与身体部分对接。需要连接的最后三组线是:电池的JST插头、编码器的三根杜邦线、扬声器的两根线。在狭小空间内操作,一把弯头镊子是必备工具。将所有线缆整理好,塞入空隙,确保没有线被挤压或引脚顶着外壳,然后缓缓合上头部与身体,用螺丝或卡扣固定。
  5. 小马的制作 :小马作为底座,互动相对简单。在每个眼窝安装一颗5mm红色LED,嘴巴安装一颗3mm蓝色LED。焊接好导线后,将六根导线(每颗LED正负极各一)汇集到小马底座内部,焊接到一个6Pin的排母上。这个排母将与南瓜骑士脚底的排针对接,完成电力和信号连接。

5. CircuitPython程序开发详解

5.1 开发环境搭建与库配置

为了让ESP32运行Python,我们需要刷入 CircuitPython 固件。这不是Arduino那样的C++环境,而是MicroPython的一个分支,对Adafruit硬件支持更好。

  1. 刷写固件 :首先从CircuitPython官网下载对应ESP32 Mini型号的 .bin 固件文件。将ESP32通过USB连接到电脑,进入下载模式(通常需要按住某个按钮再上电),使用像 esptool.py 这样的工具将固件刷入。成功后,电脑上会出现一个名为 CIRCUITPY 的可移动磁盘。
  2. 安装必要库 :CircuitPython的核心驱动以库文件形式存在。我们需要将以下库文件复制到 CIRCUITPY 磁盘的 lib 文件夹内:
    • adafruit_st7735.mpy :用于驱动ST7735芯片的TFT屏幕。
    • adafruit_bus_device :总线设备支持库。
    • adafruit_display_text , adafruit_display_shapes 等:用于显示图形和文字(可选)。
    • circuitpython-dfplayer :用于控制DFPlayer模块的库。你需要从GitHub下载该库的源代码,并将其中的 .mpy .py 文件放入 lib
    • rotaryio.mpy :这是CircuitPython内建库,通常已包含在固件中,用于读取旋转编码器。

5.2 核心交互逻辑代码剖析

程序的逻辑主线是:监听编码器的旋转动作 -> 改变当前选中的脸谱索引 -> 在屏幕上显示对应的BMP图片 -> 如果切换到特定脸谱,则触发播放对应的MP3音频。

import board
import rotaryio
import displayio
import time
from adafruit_st7735 import ST7735
# 假设你已经安装了dfplayer库,并正确导入
# from dfplayer import DFPlayer

# 1. 初始化旋转编码器
encoder = rotaryio.IncrementalEncoder(board.D9, board.D10) # 根据实际接线调整引脚
last_position = 0
current_face_index = 0
face_count = 5  # 假设有5张脸谱

# 2. 初始化TFT屏幕 (SPI通信)
spi = board.SPI()
displayio.release_displays()  # 释放可能占用的显示资源
# 引脚定义需要根据你的PCB连接确定
display_bus = displayio.FourWire(spi, command=board.D23, chip_select=board.D19, reset=board.D18)
display = ST7735(display_bus, width=160, height=128, rotation=90) # rotation根据屏幕安装方向调整

# 3. 初始化DFPlayer (UART通信)
# uart = board.UART() # 初始化UART
# df = DFPlayer(uart=uart) # 创建DFPlayer对象

# 预加载脸谱位图文件路径
face_files = ["/face01.bmp", "/face02.bmp", "/face03.bmp", "/face04.bmp", "/face05.bmp"]
# 定义特定脸谱索引对应的MP3曲目编号
special_face_songs = {2: 1, 4: 2}  # 例如,索引2的脸谱播放曲目1,索引4播放曲目2

def display_face(index):
    """在屏幕上显示指定索引的脸谱图片"""
    bitmap = displayio.OnDiskBitmap(face_files[index])
    tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)
    group = displayio.Group()
    group.append(tile_grid)
    display.show(group)

# 显示初始脸谱
display_face(current_face_index)

while True:
    # 读取编码器位置
    position = encoder.position
    # 检测位置变化
    if position != last_position:
        change = position - last_position
        # 根据旋转方向更新脸谱索引
        current_face_index = (current_face_index + change) % face_count
        print(f"切换到脸谱: {current_face_index}")
        # 更新显示
        display_face(current_face_index)
        
        # 检查是否需要播放音效
        if current_face_index in special_face_songs:
            song_num = special_face_songs[current_face_index]
            print(f"播放音效: {song_num}")
            # df.play(song_num) # 调用DFPlayer播放
            # 此处可以同时控制小马LED点亮
            # 例如:控制对应GPIO口输出高电平,点亮LED
            
        last_position = position
    time.sleep(0.01)  # 短暂延时,降低CPU占用

代码关键点解析

  • rotaryio.IncrementalEncoder 直接提供了编码器计数,无需自己处理抖动,非常方便。
  • displayio 是CircuitPython的显示框架, OnDiskBitmap 可以直接显示存储在根目录下的BMP图片。图片需要转换为160x128像素、16位色的BMP格式。
  • 主循环通过比较编码器当前位置和上一次位置来判断是否旋转及方向,进而更新 current_face_index 。取模运算 % face_count 确保了索引在0到 face_count-1 之间循环。
  • 通过一个字典 special_face_songs 来映射特定脸谱和音效,逻辑清晰,易于扩展。

6. 调试、问题排查与优化建议

6.1 常见问题与解决方案

在制作过程中,你几乎一定会遇到下面这些问题,这里是我的排查心得:

问题现象 可能原因 排查步骤与解决方案
ESP32无法连接电脑/不出现CIRCUITPY盘符 1. USB线仅供电,无数据功能。
2. 驱动未安装。
3. 固件刷写不正确或芯片进入下载模式。
1. 换一条已知好的数据线。
2. 安装CP210x或CH340等USB转串口驱动。
3. 尝试按住BOOT键再上电,强制进入下载模式重新刷固件。
TFT屏幕白屏或花屏 1. 电源供电不足。
2. SPI引脚接错。
3. 复位或命令引脚未正确初始化。
4. 屏幕初始化参数(如旋转角度)错误。
1. 确保电源模块能提供足够电流(>500mA)。
2. 用万用表或代码逐一检查SCK, MOSI, DC, CS, RST引脚连接。
3. 在代码中确认 display_bus ST7735 初始化引脚与硬件一致。
4. 调整 ST7735 初始化中的 rotation 参数(0, 90, 180, 270)。
旋转编码器读数乱跳或不变化 1. A、B相引脚接反或接触不良。
2. 未使用上拉电阻(部分编码器需外部上拉)。
3. 代码中引脚模式设置错误。
1. 交换A、B相引脚试试。
2. 在编码器A、B引脚与3.3V之间接10K上拉电阻。
3. 确保 rotaryio.IncrementalEncoder 使用的引脚支持硬件中断。
DFPlayer无声音 1. 扬声器未接或损坏。
2. 模块供电不足(需5V)。
3. RX/TX线与ESP32接反。
4. SD卡内音频文件格式或命名不符。
1. 检查扬声器焊接,可用电池直接触碰听有无电流声。
2. 确保DFPlayer的VCC接在5V上,而非3.3V。
3. 交换ESP32的TX与DFPlayer的RX,以及ESP32的RX与DFPlayer的TX。
4. SD卡格式化为FAT32,MP3文件命名为如 0001.mp3 0002.mp3 ,放入根目录。
电池耗电极快 1. 存在短路或漏电。
2. 程序未进入低功耗模式。
3. LED等外设未在闲置时关闭。
1. 断电后测量整板静态电流,正常应在几十mA以下,过高则排查短路。
2. CircuitPython下深度睡眠支持有限,可考虑在循环中加入 time.sleep() 降低主频。
3. 在非互动状态关闭屏幕背光(如果可控)和小马LED。

6.2 项目优化与扩展思路

这个基础版本完成后,你有无数种方式让它变得更有趣:

  1. 增加无线功能 :利用ESP32内置的蓝牙,你可以用手机APP来切换脸谱、控制音效,甚至上传新的脸谱图片。利用Wi-Fi,可以让多个玩具之间联动,或者从网络下载新的节日主题内容包。
  2. 丰富传感器 :加入一个加速度计,让玩具在被拿起、摇晃时做出不同反应。加入一个光线传感器,让屏幕亮度或LED在黑暗中自动调节。
  3. 改进音频 :目前的DFPlayer是单声道输出。可以升级为更高级的音频解码芯片,支持立体声甚至WAV格式,获得更好的音质。
  4. 设计更复杂的互动叙事 :不仅仅是切换脸谱播放声音。可以编写一个小故事线,通过编码器旋转、按键组合来触发不同的剧情段落,让小马的LED闪烁模式也与剧情配合。
  5. 外壳美化 :使用更高精度的树脂3D打印,或者打印后进行打磨、喷漆、旧化处理,让玩具的外观质感达到商业产品级别。

整个项目做下来,最深的体会是:硬件项目的成功,三分在创意,七分在细节。一个松动的接线、一个没考虑的公差、一行错误的引脚定义,都可能导致数小时的调试。但正是这些不断发现问题、解决问题的过程,让最终的成品“活”过来时,那种成就感是无与伦比的。从一堆塑料、电路板和代码,变成一个拥有个性和互动能力的小家伙,这种创造生命般的快乐,正是创客精神的源泉。下次,我打算给它加上蓝牙,让我的手机能成为它的遥控器,也许还能在派对上让几个这样的玩具一起跳支灯舞。

更多推荐