# 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>
## 電路圖

## 程式碼
- 處理按鈕任務
```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);
}
}
}
```