返回 登录
1

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 可手动设置(参考12)。遵照这些指南之后,别忘了用sudo usermod -aG gpio pi将用户(比如 RaspberryPi)添加至 gpio 组,重启以应用更改的内容。

小试牛刀:闪烁的 LED 灯和传感器

有 Swfit 3.0 和最新 SwiftyGPIO 的话,Cameron Perry 的教程会手把手教你在 Raspberry Pi 进行 Swift 设置,还教你怎么用 LED 灯和温度传感器。

若只有 Swift 2.x,比较实在的 SwiftyGPIO(来自于具体的 2.x 分支)使用教程可以搜 Joe(来自于独立软件开发者联盟 iachievedit),他的教程很不错,且有多个语言版本,中文版可点击这里阅览。

如何使用

SwiftyGPIO 可与 GPIO、SPI(若无可用位拆裂 VirtualSPI 替代)和 PWM,现在就逐一看看怎么使用它们吧。

GPIO

假设使用的是 Raspberry 2 开发板,GPIO pin(针脚) P2(电阻 1K Ohm 左右)和 GND 间连接 LED 灯。我们的目标是让灯亮起来。

首先要检索开发板上可用的 GPIO,想修改哪个就设置一个 reference:

let gpios = SwiftyGPIO.GPIOs(for:.RaspberryPi2)
var gp = gpios[.P2]!

以下是预定义开发板估计的值:

  • .RaspberryPiRev1 (Pi A,B Revision 1, 2012 年之前, 26 pin header)
  • .RaspberryPiRev2 (Pi A,B Revision 2, 2012 年之后, 26 pin header)
  • .RaspberryPiPlusZero (Raspberry Pi A+ 和 B+, Raspberry Zero, 均为 40 pin header)
  • .RaspberryPi2 (Raspberry Pi 2 或 3,40 pin header)
  • .BeagleBoneBlack (BeagleBone Black)
  • .CHIP (C.H.I.P.电脑,9 美元售价).
  • .BananaPi (RaspberryPi 的双胞胎)
  • .OrangePi

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 全部移除后,不论在闭包内部或其他位置,都可以自由更改。

SPI

若开发板上 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

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 会崩溃。

相关库


评论