本教程對于電子制造商來說將非常令人興奮,因為我們將使用 Arduino Nano 設計我們自己的觸控電容式鋼琴。我們將在我們的鋼琴上加入錄音和回放功能。到目前為止,我們已經使用 Arduino 制作了一些鋼琴項目,但這個項目完全不同,因為我們將使用電容式觸摸鍵作為我們的鋼琴鍵。因此,在學習如何打造有趣的鋼琴演奏的同時,我們還將探索如何在 PCB 上設計電容式觸摸鍵,因為您可以嘗試讓我們的按鍵看起來像真正的鋼琴鍵。由于其制造商PCBWay ,PCB 看起來和工作起來都像鋼琴,我們還將探索我們如何設計和制造此板,但在此之前,讓我們探索電容式觸摸傳感器及其工作原理。
電容式觸摸傳感器如何工作?
我們知道,為了形成一個具有一定電容值的電容器,我們需要兩個平行的導電板,由介電材料隔開。但是我們如何僅僅通過用手指觸摸導電板來判斷電容是否發生了變化呢?我們的答案是基于我們對電容器的基本理解。眾所周知,改變導電板的面積或兩個平行導電板之間的距離可以改變電容值。在導電板和手指之間,我們有空氣作為電介質。結果,當我們用手指觸摸板時,電容的增加不確定,因為我們的手指充當導電物體,兩個導電物體之間的距離減小了。我們知道平行板電容器的電容的基本公式是,
C = εA/d
其中“A”代表導電板的面積,“d”代表兩個導電板之間的距離,“ε”代表 AIR 的介電常數。結果,增加面積并減小兩個平行導電板之間的距離會增加電容值。在我們的例子中,觸摸導電板會減少距離,同時增加電容值。我們可以通過將導電材料連接到電阻器和微控制器的 GPIO 引腳來檢測這種變化的電容嗎?答案是,我們不能。是的,將電壓源連接到它會導致模擬電壓發生微小變化,但這不是一個非常可靠的解決方案。
如何檢測電容式觸摸傳感器中的電容變化?
那么,我們如何判斷電容值是否發生了變化呢?但是,有更好的方法來解決它。讓我們看一下下面的框圖。將其視為由微控制器(在本例中為 Arduino Nano)、1 兆歐電阻和導電板組成的基本電路。Arduino Nano 的兩條數字線連接到帶有 1 兆歐電阻的電阻回路。該電阻器也有一點與導電板相連。雖然這塊板充當電容器的單點,但它仍然可以引入電容,當我們觸摸它時會發生變化。然而,這不能簡單地通過檢測電壓變化來檢測。這條線上的電容變化并不像感應 GPIO 引腳上的值的切換那樣容易。
電容式傳感器庫如何工作?
這就是 Arduino 庫派上用場的地方。非常感謝“ CapacitiveSensor ”庫的作者Paul Bagder和Paul Stoffregen 。當我們觸摸那個導電板時,我們可以使用這個庫來檢測電容的變化。在這個庫中,一個數字引腳用作發送引腳(作為OUTPUT),而另一個用作接收引腳(作為INPUT)。發送引腳變為高電平和接收引腳變為高電平之間的持續時間是檢測電容變化的唯一方法 。 當您將發送引腳設置為高電平(或5 伏)時,電阻-電容對 會 在發送引腳變為高電平和接收引腳從發送引腳讀取高值之間產生延遲。CapacitiveSensor 庫提供了一個將發送引腳設置為HIGH的函數,然后等待并計數,直到接收引腳被讀取為 HIGH。此函數返回可用于檢測電容變化的時間值。當時間值增加或減少時, 表示電容值發生了變化。當電容較大時,接收引腳達到高電平所需的時間較長 ,而當電容較小時,接收引腳達到高電平所需的時間較短。因此,我們可以確定正常狀態是什么,然后在每次發送引腳切換時檢查更改。
我們將使用“ CapacitveSensor ”庫來檢測電容的變化。但在進入編程部分之前,讓我們為我們的項目創建電路和 PCB。在這里,我們使用EasyEDA平臺為我們的項目創建原理圖和 PCB。為了檢測電容的變化,我們將使用“ CapacitveSensor ”庫。在開始編程之前,讓我們從項目的電路和 PCB 開始。我們項目的原理圖和 PCB 是使用EasyEDA平臺創建的。在EasyEDA平臺上,我們創建了大量的PCB項目。這些項目可用于獲得如何在 EasyEDA 上設計 PCB 的概念。
使用 Arduino Nano 構建 PCB 鋼琴所需的組件
使用 Arduino Nano 構建 PCB 鋼琴需要以下組件。
Arduino納米
電阻器 (1Mega Ohm) X 8
壓電蜂鳴器
18650 電池芯
18650 電池座
18650電池充電模塊
直流到直流電壓升壓器。
使用 Arduino Nano 的 PCB 鋼琴電路圖
在以下電路圖中,八個 1Mega Ohm 電阻器連接到 Arduino Nano 的數字引腳 2 。數字引腳 3 到 10進一步連接到每個電阻的其他連接點。在下圖中,我們有一個標有“RECODINGSWITCH”的滑動開關。Arduino Nano 的數字引腳 12連接到滑動開關的“EN”引腳?;瑒娱_關的“Vs”引腳連接到 Arduino Nano 的“ 5V”引腳。滑動開關的“GND”引腳連接到 Arduino Nano 的“連接到 Arduino Nano 的“A4” 引腳。蜂鳴器的負極連接到 Arduino Nano 的接地引腳。
我們已將八個 10uF 電容器連接 到每個電阻器。每個電容器的負極引腳連接到Arduino Nano 的接地引腳。然后我們有一個電源部分,為Arduino Nano 的“Vin”引腳提供適當的 6.6V。18650 電池單元連接到18650 電池充電器模塊,充電器模塊的輸出連接到DC 到 DC 升壓器。升壓器的正輸出引腳( BOUT + ) 連接到 Arduino Nano 的“Vin” 引腳,升壓器的負輸出引腳(BOUT-)連接到Arduino Nano 的接地引腳。
注:如有需要,我們可以加電容。強烈建議使用小電容器 (20pF - 400pF) 來穩定檢測到的數據。但是,請確保電容器接地,因為這會降低并聯體電阻。但是,就我而言,我沒有使用電容器,因為沒有它們對我來說效果很好。我在上面的示意圖中提到了電容器,因此您可以在實際實施過程中輕松添加它們。按照“ CapacitveSensor ”庫文檔中的規定,以下電容器的值必須介于20pF 和 400pF之間。
PCB概述
上述原理圖的 PCB 視圖如下圖所示。您可以從我們的 GitHub 存儲庫下載項目的 Gerber 文件?;蛘?,您可以訪問EasyEDA平臺上的項目了解更多詳情。黃色用于頂層絲綢層。而綠色代表底部絲綢層。紅色代表頂層,藍色代表底層。
PCB的頂層:
現在,讓我們逐層查看PCB的每一層。頂層如下圖所示。如您所見,頂層是紅色的。我設計了每個導電板,使其看起來像鋼琴。鋼琴的每個鍵都分別連接到每個 1 兆歐電阻上。
我使用了可以在 EasyEDA 的 PCB 工具部分找到的矩形形狀,在下圖中以紅色圈出。確保按鍵的寬度足夠大,以便您可以用手指觸摸每個按鍵。就我而言,我設法繪制了寬度為 10 毫米或大于 10 毫米的每個鍵。
PCB的底層:
在底層,我們有一個完整的銅層,用于連接所有接地。您可以在 EasyEDA的“PCB Tools”中使用“Solid Region”選項。這被稱為 “銅澆注”方法。此步驟會將底層轉換為公共接地層。我們在這一層還有一些其他的銅連接。
PCB的頂層絲綢層:
下圖表示 PCB 的 Top-Silk-Layer。我們可以通過添加一些絲層或非銅層來設計我們的 PCB。我將滑動開關標記為“ RECORD”和“ PLAY”。這樣我們就可以了解我們使用的是哪種模式。我們在頂層絲綢層有 BUZZER 的足跡?!?XL6009E1”是由方形區域包圍的 DC 到 DC 升壓模塊的封裝。我們可以使用EasyEDA 上提供的相應PCB 工具添加文本和圖像。
PCB的底層絲綢層:
在 PCB 的底部絲層,我們有 Arduino Nano、8 個 1Mega ohm 電阻器、8 個電容器、一個 18650 電池座或單節電池和充電模塊的封裝。
使用 Arduino Nano 對鋼琴 PCB 進行編程
“ CapacitiveSensor ”庫非常易于使用,并且他們提供了有關如何使用該庫的很好的文檔。在進入程序之前,讓我們在 Arduino IDE 上安裝“ CapacitiveSensor”庫。您需要下載庫的 zip 文件。然后轉到Arduino IDE 工具欄下的“ Sketch -》 Include Library ”部分。使用“添加 .Zip 庫。。.”選項添加 zip 文件,如下圖所示。然后重新啟動 Arduino IDE。
現在,您可以從我們的 github 存儲庫中下載該項目的代碼,然后打開“?codes ”文件夾中的“?piano_pcb.ino?”文件。我們有一個名為“?piano_tones.h?”的自定義頭文件。此頭文件包含在主文件中以獲取一些預定義的自定義鋼琴音色。就像鋼琴一樣,每個音調都指的是一個音符。讓我們看看“piano_pcb.ino”文件里面有什么。
?
#include <電容傳感器.h> #include "piano_tones.h" #define common_pin 2 // 所有鍵的通用“發送”引腳 #define Buzzer A4 //壓電蜂鳴器的輸出引腳 #define recordbtn 12 // 錄音按鈕 #define CPin(pin) CapacitiveSensor (common_pin, pin)
?
在“?piano_pcb.ino?”文件的開頭,我們有“?CapacitiveSensor.h?”和“?piano_tones.h?”頭文件。然后我為各自的 GPIO 引腳定義了三個宏。“?common_pin”用于將“digital pin 2”設置為“Send Pin”,?“?buzzer”用于定義模擬pin 4(A4),“recordbtn”用于從“將按鈕”滑入“數字引腳 12”。然后我創建了“CPin (pin)”宏,這樣我們就不需要傳遞發送 pin(即common_pin)和CapacitiveSensor(common_pin,pin)中的接收引腳(即各個電阻器的引腳)多次。
?
整數注釋[]={NOTE_C7,NOTE_D7,NOTE_E7,NOTE_F7,NOTE_G7,NOTE_A7,NOTE_B7,NOTE_C8}; // 啟動時的聲音 int soundOnStartUp[] = { 注意_E7, 注意_E7, 0, 注意_E7, 0, 注意_C7, 注意_E7, 0, NOTE_G7, 0, 0, 0, NOTE_G6, 0, 0, 0 }; 電容式傳感器鍵[] = {CPin(3), CPin(4), CPin(5), CPin(6), CPin(7), CPin(8), CPin(9), CPin(10)};
?
調用庫并定義某些宏后,我們需要創建 3 個如上所述的數組。即notes[]、soundOnStartUp[]和keys[]。“?notes[]?”數組存儲的是鋼琴在一定音階中對應的音符(例如?NOTE_C7、NOTE_D7等)。您可以通過取消注釋我在代碼中提供的其他“?notes[]?”來更改比例。soundOnStartUp?[]數組用于存儲我在鋼琴啟動期間使用的一些曲調。該數組已在setup()函數中調用。電容式傳感器鍵[?]array 用于存儲相應的接收引腳,使用我們之前定義的CPin()函數。
?
無效記錄按鈕(){ // 設置傳感器的靈敏度。 long touch1 = keys[0].capacitiveSensor(靈敏度); long touch2 = keys[1].capacitiveSensor(靈敏度); long touch3 = keys[2].capacitiveSensor(靈敏度); long touch4 = keys[3].capacitiveSensor(靈敏度); long touch5 = keys[4].capacitiveSensor(靈敏度); long touch6 = keys[5].capacitiveSensor(靈敏度); long touch7 = keys[6].capacitiveSensor(靈敏度); long touch8 = keys[7].capacitiveSensor(靈敏度); pev_button = 按鈕; // 當我們觸摸到傳感器時,按鈕會記錄相應的數字。 如果(觸摸1 > 靈敏度) 按鈕 = 1; if (touch2 > 靈敏度) 按鈕 = 2; if (touch3 > 靈敏度) 按鈕 = 3; if (touch4 > 靈敏度) 按鈕 = 4; if (touch5 > 靈敏度) 按鈕 = 5; if (touch6 > 靈敏度) 按鈕 = 6; if (touch7 > 靈敏度) 按鈕 = 7; if (touch8 > 靈敏度) 按鈕 = 8; // 當我們沒有觸摸它時,不會產生音調。 if (touch1<=敏感度 & touch2<=敏感度 & touch3<=敏感度 & touch4<=敏感度 & touch5<=敏感度 & touch6<=敏感度 & touch7<=敏感度 & touch8<=敏感度) 按鈕 = 0; /****將按下的按鈕記錄在一個數組中***/ 如果(按鈕!= pev_button && pev_button != 0) { 記錄按鈕[button_index] = pev_button; 按鈕索引++; 記錄按鈕[按鈕索引] = 0; 按鈕索引++; } /**錄制程序結束**/ }
?
我們使用“keys[0].capacitiveSensor(sensitivity)”函數讀取每個鍵的電容值,并將其與一些指定值進行比較,以確定在上述recordButtons()函數中按下了哪個鍵。我們還跟蹤在此函數中按下按鈕的順序。記錄的值保存在記錄的button[]數組中。我們首先查看是否按下了新鍵,如果是,我們再次檢查它不是按鈕 0。但是沒有按下按鈕,因此按鈕 0 什么都沒有。我們將值保存在 if 循環內的變量button_index指定的索引位置,然后我們增加該索引值以避免覆蓋相同的位置。
?
無效playTone(){ /****Rcord 數組中每個按鈕按下之間的時間延遲***/ 如果(按鈕!= pev_button) { note_time = (millis() - start_time) / 10; 如果(注意時間!= 0){ 記錄時間[time_index] = note_time; 時間索引++; 開始時間 = 毫秒(); } Serial.println(time_index); } /**錄制程序結束**/ 如果(按鈕 == 0) { noTone(蜂鳴器); } 如果(按鈕 == 1) { 音調(蜂鳴器,注釋[0]); } 如果(按鈕 == 2) { 音調(蜂鳴器,注釋[1]); } 如果(按鈕 == 3) { 音調(蜂鳴器,注釋[2]); } 如果(按鈕 == 4) { 音調(蜂鳴器,注釋[3]); } 如果(按鈕 == 5) { 音調(蜂鳴器,注釋[4]); } 如果(按鈕 == 6) { 音調(蜂鳴器,注釋[5]); } 如果(按鈕 == 7) { 音調(蜂鳴器,注釋[6]); } 如果(按鈕 == 8) { 音調(蜂鳴器,注釋[7]); } }
?
使用各種if條件,我們將為 playTone() 函數中的按鍵播放適當的音調。該函數的完整代碼顯示在上面。我們還將使用一個名為recorded time[] 的數組來保存按下按鈕的時間長度。該過程類似于記錄按鈕序列,因為我們使用 millis() 函數來計算每個按鈕被按下的時間,然后將該值除以 10 以減小變量的大小。我們在按鈕 0 的同一時間段內不播放任何音調,這表示用戶沒有按下任何東西。
?
無效設置(){ 序列號.開始(9600); // 關閉所有通道的自動校準: for(int i=0; i<8; ++i) { 鍵[i].set_CS_AutocaL_Millis(0xFFFFFFFF); } // 將蜂鳴器設置為輸出: pinMode(蜂鳴器,輸出); pinMode(recordbtn,輸入); noTone(蜂鳴器); 延遲(10); int sizeed = sizeof(soundOnStartUp) / sizeof(int); for (int thisNote = sizeed; thisNote > 0 ; thisNote--) { 音(蜂鳴器,soundOnStartUp[thisNote]); 延遲(100); } noTone(蜂鳴器); 延遲(10); }
?
在setup?() 函數中有兩個 for 循環。在第一個循環中,我使用“keys[i].set_CS_AutocaL_Millis(0xFFFFFFFF)?”設置了鍵。第二個for 循環用于通過使用音調(蜂鳴器,soundOnStartUp[thisNote])播放soundOnStartUp[]音符。
?
無效循環(){ Serial.println(digitalRead(recordbtn)); while (digitalRead(recordbtn) == 1) //如果撥動開關設置為錄制模式 { 記錄按鈕(); 播放音(); } while (digitalRead(recordbtn) == 0) //如果撥動開關設置為播放模式 { for (int i = 0; i < sizeof(recorded_button) / 2; i++) { 延遲((記錄時間[i])* 10);//等待支付下一曲 if (recorded_button[i] == 0) noTone(蜂鳴器);//用戶沒有觸摸任何按鈕 別的 音(蜂鳴器,注釋[(recorded_button[i] - 1)]);//播放用戶觸摸的按鈕對應的聲音 } } }
?
然后,要播放錄制的音調,用戶必須在錄制后將滑動開關推到另一方向。完成此操作后,程序退出前一個 while 循環并進入第二個 while 循環,在該循環中,我們按照鍵被擊中的順序播放先前記錄的長度的音符。上面提供了完成此操作的代碼。您可以觀看下面附加的視頻以獲取更多說明。
代碼
#include
#include “piano_tones.h”
#define common_pin 2 // 所有電阻的公共“發送”引腳
#define buzzer A4 // 壓電蜂鳴器的輸出引腳
#define recordbtn 12 // 錄音button
//這個宏為每個電阻引腳創建一個電容傳感器對象
#define CPin(pin) CapacitiveSensor(common_pin, pin)
char button = 0;
整數模擬值;
字符 REC = 0;
int 記錄按鈕[200];
詮釋 pev_button;
int 靈敏度 = 2000;
int 記錄時間[200];
字符時間索引;
字符按鈕索引 = 0;
無符號長開始時間;
int note_time;
// 每個鍵對應一個音符,這里定義。取消注釋您要使用的比例:
//int notes[]={NOTE_C4,NOTE_D4,NOTE_E4,NOTE_F4,NOTE_G4,NOTE_A4,NOTE_B4,NOTE_C5}; // C 大調音階
//int notes[]={NOTE_A4,NOTE_B4,NOTE_C5,NOTE_D5,NOTE_E5,NOTE_F5,NOTE_G5,NOTE_A5}; // A-小調音階
//int notes[]={NOTE_C4,NOTE_D4,NOTE_E4,NOTE_F4,NOTE_G4,NOTE_A4,NOTE_C5,NOTE_D5}; // C 藍調音階
//int notes[] = {1300, 1500, 1700, 1900, 2000, 2300, 2600, 2700};
整數注釋[]={NOTE_C7,NOTE_D7,NOTE_E7,NOTE_F7,NOTE_G7,NOTE_A7,NOTE_B7,NOTE_C8};
//int notes[] = {1915, 1700, 1519, 1432, 1275, 1136, 1014, 956};
// 啟動聲音
int soundOnStartUp[] = {
NOTE_E7, NOTE_E7, 0, NOTE_E7,
0, NOTE_C7, NOTE_E7, 0,
注意_G7, 0, 0, 0,
注意_G6, 0, 0, 0
};
// 定義寄存器連接的引腳:
CapacitiveSensor keys[] = {CPin(3), CPin(4), CPin(5), CPin(6), CPin(7), CPin(8), CPin( 9), CPin(10)};
無效設置(){
Serial.begin(9600);
// 關閉所有通道的自動校準:
for(int i=0; i<8; ++i) {
keys[i].set_CS_AutocaL_Millis(0xFFFFFFFF);
}
// 設置蜂鳴器為輸出:
pinMode(buzzer, OUTPUT);
pinMode(recordbtn,輸入);
noTone(蜂鳴器);
延遲(10);
int sizeed = sizeof(soundOnStartUp) / sizeof(int);
for (int thisNote = sizeed; thisNote 〉 0 ; thisNote--) {
tone(buzzer, soundOnStartUp[thisNote]);
延遲(100);
}
noTone(蜂鳴器);
延遲(10);
}
void loop() {
Serial.println(digitalRead(recordbtn));
while (digitalRead(recordbtn) == 1) //如果撥動開關設置為錄制模式
{
recordButtons();
播放音();
}
while (digitalRead(recordbtn) == 0) //如果撥動開關設置為播放模式
{
for (int i = 0; i < sizeof(recorded_button) / 2; i++)
{
delay((recorded_time[i]) * 10); //等待支付下一曲
if (recorded_button[i] == 0)
noTone(buzzer);
//用戶沒有觸摸任何
其他按鈕 //播放用戶觸摸的按鈕對應的聲音
}
}
}
void recordButtons(){
// 設置傳感器的靈敏度。
long touch1 = keys[0].capacitiveSensor(靈敏度);
long touch2 = keys[1].capacitiveSensor(靈敏度);
long touch3 = keys[2].capacitiveSensor(靈敏度);
long touch4 = keys[3].capacitiveSensor(靈敏度);
long touch5 = keys[4].capacitiveSensor(靈敏度);
long touch6 = keys[5].capacitiveSensor(靈敏度);
long touch7 = keys[6].capacitiveSensor(靈敏度);
long touch8 = keys[7].capacitiveSensor(靈敏度);
pev_button = 按鈕;F1
// 當我們觸摸到傳感器時,按鈕會記錄相應的數字。
如果 (touch1 〉 靈敏度)
按鈕 = 1;
if (touch2 〉 靈敏度)
按鈕 = 2;
如果 (touch3 〉 靈敏度)
按鈕 = 3;
如果(touch4 〉 靈敏度)
按鈕 = 4;
如果 (touch5 〉 靈敏度)
按鈕 = 5;
如果 (touch6 〉 靈敏度)
按鈕 = 6;
如果 (touch7 〉 靈敏度)
按鈕 = 7;
如果 (touch8 〉 靈敏度)
按鈕 = 8;
// 當我們沒有觸摸它時,不會產生音調。
if (touch1<= 靈敏度 & touch2<= 靈敏度 & touch3<= 靈敏度 & touch4<= 靈敏度 & touch5<= 靈敏度 & touch6<= 靈敏度 & touch7<= 靈敏度 & touch8<= 靈敏度)
按鈕 = 0;
/****將按下的按鈕記錄在一個數組中***/
if (button != pev_button && pev_button != 0)
{
recorded_button[button_index] = pev_button;
按鈕索引++;
記錄按鈕[按鈕索引] = 0;
按鈕索引++;
}
/** 錄制程序結束**/
}
void playTone(){
/****Rcord 數組中每個按鈕按下之間的時間延遲***/
if (button != pev_button)
{
note_time = (millis( ) - 開始時間) / 10;
if(note_time!=0){
記錄時間[time_index] = note_time;
時間索引++;
開始時間 = 毫秒();
}
Serial.println(time_index);
}
/**錄制程序結束**/
if (button == 0)
{
noTone(蜂鳴器);
}
如果(按鈕 == 1)
{
音(蜂鳴器,注釋 [0]);
}
if (button == 2)
{
音(蜂鳴器,注釋[1]);
}
如果(按鈕 == 3)
{
音(蜂鳴器,注釋 [2]);
}
if (button == 4)
{
音(蜂鳴器,注釋[3]);
}
如果(按鈕 == 5)
{
音(蜂鳴器,注釋 [4]);
}
如果(按鈕 == 6)
{
音(蜂鳴器,注釋 [5]);
}
如果(按鈕 == 7)
{
音(蜂鳴器,注釋 [6]);
}
如果(按鈕== 8)
{
音(蜂鳴器,注釋[7]);
}
}
評論
查看更多