原標(biāo)題:SwiftyGPIO:詳解如何在開發(fā)板上使用 Swift
譯者:張新慧,技術(shù)之路,共同進(jìn)步,有優(yōu)質(zhì)移動開發(fā)、VR/AR/MR、物聯(lián)網(wǎng)原創(chuàng)技術(shù)文章歡迎發(fā)送郵件至 mobilehub@csdn.net。
SwiftyGPIO 是一個(gè)能夠通過 Swift 語言去控制基于 Linux 主板(比如:C.H.I.P. 和樹莓派)GPIO(General Purpose Input Output,通用型之輸入輸出,為嵌入式開發(fā)的概念,對應(yīng)嵌入式設(shè)備的物理接口)以完成簡單的工控功能(比如 LED 燈的顯示)的開源庫,簡單而言就是,這個(gè)庫能夠讓 Swift 與設(shè)備的 GPIO 進(jìn)行交互,從而實(shí)現(xiàn)開關(guān)功能。該項(xiàng)目基于 MIT 協(xié)議開源,其主要貢獻(xiàn)者為軟件工程師 Umberto Raimondi。
想要使用 SwiftyGPIO 庫,首先需要具備一個(gè)支持 Swift 3 及其以上版本的 Linux ARM(ARMv7 或 ARMv6)。如果你有一個(gè)基于 Ubuntu 或 Raspbian 操作系統(tǒng)的樹莓派(A、B、A+、B+、Zero、ZeroW、2、3),從這里獲取 Swift 3.0.2 或按文章介紹以及鏈接的腳本庫來進(jìn)行構(gòu)建。
如果你的 Swift 版本支持 SPM,只需將 SwiftyGPIO 作為依賴(dependency)添加到 Package.swift 中:
let package = Package( name: "MyProject", dependencies: [ .Package(url: "https://github.com/uraimo/SwiftyGPIO.git", majorVersion: 0), ... ] ... )
然后執(zhí)行 swift build
命令,編譯器會在 .build/
下創(chuàng)建一個(gè)可執(zhí)行文件。
如果你的 Swift 版本不支持 Swift Package Manager,就手動下載所有所需文件:
wget https://raw.githubusercontent.com/uraimo/SwiftyGPIO/master/Sources/SwiftyGPIO.swift https://raw.githubusercontent.com/uraimo/SwiftyGPIO/master/Sources/Presets.swift https://raw.githubusercontent.com/uraimo/SwiftyGPIO/master/Sources/SunXi.swift
下載完畢,在同一目錄下創(chuàng)建一個(gè)包含應(yīng)用代碼的附加文件,命名為 main.swift
。當(dāng)代碼就緒,就用 swiftc *.swift
來編譯,編譯器會創(chuàng)建一個(gè) main
可執(zhí)行文件。
注意:與 GPIO 交互時(shí),不論是通過 sysfs 還是 mmapped 寄存器,如果操作系統(tǒng)沒有預(yù)定義的用戶組來獲取這些函數(shù),就需要用 sudo ./main 動用 root 權(quán)限來運(yùn)行應(yīng)用。如果 RaspberryPi 使用 2016 年 11 月更新的最新 Raspbian 或者 16.04 Xenial 及以上的 Ubuntu,只需要./main 來啟動應(yīng)用?;旌舷到y(tǒng)的話,listeners(監(jiān)聽器)必須要求 root 權(quán)限。
替代方案是,具體用戶組獲取 GPIO 可手動設(shè)置(參考1、2)。遵照這些指南之后,別忘了用sudo usermod -aG gpio pi
將用戶(比如 RaspberryPi)添加至 gpio 組,重啟以應(yīng)用更改的內(nèi)容。
有 Swfit 3.0 和最新 SwiftyGPIO 的話,Cameron Perry 的教程會手把手教你在 Raspberry Pi 進(jìn)行 Swift 設(shè)置,還教你怎么用 LED 燈和溫度傳感器。
若只有 Swift 2.x,比較實(shí)在的 SwiftyGPIO(來自于具體的 2.x 分支)使用教程可以搜 Joe(來自于獨(dú)立軟件開發(fā)者聯(lián)盟 iachievedit),他的教程很不錯(cuò),且有多個(gè)語言版本,中文版可點(diǎn)擊這里閱覽。
SwiftyGPIO 可與 GPIO、SPI(若無可用位拆裂 VirtualSPI 替代)和 PWM,現(xiàn)在就逐一看看怎么使用它們吧。
假設(shè)使用的是 Raspberry 2 開發(fā)板,GPIO pin(針腳) P2(電阻 1K Ohm 左右)和 GND 間連接 LED 燈。我們的目標(biāo)是讓燈亮起來。
首先要檢索開發(fā)板上可用的 GPIO,想修改哪個(gè)就設(shè)置一個(gè) reference:
let gpios = SwiftyGPIO.GPIOs(for:.RaspberryPi2) var gp = gpios[.P2]!
以下是預(yù)定義開發(fā)板估計(jì)的值:
GPIOs(for:)返回的地圖包括不同具體開發(fā)板所有 GPIO,如圖解所示。
另外一種方案是,如果開發(fā)板不受支持,可使用開發(fā)板的 SysFS GPIO Id 來手動安裝所有 GPIO 對象:
var gp = GPIO(name: "P2",id: 2) // User defined name and GPIO Id
用戶已定義名稱以及 GPIO Id。
下一步是設(shè)置接口 direction,GPIODirection.IN 或 GPIODirection.OUT 均可,此處選擇后者:
gp.direction = .OUT然后將 pin 值改為“1”——上升(HIGH):
gp.value = 1
LED 燈就亮了。
現(xiàn)在假設(shè)開關(guān)連接至 P2,讀取經(jīng)過 P2 接口的值,direction 必須設(shè)置為.IN,值可從 value property(值屬性)讀取。
gp.direction = .IN let current = gp.value
GPIO 對象上其他屬性(如 edge 和 active low)屬于 GPIO 附加屬性,可自行設(shè)置,但不是必選。詳細(xì)描述參照內(nèi)核文件。
當(dāng) pin 值變化時(shí),GPIO 也支持閉包。添加閉包的指令有 onRaising(pin 值從 0 到 1)、onFalling
(pin 值從 1 到 0)和onChange
(只要 pin 值發(fā)生改變):
let gpios = SwiftyGPIO.GPIOs(for:.RaspberryPi2)var gp = gpios[.P2]! gp.onRaising{ gpio in print("Transition to 1, current value:" + String(gpio.value)) } gp.onFalling{ gpio in print("Transition to 0, current value:" + String(gpio.value)) } gp.onChange{ gpio in gpio.clearListeners() print("The value changed, current value:" + String(gpio.value)) }
閉包接受的唯一參數(shù)是已更新的 GPIO 對象的 reference,因此無需使用外部變量。調(diào)用 clearListeners()移除所有負(fù)責(zé)監(jiān)聽的閉包并禁用 the changes handler。GPIO 檢查更新期間,pin direction 無法更改(設(shè)置為.IN)。但 listeners 全部移除后,不論在閉包內(nèi)部或其他位置,都可以自由更改。
若開發(fā)板上 SwiftyGPIO 已預(yù)設(shè) SPI 連接,就能在預(yù)定義開發(fā)板上調(diào)用 hardwareSPIs(for:)檢索可用 SPI(Swift 2.x 則調(diào)用 getHardwareSPIsForBoard)。
RaspberryPi 和其他開發(fā)板上,硬件 SPI SysFS 界面非默認(rèn)啟動。wiki 上有設(shè)置向?qū)Э晒﹨㈤啞?
再比如使用 RaspberryPi 2,其雙向 SPI 由 SwiftyGPIO 作為單項(xiàng) SPIObjects 來管理:
let spis = SwiftyGPIO.hardwareSPIs(for:.RaspberryPi2) var spi = spis?[0]
第一個(gè)返回項(xiàng)是輸出信道,可在 SPIObject 上調(diào)用 isOut 方法來驗(yàn)證。
或者,可以用兩個(gè) GPIO 創(chuàng)建一個(gè)軟件 SPI。一個(gè) GPIO 作為 clock pin,另外一個(gè)用來發(fā)送數(shù)據(jù)。這種位拆裂 SPI 比硬件 SPI 慢,能不用就不用。
創(chuàng)建一個(gè)軟件 SPI,檢索兩個(gè) pin,創(chuàng)建一個(gè) VirtualSPI 對象。
let gpios = SwiftyGPIO.GPIOs(for:.RaspberryPi2) var sclk = gpios[.P2]! var dnmosi = gpios[.P3]! var spi = VirtualSPI(dataGPIO:dnmosi,clockGPIO:sclk)
兩個(gè)對象遵守同一 SPIObject 協(xié)議,因此提供同一方法。要區(qū)分硬件和軟件 SPIObject,要用 isHardware 方法。
在 SPI 上發(fā)送 1 字節(jié)及以上,使用 sendData 方法。最簡單的形式下,它只需一列 UInt8 作為參數(shù):
spi?.sendData([UInt(42)])但對于軟件 SPI(硬件 SPI 忽略這些值),可自己決定字節(jié)順序(MSB、LSB —— 最高/最低有效位)和連續(xù)兩字節(jié)之間的延時(shí)(clock width,默認(rèn)值為 0):
spi?.sendData([UInt(42)], order:.LSBFIRST, clockDelayUsec:1000)
PWM 輸出信號可驅(qū)動伺服電機(jī)、RGB LED 燈及其他設(shè)備,或者只有數(shù)字 GPIO 端口時(shí),粗略估計(jì) analog 輸出值。
如果開發(fā)板有 PWM 端口且支持 SwiftyGPIO(RaspberryPi 開發(fā)板),用 hardwarePWMs 工廠方法來檢索可用的 PWMOutput 對象。
let pwms = SwiftyGPIO.hardwarePWMs(for:.RaspberryPi2)! let pwm = (pwms[0]?[.P18])!
該方法返回所有支持 PWM 功能端口,由控制它們的 PWM 頻道進(jìn)行分類。
每個(gè)頻道只能使用一個(gè)端口,而 Raspberries 有兩個(gè)頻道,可以同時(shí)使用兩個(gè) PWM 輸出,比如 GPIO12 和 GPIO13 或 GPIO18 和 GPIO19。
計(jì)劃使用哪個(gè)端口,且檢索到 PWMOutput 時(shí),需要初始化來選擇 PWM 功能。在此類開發(fā)板上,端口往往不止一個(gè)功能(簡單的 GPIO、SPI、PWM 等),可以隨心選擇來配置專用的寄存器。
pwm.initPWM()調(diào)用 startPWM 來開始 PWM 信號,該指令時(shí)長以納秒計(jì)(如有頻率,轉(zhuǎn)換為 1/frequency),工作周期以百分比計(jì)。
print("PWM from GPIO18 with 500ns period and 50% duty cycle") pwm.startPWM(period: 500, duty: 50)
一旦調(diào)用此方法,ARM SoC 的 PWM 子系統(tǒng)會開始生成信號,無需干預(yù),程序會自己執(zhí)行;如果想等就插入休眠(sleep,以秒計(jì))指令。
調(diào)用 stopPWM()方法來停止 PWM 信號:
pwm.stopPWM()
若想改變信號,不需要停止之前發(fā)出的,只需要用不同參數(shù)調(diào)用 startPWM。
這一特征運(yùn)用 M/S 算法,已經(jīng)過 300ns(毫微秒)-200ms(毫秒)區(qū)間內(nèi)的信號測試,在此區(qū)間外生成信號可能導(dǎo)致大量抖動,一些應(yīng)用是受不了的。如果手頭有示波器,且想要在該區(qū)間兩頭生成信號,就要不間斷確認(rèn)生成的信號正常。
不同開發(fā)板的例子在 Examples 目錄下面,可以挑一些做改良實(shí)驗(yàn)。
接下來這個(gè)例子是在 C.H.I.P.開發(fā)板上運(yùn)行的,標(biāo)出了所有 GPIO0 屬性當(dāng)下的值,改了 direction 和值,然后顯示在屬性上:
let gpios = SwiftyGPIO.GPIOs(for:.CHIP) var gp0 = gpios[.P0]! print("Current Status") print("Direction: "+gp0.direction.rawValue) print("Edge: "+gp0.edge.rawValue) print("Active Low: "+String(gp0.activeLow)) print("Value: "+String(gp0.value)) gp0.direction = .OUT gp0.value = 1 print("New Status") print("Direction: "+gp0.direction.rawValue) print("Edge: "+gp0.edge.rawValue) print("Active Low: "+String(gp0.activeLow)) print("Value: "+String(gp0.value))第二個(gè)例子可以使 LED 燈以 150ms 的頻率閃動:
import Glibc let gpios = SwiftyGPIO.GPIOs(for:.CHIP)var gp0 = gpios[.P0]! gp0.direction = .OUT repeat{ gp0.value = (gp0.value == 0) ? 1 : 0 usleep(150*1000) }while(true)雖然不能用 CHIP 測試硬件 SPI,但 SwiftyGPIO 也提供了 SPI 界面的位拆裂軟件執(zhí)行,只要兩個(gè) GPIO 來初始化即可:
let gpios = SwiftyGPIO.GPIOs(for:.CHIP) var sclk = gpios[.P0]! var dnmosi = gpios[.P1]! var spi = VirtualSPI(dataGPIO:dnmosi,clockGPIO:sclk) pi.sendData([UInt8(truncatingBitPattern:0x9F)])
注意:我們使用構(gòu)造函數(shù) UInt8(truncatingBitPattern:)來轉(zhuǎn)換 0x9F Int,雖然這里并非必選項(xiàng),但對于用戶提供(user-provided)或計(jì)算(calculated)整數(shù)很推薦,因?yàn)?Swift 不支持隱式截?cái)噢D(zhuǎn)化為更小整數(shù)型,如果想轉(zhuǎn)化的 Int 不合適 UInt8,Swift 會崩潰。