原标题:SwiftyGPIO:详解如何在开发板上使用 Swift
译者:张新慧,技术之路,共同进步,有优质移动开发、VR/AR/MR、物联网原创技术文章欢迎发送邮件至 mobilehub@csdn.net。
SwiftyGPIO 是一个能够通过 Swift 语言去控制基于 Linux 主板(比如:C.H.I.P. 和树莓派)GPIO(General Purpose Input Output,通用型之输入输出,为嵌入式开发的概念,对应嵌入式设备的物理接口)以完成简单的工控功能(比如 LED 灯的显示)的开源库,简单而言就是,这个库能够让 Swift 与设备的 GPIO 进行交互,从而实现开关功能。该项目基于 MIT 协议开源,其主要贡献者为软件工程师 Umberto Raimondi。
想要使用 SwiftyGPIO 库,首先需要具备一个支持 Swift 3 及其以上版本的 Linux ARM(ARMv7 或 ARMv6)。如果你有一个基于 Ubuntu 或 Raspbian 操作系统的树莓派(A、B、A+、B+、Zero、ZeroW、2、3),从这里获取 Swift 3.0.2 或按文章介绍以及链接的脚本库来进行构建。
如果你的 Swift 版本支持 SPM,只需将 SwiftyGPIO 作为依赖(dependency)添加到 Package.swift 中:
let package = Package( name: "MyProject", dependencies: [ .Package(url: "https://github.com/uraimo/SwiftyGPIO.git", majorVersion: 0), ... ] ... )
然后执行 swift build
命令,编译器会在 .build/
下创建一个可执行文件。
如果你的 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
下载完毕,在同一目录下创建一个包含应用代码的附加文件,命名为 main.swift
。当代码就绪,就用 swiftc *.swift
来编译,编译器会创建一个 main
可执行文件。
注意:与 GPIO 交互时,不论是通过 sysfs 还是 mmapped 寄存器,如果操作系统没有预定义的用户组来获取这些函数,就需要用 sudo ./main 动用 root 权限来运行应用。如果 RaspberryPi 使用 2016 年 11 月更新的最新 Raspbian 或者 16.04 Xenial 及以上的 Ubuntu,只需要./main 来启动应用。混合系统的话,listeners(监听器)必须要求 root 权限。
替代方案是,具体用户组获取 GPIO 可手动设置(参考1、2)。遵照这些指南之后,别忘了用sudo usermod -aG gpio pi
将用户(比如 RaspberryPi)添加至 gpio 组,重启以应用更改的内容。
有 Swfit 3.0 和最新 SwiftyGPIO 的话,Cameron Perry 的教程会手把手教你在 Raspberry Pi 进行 Swift 设置,还教你怎么用 LED 灯和温度传感器。
若只有 Swift 2.x,比较实在的 SwiftyGPIO(来自于具体的 2.x 分支)使用教程可以搜 Joe(来自于独立软件开发者联盟 iachievedit),他的教程很不错,且有多个语言版本,中文版可点击这里阅览。
SwiftyGPIO 可与 GPIO、SPI(若无可用位拆裂 VirtualSPI 替代)和 PWM,现在就逐一看看怎么使用它们吧。
假设使用的是 Raspberry 2 开发板,GPIO pin(针脚) P2(电阻 1K Ohm 左右)和 GND 间连接 LED 灯。我们的目标是让灯亮起来。
首先要检索开发板上可用的 GPIO,想修改哪个就设置一个 reference:
let gpios = SwiftyGPIO.GPIOs(for:.RaspberryPi2) var gp = gpios[.P2]!
以下是预定义开发板估计的值:
GPIOs(for:)返回的地图包括不同具体开发板所有 GPIO,如图解所示。
另外一种方案是,如果开发板不受支持,可使用开发板的 SysFS GPIO Id 来手动安装所有 GPIO 对象:
var gp = GPIO(name: "P2",id: 2) // User defined name and GPIO Id
用户已定义名称以及 GPIO Id。
下一步是设置接口 direction,GPIODirection.IN 或 GPIODirection.OUT 均可,此处选择后者:
gp.direction = .OUT然后将 pin 值改为“1”——上升(HIGH):
gp.value = 1
LED 灯就亮了。
现在假设开关连接至 P2,读取经过 P2 接口的值,direction 必须设置为.IN,值可从 value property(值属性)读取。
gp.direction = .IN let current = gp.value
GPIO 对象上其他属性(如 edge 和 active low)属于 GPIO 附加属性,可自行设置,但不是必选。详细描述参照内核文件。
当 pin 值变化时,GPIO 也支持闭包。添加闭包的指令有 onRaising(pin 值从 0 到 1)、onFalling
(pin 值从 1 到 0)和onChange
(只要 pin 值发生改变):
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)) }
闭包接受的唯一参数是已更新的 GPIO 对象的 reference,因此无需使用外部变量。调用 clearListeners()移除所有负责监听的闭包并禁用 the changes handler。GPIO 检查更新期间,pin direction 无法更改(设置为.IN)。但 listeners 全部移除后,不论在闭包内部或其他位置,都可以自由更改。
若开发板上 SwiftyGPIO 已预设 SPI 连接,就能在预定义开发板上调用 hardwareSPIs(for:)检索可用 SPI(Swift 2.x 则调用 getHardwareSPIsForBoard)。
RaspberryPi 和其他开发板上,硬件 SPI SysFS 界面非默认启动。wiki 上有设置向导可供参阅。
再比如使用 RaspberryPi 2,其双向 SPI 由 SwiftyGPIO 作为单项 SPIObjects 来管理:
let spis = SwiftyGPIO.hardwareSPIs(for:.RaspberryPi2) var spi = spis?[0]
第一个返回项是输出信道,可在 SPIObject 上调用 isOut 方法来验证。
或者,可以用两个 GPIO 创建一个软件 SPI。一个 GPIO 作为 clock pin,另外一个用来发送数据。这种位拆裂 SPI 比硬件 SPI 慢,能不用就不用。
创建一个软件 SPI,检索两个 pin,创建一个 VirtualSPI 对象。
let gpios = SwiftyGPIO.GPIOs(for:.RaspberryPi2) var sclk = gpios[.P2]! var dnmosi = gpios[.P3]! var spi = VirtualSPI(dataGPIO:dnmosi,clockGPIO:sclk)
两个对象遵守同一 SPIObject 协议,因此提供同一方法。要区分硬件和软件 SPIObject,要用 isHardware 方法。
在 SPI 上发送 1 字节及以上,使用 sendData 方法。最简单的形式下,它只需一列 UInt8 作为参数:
spi?.sendData([UInt(42)])但对于软件 SPI(硬件 SPI 忽略这些值),可自己决定字节顺序(MSB、LSB —— 最高/最低有效位)和连续两字节之间的延时(clock width,默认值为 0):
spi?.sendData([UInt(42)], order:.LSBFIRST, clockDelayUsec:1000)
PWM 输出信号可驱动伺服电机、RGB LED 灯及其他设备,或者只有数字 GPIO 端口时,粗略估计 analog 输出值。
如果开发板有 PWM 端口且支持 SwiftyGPIO(RaspberryPi 开发板),用 hardwarePWMs 工厂方法来检索可用的 PWMOutput 对象。
let pwms = SwiftyGPIO.hardwarePWMs(for:.RaspberryPi2)! let pwm = (pwms[0]?[.P18])!
该方法返回所有支持 PWM 功能端口,由控制它们的 PWM 频道进行分类。
每个频道只能使用一个端口,而 Raspberries 有两个频道,可以同时使用两个 PWM 输出,比如 GPIO12 和 GPIO13 或 GPIO18 和 GPIO19。
计划使用哪个端口,且检索到 PWMOutput 时,需要初始化来选择 PWM 功能。在此类开发板上,端口往往不止一个功能(简单的 GPIO、SPI、PWM 等),可以随心选择来配置专用的寄存器。
pwm.initPWM()调用 startPWM 来开始 PWM 信号,该指令时长以纳秒计(如有频率,转换为 1/frequency),工作周期以百分比计。
print("PWM from GPIO18 with 500ns period and 50% duty cycle") pwm.startPWM(period: 500, duty: 50)
一旦调用此方法,ARM SoC 的 PWM 子系统会开始生成信号,无需干预,程序会自己执行;如果想等就插入休眠(sleep,以秒计)指令。
调用 stopPWM()方法来停止 PWM 信号:
pwm.stopPWM()
若想改变信号,不需要停止之前发出的,只需要用不同参数调用 startPWM。
这一特征运用 M/S 算法,已经过 300ns(毫微秒)-200ms(毫秒)区间内的信号测试,在此区间外生成信号可能导致大量抖动,一些应用是受不了的。如果手头有示波器,且想要在该区间两头生成信号,就要不间断确认生成的信号正常。
不同开发板的例子在 Examples 目录下面,可以挑一些做改良实验。
接下来这个例子是在 C.H.I.P.开发板上运行的,标出了所有 GPIO0 属性当下的值,改了 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))第二个例子可以使 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 界面的位拆裂软件执行,只要两个 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)])
注意:我们使用构造函数 UInt8(truncatingBitPattern:)来转换 0x9F Int,虽然这里并非必选项,但对于用户提供(user-provided)或计算(calculated)整数很推荐,因为 Swift 不支持隐式截断转化为更小整数型,如果想转化的 Int 不合适 UInt8,Swift 会崩溃。