Arduino项目:从零打造可扩展的八度电子琴
1. 项目概述:为什么选择可扩展八度电子琴
当你第一次用Arduino制作简易电子琴时,可能会觉得8个音符已经够用了。但真正上手弹奏后就会发现,单八度的音域实在太窄,连《小星星》都弹不完整。这就是为什么我们需要打造一个可扩展八度的电子琴——它能让你的DIY乐器瞬间拥有专业键盘的演奏体验。
我在三年前做过一个基础版电子琴,当时用8个按钮对应中音区的C4到C5。后来想教侄子弹琴时,发现没有低音区根本没法演示左手伴奏。于是花了两个周末改造出了支持三个八度的版本,现在连《欢乐颂》都能完整演奏。这个项目的核心价值在于:
- 真实乐器体验:通过高低八度切换,音域覆盖C3到C6(相当于61键电子琴的中音区)
- 硬件可扩展:基础版只需10个引脚(7个琴键+3个八度切换),进阶版可用矩阵键盘扩展到21键
- 代码模块化:音阶频率用二维数组存储,后期添加特效音色只需新增数组
最让我惊喜的是,这个改造后的电子琴成了创客空间的明星项目。有学员甚至用它配合光敏电阻做出了根据光线强弱变调的创意功能——这正好体现了可扩展设计的优势。
2. 硬件设计:省引脚的高效方案
2.1 基础电路搭建
先来看最核心的元器件选型。我强烈推荐使用无源蜂鸣器而不是有源蜂鸣器,因为:
- 无源蜂鸣器可以通过PWM产生不同频率的声音
- 有源蜂鸣器只能固定频率发声
- 价格相差无几(淘宝上都在2-5元区间)
这是我的实测电路配置清单:
- Arduino Uno开发板 ×1
- 无源蜂鸣器(额定电压5V) ×1
- 轻触按键开关 ×10(7个琴键+3个八度切换)
- 10kΩ电阻 ×10
- 面包板及杜邦线若干
接线时有个容易踩的坑:蜂鸣器正极要接带PWM功能的数字引脚(数字引脚3/5/6/9/10/11)。我第一次接在引脚2上死活不出声,后来查手册才发现PWM限制。推荐接法:
蜂鸣器正极 → 引脚10 蜂鸣器负极 → GND 按键一端 → 数字引脚2-8(琴键)、9-11(八度切换) 按键另一端 → 通过10kΩ电阻接GND2.2 进阶省引脚方案
如果觉得占用太多引脚,可以用模拟输入+分压电路的方案。我在竹女中的案例里看到个巧妙的设计:只用1个模拟引脚就能读取多个按键状态。原理是利用不同电阻组合产生不同的电压值:
按键1按下 → 电压=5V×R1/(R1+R2) 按键2按下 → 电压=5V×R1/(R1+R3) ...具体操作时要注意:
- 电阻值要成倍数差异(如1kΩ、2kΩ、4kΩ)
- 需要软件消抖处理(后面代码部分会讲)
- 精度要求高的可以加个0.1μF电容滤波
这是我测试过的电阻组合方案(对应7个琴键):
| 按键 | 串联电阻值 | 理论电压值 | |------|------------|------------| | C | 1kΩ | 0.71V | | D | 2kΩ | 1.18V | | E | 3kΩ | 1.54V | | F | 4kΩ | 1.82V | | G | 5kΩ | 2.04V | | A | 6kΩ | 2.22V | | B | 7kΩ | 2.38V |3. 核心代码:实现八度切换的秘诀
3.1 音阶频率存储方案
玩过音乐的朋友都知道,不同八度的同名音符频率是2倍关系。比如:
- 中音C5 = 523Hz
- 高音C6 = 1047Hz (523×2)
- 低音C4 = 262Hz (523/2)
在代码中我们可以用二维数组来存储这些关系。参考博客园那位网友的方案,我优化后的频率表是这样的:
const int NOTES[3][7] = { {262, 294, 330, 349, 392, 440, 494}, // 低音区(C4-B4) {523, 587, 659, 698, 784, 880, 988}, // 中音区(C5-B5) {1047, 1175, 1319, 1397, 1568, 1760, 1976} // 高音区(C6-B6) };实际使用时发现个问题:同时按下八度切换键和琴键时会有冲突。后来加了个currentOctave全局变量才解决:
int currentOctave = 1; // 默认中音区 void checkOctaveButtons() { if(digitalRead(9)==LOW) currentOctave=0; // 低音 if(digitalRead(10)==LOW) currentOctave=1; // 中音 if(digitalRead(11)==LOW) currentOctave=2; // 高音 }3.2 按键消抖与响应优化
机械按键最大的问题就是抖动——物理接触时会产生多次通断。这是我用示波器抓取的按键信号:
理想信号: ______|¯¯¯¯¯|______ 实际信号: ___|¯|_|¯|__|¯|____解决方法有两种:
- 硬件消抖:并联0.1μF电容
- 软件消抖:检测到按键按下后延时10-50ms
我更喜欢软件方案,因为不增加硬件成本。这里分享一个经过实战检验的按键检测函数:
bool isKeyPressed(int pin) { if(digitalRead(pin) == HIGH) return false; delay(15); // 消抖延时 if(digitalRead(pin) == LOW) { while(digitalRead(pin) == LOW); // 等待释放 return true; } return false; }在loop()中这样调用:
void loop() { checkOctaveButtons(); for(int i=0; i<7; i++) { if(isKeyPressed(i+2)) { // 引脚2-8对应琴键 tone(10, NOTES[currentOctave][i], 100); } } }4. 功能扩展:让你的电子琴更专业
4.1 添加LED音阶指示
想让电子琴更有视觉冲击力?可以给每个音阶加LED指示灯。我在引脚12-18接了7个LED,修改后的播放函数:
void playNote(int noteIndex) { tone(10, NOTES[currentOctave][noteIndex], 100); digitalWrite(12+noteIndex, HIGH); delay(100); digitalWrite(12+noteIndex, LOW); }更炫酷的做法是用RGB LED,不同音阶显示不同颜色。需要用到FastLED库:
#include <FastLED.h> #define LED_PIN 13 #define NUM_LEDS 7 CRGB leds[NUM_LEDS]; void setup() { FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS); } void playNote(int noteIndex) { tone(10, NOTES[currentOctave][noteIndex], 100); leds[noteIndex] = CHSV(noteIndex*36, 255, 255); // 色相按音阶递增 FastLED.show(); delay(100); leds[noteIndex] = CRGB::Black; FastLED.show(); }4.2 录制与回放功能
通过添加一个滑动变阻器(接模拟引脚A0),可以实现简单的录音回放:
#include <EEPROM.h> int record[100]; // 存储音符序列 int recordTime[100]; // 存储时值 int recordIndex = 0; void recordMode() { int startTime = millis(); while(digitalRead(8)==LOW) { // 长按C键进入录音 int note = map(analogRead(A0), 0, 1023, 0, 6); if(note != record[recordIndex-1]) { record[recordIndex] = note; recordTime[recordIndex] = millis()-startTime; recordIndex++; } } } void playBack() { for(int i=0; i<recordIndex; i++) { tone(10, NOTES[currentOctave][record[i]], 100); delay(recordTime[i+1]-recordTime[i]); } }这个方案用EEPROM存储录音数据,断电后也不会丢失。我在创客马拉松上见过更高级的实现——用SD卡模块存储MIDI格式,可以录制多首曲子。
