承接前一章節的基礎 LED 控制,本篇將進一步探索微控制器開發的複雜性。我們將焦點置於如何在單一執行緒的硬體環境中,透過 TinyGo 的 Goroutine 協程機制,模擬出併發執行的效果。此設計模式不僅解決了交通號誌自動循環與即時行人請求之間的衝突,也體現了在資源受限設備上,透過軟體架構優化系統反應能力的核心思想。本文將詳解其狀態變數的傳遞與協作式多工的具體實現,為嵌入式系統的併發控制提供一個清晰的實作範例。
智慧交通控制系統的建構:TinyGo實戰與硬體互動(續)
玄貓深信,從基礎的硬體控制到複雜的系統整合,是高科技養成中不可或缺的環節。在上一章成功點亮LED後,本章將引導開發者運用TinyGo和Arduino UNO,逐步建構一個功能完善的智慧交通控制系統。這不僅涉及多個外部LED的控制,還將引入按鈕輸入、GPIO操作、電阻知識以及TinyGo的Goroutine應用,全面提升硬體與軟體協同開發的能力。
整合行人燈的智慧交通控制系統(續)
初始化邏輯(續)
玄貓將繼續完善main函數中的初始化部分,配置所有LED和按鈕引腳。
- 初始化並配置主幹道交通燈LED引腳:
greenLED:連接到machine.D11。yellowLED:連接到machine.D12。redLED:連接到machine.D13。 所有這些引腳都將使用之前定義的outputConfig進行配置。
greenLED := machine.D11
yellowLED := machine.D12
redLED := machine.D13
greenLED.Configure(outputConfig)
yellowLED.Configure(outputConfig)
redLED.Configure(outputConfig)
- 初始化並配置行人燈LED引腳:
pedestrianGreen:連接到machine.D4。pedestrianRed:連接到machine.D5。 這些引腳也將使用outputConfig進行配置。
pedestrianGreen := machine.D4
pedestrianRed := machine.D5
pedestrianGreen.Configure(outputConfig)
pedestrianRed.Configure(outputConfig)
- 初始化並配置按鈕輸入引腳:
inputConfig:定義為PinInput模式。buttonInput:連接到machine.D2。 按鈕引腳將使用inputConfig進行配置。
inputConfig := machine.PinConfig{Mode: machine.PinInput}
buttonInput := machine.D2
buttonInput.Configure(inputConfig)
至此,所有必要的引腳和全局變數都已完成初始化。值得注意的是,machine.D13等引腳常數的類型是machine.Pin。
編寫交通燈邏輯函數 trafficLights
玄貓現在將把交通燈的控制邏輯封裝到一個獨立的函數trafficLights中。這個函數將接收所有LED引腳作為參數,並在一個無限循環中處理交通燈的狀態切換。
func trafficLights(redLED, greenLED, yellowLED, pedestrianRED, pedestrianGreen machine.Pin) {
// 在函數開始時,確保行人紅燈亮,行人綠燈滅
pedestrianRED.High()
pedestrianGreen.Low()
for {
// 檢查是否有行人請求(stopTraffic變數)
if stopTraffic {
// 如果有行人請求,進入行人過街模式
// 1. 主幹道交通燈轉為紅燈
redLED.High()
yellowLED.Low()
greenLED.Low()
// 2. 行人燈轉為綠燈,持續3秒
pedestrianGreen.High()
pedestrianRED.Low()
time.Sleep(3 * time.Second)
// 3. 行人燈轉回紅燈
pedestrianGreen.Low()
pedestrianRED.High()
// 4. 重置stopTraffic為false,表示已處理完畢
stopTraffic = false
} else {
// 如果沒有行人請求,則保持行人紅燈亮
pedestrianGreen.Low()
pedestrianRED.High()
}
// 正常交通燈流程 (在沒有行人請求時執行)
// 紅燈階段
redLED.High()
yellowLED.Low()
greenLED.Low()
time.Sleep(time.Second * 3) // 紅燈持續3秒
// 紅黃燈階段
yellowLED.High()
time.Sleep(time.Second * 1) // 紅黃燈持續1秒
// 綠燈階段
redLED.Low()
yellowLED.Low()
greenLED.High()
time.Sleep(time.Second * 3) // 綠燈持續3秒
// 黃燈階段
greenLED.Low()
yellowLED.High()
time.Sleep(time.Second * 1) // 黃燈持續1秒
yellowLED.Low() // 黃燈熄滅,準備下一個紅燈循環
}
}
trafficLights函數邏輯解析:
- 初始狀態:函數一開始,確保行人紅燈亮起,行人綠燈熄滅,這是系統的預設安全狀態。
- 行人請求處理:
if stopTraffic:檢查stopTraffic全局變數。如果為true,表示有行人請求。- 主幹道紅燈:強制主幹道交通燈變為紅燈,確保車輛停止。
- 行人綠燈:行人燈變為綠燈,持續3秒,允許行人過街。
- 行人紅燈恢復:3秒後,行人燈變回紅燈。
- 重置
stopTraffic:將stopTraffic設為false,表示本次行人請求已處理完畢,系統可以恢復正常流程。
- 正常交通燈流程:在
if stopTraffic區塊之後,是之前實現的標準交通燈循環邏輯(紅燈 -> 紅黃燈 -> 綠燈 -> 黃燈 -> 紅燈)。這個流程會持續運行,直到再次有行人請求將stopTraffic設為true。
實現主邏輯:Goroutine的應用
現在,玄貓需要將trafficLights函數與按鈕輸入處理結合起來。由於微控制器通常只有一個處理器核心,無法實現真正的並行執行。然而,TinyGo透過Goroutine提供了輕量級的併發機制,可以在單一執行緒上模擬多任務。
在Arduino UNO上使用Goroutine時,需要注意其資源限制。Goroutine在TinyGo中是透過協程(co-routines)實現的,它們在單一執行緒上進行協作式多工,而非搶佔式多工。這意味著一個Goroutine必須主動讓出控制權(例如透過time.Sleep或等待I/O)才能讓其他Goroutine運行。
玄貓將在main函數中啟動trafficLights函數作為一個Goroutine,同時在主Goroutine中持續監測按鈕輸入。
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
component "main 函數" as Main {
rectangle "初始化所有GPIO引腳" as InitPins
rectangle "初始化 stopTraffic = false" as InitStopTraffic
rectangle "啟動 trafficLights Goroutine" as StartTrafficLightsGo
rectangle "無限循環監測按鈕" as MonitorButton
}
component "trafficLights Goroutine" as TrafficLightsGo {
rectangle "無限循環" as TrafficLoop
rectangle "檢查 stopTraffic 狀態" as CheckStopTraffic
rectangle "處理行人過街邏輯" as PedestrianLogic
rectangle "執行正常交通燈邏輯" as NormalTrafficLogic
}
Main --> InitPins
Main --> InitStopTraffic
InitStopTraffic --> StartTrafficLightsGo : 啟動
StartTrafficLightsGo --> MonitorButton
MonitorButton --> CheckStopTraffic : 設置 stopTraffic
CheckStopTraffic --> PedestrianLogic : (if stopTraffic)
CheckStopTraffic --> NormalTrafficLogic : (else)
PedestrianLogic --> TrafficLoop : 重置 stopTraffic
NormalTrafficLogic --> TrafficLoop
@enduml
看圖說話:
此圖示展示了智慧交通控制系統的程式邏輯架構。main函數負責初始化所有GPIO引腳和stopTraffic變數,然後啟動trafficLights函數作為一個獨立的Goroutine。main函數本身則進入一個無限循環,持續監測按鈕輸入。trafficLights Goroutine內部也包含一個無限循環,它會首先檢查stopTraffic變數的狀態。如果stopTraffic為真,則執行行人過街的邏輯,包括將主幹道交通燈轉紅、行人綠燈亮起等,並在處理完畢後重置stopTraffic。如果stopTraffic為假,則執行正常的交通燈切換邏輯。這種結構允許系統同時處理按鈕輸入和交通燈的常規運行,實現了併發控制。
在main函數中,我們將這樣做:
func main() {
// ... (所有初始化代碼,如前面所述) ...
// 啟動 trafficLights 函數作為一個 Goroutine
go trafficLights(redLED, greenLED, yellowLED, pedestrianRed, pedestrianGreen)
// 主 Goroutine 負責監測按鈕輸入
for {
if buttonInput.Get() {
// 如果按鈕被按下,設定 stopTraffic 為 true
// 讓 trafficLights Goroutine 知道需要處理行人請求
stopTraffic = true
// 為了避免重複觸發,可以加入短暫延遲或邊緣檢測邏輯
time.Sleep(time.Millisecond * 200) // 簡單的去抖動
}
// 為了讓其他 Goroutine 有機會運行,主 Goroutine 應適當讓出控制權
time.Sleep(time.Millisecond * 10) // 短暫延遲
}
}
透過go trafficLights(...),trafficLights函數將在一個新的Goroutine中運行,獨立於main函數的循環。main函數的循環則專注於讀取按鈕狀態,並透過stopTraffic變數與trafficLights Goroutine進行通信。這種設計允許系統同時處理交通燈的自動切換和按鈕觸發的行人請求,實現了更為智慧的交通控制。
縱觀現代管理者的多元挑戰,從單一任務執行邁向多重事件的協同處理,已不僅是技術需求,更是思維框架的躍遷。本次智慧交通系統的建構,其核心價值並非僅止於點亮LED或讀取按鈕,而在於透過TinyGo的Goroutine,實現了從循序邏輯到輕量級併發的典範轉移。相較於傳統的單一循環(super loop)架構,這種併發模型能更優雅地處理非同步事件,顯著提升系統的反應能力與擴展性。
然而,此一突破也伴隨著新的挑戰。以全局變數stopTraffic作為狀態溝通的機制,雖在當前情境下簡潔有效,但在更複雜的多任務系統中,將衍生出對狀態同步、資源競爭(race condition)與優先級管理的深層次考量。這正是從基礎實作走向高可靠性系統設計的關鍵瓶頸。
展望未來,隨著物聯網與邊緣運算裝置需處理日益複雜的多重感測器融合與即時通訊需求,類似Goroutine的輕量級併發模型將成為標準配備。能夠掌握此類架構的開發者,將具備設計高韌性、高響應性嵌入式系統的核心能力。
玄貓認為,對於追求技術突破的專業人士而言,這次實作的真正收穫,在於完成一次從「循序執行者」到「併發協調者」的思維升級。深刻理解並熟練運用這種設計典範,是從單純的功能實現者,晉升為複雜系統架構師的關鍵一步。