# LAB08 利用ADCTouch製作小鋼琴 ## DEMO <iframe width="560" height="315" src="https://www.youtube.com/embed/fVGOvPa7n7s?si=rt9-rS5HWgSYR54W" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> ## 電路圖 ![image](https://hackmd.io/_uploads/H1WTbyRXkg.png) ## 程式碼 - 處理按鈕任務 ```cpp= void handleButtons(void *parameter) { /* * INFO: * This Function is a thread to proccess 3 buttom * Argv: * NULL */ while(true){ switch (keyUp.check()){ case Switch::RELEASED_FROM_PRESS: case Switch::PRESSING: keyShift++; if (keyShift>=2) keyShift =2; } switch (keyDown.check()){ case Switch::RELEASED_FROM_PRESS: case Switch::PRESSING: keyShift--; if (keyShift<=0) keyShift = 0; } switch (play.check()){ case Switch::RELEASED_FROM_PRESS: case Switch::PRESSING: isPlay = !isPlay; } vTaskDelay(50 / portTICK_PERIOD_MS); } } ``` - 偵測ADC Touch - 大致的偵測處理 ```cpp= int measure = touchRead(TOUCH_PIN[i]); ... // If is touched if ( measure < threshold[i] * multiplier){ ... } else { ... } ``` - 考量到ADC Touch會因為環境(溫度濕度等)影響,因此使用動態的方式改變臨界值 ```cpp= threshold[i] = threshold[i] * 0.9 + measure * 0.1; ``` - 為了讓他更穩定,我有用按鈕的邏輯將訊號變得比較穩定 ```cpp= unsigned long currentTime = millis(); if (currentTime - lastTouchTime[i] > debounceTime) { lastTouchTime[i] = currentTime; // Previous Status is not equal to this status => Change Tone if (indx != i) ledcWriteTone(0, (float)Tone[i] * pow(2, keyShift )); // Store This Status indx = i; // Flag Value to assure this session has been buzzer flag = true; } ``` - 關於升八度,數學邏輯: $f*2^{key}$ ## 完整程式碼 ```cpp= #include <switch.h> #define BUZZER_PIN 16 #define SMOOTH 500 const char TOUCH_PIN[8] = {4, 12, 15, 13, 27, 14, 32, 33}; const char LED_PIN[8] = {26, 5, 2, 0, 17, 18, 22, 23}; const int Tone[8] = {524, 494, 440, 392, 349, 330, 294, 262 }; const float multiplier = 0.5; const char key[] = { 0,0,0, 0,0,0, 0,0,0,0,0, 0,0,0,0,0,0,0,0,0, 0,0,1,1,1,1 }; const char melody[] = { 5, 5, 5, 5, 5, 5, 5, 4, 7, 6, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 5, 6, 3 }; const int noteDurations[] = { 4, 4, 2, 4, 4, 2, 4, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4, 4, 4, 2, 2 }; const int debounceTime = 50; int previousReadings[8][SMOOTH]; unsigned int threshold[8]; unsigned long lastTouchTime[8] = {0}; int indx = -1; int keyShift = 0; bool isPlay = false; Switch keyUp(19, LOW, true); Switch keyDown(21, LOW, true); Switch play(25, LOW, true); int average(int* arr){ /* * INFO: * This Functions is used to compute the average of previousReadings[i] * Input: * arr: int* (a array and it's length must be SMOOTH) * Output: * avg: int (a average value of previousReadings[i]) */ unsigned long sum = 0; int arr_size = *(&arr + 1) - arr; for(int i = 0; i < arr_size; i++){ sum += arr[i]; } return sum / SMOOTH; } void handleButtons(void *parameter) { /* * INFO: * This Function is a thread to proccess 3 buttom * Argv: * NULL */ while(true){ switch (keyUp.check()){ case Switch::RELEASED_FROM_PRESS: case Switch::PRESSING: keyShift++; if (keyShift>=2) keyShift =2; } switch (keyDown.check()){ case Switch::RELEASED_FROM_PRESS: case Switch::PRESSING: keyShift--; if (keyShift<=0) keyShift = 0; } switch (play.check()){ case Switch::RELEASED_FROM_PRESS: case Switch::PRESSING: isPlay = !isPlay; } vTaskDelay(50 / portTICK_PERIOD_MS); } } void setup(){ Serial.begin(9600); // Set up buzzer to pwn ledcSetup(0, 7500, 16); ledcAttachPin(BUZZER_PIN, 0); // Measure value of don't touch for (int i=0; i<8; i++){ for(int j = 0; j < 100; j++){ previousReadings[i][j] = touchRead(TOUCH_PIN[i]); delay(1); } } // Compute the average of previousReadings[i] for (int i=0; i<8; i++){ threshold[i] = average(previousReadings[i]); printf("%d\n", threshold[i]); } // Set up LED for (int i=0; i<8; i++){ pinMode(LED_PIN[i], OUTPUT); digitalWrite(LED_PIN[i], LOW); } xTaskCreate(handleButtons, "Button Task", 1024, NULL, 0, NULL); } void loop(){ if (!isPlay){ bool flag = false; for (int i=0; i<8; i++){ int measure = touchRead(TOUCH_PIN[i]); printf("%d\n", measure); // Predict Next threshold threshold[i] = threshold[i] * 0.9 + measure * 0.1; // If is touched if ( measure < threshold[i] * multiplier){ digitalWrite(LED_PIN[i], HIGH); unsigned long currentTime = millis(); if (currentTime - lastTouchTime[i] > debounceTime) { lastTouchTime[i] = currentTime; // Previous Status is not equal to this status => Change Tone if (indx != i) ledcWriteTone(0, (float)Tone[i] * pow(2, keyShift )); // Store This Status indx = i; // Flag Value to assure this session has been buzzer flag = true; } } else { digitalWrite(LED_PIN[i], LOW); } } // Has not touched during this session if (!flag) { ledcWriteTone(0, 0); indx = -1 ; } vTaskDelay(500 / portTICK_PERIOD_MS); } else { // Playing Chismas song int songLength = sizeof(melody) / sizeof(melody[0]); for (int i =0; i<8 ;i++){ digitalWrite(LED_PIN[i], LOW); } for (int thisNote = 0; thisNote < songLength; thisNote++) { // If "Playing" buttom has been tick => break out the scope if (!isPlay) break; int noteFrequency = (float)Tone[melody[thisNote]]*pow(2, key[thisNote] ); int noteDuration = 1000 / noteDurations[thisNote]; digitalWrite(LED_PIN[melody[thisNote]], HIGH); if (noteFrequency > 0) { ledcWriteTone(0, noteFrequency); } else { ledcWriteTone(0, 0); } vTaskDelay(noteDuration/ portTICK_PERIOD_MS); ledcWriteTone(0, 0); digitalWrite(LED_PIN[melody[thisNote]], LOW); vTaskDelay(noteDuration * 0.3/ portTICK_PERIOD_MS); } } } ```