---
# System prepended metadata

title: LAB5 使用 SWITCH BUTTON、Servo 與 蜂鳴器 設計柵欄哥
tags: [嵌入式系統]

---

# 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);  // 設定藍色
}
```