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

- 這次嘗試用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); // 設定藍色
}
```