在嵌入式系統開發中,將硬體抽象化為易於使用的軟體驅動程式是關鍵的一步。本章節接續基礎的輸出入控制,深入探討如何透過 TinyGo 駕馭伺服馬達此類致動器。文章的核心在於比較兩種實現脈衝寬度調變(PWM)的技術:軟體模擬與硬體支援。軟體模擬 PWM 雖然具備高度彈性,但會消耗大量 CPU 資源且可能影響時序精準度;相對地,利用微控制器內建的硬體 PWM 模組,則能以極低的處理器負擔達成穩定且精確的信號輸出。透過實作這兩種驅動程式,開發者不僅能掌握伺服馬達的控制原理,更能深刻體會到在資源受限的環境下,選擇合適技術方案的重要性,為後續整合更複雜的智慧鎖功能打下堅實的理論與實踐基礎。
智慧安全鎖的建構:TinyGo、鍵盤與伺服馬達的整合(續)
玄貓深信,高科技養成不僅止於理論,更在於實踐與創新。在掌握了LED和按鈕的基礎控制後,本章將引導開發者進入更具挑戰性的領域:建構一個智慧安全鎖。這將結合4x4鍵盤的輸入、伺服馬達的精確控制,並深入探討序列埠通信在除錯中的關鍵作用。最終目標是實現一個能透過密碼解鎖的實用裝置。
控制伺服馬達(續)
編寫伺服控制邏輯
玄貓將在servo/driver.go檔案中編寫伺服馬達的驅動邏輯。這個驅動程式將透過軟體模擬PWM信號來控制伺服馬達。
// Chapter03/controlling-servo/servo/driver.go
package servo
import (
"machine"
"time"
)
// 1. 定義 PWM 相關的常數
// 這些常數定義了不同角度所需的脈衝寬度 (Duty Cycle)
// 以及一個 20ms 週期中剩餘的靜止時間 (Remaining Period)。
// 伺服馬達週期通常為 20ms (50Hz)。
const (
// 中心位置 (0 度)
centerDutyCycle = 1500 * time.Microsecond // 1.5ms 脈衝
centerRemainingPeriod = 18500 * time.Microsecond // 20ms - 1.5ms = 18.5ms
// 左側位置 (+90 度)
leftDutyCycle = 2000 * time.Microsecond // 2ms 脈衝
leftRemainingPeriod = 18000 * time.Microsecond // 20ms - 2ms = 18ms
// 右側位置 (-90 度)
rightDutyCycle = 1000 * time.Microsecond // 1ms 脈衝
rightRemainingPeriod = 19000 * time.Microsecond // 20ms - 1ms = 19ms
)
// 2. 創建 Driver 結構體
// Driver 結構體包含一個 machine.Pin 成員,代表連接伺服馬達 PWM 信號線的引腳。
type Driver struct {
pin machine.Pin
}
// 3. 定義 Configure 函數
// Configure 函數用於初始化伺服馬達驅動程式。
// 它接收一個 machine.Pin 參數,並將其設定為輸出模式。
func (servo *Driver) Configure(pin machine.Pin) {
servo.pin = pin
servo.pin.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
// 4. 定義 Right 函數,讓伺服馬達轉向右側 (-90 度)
// 為了簡化,這裡只讓馬達向右轉動一點點,模擬鎖定動作。
// 實際應用中,可能需要更精確的控制。
func (servo *Driver) Right() {
// 這裡的迴圈是為了讓馬達緩慢移動或確保動作完成
// 每次迴圈產生一個右轉的 PWM 脈衝
for position := 0; position <= 4; position++ { // 迴圈四次,使馬達轉動約 30 度
servo.pin.High() // 將引腳拉高,開始脈衝
time.Sleep(rightDutyCycle) // 維持高電平 1ms
servo.pin.Low() // 將引腳拉低,結束脈衝
time.Sleep(rightRemainingPeriod) // 保持低電平 19ms,完成 20ms 週期
}
}
// 為了完整性,我們可以添加 Center 和 Left 函數,但本例僅需 Right
func (servo *Driver) Center() {
for position := 0; position <= 4; position++ {
servo.pin.High()
time.Sleep(centerDutyCycle)
servo.pin.Low()
time.Sleep(centerRemainingPeriod)
}
}
func (servo *Driver) Left() {
for position := 0; position <= 4; position++ {
servo.pin.High()
time.Sleep(leftDutyCycle)
servo.pin.Low()
time.Sleep(leftRemainingPeriod)
}
}
測試軟體PWM驅動程式:
玄貓現在可以在main.go中編寫一個簡單的程式來測試這個伺服驅動程式。
// Chapter03/controlling-servo/main.go
package main
import (
"machine"
"Chapter03/controlling-servo/servo" // 引入自訂的 servo 套件
)
func main() {
servoDevice := servo.Driver{}
servoDevice.Configure(machine.D11) // 配置伺服馬達連接到 D11 引腳
servoDevice.Right() // 讓伺服馬達轉向右側
}
燒錄與測試: 將程式燒錄到Arduino Uno:
tinygo flash --target=arduino Chapter03/controlling-servo/main.go
燒錄完成後,玄貓會觀察到SG90伺服馬達轉動一個小角度。恭喜玄貓,這是第一次透過程式碼實現了物理運動!
使用硬體PWM的伺服驅動程式 (推薦)
當TinyGo的PWM重構完成並合併到主線後,玄貓將會希望使用基於硬體PWM的驅動程式,因為它通常更精確且不佔用CPU時間。玄貓將創建一個新的驅動程式來利用硬體PWM。
- 專案結構:
- 在
Chapter03資料夾下,建立一個名為servo-pwm的新資料夾。 - 在
servo-pwm內部,建立driver.go檔案,並將其套件名稱設定為servopwm。
- 定義常數與結構體:
// Chapter03/servo-pwm/servo-pwm/driver.go
package servopwm
import (
"machine"
"time"
)
const period = 20 * time.Millisecond // PWM 週期為 20ms (50Hz)
type Device struct {
pwm machine.PWM
pin machine.Pin
channel uint8 // PWM 通道號
}
- 構造函數:
// Chapter03/servo-pwm/servo-pwm/driver.go
// NewDevice 創建並返回一個新的 Device 實例。
// timer 是用於 PWM 的計時器,pin 是連接伺服馬達的引腳。
func NewDevice(timer machine.PWM, pin machine.Pin) *Device {
return &Device{
pwm: timer,
pin: pin,
}
}
- 配置函數:
// Chapter03/servo-pwm/servo-pwm/driver.go
// Configure 函數配置 PWM 介面,設定週期並獲取輸出引腳的通道。
func (d *Device) Configure() error {
err := d.pwm.Configure(machine.PWMConfig{
Period: period, // 設定 PWM 週期
})
if err != nil {
return err
}
// 獲取指定引腳的 PWM 通道
d.channel, err = d.pwm.Channel(d.pin)
if err != nil {
return err
}
return nil
}
- 位置設定函數:
// Chapter03/servo-pwm/servo-pwm/driver.go
// Right 函數將伺服馬達轉到右側 (-90 度),脈衝寬度為 1000 微秒。
func (d *Device) Right() {
d.setDutyCycle(1000 * time.Microsecond)
}
// Center 函數將伺服馬達轉到中心 (0 度),脈衝寬度為 1500 微秒。
func (d *Device) Center() {
d.setDutyCycle(1500 * time.Microsecond)
}
// Left 函數將伺服馬達轉到左側 (+90 度),脈衝寬度為 2000 微秒。
func (d *Device) Left() {
d.setDutyCycle(2000 * time.Microsecond)
}
- 設定工作週期函數:
// Chapter03/servo-pwm/servo-pwm/driver.go
// setDutyCycle 函數根據給定的脈衝寬度 (cycle) 設定 PWM 的工作週期。
func (d *Device) setDutyCycle(cycle time.Duration) {
// 計算 PWM 值的公式:(PWM_Top值 * 脈衝寬度) / 週期
// d.pwm.Top() 返回 PWM 計數器的最大值
value := uint64(d.pwm.Top()) * uint64(cycle) / uint64(period)
d.pwm.Set(d.channel, uint32(value)) // 設定指定通道的 PWM 值
}
測試硬體PWM驅動程式:
玄貓現在可以在main.go中編寫一個替代程式來測試這個硬體PWM伺服驅動程式。
// Chapter03/controlling-servo-pwm/main.go
package main
import (
"machine"
"time"
"Chapter03/controlling-servo-pwm/servopwm" // 引入新的 servopwm 套件
)
func main() {
// 創建一個新的 Device 實例,使用 Timer1 和 D9 引腳
// 注意:Arduino Uno 的 D9 引腳支援 PWM
servo := servopwm.NewDevice(machine.Timer1, machine.D9)
err := servo.Configure() // 配置伺服馬達
if err != nil {
for {
println("無法配置伺服馬達:", err.Error())
time.Sleep(time.Second)
}
}
// 循環讓伺服馬達在左、中、右三個位置之間移動
for {
servo.Left()
time.Sleep(time.Second)
servo.Center()
time.Sleep(time.Second)
servo.Right()
time.Sleep(time.Second)
}
}
燒錄與測試: 將程式燒錄到Arduino Uno:
tinygo flash --target=arduino Chapter03/controlling-servo-pwm/main.go
燒錄完成後,玄貓會觀察到SG90伺服馬達每秒在左、中、右三個位置之間來回擺動。這個基於硬體PWM的驅動程式將提供更穩定和精確的控制。
@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
package “controlling-servo-pwm” { component “main.go” as MainHW package “servopwm” { component “driver.go” as DriverHW class “Device Struct” as DeviceStruct {
- pwm: machine.PWM
- pin: machine.Pin
- channel: uint8 } DeviceStruct – “NewDevice()” : 構造函數 DeviceStruct – “Configure()” : 初始化 PWM DeviceStruct – “Left()” DeviceStruct – “Center()” DeviceStruct – “Right()” DeviceStruct – “setDutyCycle()” : 內部控制 } }
MainHW –> DriverHW : 引入 servopwm 套件 MainHW –> DeviceStruct : 實例化 Device MainHW –> “Configure()” : 配置 PWM 硬體 MainHW –> “Left()” MainHW –> “Center()” MainHW –> “Right()” : 循環控制伺服馬達
“setDutyCycle()” –> “machine.PWM” : 設定 PWM 輸出
actor “使用者 (觀察馬達)” as User User –> “SG90 伺服馬達” as ServoMotor : 物理運動
@enduml
看圖說話:
此圖示描繪了基於硬體PWM的伺服馬達控制驅動程式的架構。controlling-servo-pwm專案包含main.go主程式和servopwm套件中的driver.go。driver.go定義了Device結構體,其中包含machine.PWM、machine.Pin和channel等成員。它提供了NewDevice構造函數、Configure函數(用於初始化PWM硬體、設定週期並獲取通道),以及Left、Center、Right等高級函數來控制伺服馬達的預設角度。這些高級函數內部呼叫setDutyCycle來計算並設定PWM的具體工作週期。main.go負責實例化Device,呼叫Configure進行硬體初始化,然後在無限循環中依序呼叫Left、Center、Right,使伺服馬達在不同角度之間擺動。最終,使用者可以觀察到SG90伺服馬達的物理運動。
玄貓現在已經掌握了如何透過兩種方式(軟體模擬和硬體PWM)控制伺服馬達。接下來的任務是將鍵盤輸入和伺服馬達控制整合起來,建構出一個完整的安全鎖系統。
縱觀從軟體模擬到硬體驅動的兩種伺服馬達控制方案,其差異不僅是技術路徑的選擇,更反映了嵌入式開發者從概念驗證邁向產品級實踐的思維躍遷。軟體PWM以其直觀的邏輯,為初學者提供了理解控制原理的絕佳入口,但其對CPU資源的獨占性與計時精度的不穩定性,使其在多工環境中迅速成為效能瓶頸。相較之下,硬體PWM方案雖然初始配置較為抽象,需深入理解微控制器的計時器與通道機制,卻能將控制任務徹底下放給專用硬體,從而釋放CPU以處理如鍵盤掃描、狀態管理等更高層次的邏輯,確保了系統的即時性與穩定性。
這種從「軟」到「硬」的演進,預示著未來物聯網裝置開發的必然趨勢:核心功能將越來越依賴硬體加速,而軟體則專注於複雜的應用邏輯與使用者互動。掌握這層轉換,是開發者能否建構高效、可靠多工系統的關鍵分水嶺。
玄貓認為,精通硬體PWM不僅是完成此智慧安全鎖專案的技術要求,更是開發者從「實現功能」提升至「建構系統」層次的必經修煉。優先投資於理解並善用底層硬體特性,將為未來應對更複雜的嵌入式挑戰奠定堅實基礎。