儿童拼图机器人语音复读

💡 划重点

  • 主控:ESP32,双I2S接口同时驱动音频输入与输出
  • 语音链路:INMP441采集 → SD卡存储 → MAX98357A播放
  • 拼图完成触发:霍尔/磁簧开关或扫码模组识别拼图结果
  • 成本约束:所有物料合计低于85元
  • 学习价值:覆盖I2S通信、DMA缓冲、SD文件读写、中断触发、低功耗唤醒

需求洞察

目标用户:3-6岁学前儿童,以及陪玩的家长或幼教老师。该年龄段儿童正处于语言敏感期,需要大量重复性语音输入来建立词汇与发音之间的联系。

真实痛点:市面上的拼图玩具多为静态产品,拼完即结束,缺少互动反馈。而早教机虽能讲故事、读诗词,却无法与儿童正在进行的实体操作(拼图)形成关联。家长陪伴时间有限,无法时刻为孩子讲解每一块拼图对应的单词或短句。若拼图错误或完成,设备不会给出任何鼓励或纠正,弱化了“试错-反馈-强化”的学习闭环。

为什么值得做

  1. 造价极低,利用ESP32的硬件I2S外设,比树莓派方案成本下降80%以上。
  2. 语音复读直接嵌入物理拼图流程,每个拼图块对应一段预设语音,孩子完成拼图时自动播放,比单纯按键触发的复读机更具情境感。
  3. 家长可通过SD卡自定义语音内容,无需额外APP或云端依赖,断网可用,没有隐私外泄风险。
  4. 项目兼具硬件接线、信号采集、文件系统操作与结构设计,是STEAM教育的典型案例。

方案设计

感知层

控制层

执行层

日志/联网层

物料清单

序号 名称 型号/规格 数量 用途说明 估算单价(元)
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引脚。

结构与外壳

核心代码

以下是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,根据实际安装调整逻辑即可。

调试步骤

  1. 上电基础检测:先不接功放与麦克风,用手机USB供电ESP32,检查串口是否正常输出“System Ready”。
  2. SD卡识别:插入预置有/audio文件夹,内含单调正弦波测试test.wav的TF卡,调用playWav("/audio/test.wav"),若听到声音说明SD与I2S功放通路正常。
  3. 麦克风验证:编写临时测试代码,通过I2S读取INMP441 100ms数据并打印幅值。对着麦克风吹气,观察幅值跳变。若数值为0或恒定噪声,检查SCK/WS是否接反,INMP441的L/R引脚接地(左声道)。
  4. 干簧管触发测试:用手持磁铁靠近干簧管,串口打印引脚状态,确认高低电平逻辑与代码匹配。
  5. 单块拼图完整联调:放置第一块拼图,确认只播放一次slot1.wav,并且重复靠近不会循环触发(代码中slotDone防抖)。
  6. 全流程测试:依次放置四块拼图,最终播放奖励音频。若中途放错位置(实际无安装错误检测物理),确认该槽位不触发对应语音。
  7. 音量/功耗检查:调整MAX98357A增益引脚(GAIN接VDD/1.2V等)至适合室内音量。测静态电流应低于60mA(屏幕关闭、未放拼图时)。
  8. 耐久验证:连续拼图30次无死机,SD卡不会发生文件损坏。

在设计机时会将拼图槽位做一些防误触机制——每块拼图块底部磁铁必须在正确朝向时才能导通对应干簧管。拼错时没有开关反应,此时3秒后播放“再试试看”的柔性引导,而不是直接报错。最终引导儿童观察图案颜色、缺口形状来推理正确方位,整个项目就从“普通拼图”升级为一个有耐心、会听、会反馈的学习助手。

设计图

儿童拼图机器人语音复读结构flow
1
感知输入
2
控制处理
3
执行反馈
4
数据上报

设计逻辑映射如下:

感知输入包含磁簧管与INMP441录音信息,控制处理即ESP32解析映射表与状态机,执行反馈通过MAX98357A驱动扬声器,数据上报可选走串口/SD卡日志实现。整体链路单机离线闭环,无需云端即可完成交互。