儿童拼图机器人语音复读
💡 划重点
- 主控:ESP32,双I2S接口同时驱动音频输入与输出
- 语音链路:INMP441采集 → SD卡存储 → MAX98357A播放
- 拼图完成触发:霍尔/磁簧开关或扫码模组识别拼图结果
- 成本约束:所有物料合计低于85元
- 学习价值:覆盖I2S通信、DMA缓冲、SD文件读写、中断触发、低功耗唤醒
需求洞察
目标用户:3-6岁学前儿童,以及陪玩的家长或幼教老师。该年龄段儿童正处于语言敏感期,需要大量重复性语音输入来建立词汇与发音之间的联系。
真实痛点:市面上的拼图玩具多为静态产品,拼完即结束,缺少互动反馈。而早教机虽能讲故事、读诗词,却无法与儿童正在进行的实体操作(拼图)形成关联。家长陪伴时间有限,无法时刻为孩子讲解每一块拼图对应的单词或短句。若拼图错误或完成,设备不会给出任何鼓励或纠正,弱化了“试错-反馈-强化”的学习闭环。
为什么值得做:
- 造价极低,利用ESP32的硬件I2S外设,比树莓派方案成本下降80%以上。
- 语音复读直接嵌入物理拼图流程,每个拼图块对应一段预设语音,孩子完成拼图时自动播放,比单纯按键触发的复读机更具情境感。
- 家长可通过SD卡自定义语音内容,无需额外APP或云端依赖,断网可用,没有隐私外泄风险。
- 项目兼具硬件接线、信号采集、文件系统操作与结构设计,是STEAM教育的典型案例。
方案设计
感知层
- 拼图底座下方埋设一组干簧管,每个拼图区位置嵌入一颗小磁铁。当儿童放置正确拼图块时,磁簧开关导通,产生一个下降沿触发信号。
- 或采用更简化的方案:每个拼图槽位安装一个微动开关(考虑到成本与通用性,本项目采用磁簧管方案)。
- 音频采集:INMP441通过I2S接口实时采样环境声音,预先录制用户(家长或老师)的声音,或现场录入儿童跟读内容,存入SD卡WAV文件。
控制层
- ESP32主控。系统启动后,首先读取SD卡中的
config.txt配置文件,建立「拼图槽位编号 → WAV文件路径」映射表。 - 当感知层检测到第N块拼图放入正确位置,ESP32通过I2S2接口读取对应的WAV文件,并发送至MAX98357A功放播放。
- 支持“无拼图动作时进入轻睡模式”,ESP32在深度睡眠下仅有GPIO唤醒中断生效,以降低静态功耗。
- 若在30秒内拼完所有拼图,系统自动拼接语音“你真棒!全部完成!”并循环播放三次。
执行层
- MAX98357A音频放大器连接一个4Ω/3W扬声器。输出的语音不仅包含拼图内容,还包含对儿童反馈的鼓励音效。
- 当一个槽位放入了错误的拼图块(磁簧开关未导通但另一开关误触),播放“再试试看”语音,提示儿童换一块试试。
日志/联网层
- 本项目定位为离线教育硬件,不依赖Wi-Fi。但ESP32具备双核与Wi-Fi能力,开发阶段预留串口日志输出用于调试。
- 每次完成拼图,事件可记录到SD卡
history.log中,时间戳由内部RTC维护(或通过NTP对时后离线运行),便于家长查看行为数据。 - 可选:预留MQTT上报接口,若用户在
config.txt中填写Wi-Fi账号,系统在拼图全部完成后自动上报“今日完成次数/平均耗时/错误次数“至私有服务器或巴法云等平台。
物料清单
| 序号 | 名称 | 型号/规格 | 数量 | 用途说明 | 估算单价(元) |
|---|---|---|---|---|---|
| 1 | 主控板 | ESP32 DevKit V1 (30pin) | 1 | 主控逻辑、I2S、中断 | 16.00 |
| 2 | I2S功放 | MAX98357A模块 | 1 | 输出语音至扬声器 | 8.50 |
| 3 | I2S麦克风 | INMP441模块 | 1 | 录音输入与现场复读 | 11.00 |
| 4 | 存储 | MicroSD卡模块 + 4GB TF卡 | 1套 | 存放音频与日志 | 6.00 |
| 5 | 磁簧开关 | MKA10101 | 4 | 感知四个拼图槽位 | 2.00 |
| 6 | 磁铁 | 5mm x 1mm 圆形强磁 | 4 | 嵌入拼图块中 | 0.40 |
| 7 | 扬声器 | 4Ω 3W 直径40mm | 1 | 发声单元 | 3.50 |
| 8 | 电池组 | 18650电池盒 + 2节18650(或使用充电宝模块) | 1 | 便携供电,5V输出 | 12.00 |
| 9 | 电源模块 | AMS1117-3.3V降压模块 | 1 | ESP32外部引脚供电(如适用) | 1.50 |
| 10 | 杜邦线/PCB板 | 镀金杜邦线若干、洞洞板 | 若干 | 连接 | 6.00 |
| 11 | 外壳辅料 | 亚克力板/3D打印件(0.2kg耗材)+ 螺钉 | 1套 | 结构与拼图底板 | 15.00 |
| 12 | 电阻 | 10kΩ、330Ω各5只 | 若干 | 干簧管上拉/限流 | 1.00 |
| 13 | 轻触按键 | 6x6x5mm | 1 | 录音切换(强制录入模式) | 0.50 |
| 合计 | 约 83.40 元 |
上述合计已经低于85元目标成本,预留了约1.6元浮动空间。如果4GB TF卡已有,总价还能进一步压缩到77元左右。
接线与结构
GPIO分配与理由
| 外设 | 引脚 | 说明 |
|---|---|---|
| MAX98357A BCLK | GPIO26 | I2S时钟(系统I2S0) |
| MAX98357A LRC | GPIO25 | 左右声道时钟 |
| MAX98357A DIN | GPIO22 | 音频数据输出 |
| INMP441 SCK (BCLK) | GPIO32 | 麦克风I2S时钟 |
| INMP441 WS (LRC) | GPIO33 | 麦克风字选择 |
| INMP441 SD (DOUT) | GPIO35 | 麦克风数据输入 |
| 干簧管0 | GPIO14 | 1号拼图槽位(输入,上拉) |
| 干簧管1 | GPIO27 | 2号槽位 |
| 干簧管2 | GPIO12 | 3号槽位 |
| 干簧管3 | GPIO13 | 4号槽位 |
| 录音按键 | GPIO15 | 低电平有效,用于按住说话录制 |
微调说明:麦克风没有使用与功放相同的BCLK/LRC(GPIO26/GPIO25),因为ESP32支持多个I2S外设,分离后可以避免麦克风时钟干扰功放输出波形,减少可闻底噪。同时INMP441的供电连接到3.3V,而MAX98357A的供电允许5V以提升输出功率。SD卡的CS使用GPIO5,SCK用GPIO18,MOSI=GPIO23,MISO=GPIO19,这些是标准VSPI引脚。
结构与外壳
- 外壳由一个底部基座和四块拼图块组成。基座挖有4个圆形凹槽,每个槽底部中心放置磁簧开关。
- ESP32、功放和电池盒固定在基座一侧的“控制舱”内。扬声器开口朝向底板底部一侧,音量经反射孔传出。
- INMP441布置在面板浅朝向位置,开小孔以接收外部声源。
- SD卡卡槽从控制舱侧面露出,方便热插拔更换音频。
- 拼接顺序通过拼图底部的磁铁极性一致性来限定——每块拼图背面磁铁安装位置不一样,使得只有在正确槽位时才能导通对应的干簧管。
核心代码
以下是Arduino框架下的PlatformIO项目骨架,重点展示双I2S与拼图反馈逻辑:
#include <Arduino.h>
#include <driver/i2s.h>
#include <FS.h>
#include <SD.h>
#include <SPI.h>
#define SPEAKER_BCLK 26
#define SPEAKER_LRC 25
#define SPEAKER_DOUT 22
#define MIC_SCK 32
#define MIC_WS 33
#define MIC_SD 35
#define SD_CS 5
#define REED_0 14
#define REED_1 27
#define REED_2 12
#define REED_3 13
#define REC_BUTTON 15
#define I2S_SPEAKER_PORT I2S_NUM_0
#define I2S_MIC_PORT I2S_NUM_1
const char* slotFiles[4] = {"/audio/slot1.wav", "/audio/slot2.wav", "/audio/slot3.wav", "/audio/slot4.wav"};
const char* rewardFile = "/audio/reward.wav";
const char* tryAgainFile = "/audio/tryagain.wav";
bool slotDone[4] = {false, false, false, false};
int reedPins[4] = {REED_0, REED_1, REED_2, REED_3};
void initSpeaker() {
i2s_config_t cfg = {};
cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
cfg.sample_rate = 16000;
cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT;
cfg.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
cfg.communication_format = I2S_COMM_FORMAT_STAND_I2S;
cfg.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1;
cfg.dma_buf_count = 8;
cfg.dma_buf_len = 512;
i2s_driver_install(I2S_SPEAKER_PORT, &cfg;, 0, NULL);
i2s_pin_config_t pinCfg = {};
pinCfg.bck_io_num = SPEAKER_BCLK;
pinCfg.ws_io_num = SPEAKER_LRC;
pinCfg.data_out_num = SPEAKER_DOUT;
pinCfg.data_in_num = I2S_PIN_NO_CHANGE;
i2s_set_pin(I2S_SPEAKER_PORT, &pinCfg;);
}
void initMicrophone() {
i2s_config_t cfg = {};
cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX);
cfg.sample_rate = 16000;
cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT;
cfg.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
cfg.communication_format = I2S_COMM_FORMAT_STAND_I2S;
cfg.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1;
cfg.dma_buf_count = 8;
cfg.dma_buf_len = 512;
i2s_driver_install(I2S_MIC_PORT, &cfg;, 0, NULL);
i2s_pin_config_t pinCfg = {};
pinCfg.bck_io_num = MIC_SCK;
pinCfg.ws_io_num = MIC_WS;
pinCfg.data_in_num = MIC_SD;
pinCfg.data_out_num = I2S_PIN_NO_CHANGE;
i2s_set_pin(I2S_MIC_PORT, &pinCfg;);
}
bool playWav(const char* path) {
File f = SD.open(path);
if (!f) return false;
uint8_t buf[512];
size_t bytesRead = 0;
// 跳过44字节WAV头
f.seek(44);
while (f.available()) {
bytesRead = f.read(buf, sizeof(buf));
size_t written = 0;
while (written < bytesRead) {
size_t ret = i2s_write(I2S_SPEAKER_PORT, buf + written, bytesRead - written, &written;, portMAX_DELAY);
if (ret != ESP_OK) break;
}
}
f.close();
return true;
}
void checkSlotsAndPlay() {
int completedCount = 0;
for (int i = 0; i < 4; i++) {
if (slotDone[i]) {
completedCount++;
continue;
}
if (digitalRead(reedPins[i]) == HIGH) { // 干簧管导通,磁铁靠近
slotDone[i] = true;
playWav(slotFiles[i]);
delay(300);
}
}
if (!slotDone[0] && digitalRead(reedPins[0]) == LOW) {
// 如果1号槽位还没完成且开关未触发,不做操作
}
if (completedCount == 4) {
playWav(rewardFile);
delay(500);
// 全部完成后重置,允许开始下一轮拼图
for (int j = 0; j < 4; j++) slotDone[j] = false;
}
}
void setup() {
Serial.begin(115200);
// 初始化干簧管输入,内部上拉,磁铁靠近时引脚读到高电平
for (int i = 0; i < 4; i++) {
}
if (!SD.begin(SD_CS)) {
return;
}
initSpeaker();
initMicrophone();
Serial.println("System Ready. 开始拼图吧!");
}
void loop() {
// 若录音按键被按住,则进行约3秒的现场录音处理,这里以官方i2s读为例写入SD
if (digitalRead(REC_BUTTON) == LOW) {
// 简易录音逻辑:从I2S_MIC_PORT读取数据并拼接WAV头,存到/tryagain.wav
// (完整代码见源码包,此处省略头写入细节)
}
checkSlotsAndPlay();
delay(50);
}
代码中干簧管默认拉低,磁铁靠近时由于内部上拉,外部导通使引脚读到HIGH,根据实际安装调整逻辑即可。
调试步骤
- 上电基础检测:先不接功放与麦克风,用手机USB供电ESP32,检查串口是否正常输出“System Ready”。
- SD卡识别:插入预置有
/audio文件夹,内含单调正弦波测试test.wav的TF卡,调用playWav("/audio/test.wav"),若听到声音说明SD与I2S功放通路正常。 - 麦克风验证:编写临时测试代码,通过I2S读取INMP441 100ms数据并打印幅值。对着麦克风吹气,观察幅值跳变。若数值为0或恒定噪声,检查SCK/WS是否接反,INMP441的L/R引脚接地(左声道)。
- 干簧管触发测试:用手持磁铁靠近干簧管,串口打印引脚状态,确认高低电平逻辑与代码匹配。
- 单块拼图完整联调:放置第一块拼图,确认只播放一次
slot1.wav,并且重复靠近不会循环触发(代码中slotDone防抖)。 - 全流程测试:依次放置四块拼图,最终播放奖励音频。若中途放错位置(实际无安装错误检测物理),确认该槽位不触发对应语音。
- 音量/功耗检查:调整MAX98357A增益引脚(GAIN接VDD/1.2V等)至适合室内音量。测静态电流应低于60mA(屏幕关闭、未放拼图时)。
- 耐久验证:连续拼图30次无死机,SD卡不会发生文件损坏。
在设计机时会将拼图槽位做一些防误触机制——每块拼图块底部磁铁必须在正确朝向时才能导通对应干簧管。拼错时没有开关反应,此时3秒后播放“再试试看”的柔性引导,而不是直接报错。最终引导儿童观察图案颜色、缺口形状来推理正确方位,整个项目就从“普通拼图”升级为一个有耐心、会听、会反馈的学习助手。
设计图
设计逻辑映射如下:
感知输入包含磁簧管与INMP441录音信息,控制处理即ESP32解析映射表与状态机,执行反馈通过MAX98357A驱动扬声器,数据上报可选走串口/SD卡日志实现。整体链路单机离线闭环,无需云端即可完成交互。