1. 项目概述当玩具鱼学会“说话”几年前我在一个旧货市场淘到了一条经典的“大嘴比利鱼”Big Mouth Billy Bass玩具。它原本的功能很简单按下按钮它会摇头摆尾地唱起《Take Me to the River》。作为一个嵌入式开发的老兵我总觉得它的潜力不止于此。为什么它只能唱一首歌为什么它不能根据我的指令做出反应一个想法在我脑中成型用Arduino给它装上“大脑”和“耳朵”让它不仅能动还能“听懂”遥控器的指令并“开口说话”。这个项目我称之为“基于Arduino与红外遥控的智能语音鱼改造”。其核心目标是赋予这条机械鱼全新的、可编程的交互生命。我们不再依赖它内置的单一传感器和固定程序而是通过两个Arduino Uno微控制器分别接管其电机驱动系统和音频播放系统。一个Arduino配合L293D电机驱动板负责解析来自电视遥控器的红外信号并精确控制鱼嘴开合、头部转动和尾部摆动。另一个Arduino则搭载SD卡模块负责存储和播放我们预先录制好的语音片段比如“你好”、“再见”并通过一个简单的音频放大电路驱动扬声器。最终你只需要拿起家里的任意一个电视遥控器按下音量键鱼嘴就会开合按下频道键鱼尾就会摆动甚至按下特定的媒体键它能一边转头一边用合成语音向你问好。这不仅仅是一个有趣的玩具改造更是一个涵盖了红外通信协议解析、直流电机PWM控制、SD卡文件系统操作、WAV音频文件解码播放以及多微控制器系统协同的综合性嵌入式系统实践案例。无论你是想入门硬件编程的爱好者还是希望寻找一个综合性练手项目的电子工程师这个项目都能让你在动手的乐趣中串联起多个关键知识点。2. 核心硬件选型与电路设计解析改造的第一步是“解剖”比利鱼并规划好整个系统的硬件架构。我们需要明确每个部分的功能、选型依据以及它们之间如何“对话”。2.1 主控与核心模块选型1. 为什么需要两个Arduino Uno这是本项目第一个关键设计决策。原文中提到根本原因在于引脚冲突。L293D电机驱动扩展板Shield和SD卡模块Shield都需要占用Arduino Uno的特定数字引脚和SPI总线引脚。例如很多SD卡模块使用引脚10-13作为SPI通信而部分电机驱动板也会使用这些引脚进行控制。如果强行堆叠会导致信号冲突系统无法工作。使用两个独立的Arduino一个专司电机驱动我们称之为“电机控制板”一个专司音频播放“音频控制板”是最清晰、最可靠的解决方案。它们之间通过共享红外接收器的信号和共地来协同。2. 电机驱动板L293D H桥驱动芯片比利鱼内部的电机是小型直流电机。控制它们正反转需要H桥电路。L293D是一颗经典的双H桥电机驱动芯片一片就能独立驱动两个直流电机。我们选用现成的L293D电机驱动扩展板极大简化了接线和供电设计。这里有一个重要细节电机驱动板的供电。比利鱼使用4节AA电池6V而Arduino通过USB供电只有5V且电流有限。因此我们必须使用外部电源即鱼体内的电池为电机供电。这需要在电机驱动板上移除一个关键的“PWR”跳线帽将板载的电机电源V_Motor与Arduino的5V电源隔离防止大电流倒灌损坏Arduino。3. 音频播放模块SD卡模块与TMRpcm库为了让鱼“说话”我们需要一个存储语音文件和播放的解决方案。SD卡模块成本低廉、容量大、易于读写是存储多个WAV语音文件的理想选择。播放音频则需要一个高效的库TMRpcm库正是为Arduino播放低质量PCM WAV音频而优化的。它利用Arduino的定时器产生PWM信号来模拟音频波形从引脚9直接输出。虽然音质无法与专业解码芯片相比但对于播放简短人声指令绰绰有余。4. 红外接收与遥控器我们选用最常见的VS1838B红外接收头。它内部集成了滤波、解调和整形电路可以直接输出TTL电平的编码信号给Arduino。遥控器则可以是任何闲置的电视或机顶盒遥控器这利用了家电遥控器编码的普遍性。我们需要通过代码“学习”每个按键对应的红外编码值。2.2 系统电路连接详解整个系统的电路可以看作三个相对独立又互相关联的部分电源与电机驱动、红外接收与信号分发、音频生成与放大。电源与电机驱动部分电源路径比利鱼的4节AA电池正极红线被切断中间串联一个单刀双掷1P2T开关作为整个系统的总开关。开关输出端连接到L293D电机驱动板的“EXT_PWR”端子为电机提供动力电源。电机连接鱼体内的两个电机嘴部电机、头/尾电机的导线也被切断。通过一个四刀双掷4P2T开关我们实现了信号路由切换一档将电机连接回鱼原有的控制板恢复原厂唱歌功能另一档则将电机连接到L293D驱动板的输出端接入我们的Arduino控制系统。这是实现“模式切换”的硬件基础。控制信号L293D驱动板堆叠在“电机控制板”Arduino上通过板载引脚直接通信。电机的控制线来自4P2T开关连接到驱动板的电机输出端子。红外接收与信号分发部分红外接收头其VCC接5VGND接地信号输出端OUT需要同时连接到两个Arduino。这里采用“一线分两路”的方式接收头的信号线先接入面包板然后通过两根跳线分别连接到“电机控制板”和“音频控制板”的数字引脚2这是IRremote库常用的中断引脚。供电红外接收头仅由“电机控制板”的5V引脚供电即可两个Arduino共地GND连接在一起确保信号参考电平一致。音频生成与放大部分音频输出“音频控制板”的引脚9输出PWM音频信号。直接驱动扬声器声音小且含有高频噪声因此需要滤波和放大。低通滤波电路这是一个简单的RC无源滤波器由一枚10kΩ电阻和一枚0.1μF100nF电容组成。其作用是滤除PWM产生的高频载波通常为31.25kHz或更高只留下我们需要的音频信号人声频率一般在300Hz-3.4kHz。计算其截止频率 fc 1/(2πRC) ≈ 1/(23.1410000*0.0000001) ≈ 160Hz。这个值略低于人声最低频能有效滤除高频噪声虽然会损失一点低音但对语音清晰度影响不大。功率放大滤波后的音频信号依然很弱。我们使用一个现成的微型USB功放模块如文中提到的Anker SoundCore mini的核心部分或更常见的PAM8403类功放板。将滤波后的信号接入功放的音频输入功放输出接回比利鱼原有的扬声器或一个外置小喇叭。功放模块由另一个USB电源或直接从Arduino的5V取电需注意电流是否足够。注意焊接延长线和制作接口是耗时但至关重要的步骤。务必使用不同颜色的导线区分功能如红正、黑负、黄信号并给每个焊接点套上热缩管绝缘。在最终组装前强烈建议使用面包板和杜邦线进行全系统功能测试确认电机动作、红外接收、音频播放全部正常后再进行内部布线固定。3. 软件逻辑与代码实现深度剖析硬件是躯体软件是灵魂。这个项目的代码分为三个核心部分分别运行在两个Arduino上。3.1 电机控制板的程序逻辑这块板子的任务是监听红外信号并驱动电机做出相应动作。我们使用IRremote库来解码使用AFMotor库Adafruit Motor Shield库来控制L293D。1. 红外信号解码与映射首先需要“学习”你的遥控器。上传SimpleReceiver-changed示例代码后打开串口监视器按下遥控器按键你会看到类似Protocol: SAMSUNG, Address: 0x707, Command: 0x7的输出。其中Command值就是我们需要的按键编码。 在Billy-Bass-motors.ino主程序中我们需要在loop()函数里不断检查是否收到红外信号(IrReceiver.decode())。一旦收到就提取其decodedIRData.command值并与我们预设的编码进行比对。// 示例定义按键编码根据你的遥控器实际学习到的值修改 #define IR_CODE_MOUTH_OPEN 0x7 // 音量 对应张嘴 #define IR_CODE_MOUTH_CLOSE 0xB // 音量- 对应闭嘴 #define IR_CODE_HEAD_TURN 0xF // 静音键 对应转头 // ... 其他按键定义 void loop() { if (IrReceiver.decode()) { uint16_t command IrReceiver.decodedIRData.command; if (command IR_CODE_MOUTH_OPEN) { openMouth(); } else if (command IR_CODE_MOUTH_CLOSE) { closeMouth(); } else if (command IR_CODE_HEAD_TURN) { turnHead(); } // ... 其他else if分支 IrReceiver.resume(); // 准备接收下一个信号 } }2. 电机驱动与控制函数AFMotor库使得控制电机变得非常简单。我们需要创建两个电机对象一个对应嘴部电机一个对应头/尾电机。注意头尾共用一个电机通过改变电流方向来控制不同部位。#include AFMotor.h // 在L293D Shield上电机接在M1和M2端口 AF_DCMotor mouthMotor(1); // 嘴部电机接M1 AF_DCMotor headTailMotor(2); // 头/尾电机接M2 void openMouth() { mouthMotor.setSpeed(200); // 设置速度0-255 mouthMotor.run(FORWARD); // 正向转动张嘴 delay(300); // 转动300毫秒 mouthMotor.run(RELEASE); // 释放电机让弹簧复位或运行REVERSE闭口 } void turnHead() { headTailMotor.setSpeed(200); headTailMotor.run(FORWARD); // 正向转动转头 delay(500); headTailMotor.run(RELEASE); // 注意需要另一个函数和按键让头转回或者用REVERSE并延时后RELEASE }实操心得电机的run(RELEASE)命令是切断电源让电机自由停止。对于比利鱼这种靠弹簧复位的机构通常用RELEASE让弹簧将部件拉回原位。但有时弹簧力不足可能需要主动让电机短暂反向转动来确保复位到位。这需要根据实际机械结构调试delay的时间。3.2 音频控制板的程序逻辑这块板子的任务是同样监听红外信号但只响应触发语音的按键并从SD卡播放对应的WAV文件。1. 音频文件准备与格式要求这是最容易出错的一步。TMRpcm库对WAV文件格式有严格要求格式WAV (PCM)位深8位无符号 (8 Bit Unsigned PCM)采样率16000 Hz声道单声道 (Mono)使用Audacity处理音频时务必在“项目频率”处设置为16000 Hz导出时选择“WAV (Microsoft)”格式并在“编码”下拉框中选择“无符号8位PCM”。文件名必须遵循8.3格式主文件名不超过8字符扩展名.wav例如hello.wav、bye.wav。2. 代码实现红外触发与播放音频板同样使用IRremote库解码。我们创建一个独立的按键编码集合用于触发语音。#include SD.h #include TMRpcm.h #include SPI.h TMRpcm audio; #define SD_ChipSelectPin 4 // SD卡模块的CS引脚通常接Arduino的4脚 void setup() { Serial.begin(9600); audio.speakerPin 9; // 音频从9号引脚输出 if (!SD.begin(SD_ChipSelectPin)) { Serial.println(SD卡初始化失败); return; } audio.setVolume(5); // 设置音量0-7 IrReceiver.begin(IR_RECEIVE_PIN); // 初始化红外接收 } void loop() { if (IrReceiver.decode()) { uint16_t command IrReceiver.decodedIRData.command; if (command IR_CODE_SAY_HELLO) { audio.play(hello.wav); } else if (command IR_CODE_SAY_BYE) { audio.play(bye.wav); } IrReceiver.resume(); } }3. 实现动作与语音的联动如何让鱼在转头的同时说话这需要两个Arduino协同。有两种思路思路一主从触发。在电机控制板的代码中当检测到“媒体播放”键时除了驱动电机做一套复杂动作如转头、摆尾还可以通过一个额外的IO引脚发送一个高/低电平信号给音频控制板。音频控制板检测到这个信号后播放对应的语音文件。这需要增加一根信号线。思路二双机并行解码本项目采用。两个Arduino同时监听同一个红外接收头。我们为复杂的联动动作定义一个专属红外编码如0x8C。在电机控制板中这个编码触发一系列电机动作序列在音频控制板中同一个编码触发播放“Hello”语音。由于红外信号是广播式的两个Arduino几乎能同时收到并执行各自的任务从而实现“边动边说”的效果。这种方式硬件接线最简单但要求两个程序对联动指令的定义完全一致。3.3 系统集成与模式切换逻辑硬件上的4P2T开关是实现“原厂模式”和“智能模式”切换的关键。其逻辑如下原厂模式开关拨到一侧将鱼的两个电机和电源线重新连接回鱼体内的原始控制板。此时按下鱼身上的按钮它依然会执行原始的唱歌跳舞程序我们的Arduino系统完全断电隔离。智能模式开关拨到另一侧将电机和电源线切换到我们的Arduino控制系统。此时遥控器生效Arduino系统掌控一切。这种设计非常实用既保留了玩具的原始功能又增加了可玩性也便于在智能系统调试失败时快速恢复。4. 分步实操指南与现场踩坑记录理论清晰后我们进入动手环节。我将结合原文步骤补充大量实际操作中容易忽略的细节和“坑点”。4.1 步骤一拆解比利鱼与线路改造核心操作安全地打开鱼身识别并延长电机和电源线。拆解用合适的螺丝刀卸下鱼背后的6颗螺丝。小心分离前后壳注意可能有卡扣或排线连接。识别线路找到电路板。通常连接电机的线束会从电路板的一个接插件引出。常见配置是红/灰线控制嘴部电机橙/黑线控制头尾电机。红/黑线来自电池盒。务必用万用表确认将鱼置于唱歌状态用电压档测量哪对线有电压变化用电流档串联测量工作电流验证是否在L293D的驱动能力每通道0.6A之内。切割与延长在每根待接线的中间位置剪断。剪断前用标签纸做好标记如“嘴电机红”、“电池正极”这是避免后续接错的救命稻草。焊接延长线时先给导线镀锡。将鱼体内的短线和新延长线拧在一起再上锡焊接这样更牢固。套上热缩管用热风枪或打火机小心加热收缩。开孔规划好音频接口、模式开关、总开关在鱼背后的位置。用铅笔标记先用小钻头定位再用合适尺寸的钻头扩孔。开穿线孔时注意边缘打磨光滑防止磨损线材。踩坑记录焊接电池盒负极黑线的公共端时原焊点很容易因加热而脱落。我的方法是先用吸锡器清理旧焊点然后将电池盒引出的黑线、蓝线如有以及我们的新延长线三股线先拧紧在一起再整体焊接在电池盒的金属片上。可以先用高温胶带临时固定线头再进行焊接。4.2 步骤二至四电机控制系统搭建与测试核心操作组装电机驱动板编写测试代码学习遥控器编码。硬件组装将L293D电机驱动板对齐引脚稳稳压入“电机控制板”Arduino。务必移除板上的“PWR”跳线帽这是使用外部电机电源的关键。库安装在Arduino IDE中通过“工具”-“管理库”搜索安装Adafruit Motor Shield V1库对应V1板或Adafruit Motor Shield V2库。同时安装IRremote库。基础测试先不接鱼上传一个简单的电机测试代码如让电机正转1秒停1秒反转1秒用万用表测量驱动板输出端子是否有电压变化确保驱动板工作正常。连接与测试将比利鱼的电机线通过4P2T开关的对应端子接到驱动板输出端。上传Billy-Bass-Serial-Monitor代码。打开串口监视器输入H/h, T/t, M/m等字符测试每个动作是否正常。观察电机运动是否顺畅有无卡顿或异响。红外学习连接好红外接收头VCC-5V, GND-GND, OUT-引脚2。上传SimpleReceiver-changed代码。打开串口监视器对准接收头按下遥控器按键记录下你计划使用的每个按键的Command值。建议用Excel或纸笔记下来并标注好计划赋予的功能。注意事项红外接收头对环境光敏感尤其是日光和节能灯可能包含红外成分造成干扰。测试时最好在室内白炽灯或LED灯下并避免强光直射接收头。如果发现接收不稳定可以尝试给接收头套上一小段黑色热缩管作为遮光罩。4.3 步骤五至八音频系统搭建与联合调试核心操作准备音频文件搭建音频播放电路编写联动代码。音频文件制作录音用Audacity录音时确保麦克风音量适中波形峰值在-3dB左右不要爆音波形上下顶格。处理务必执行“轨道” - “混音” - “混音为单声道”。根据需要应用“标准化”效果推荐-3dB使音量一致或使用“均衡器”微调音色。导出导出设置是重中之重格式选“WAV微软”编码选“无符号8位PCM”。保存为8.3格式文件名。SD卡格式化使用电脑的磁盘工具将SD卡格式化为FAT32对于大于2GB的卡或FAT16对于小容量卡文件系统。分配单元大小簇大小建议选择默认或32KB。这是确保Arduino SD库能正确识别的关键。音频电路搭建在面包板上搭建RC低通滤波器。从Arduino引脚9 - 0.1uF电容 - 10k电阻 - 功放板音频输入正极。Arduino GND直接连接到功放板音频输入负极。检查无误后再通电。分步测试SD卡测试先单独测试SD卡模块。上传CardInfo示例看串口能否正确列出卡内文件包括你的WAV文件。音频播放测试上传一个最简单的TMRpcm播放示例如play例程修改文件名测试是否能通过引脚9输出声音到耳机或功放。红外触发音频测试最后将红外接收和音频播放代码整合测试按下特定按键是否能播放对应语音。联合调试将两个Arduino、面包板、功放板通过杜邦线连接成一个完整系统。先测试电机控制功能再测试音频播放功能最后测试联动功能如按某个键既转头又说话。注意观察两个系统同时工作时电源是否稳定电机启动瞬间电流较大可能引起电压跌落导致另一个Arduino复位。4.4 步骤九内部布局与最终装配核心操作将所有电子部件合理、稳固地安装到鱼体内。规划空间比利鱼内部空间有限。先将所有部件两个Arduino、面包板、功放板、开关在鱼壳外摆好规划走线路径。音频控制板因只有SD卡模块较薄可以贴在鱼背上部。电机控制板连同驱动板、面包板、功放板可以固定在鱼背下部的塑料支架上。固定主板使用尼龙扎带或螺丝将Arduino板固定在鱼壳内部预设的骨架上或自行钻孔固定。确保所有螺丝和金属部件不会短路电路板背面必要时使用绝缘垫片。布线整理用扎带将线束捆扎整齐避免缠绕在运动部件如连杆、齿轮上。给红外接收头焊接一个3针杜邦头母座方便后续拆卸。将接收头用黑色电工胶布固定在鱼嘴附近或鱼眼位置确保其接收窗朝向鱼前方无遮挡。功能验证合盖前最后一次上电测试所有功能模式切换、遥控器控制电机、遥控器触发语音、联动动作。确认无误。合盖与美化小心合上后盖拧紧螺丝。可以用白色标签纸打印开关和音频接口的标识贴在鱼壳外使其看起来更像个“正经”的改造产品。5. 常见问题排查与进阶优化思路即使按照步骤操作你也可能会遇到一些问题。这里汇总了一些常见故障及其解决方法。5.1 电机相关问题问题现象可能原因排查步骤与解决方案电机完全不转1. 电源未接通或开关损坏。2. L293D驱动板“PWR”跳线未移除。3. 电机线接错或虚焊。4. 程序未正确上传或库未安装。1. 用万用表检查电池电压检查开关通断。2. 确认电机驱动板上的“PWR”跳线帽已拔掉。3. 用万用表蜂鸣档检查从开关到驱动板端子的线路连通性。4. 上传最简单的电机测试代码如motor.run(FORWARD)打开串口监视器看是否有输出。电机只朝一个方向转1. 电机线序接反。2. H桥某一半损坏概率低。3. 程序逻辑错误只发送了单向指令。1. 交换电机两根线的接线顺序。2. 更换电机驱动板或Arduino引脚测试。3. 检查代码中run(FORWARD)和run(REVERSE)是否正确调用。电机动作无力或卡顿1. 电池电量不足。2. 电机驱动板供电电压不足外部电源未接好。3. 机械结构阻力大润滑不足。1. 更换全新电池。2. 检查“EXT_PWR”端子接线是否牢固测量电机工作时的电压。3. 在齿轮和连杆关节处少量涂抹塑料用润滑脂。5.2 红外与音频相关问题问题现象可能原因排查步骤与解决方案遥控器无反应1. 红外接收头引脚接错。2. 遥控器电池没电或非红外遥控如射频。3. 环境光干扰太强。4.IRremote库引脚定义冲突。1. 确认VCC、GND、OUT三线对应正确。2. 用手机摄像头对准遥控器发射管按键时观察是否有紫光闪烁。3. 移至光线较暗处测试或给接收头加遮光罩。4. 尝试更换红外接收头连接的Arduino引脚如换到3号引脚并在代码中IrReceiver.begin(3)。能收到信号但动作错乱1. 按键编码Command值记录错误或代码中写错。2. 两个Arduino解码协议不一致。1. 重新运行学习代码仔细核对每个按键的十六进制编码。2. 确保两个Arduino代码中#define的协议类型如DECODE_SAMSUNG一致。无声音或声音失真1. SD卡未格式化或文件格式不对。2. 音频文件格式不符合TMRpcm要求。3. 低通滤波器电阻/电容值错误或虚焊。4. 功放板未供电或音量调至最小。5. 扬声器线未接或损坏。1. 重新格式化为FAT32检查文件是否在根目录。2. 用Audacity重新检查并导出文件确认是8位、16kHz、单声道、无符号PCM。3. 用示波器或万用表交流档检查引脚9和滤波后是否有信号变化。4. 检查功放板供电LED是否亮起调节音量电位器。5. 直接用手触碰音频线头听扬声器是否有嗡嗡声检查连接。播放音频时电机动作异常1. 电源功率不足音频播放瞬间拉低电压导致电机控制板复位。2. 地线干扰共地不良。1. 使用容量更大的电池如锂电池组或为两个Arduino分别独立供电。2. 确保所有GND点电池负极、两个Arduino的GND、功放板GND都可靠连接在同一点或使用更粗的导线作为地线。5.3 进阶优化与扩展想法完成基础功能后你可以尝试以下升级让这条鱼变得更“聪明”电源管理升级拆除笨重的4节AA电池改用一块3.7V的18650锂电池配合5V升压模块为整个系统供电。可以增加一个微型充电模块实现USB充电让鱼摆脱线缆束缚。增加感知能力在鱼嘴内部安装一个红外避障传感器或超声波传感器。编写程序当检测到前方有物体比如你的手时自动触发“打招呼”的联动动作实现非接触式交互。语音内容自定义将SD卡模块换成带有串口的MP3解码模块如DFPlayer Mini。这种模块音质更好支持直接播放MP3文件且可以通过串口指令控制播放更稳定功能也更丰富如循环播放、指定曲目。无线化与物联网将其中一个Arduino替换为ESP8266或ESP32开发板。这样鱼不仅可以被遥控器控制还能连接Wi-Fi通过手机App、网页甚至语音助手如天猫精灵、小爱同学进行控制。你可以远程让家里的鱼对你说话。动作序列编程目前每个按键触发一个简单动作。你可以编写更复杂的动作序列函数例如一个“跳舞”函数里面包含转头、摆尾、张嘴、闭嘴的多个延时组合然后用一个按键触发整个舞蹈配合播放一段完整的音乐。改造的乐趣在于突破原有限制赋予旧物新的生命。这条会说话的鱼只是一个起点它所涉及的硬件集成、信号处理、编程逻辑是通往更复杂智能硬件世界的一块绝佳跳板。最重要的是在整个过程中保持耐心享受从发现问题、排查故障到最终让机器按你意愿行动所带来的成就感。