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轻触按键开关 ×107个琴键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/(R1R2) 按键2按下 → 电压5V×R1/(R1R3) ...具体操作时要注意电阻值要成倍数差异如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) currentOctave0; // 低音 if(digitalRead(10)LOW) currentOctave1; // 中音 if(digitalRead(11)LOW) currentOctave2; // 高音 }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 i0; i7; i) { if(isKeyPressed(i2)) { // 引脚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(12noteIndex, HIGH); delay(100); digitalWrite(12noteIndex, LOW); }更炫酷的做法是用RGB LED不同音阶显示不同颜色。需要用到FastLED库#include FastLED.h #define LED_PIN 13 #define NUM_LEDS 7 CRGB leds[NUM_LEDS]; void setup() { FastLED.addLedsWS2812, 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 i0; irecordIndex; i) { tone(10, NOTES[currentOctave][record[i]], 100); delay(recordTime[i1]-recordTime[i]); } }这个方案用EEPROM存储录音数据断电后也不会丢失。我在创客马拉松上见过更高级的实现——用SD卡模块存储MIDI格式可以录制多首曲子。
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轻触按键开关 ×107个琴键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/(R1R2) 按键2按下 → 电压5V×R1/(R1R3) ...具体操作时要注意电阻值要成倍数差异如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) currentOctave0; // 低音 if(digitalRead(10)LOW) currentOctave1; // 中音 if(digitalRead(11)LOW) currentOctave2; // 高音 }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 i0; i7; i) { if(isKeyPressed(i2)) { // 引脚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(12noteIndex, HIGH); delay(100); digitalWrite(12noteIndex, LOW); }更炫酷的做法是用RGB LED不同音阶显示不同颜色。需要用到FastLED库#include FastLED.h #define LED_PIN 13 #define NUM_LEDS 7 CRGB leds[NUM_LEDS]; void setup() { FastLED.addLedsWS2812, 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 i0; irecordIndex; i) { tone(10, NOTES[currentOctave][record[i]], 100); delay(recordTime[i1]-recordTime[i]); } }这个方案用EEPROM存储录音数据断电后也不会丢失。我在创客马拉松上见过更高级的实现——用SD卡模块存储MIDI格式可以录制多首曲子。