---
# System prepended metadata

title: LAB08 利用ADCTouch製作小鋼琴
tags: [嵌入式系統]

---

# 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);  
        }
    }
}
```