在嵌入式系統開發中,從單一元件控制進階到多工協作是關鍵的一步。本章節將基於 Arduino UNO 硬體平台,深入探討如何利用 TinyGo 的併發模型來建構一個具備行人請求功能的智慧交通號誌。核心挑戰在於同時管理主幹道號誌的固定週期與來自外部按鈕的非同步中斷請求。透過 Goroutine 的應用,我們將實現一個協作式多工系統,其中一個 Goroutine 負責號誌的狀態轉換,而主 Goroutine 則持續監測使用者輸入。此過程不僅展示了 GPIO 的輸入與輸出配置,更揭示了在資源有限的微控制器上,排程器如何運作以及 time.Sleep 在讓渡處理器資源與實現簡易去抖動(Debouncing)中的雙重作用,為開發更複雜的互動式硬體應用奠定理論基礎。
智慧交通控制系統的建構:TinyGo實戰與硬體互動(續)
玄貓深信,從基礎的硬體控制到複雜的系統整合,是高科技養成中不可或缺的環節。在上一章成功點亮LED後,本章將引導開發者運用TinyGo和Arduino UNO,逐步建構一個功能完善的智慧交通控制系統。這不僅涉及多個外部LED的控制,還將引入按鈕輸入、GPIO操作、電阻知識以及TinyGo的Goroutine應用,全面提升硬體與軟體協同開發的能力。
整合行人燈的智慧交通控制系統(續)
實現主邏輯:Goroutine的應用(續)
玄貓現在將把所有初始化、Goroutine啟動和按鈕監測的程式碼整合到main函數中。
package main
import (
"machine"
"time"
)
// stopTraffic 是一個全局變數,用於在不同 Goroutine 間通信
var stopTraffic bool
// trafficLights 函數負責交通燈和行人燈的邏輯控制
func trafficLights(redLED, greenLED, yellowLED, pedestrianRed, pedestrianGreen machine.Pin) {
// 初始狀態:行人紅燈亮,行人綠燈滅
pedestrianRed.High()
pedestrianGreen.Low()
for {
// 檢查是否有行人請求
if stopTraffic {
// 處理行人過街邏輯
redLED.High() // 主幹道紅燈亮
yellowLED.Low() // 主幹道黃燈滅
greenLED.Low() // 主幹道綠燈滅
pedestrianGreen.High() // 行人綠燈亮
pedestrianRed.Low() // 行人紅燈滅
time.Sleep(3 * time.Second) // 行人綠燈持續3秒
pedestrianGreen.Low() // 行人綠燈滅
pedestrianRed.High() // 行人紅燈亮
stopTraffic = false // 重置標誌,表示行人請求已處理
time.Sleep(time.Second * 1) // 給予一點緩衝時間
}
// 正常交通燈流程
// 紅燈階段
redLED.High()
yellowLED.Low()
greenLED.Low()
time.Sleep(time.Second * 3)
// 紅黃燈階段
yellowLED.High()
time.Sleep(time.Second * 1)
// 綠燈階段
redLED.Low()
yellowLED.Low()
greenLED.High()
time.Sleep(time.Second * 3)
// 黃燈階段
greenLED.Low()
yellowLED.High()
time.Sleep(time.Second * 1)
yellowLED.Low() // 黃燈熄滅,準備下一個紅燈循環
}
}
func main() {
// 初始化全局變數
stopTraffic = false
// 配置輸出引腳
outputConfig := machine.PinConfig{Mode: machine.PinOutput}
// 主幹道交通燈LED
redLED := machine.D13
yellowLED := machine.D12
greenLED := machine.D11
redLED.Configure(outputConfig)
yellowLED.Configure(outputConfig)
greenLED.Configure(outputConfig)
// 行人燈LED
pedestrianGreen := machine.D4
pedestrianRed := machine.D5
pedestrianGreen.Configure(outputConfig)
pedestrianRed.Configure(outputConfig)
// 配置輸入引腳 (按鈕)
// 使用內部下拉電阻可以簡化外部電路,但此處仍依據原先設計使用外部下拉電阻
// 因此配置為 PinInput 即可,外部電阻會處理下拉
inputConfig := machine.PinConfig{Mode: machine.PinInput}
buttonInput := machine.D2
buttonInput.Configure(inputConfig)
// 確保行人紅燈初始亮起,行人綠燈滅
pedestrianRed.High()
pedestrianGreen.Low()
// 啟動 trafficLights 函數作為一個 Goroutine
go trafficLights(redLED, greenLED, yellowLED, pedestrianRed, pedestrianGreen)
// 主 Goroutine 負責監測按鈕輸入
for {
if buttonInput.Get() {
// 如果按鈕被按下,設定 stopTraffic 為 true
stopTraffic = true
// 簡單的去抖動,避免單次按壓觸發多次
time.Sleep(50 * time.Millisecond)
}
// 為了讓 Goroutine 調度器有機會運行其他 Goroutine,主循環需要適當讓出控制權
// 這裡的延遲時間是關鍵,過短可能導致其他 Goroutine 難以執行,過長則反應遲鈍
time.Sleep(50 * time.Millisecond)
}
}
燒錄程式:
由於專案中使用了Goroutine,TinyGo編譯器需要啟用排程器。在燒錄時,玄貓需要為tinygo flash命令添加-scheduler tasks參數。
tinygo flash -scheduler tasks -target=arduino Chapter02/traffic-lights-pedestrian/main.go
燒錄完成後,交通燈將按正常順序循環。當按下按鈕時,主幹道交通燈會轉為紅燈,行人燈會變為綠燈,持續3秒後,行人燈變回紅燈,主幹道交通燈恢復正常循環。
關於Goroutine與排程器:
Arduino UNO等微控制器資源有限,其處理器通常是單核心單執行緒。TinyGo的Goroutine並非真正的並行執行,而是透過一個輕量級的排程器在單一執行緒上進行協作式多工。這意味著Goroutine必須主動讓出控制權(例如透過time.Sleep或等待I/O操作)才能讓其他Goroutine有機會執行。如果一個Goroutine長時間佔用CPU而不讓出控制權,其他Goroutine將無法運行。因此,在監測按鈕的循環中加入time.Sleep(50 * time.Millisecond)是非常必要的,它為trafficLights Goroutine提供了執行時間。
資源限制: 由於Arduino UNO的記憶體(RAM)非常有限,過於複雜的Goroutine使用可能會導致記憶體不足。對於本專案這種相對簡單的應用,Goroutine是可行的,但對於更複雜的系統,可能需要考慮更強大的微控制器或不同的設計模式。
思考與延伸
- 限流電阻的作用:為什麼在LED和GPIO埠之間需要串聯一個電阻?
- 玄貓思索:電阻的主要作用是限制流過LED的電流。LED有其額定的工作電流和電壓。如果沒有電阻,直接將LED連接到GPIO埠,由於GPIO埠輸出的電壓通常高於LED的順向電壓,會導致過大的電流流過LED,輕則縮短LED壽命,重則燒毀LED甚至損壞微控制器的GPIO埠。電阻根據歐姆定律($R = (V_s - V_{LED}) / I_{LED}$)來將電流限制在安全範圍內。
- 防止信號浮空的方法:如何防止輸入信號浮空?
- 玄貓思索:防止信號浮空主要有兩種方法:
- 外部上拉/下拉電阻:如本專案中為按鈕使用的10K歐姆下拉電阻,它確保按鈕未按下時引腳處於明確的低電平。上拉電阻則確保未按下時引腳處於高電平。
- 內部上拉/下拉電阻:許多微控制器內建了可程式設計的上拉/下拉電阻。透過程式碼配置GPIO引腳為
PinInputPullup或PinInputPulldown模式,可以啟用這些內部電阻,省去外部元件。
- 按鈕狀態檢查後為何需要延遲:為什麼在檢查按鈕狀態的循環中需要加入
time.Sleep?
- 玄貓思索:在單執行緒的微控制器環境中,
time.Sleep是讓出CPU控制權給TinyGo排程器的關鍵機制。如果按鈕監測循環不包含延遲,它會持續高速運行,佔用所有CPU時間,導致trafficLightsGoroutine無法獲得執行機會,從而無法更新交通燈狀態。延遲允許排程器在main函數暫停時切換到其他Goroutine。此外,延遲也有助於按鈕的去抖動,避免單次物理按壓被誤讀為多次。
- 程式碼修改挑戰:如何修改程式碼以實現以下行為? a. 當按鈕按下時,關閉交通燈的紅綠LED,讓黃色LED閃爍。 b. 當按鈕再次按下時,恢復正常的相位旋轉。
- 玄貓思索:這需要引入一個新的狀態變數來追蹤黃燈閃爍模式,並修改按鈕處理邏輯:
- 引入狀態變數:例如,
var yellowBlinkMode bool。 - 按鈕邏輯修改:
- 在按鈕被按下時,切換
yellowBlinkMode的狀態。 - 如果
yellowBlinkMode為true:將stopTraffic設為true(確保主幹道紅燈亮,行人紅燈亮),然後在trafficLights函數中,當stopTraffic處理完畢後,進入一個黃燈閃爍的循環,直到yellowBlinkMode變為false。 - 如果
yellowBlinkMode為false:恢復正常的交通燈邏輯。 trafficLights函數修改:在stopTraffic處理完畢後,增加一個判斷yellowBlinkMode的邏輯分支。如果為true,則進入一個循環,交替點亮和熄滅黃燈,並延遲。如果為false,則執行正常的交通燈循環。
展望未來
在下一章中,玄貓將學習如何讀取4x4鍵盤的輸入,並控制伺服馬達。這些知識將被應用於建構一個安全鎖,當輸入正確的密碼時,伺服馬達將觸發解鎖機制。這將進一步拓展玄貓在嵌入式系統開發中的技能,包括:
- 序列埠通信:學習如何向序列埠寫入資訊並監控,這對於應用程式的除錯至關重要。
- 自訂驅動程式:為4x4鍵盤編寫自己的驅動程式,這不僅可以用作密碼輸入,也可以作為通用的控制器輸入。
這些進階的硬體互動和軟體設計技能將使玄貓能夠開發出更具功能性和互動性的嵌入式應用。
深入剖析此智慧交通系統的建構方法可以發現,其價值不僅在於完成一項功能,更在於引入了一種截然不同的開發思維。這項實踐代表著從單純的硬體控制,躍升至軟硬體協同的系統設計層次,是一次重要的思維框架突破。
與傳統嵌入式開發的單一循環(loop)模式相比,TinyGo的Goroutine提供了一種更優雅的並行抽象,讓開發者能以高階軟體的思維處理多工任務。然而,這也帶來了新的挑戰:開發者必須深刻理解協作式多工的本質,精準運用time.Sleep等機制讓出CPU控制權,並時刻警惕Arduino UNO有限的記憶體資源。這種在限制中尋求優雅架構的過程,正是從「寫碼」到「設計系統」的關鍵轉化點,極大地鍛鍊了資源最佳化的能力。
未來,隨著IoT裝置功能日益複雜,這種將高階語言的並行模型應用於資源受限環境的能力,將成為區分開發者層次的核心指標。它預示著應用邏輯與底層控制的邊界將愈發模糊,催生出更具彈性與擴展性的嵌入式解決方案。
玄貓認為,掌握這種在硬體限制與軟體抽象之間取得平衡的技藝,不僅是技術上的突破,更是開發者建立系統性思維、邁向更高階設計能力的必經之路,其價值遠超單一專案的完成。