用于 Swift 构建的单板计算机的 GPIO 库。
这个库很大程度上基于 SwiftyGPIO,并且与其共享一些代码。重写的原因是尝试通过在与硬件寄存器交互时加强类型安全,并简化开发者交互来提高代码的可读性和可维护性。事实证明,重写比尝试就地重构更快更容易进行测试。
实验性
一个简单的例子,每秒钟切换 Raspberry Pi 上的引脚 GPIO12/BCM12 的开和关
let gpios = SingleBoard.raspberryPi.gpio
gpios[12].mode = .output
gpios[12].setPullup(.down)
while true {
print("On!")
gpios[12].value = true
usleep(1_000_000)
print("Off!")
gpios[12].value = false
usleep(1_000_000)
}
事情可以更进一步,例如,你想同时对 4 个引脚执行此操作。 SingleBoard 支持引脚集的概念,以便与一组引脚而不是单个引脚进行交互。 该接口在细微之处有所不同,但非常相似
let gpios = SingleBoard.raspberryPi.gpio
let pins: PinSet = [.p22, .p23, .p24, .p25]
gpios[pins].setMode(.output)
gpios[pins].setPullup(.down)
while true {
print("On!")
gpios[pins].value = true
usleep(1_000_000)
print("Off!")
gpios[pins].value = false
usleep(1_000_000)
}
如果你只是想要一点类型安全,你也可以使用单个引脚的引脚集,但在某些情况下,它可能会慢一些,具体取决于你针对的板子以及你执行的操作。 就 Raspberry Pi 而言,一次读取或写入多个引脚非常快。 一次配置多个引脚的上拉电阻也是如此。 但是,使用引脚集设置输入/输出模式很方便,但比使用引脚索引慢
gpios[.p12].value = true
gpios[[.p12, .p18]].setMode(.output)
gpios[[.p12, .p13, .p18, .p19]].setPullup(.down)
这是一个简短的示例,它写入地址为0x40
的设备,以在偏移量0x06
处写入1
。
// Get a connection for a device with address 0x40 on the board's primary bus
print(SingleBoard.raspberryPi.i2cMainBus.busId)
let i2cDevice = SingleBoard.raspberryPi.i2cMainBus[0x40]
i2cDevice.writeByte(to: 0x06, value: 1)
一个板子的主总线被认为是那些主要供单板计算机开发者使用的总线,而不是供系统使用的总线。
在 Raspberry Pi 上,总线 0 被系统用于少量事物,包括识别 HAT。 总线 1 通过引脚 3 和 5 暴露出来供爱好者使用,因此总线 1 被认为是主总线。 在 Rock 64 上,情况正好相反。 总线 0 通过引脚 3 和 5 暴露出来,而总线 1 用于 HAT 和系统设备。 Pine A64 就像 Raspberry Pi。 通过将主总线公开为属性,可以更容易地编写可以处理多个类似板子的代码。
基本读/写功能的完整集如下
var reachable: Bool { get }
//
// I2C API
//
func readByte() -> UInt8
func readWord() -> UInt16
func readData(length: Int) -> Data
func decode<T: I2CReadable>() -> T
func writeByte(value: UInt8)
func writeWord(value: UInt16)
func writeData(value: Data)
func encode<T: I2CWritable>(value: T)
//
// SMBus
//
func readByte(command: UInt8) -> UInt8
func readWord(command: UInt8) -> UInt16
func readData(command: UInt8) -> Data
func decode<T: I2CReadable>(command: UInt8) -> T
func writeQuick()
func writeByte(command: UInt8, value: UInt8)
func writeWord(command: UInt8, value: UInt16)
func writeData(command: UInt8, value: Data)
func encode<T: I2CWritable>(command: UInt8, value: T)
但除此之外,还支持自动处理符合RawRepresentable
和OptionSet
的类型。 例如,任何由 UInt8 支持的 RawRepresentable 都可以用于读取或写入字节和字
enum MyDeviceRegisters: UInt8, RawRepresentable { /* ... */ }
i2cDevice.writeByte(command: MyDeviceRegisters.control, value: 0x48)
更进一步,如果RawRepresentable
和OptionSet
由UInt8
或UInt16
支持,您也可以将它们用作字节或字本身
enum MyDeviceRegisters: UInt8, RawRepresentable { /* ... */ }
struct MyControlRegister: OptionSet {
let rawValue: UInt16
/* ... */
}
let controlValue: MyControlRegister = [.sleep, .reset]
i2cDevice.write(command: MyDeviceRegisters.control, value: controlValue)
读取方式相同。
对于更复杂的类型,你可以实现一些协议来启用在端点上调用encode
和decode
与您自己的类型。 这些利用Data
来存储缓冲区,但可以使代码保持更简洁。
protocol I2CReadable { /* ... */ }
protocol I2CWritable { /* ... */ }
protocol I2CReadWritable { /* ... */ }
这里的想法是使使用更严格的类型来表示与 I2C 设备交互时要读取和写入的值更容易一些。 这些技术已在 PCA9685 和 MCP4725 库中得到演示。
这是一个非常简单的例子,告诉 Raspberry Pi 在通道 0 上输出,使用其两个输出引脚
let pwmChannel = SingleBoard.raspberryPi.pwm[0]
pwmChannel.enable(pins: pwmChannel.pins)
pwmChannel.start(period: 1_000_000 /* nanoseconds */, dutyCycle: 0.5)
与 GPIO 类似,启用引脚上的输出可以使用引脚索引或引脚集
let pwmChannel = SingleBoard.raspberryPi.pwm[0]
pwmChannel.enable(pin: 12)
pwmChannel.enable(pins: .p18)
pwmChannel.enable(pins: [.p12, .p18])