學完本章你將能夠:

  • 設定推挽、開漏、上拉/下拉,並為繼電器與蜂鳴器選對模式。

  • 寫出設備抽象層,讓上層只認 |dev_light_set()| 而不是腳位號碼。

  • 處理繼電器的低電平觸發與續流二極體,避免 MCU 腳直接扛電感負載。

  • 用有限狀態避免「蜂鳴器卡在響」這種產品級醜態。

語音指令最後一定落到「改某根腳的電位」。先把這層做對、做乾淨,後面的 RTOS 與 AI 才有地方接。

電氣與安全

  • LED:串 220 \(\Omega\)–1 k\(\Omega\),MCU 腳推挽輸出。

  • 主動蜂鳴器:自帶振盪,GPIO 高/低即可;被動蜂鳴器要 PWM(下一章)。教學用主動較單純。

  • 繼電器模組:幾乎都是「光耦 + 電晶體」,標 IN 的腳給 3.3 V GPIO。注意有的模組低電平觸發。

  • 絕對不要用 MCU 腳直接驅繼電器線圈或風扇。電感踢回會 毁掉腳位。

邏輯層與功率層必須分開

設備抽象:不要讓任務知道腳位

應用層出現 |HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, ...)| 是壞味道。 腳位會因改板而變;語音任務不該重新編譯只因為 LED 換腳。本課程的 dev_* API 定義在 firmware/common/:

typedef enum DEV_LIGHT = 0, DEV_FAN, DEV_DOOR, DEV_RELAY, DEV_BUZZER, DEV_COUNT dev_id_t;

void dev_init(void); void dev_light_set(uint8_t on); void dev_light_set_level(uint8_t percent); /* 0–100,PWM 章實作 */ void dev_relay_set(uint8_t on); void dev_buzzer_beep(uint16_t ms); void dev_all_off(void);

路徑 A/B 與第 10 章狀態機都只呼叫這些函式。換板時只改 pin_map.h。

實驗:四個設備的手動測試

firmware/lab02_gpio_devices/。序列埠下指令: L1/L0 燈、R1/R0 繼電器、 B 蜂鳴 200 ms、A 全關。尚未使用 FreeRTOS。

void app_main(void)

uint8_t ch; dev_init(); app_log("lab02: L1/L0 R1/R0 B Å");

for (;;) if (HAL_UART_Receive(&huart3, &ch, 1, 10) != HAL_OK) continue; switch (ch) case ’L’: /* 下一個位元為 1 或 0,見完整檔 */ break; case ’B’: dev_buzzer_beep(200); break; case ’A’: dev_all_off(); break; default: break;

建議用麵包板先驗證每一路,再焊或接杜邦線到最終佈局。繼電器吸合時用 電表量負載側,不要用耳朵當唯一驗收。

蜂鳴器:有時限的動作

產品不能「指令來了就 |WritePin(1)| 然後忘記關」。|dev_buzzer_beep| 在本章用 |HAL_Delay| 阻塞實作;進 FreeRTOS 後改成軟體定時器(第 7 章),避免卡死語音任務。

void dev_buzzer_beep(uint16_t ms) HAL_GPIO_WritePin(BUZZ_PORT, BUZZ_PIN, GPIO_PIN_SET); HAL_Delay(ms); HAL_GPIO_WritePin(BUZZ_PORT, BUZZ_PIN, GPIO_PIN_RESET);

為繼電器加上「最短吸合 100 ms」保護,避免 UART 雜訊造成繼電器顫動。 再增加一條 T 指令:連續切換 LED 10 次,每次 80 ms,模擬 「找到裝置」的視覺回饋——喚醒成功時會用到同樣的模式。