用Arduino改造老式电话为MIDI控制器:从矩阵扫描到声码器应用 1. 项目概述从废弃电话到音乐创作工具几年前我在一家旧货店偶然发现了一部西南贝尔Southwestern Bell的Freedom Phone型号FT325。它静静地躺在角落外壳有些许划痕但整体结构完好听筒里甚至还能隐约闻到一丝老式塑料和灰尘混合的独特气味。当时我就在想这部曾经连接千家万户、承载无数通话的机器除了作为怀旧摆件或孩子的玩具还能做些什么它的核心——那个经典的12键双音多频DTMF按键阵列——本质上是一个精密的矩阵开关。这个想法在我脑中盘旋了很久直到我决定将它从一部即将被遗忘的通讯设备改造为一个可以连接现代数字音乐工作站DAW的MIDI控制器。这个项目的核心目标很明确利用Arduino微控制器读取旧电话按键矩阵的状态并将其转换为标准的MIDI音符信号输出。这样一来每个电话按键就对应一个音符或和弦配合保留的听筒麦克风它甚至可以变成一个带有音频输入的独特乐器或效果控制器。这不仅仅是简单的“废物利用”更是一次对硬件底层原理的探索和创意编程的实践。整个过程涉及硬件逆向工程、嵌入式系统编程、数字音频协议应用等多个领域非常适合对硬件改造、电子音乐或嵌入式开发感兴趣的爱好者。无论你是想为你的音乐工作室增添一件独一无二的控制器还是想深入学习GPIO矩阵扫描和MIDI协议这个项目都能提供一条清晰、有趣的路径。2. 核心硬件设计与逆向工程思路2.1 设备选型与核心需求分析选择一部老式按键电话作为改造对象并非偶然。这类电话的按键部分通常采用矩阵扫描方式工作这与现代计算机键盘的原理相似能用最少的导线控制最多的按键是硬件逆向工程的理想样本。我选择的西南贝尔Freedom Phone其内部结构相对规整便于拆解。在微控制器方面我手头有一块闲置多年的Arduino Diecimila它虽然“年事已高”但核心的ATmega168芯片性能对于本项目绰绰有余。选择它的理由很简单它拥有足够数量的数字I/O引脚本项目需要约16个并且社区支持完善。任何具有类似GPIO数量的开发板如Arduino Uno、Nano甚至是ESP32都可以作为替代。项目的核心硬件需求可以分解为以下几点信号输入需要可靠地检测12个电话按键0-9 * #的按下与释放状态。信号输出需要生成标准的MIDI信号通过5针DIN接口输出到合成器或音频接口。状态反馈利用电话自带的指示灯如果有或外接LED提供视觉反馈。音频通路保留并激活听筒中的麦克风将其信号引出以便接入调音台或音频接口。供电与结构为整个系统供电通常通过USB并将所有新组件稳固地安装在电话机壳内。2.2 电话机芯的拆解与按键矩阵测绘拆解是第一步也是了解“对手”的关键。拧下底座的三颗螺丝就能打开话机的外壳。内部映入眼帘的是一个充满时代感的电路板上面还有两个用于振铃的电磁线圈。为了给新的控制板腾出空间我移除了这些线圈和大部分不再需要的原有电路。真正的宝藏是连接着按键的那条排线。它通常有8到12根线。我的这部电话有11根线。接下来的任务就是破解这11根线与12个按键之间的对应关系。如果盲目测试11根线两两组合有55种可能为12个按键逐一配对会非常低效。正确的方法是使用万用表的“通断测试”档位。将表笔任意接触两根线然后依次按下每个按键。当按下某个按键时万用表发出蜂鸣就证明当前这两根线正是该按键所连接的两端。为了提高效率我制作了一个简单的“压按工具”——用一把螺丝刀和一卷较重的焊锡丝压在待测按键上解放双手来操作万用表表笔。通过系统性的测试我绘制出了完整的映射表。结果揭示了一个清晰的3x4矩阵结构行线Row对应线号 #1, #2, #4。列线Column对应线号 #8, #7, #5。每个按键连接特定的某一行和某一列。例如按键“1”连接行线#1和列线#8。星号*和井号#键比较特殊它们各自独占两根线*连接#2和#6#连接#9和#10没有接入矩阵。线#11则空置未用。注意不同型号、不同产地的电话其按键矩阵的布线方式可能完全不同。上述映射关系仅适用于我手中的这部西南贝尔FT325。你的电话可能需要完全不同的映射表。耐心和系统的测试方法是成功的关键。理解这个矩阵意义重大。它不仅是硬件连接的基础也解释了电话拨号时双音多频DTMF的产生原理每一行和每一列对应一个特定的频率按键按下时两个频率同时发出交换机据此识别号码。虽然我们的MIDI控制器不再需要产生这些音频频率但利用这个矩阵结构可以极大地简化我们的电路和程序。3. 核心电路搭建与Arduino编程3.1 从面包板到原型盾电路连接实战在将一切焊死之前必须在面包板上进行原型验证。我将电话排线的11根线除了空置的#11分别接入面包板的一排插孔然后用杜邦线将它们连接到Arduino的数字引脚上。为了避免与后续的串口通信冲突我避开了引脚0RX和1TX从引脚2开始分配。一个典型的连接方案如下根据你的实际测绘结果调整Arduino引脚 2, 4, 5 - 电话矩阵行线 #1, #2, #4Arduino引脚 6, 8, 9 - 电话矩阵列线 #5, #7, #8Arduino引脚 3, 7, 10, 11 - 电话特殊键线 (#, 0, *, 等)为了获得即时反馈我在Arduino的13号引脚板载LED上连接了一个外置LED需串联一个220Ω电阻。这样任何按键被按下时LED都会亮起方便调试。3.2 矩阵按键扫描与状态读取编程Arduino需要持续扫描这个3x4矩阵以检测按键动作。扫描的原理是逐行或逐列设置输出并读取所有列或行的状态。例如先将行线#1设置为低电平其他行线设置为高阻态输入模式然后读取所有列线的电平。如果连接行#1和列#8的按键“1”被按下那么列#8就会被拉低从而被检测到。为了优雅地处理这个过程我编写了一个简单的MatrixButton类因为当时没找到完全符合需求的现成库。这个类封装了每个按键对应的两个引脚以及去抖动逻辑。// MatrixButton.h 简化示例 class MatrixButton { private: byte pin1, pin2; bool lastState; unsigned long lastDebounceTime; // ... 其他去抖动相关变量 public: MatrixButton(byte p1, byte p2) : pin1(p1), pin2(p2) {} void begin() { pinMode(pin1, INPUT_PULLUP); // 初始化时设为上拉输入 pinMode(pin2, INPUT_PULLUP); lastState HIGH; } bool read(bool changed) { // 读取当前物理状态考虑去抖动 bool currentState digitalRead(pin1) digitalRead(pin2); // 简化逻辑实际需扫描 // ... 去抖动算法 ... changed (currentState ! lastState); lastState currentState; return currentState; // 返回稳定后的状态 } };在主程序中我定义了一个MatrixButton数组来对应12个按键#include MatrixButton.h // 定义按键映射{行引脚 列引脚} MatrixButton keypad[] { {2, 9}, // 1 {2, 8}, // 2 {2, 6}, // 3 {4, 9}, // 4 {4, 8}, // 5 {4, 6}, // 6 {5, 9}, // 7 {5, 8}, // 8 {5, 6}, // 9 {10, 11}, // * {7, 8}, // 0 {7, 3} // # }; int ledPin 13; void setup() { Serial.begin(9600); // 用于调试 for (byte i 0; i 12; i) { keypad[i].begin(); } pinMode(ledPin, OUTPUT); } void loop() { bool anyKeyPressed false; for (byte i 0; i 12; i) { bool changed; bool state keypad[i].read(changed); // 读取状态 if (state LOW) { // 假设按下为低电平 anyKeyPressed true; if (changed) { Serial.print(Key ); Serial.print(i); Serial.println( PRESSED); // 后续这里将触发MIDI音符 } } else if (changed) { Serial.print(Key ); Serial.print(i); Serial.println( RELEASED); // 后续这里将关闭MIDI音符 } } digitalWrite(ledPin, anyKeyPressed ? HIGH : LOW); delay(10); // 简单的循环延迟实际可用更精确的定时 }这段代码运行后打开Arduino IDE的串口监视器按下电话按键你应该能看到对应的按键索引和动作被打印出来。这是确保硬件连接和扫描逻辑正确的关键一步。实操心得按键去抖动至关重要。机械触点闭合时会产生短暂的、快速的通断抖动程序会误判为多次按下。上述类中的去抖动逻辑未完整展示通常通过检测状态变化后等待一段稳定时间如5-50毫秒再次确认来实现。忽略这一点会导致MIDI音符疯狂重复触发。4. MIDI协议集成与音乐功能实现4.1 MIDI硬件连接与软件库配置MIDI使用电流环协议逻辑“0”为电流流通约5mA。Arduino的TX引脚输出的是0V/5V的电压信号需要转换为电流信号。标准做法是使用一个220Ω的电阻和一个反相缓冲电路通常使用6N138或类似的光电耦合器来构建隔离电路防止地线噪声。一个经典且简单的输出电路如下Arduino Pin 1 (TX) ---[220Ω]------||--- MIDI Pin 5 | [220Ω] | GND --- MIDI Pin 2注此处为示意||代表光耦的发光二极管端。实际制作请务必搜索并参考“Arduino MIDI Out Schematic”以确保正确连接特别是光耦的引脚和限流电阻值。在软件上我们使用强大的MIDI Library。通过Arduino IDE的库管理器搜索并安装“MIDI Library”。引入库后初始化非常简单#include MIDI.h MIDI_CREATE_DEFAULT_INSTANCE(); // 创建MIDI实例 void setup() { MIDI.begin(); // 启动MIDI通信 // ... 其他初始化代码 }4.2 音符映射、音阶设计与程序逻辑接下来我们要将12个按键映射到有音乐性的音符上。MIDI音符用0-127的数字表示60代表中央CC4。我选择了一个C大调五声音阶加上大七度音这是一个在流行、摇滚和蓝调中都非常和谐的音阶。我为它设计了两组八度分布在电话键盘的上下两排。const byte midiChannel 1; // MIDI通道1 // 音阶定义相对于根音60的半音偏移量 const int8_t scale[12] { 0, 2, 4, // 第一排C, D, E 7, 9, 11, // 第二排G, A, B 12, 14, 16, // 第三排C5, D5, E5 19, 21, 23 // 第四排G5, A5, B5 }; // 你可以轻松改变根音 const byte rootNote 60; // 中央C // 或者尝试其他音阶例如小调五声音阶{0, 3, 5, 7, 10, ...}现在在主循环中当检测到按键按下或释放时发送对应的MIDI信息void loop() { for (byte i 0; i 12; i) { bool changed; bool state keypad[i].read(changed); if (changed) { if (state LOW) { // 按键按下 byte noteToPlay rootNote scale[i]; MIDI.sendNoteOn(noteToPlay, 127, midiChannel); // 力度设为最大127 Serial.print(Note ON: ); Serial.println(noteToPlay); } else { // 按键释放 byte noteToStop rootNote scale[i]; MIDI.sendNoteOff(noteToStop, 0, midiChannel); // 力度设为0 Serial.print(Note OFF: ); Serial.println(noteToStop); } } } // ... 可能的其他任务 }将代码上传到Arduino用MIDI线连接你的合成器或电脑的MIDI接口需要MIDI转USB适配器按下电话按键你应该能听到对应的音符了至此一个基础的MIDI控制器已经诞生。4.3 功能扩展复音、音色切换与CC控制基础的单音控制器只是开始。我们可以利用电话的其他部件实现更复杂的功能复音支持当前的逻辑可以同时处理多个按键的按下与释放天生支持复音。确保你的音源支持即可。利用重拨键Redial作为功能键将电话侧面的重拨键连接到另一个Arduino引脚将其设置为“模式切换”键。按下它可以循环切换不同的音阶如大调、小调、布鲁斯音阶、八度或甚至不同的MIDI通道用于控制不同乐器。利用脉冲/音频切换开关另一个开关可以映射为MIDI控制改变Control Change信息发送器。例如拨动它时发送一个CC信息如CC64延音踏板让你的音乐软件做出响应。指示灯Blinkenlights控制电话上的四个状态灯是绝佳的视觉反馈。我们可以用它们来指示当前模式、八度或激活状态。使用FastLED或简单的digitalWrite来控制它们让设备交互更直观。5. 音频通路恢复与系统集成5.1 听筒麦克风的激活与信号调理老式电话的听筒里通常包含一个驻极体麦克风。它需要一个小偏置电压通常2-10V才能工作。拆开听筒你会找到两根连接麦克风的细线通常是黄色和黑色。我们需要为它搭建一个简单的供电和信号调理电路。一个经典的方案是使用电阻分压式偏置电路从Arduino的3.3V或5V引脚引出电源。串联一个2.2kΩ - 10kΩ的电阻到麦克风的正极通过万用表测量与外壳连通的一般是负极。麦克风的输出正极通过一个1-10uF的隔直电容耦合出来去除直流偏置电压。电容的另一端就是你的音频信号输出可以连接到一个1/4英寸6.35mm耳机插座的尖端Tip。麦克风的负极和音频输出的地线Sleeve连接到公共地。为了只在拿起听筒时通电我们可以利用话机底座上的叉簧开关。这个开关在听筒放下时是断开的拿起时闭合。将这个开关串联在给麦克风供电的电路中实现自动开关机。注意事项电话麦克风的输出电平非常低属于“麦克风电平”远低于“线路电平”。直接接入调音台或音频接口的线路输入可能会声音很小且噪声大。最佳实践是接入调音台的“麦克风输入口”带有话放或者使用一个简单的基于运算放大器如LM386的预放大器电路将信号放大到线路电平。5.2 内部布线、屏蔽与组装工艺当所有功能在面包板上测试无误后就该进行永久性安装了。我选择将元件焊接在一块Arduino原型扩展板上这样可以直接插在Diecimila上方结构紧凑。使用2.54mm间距的JST XH连接器来连接电话的排线和其他部件如麦克风线、叉簧开关线这样便于日后维修。安装过程中的几个关键点空间规划需要用电磨或珠宝锯小心地切除一部分原电路板为Arduino盾板腾出空间。务必反复比对确保听筒叉簧开关能被正常压下不会被下方的电线或元件卡住。走线与固定使用尼龙扎带或热熔胶将电线妥善固定防止其在壳内晃动或与运动部件如叉簧摩擦。麦克风信号线最好使用屏蔽线并将屏蔽层单点接地以减少噪音。外壳改造在话机底座后壳上开孔用于安装MIDI输出接口、音频输出接口和USB供电口。使用阶梯钻头可以干净地开出不同直径的孔。先精确测量用记号笔标出位置从内侧确认没有结构冲突再从小直径开始慢慢扩大。MIDI接口通常需要两个小螺丝固定孔和一个大的五针孔要格外细心。6. 高级应用软件声码器与创意延展6.1 将设备整合至数字音频工作站完成硬件改造后这部“MIDI电话”就成为了一个标准的USB-MIDI设备通过Arduino的USB转串口和一个音频输入设备。在Ableton Live、Logic Pro或FL Studio等DAW中你需要进行两步设置MIDI设置在DAW的偏好设置中启用Arduino对应的MIDI输入端口。然后创建一个MIDI轨道输入选择该端口输出指向一个你喜欢的软件合成器如Serum、Massive或DAW自带的合成器。音频设置创建一个音频轨道输入选择你电脑上接入电话麦克风的那个音频接口通道。现在当你对着听筒说话或唱歌声音就会进入这个轨道。6.2 实现实时声码器效果声码器Vocoder的本质是用一个音频信号的频谱通常是人声去调制另一个音频信号的音色通常是合成器 pad。我们的设备完美地提供了这两个信号源调制信号Modulator听筒麦克风输入的人声。载波信号Carrier由电话按键触发的MIDI音符所驱动的合成器音色。在DAW中实现声码器效果非常直观创建一条MIDI轨道加载一个适合做声码器载波的合成器推荐使用富含谐波的音色如锯齿波、方波并保持长延音。将该轨道的输入设置为我们的MIDI电话。创建一条音频轨道输入设置为电话麦克风。在这条轨道上插入一个声码器效果器插件如Ableton Live的“Vocoder” Logic Pro的“EVOC 20”等。在声码器插件的设置中通常有一个“Carrier”或“Modulator”源选择。选择“External”或“Sidechain”并将其源指定为上面那条合成器MIDI轨道。现在当你按下电话按键发出一个和弦同时对着听筒说话合成器的音高就会被人声音调的频谱所“塑造”产生出经典的机器人或和声效果。你可以尝试按下不同的和弦来“伴奏”你的人声。6.3 项目优化与更多创意可能这个项目有巨大的扩展空间压力感应与力度目前的按键只有开/关状态。可以尝试改造按键或在下方安装压敏电阻FSR让按压力度映射为MIDI力度值使演奏更具表现力。旋转拨号盘改造如果你的电话是更老的转盘式可以将拨号盘改造为一个高精度的MIDI控制器用于控制弯音轮、调制轮或参数自动化。无线化使用像ESP32这样的带有蓝牙的微控制器可以实现蓝牙MIDI彻底摆脱线缆束缚。定制化PCB如果想让作品更专业、更可靠可以使用Eagle或KiCad设计一块定制PCB将Arduino、MIDI输出电路、音频放大电路全部集成在一块板上直接安装在电话内部。改造过程中最令人着迷的时刻莫过于当第一声MIDI音符从你的合成器中响起而触发它的却是那个充满岁月痕迹的塑料按键。它连接了过去与现在硬件与软件物理世界与数字艺术。每一次按下不仅产生一个音符更像是在完成一次跨越时空的对话。这个项目教会我的远不止是几行代码或焊接技巧更是一种看待旧物的思维方式它们的核心功能或许已逝但其物理形态和内部结构往往是等待被重新定义的、绝佳的创作素材。