# LAB5 使用 SWITCH BUTTON、Servo 與 蜂鳴器 設計柵欄哥 ## DEMO <iframe width="560" height="315" src="https://www.youtube.com/embed/sqqvTA2bwbM?si=kPbnwYeMrxz_tgND" 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> ## 電路圖 ![HW_05_13_電路](https://hackmd.io/_uploads/r1eLITvgJe.jpg) - 這次嘗試用threads寫,有夠難寫討厭 - 任務主要有兩個 - 升降任務 :::spoiler 升降任務 ```cpp= void taskRaise(void *pvRarm) { while (true) { vTaskDelay(100 / portTICK_PERIOD_MS); // 在非緊急狀態下執行 if (isRaising && !isEmergency) { servo.attach(SERVO_PIN, 500, 2400); setColor(0, 0, 0); for(int i=0; i<=90 ;i+=5){ servo.write(i); vTaskDelay(30 / portTICK_PERIOD_MS); } vTaskDelay(3000 / portTICK_PERIOD_MS); setColor(0, 0, 255); for(int i=90; i>=0 ;i-=5){ servo.write(i); vTaskDelay(30 / portTICK_PERIOD_MS); } setColor(0, 0, 0); isRaising = false; // 重置提升狀態 } } } ``` ::: - 蜂鳴器任務 :::spoiler 蜂鳴器任務 ```cpp= void taskBuzzer(void *pvParam) { while (true) { vTaskDelay(100 / portTICK_PERIOD_MS); if (isRaising && !isEmergency) { // 控制蜂鳴器的聲音 ledcWriteTone(0, 600); // 發出 600 Hz 聲音 vTaskDelay(500 / portTICK_PERIOD_MS); // 持續 1 秒 ledcWriteTone(0, 400); // 發出 400 Hz 聲音 vTaskDelay(500 / portTICK_PERIOD_MS); // 持續 1 秒 ledcWriteTone(0, 0); // 停止聲音 vTaskDelay(100 / portTICK_PERIOD_MS); // 持續 1 秒 } } } ``` ::: - 中斷事件: 緊急按鈕按下時 => 執行緒暫停,交由ISR處理~~支語是線程,支語警察不要開單~~ :::spoiler 綁定硬體 ```cpp attachInterrupt(EMERGENCY_SWITCH_PIN, emergencyISR, FALLING); ``` ::: :::spoiler ISR ```cpp= void IRAM_ATTR emergencyISR() { setColor(0, 0, 0); unsigned long currentTime = millis(); if ((currentTime - lastDebounceTime) > debounceDelay) { isEmergency = !isEmergency; // 切換緊急狀態 if (isEmergency) { shouldMoveServoTo90 = true; // 在緊急情況下移動到90度 vTaskSuspend(xHandle); // 暫停伺服馬達的動作 } else { shouldMoveServoTo90 = true; servo.attach(SERVO_PIN, 500, 2400); // 恢復伺服的控制信號 servo.write(0); // 設置一個初始位置 vTaskResume(xHandle); // 恢復伺服馬達的動作 } lastDebounceTime = currentTime; // 更新上次中斷時間 } } ``` ::: ## 程式碼 ```cpp= #include <ESP32_Servo.h> #include <switch.h> #include <analogWrite.h> #define SWITCH1_PIN 32 // 左側開關 #define SWITCH2_PIN 33 // 右側開關 #define EMERGENCY_SWITCH_PIN 25 // 緊急按鈕 #define redPin 4 // 紅色LED接腳 #define greenPin 16 // 綠色LED接腳 #define bluePin 17 // 藍色LED接腳 #define BUZZER_PIN 26 // 蜂鳴器接腳 #define SERVO_PIN 12 // 伺服馬達接腳 #define BITS 10 Servo servo; Switch out(SWITCH1_PIN, LOW, true); Switch in(SWITCH2_PIN, LOW, true); volatile bool shouldMoveServoTo90 = false; // 新增變數控制移動到90度 volatile bool shouldMoveServoTo0 = false; // 新增變數控制移動到0度 volatile unsigned long lastDebounceTime = 0; const unsigned long debounceDelay = 200; // 設定200毫秒的防抖延遲 volatile bool isEmergency = false; // 使用 volatile 以確保中斷處理程序正確存取該變數 bool isRaising = false; TaskHandle_t xHandle; // 中斷處理函數 void IRAM_ATTR emergencyISR() { setColor(0, 0, 0); unsigned long currentTime = millis(); if ((currentTime - lastDebounceTime) > debounceDelay) { isEmergency = !isEmergency; // 切換緊急狀態 if (isEmergency) { shouldMoveServoTo90 = true; // 在緊急情況下移動到90度 vTaskSuspend(xHandle); // 暫停伺服馬達的動作 } else { shouldMoveServoTo90 = true; servo.attach(SERVO_PIN, 500, 2400); // 恢復伺服的控制信號 servo.write(0); // 設置一個初始位置 vTaskResume(xHandle); // 恢復伺服馬達的動作 } lastDebounceTime = currentTime; // 更新上次中斷時間 } } void setup() { pinMode(SWITCH1_PIN, INPUT_PULLUP); pinMode(SWITCH2_PIN, INPUT_PULLUP); pinMode(EMERGENCY_SWITCH_PIN, INPUT_PULLUP); pinMode(redPin, OUTPUT); pinMode(greenPin, OUTPUT); pinMode(bluePin, OUTPUT); ledcSetup(0, 20000, BITS); ledcAttachPin(BUZZER_PIN, 0); ledcWriteTone(0, 0); setColor(0, 0, 0); servo.attach(SERVO_PIN, 500, 2400); servo.write(0); delay(100); xTaskCreate(taskRaise, "task Raise", 1500, NULL, 0, &xHandle); xTaskCreate(taskBuzzer, "TaskBuzzer", 1024, NULL, 1, NULL); // 綁定中斷到緊急開關引腳 attachInterrupt(EMERGENCY_SWITCH_PIN, emergencyISR, FALLING); // 偵測按下按鈕的情況 } void taskRaise(void *pvRarm) { while (true) { vTaskDelay(100 / portTICK_PERIOD_MS); // 在非緊急狀態下執行 if (isRaising && !isEmergency) { servo.attach(SERVO_PIN, 500, 2400); setColor(0, 0, 0); for(int i=0; i<=90 ;i+=5){ servo.write(i); vTaskDelay(30 / portTICK_PERIOD_MS); } vTaskDelay(3000 / portTICK_PERIOD_MS); setColor(0, 0, 255); for(int i=90; i>=0 ;i-=5){ servo.write(i); vTaskDelay(30 / portTICK_PERIOD_MS); } setColor(0, 0, 0); isRaising = false; // 重置提升狀態 } } } void taskBuzzer(void *pvParam) { while (true) { vTaskDelay(100 / portTICK_PERIOD_MS); if (isRaising && !isEmergency) { // 控制蜂鳴器的聲音 ledcWriteTone(0, 600); // 發出 600 Hz 聲音 vTaskDelay(500 / portTICK_PERIOD_MS); // 持續 1 秒 ledcWriteTone(0, 400); // 發出 400 Hz 聲音 vTaskDelay(500 / portTICK_PERIOD_MS); // 持續 1 秒 ledcWriteTone(0, 0); // 停止聲音 vTaskDelay(100 / portTICK_PERIOD_MS); // 持續 1 秒 } } } void loop() { printf("%d\n", isEmergency); // 檢查左側開關 if (!isRaising && !isEmergency) { setColor(0, 0, 0); // 確保LED關閉 } switch (out.check()) { case Switch::RELEASED_FROM_PRESS: case Switch::PRESSING: // 只在非緊急狀態下設置 isRaising if (!isEmergency) { isRaising = true; // 左側開關觸發 } break; } if (shouldMoveServoTo90) { servo.attach(SERVO_PIN, 500, 2400); servo.write(90); delay(500); // 等待馬達轉動完成 servo.detach(); // 停止伺服控制 shouldMoveServoTo90 = false; // 重置狀態 } // 如果應該移動到0度,執行動作 if (shouldMoveServoTo0) { servo.attach(SERVO_PIN, 500, 2400); // 恢復伺服的控制信號 servo.write(0); // 設置一個初始位置 vTaskResume(xHandle); // 恢復伺服馬達的動作 shouldMoveServoTo0 = false; // 重置狀態 } // 檢查右側開關 switch (in.check()) { case Switch::RELEASED_FROM_PRESS: case Switch::PRESSING: // 只在非緊急狀態下設置 isRaising if (!isEmergency) { isRaising = true; // 右側開關觸發 } break; } delay(20); // 短暫延遲 } // 設定RGB LED顏色 void setColor(int red, int green, int blue) { analogWrite(redPin, red); // 設定紅色 analogWrite(greenPin, green); // 設定綠色 analogWrite(bluePin, blue); // 設定藍色 } ```