diff --git a/README.md b/README.md index 733e38b..c1f3819 100644 --- a/README.md +++ b/README.md @@ -27,22 +27,24 @@ Examples of *[device libraries](#libraries)* and *[complete projects](#awesome- ##### Content: +- [Summary](#summary) + - [Content:](#content) - [Supported Boards](#supported-boards) - [Installation](#installation) -- [Your First Project: Blinking Leds And Sensors](#your-first-project-blinking-leds-and-sensors) +- [Your First Project: Blinking leds and sensors](#your-first-project-blinking-leds-and-sensors) - [Usage](#usage) - - [GPIO](#gpio) - - [SPI](#spi) - - [I2C](#i2c) - - [PWM](#pwm) - - [Pattern-based signal generator via PWM](#pattern-based-signal-generator-via-pwm) - - [UART](#uart) - - [1-Wire](#1-wire) + - [GPIO](#gpio) + - [SPI](#spi) + - [I2C](#i2c) + - [PWM](#pwm) + - [Pattern-based signal generator via PWM](#pattern-based-signal-generator-via-pwm) + - [UART](#uart) + - [1-Wire](#1-wire) - [Examples](#examples) - [Built with SwiftyGPIO](#built-with-swiftygpio) - - [Device Libraries](#libraries) - - [Awesome Projects](#awesome-projects) - - [Support Libraries](#support-libraries) + - [Libraries](#libraries) + - [Awesome Projects](#awesome-projects) + - [Support libraries](#support-libraries) - [Additional documentation](#additional-documentation) @@ -311,7 +313,23 @@ func readByte(_ address: Int) -> UInt8 func readByte(_ address: Int, command: UInt8) -> UInt8 func readWord(_ address: Int, command: UInt8) -> UInt16 func readData(_ address: Int, command: UInt8) -> [UInt8] -func readI2CData(_ address: Int, command: UInt8) -> [UInt8] +func readI2CData(_ address: Int, command: UInt8, length: UInt8) -> [UInt8] +func readRaw(_ address: Int, length: Int) -> [UInt8] +func writeAndRead(_ address: Int, write: [UInt8], readLength: UInt) -> [UInt8] + +``` + +There are also versions of the read functions that throw an error to indicate a read failure: + +```swift +func tryReadByte(_ address: Int) throws -> UInt8 +func tryReadByte(_ address: Int, command: UInt8) throws -> UInt8 +func tryReadWord(_ address: Int, command: UInt8) throws -> UInt16 +func tryReadData(_ address: Int, command: UInt8) throws -> [UInt8] +func tryReadI2CData(_ address: Int, command: UInt8, length: UInt8) throws -> [UInt8] +func tryReadRaw(_ address: Int, length: Int) throws -> [UInt8] +func tryWriteAndRead(_ address: Int, write: [UInt8], readLength: UInt) throws -> [UInt8] + ``` Reading and writing data blocks supports two modes, a standard SMBus mode (`readData` and `writeData`) that prepends the length of the block before the actual data, and an old style I2C mode (`readI2CData` and `writeI2CData`) that just send the data without additional metadata. Depending on the device, only one of the two modes will be supported. @@ -322,16 +340,16 @@ Let's suppose that we want to read the seconds register (id 0) from a DS1307 RTC print(i2c.readByte(0x68, command: 0)) //Prints the value of the 8bit register ``` -You should choose the same way one of the write functions available, just note that `writeQuick` is used to perform quick commands and does not perform a normal write. SMBus's quick commands are usually used to turn on/off devices or perform similar tasks that don't require additional parameters. +You should choose the same way one of the write functions available, just note that `writeQuick` is used to perform quick commands and does not perform a normal write. SMBus's quick commands are usually used to turn on/off devices or perform similar tasks that don't require additional parameters. The write functions return `true` on success and `false` on failure. ```swift -func writeQuick(_ address: Int) +func writeQuick(_ address: Int) -> Bool -func writeByte(_ address: Int, value: UInt8) -func writeByte(_ address: Int, command: UInt8, value: UInt8) -func writeWord(_ address: Int, command: UInt8, value: UInt16) -func writeData(_ address: Int, command: UInt8, values: [UInt8]) -func writeI2CData(_ address: Int, command: UInt8, values: [UInt8]) +func writeByte(_ address: Int, value: UInt8) -> Bool +func writeByte(_ address: Int, command: UInt8, value: UInt8) -> Bool +func writeWord(_ address: Int, command: UInt8, value: UInt16) -> Bool +func writeData(_ address: Int, command: UInt8, values: [UInt8]) -> Bool +func writeI2CData(_ address: Int, command: UInt8, values: [UInt8]) -> Bool ``` While using the I2C functionality doesn't require additional software to function, the tools contained in `i2c-tools` are useful to perform I2C transactions manually to verify that everything is working correctly. diff --git a/Sources/I2C.swift b/Sources/I2C.swift index 7df0ecb..94b5b17 100644 --- a/Sources/I2C.swift +++ b/Sources/I2C.swift @@ -28,6 +28,8 @@ import Darwin.C #endif +import Foundation + extension SwiftyGPIO { public static func hardwareI2Cs(for board: SupportedBoard) -> [I2CInterface]? { @@ -70,18 +72,29 @@ extension SwiftyGPIO { public protocol I2CInterface { func isReachable(_ address: Int) -> Bool - func setPEC(_ address: Int, enabled: Bool) + @discardableResult func setPEC(_ address: Int, enabled: Bool) -> Bool func readByte(_ address: Int) -> UInt8 func readByte(_ address: Int, command: UInt8) -> UInt8 func readWord(_ address: Int, command: UInt8) -> UInt16 func readData(_ address: Int, command: UInt8) -> [UInt8] - func readI2CData(_ address: Int, command: UInt8) -> [UInt8] - func writeQuick(_ address: Int) - func writeByte(_ address: Int, value: UInt8) - func writeByte(_ address: Int, command: UInt8, value: UInt8) - func writeWord(_ address: Int, command: UInt8, value: UInt16) - func writeData(_ address: Int, command: UInt8, values: [UInt8]) - func writeI2CData(_ address: Int, command: UInt8, values: [UInt8]) + func readI2CData(_ address: Int, command: UInt8, length: UInt8) -> [UInt8] + func readRaw(_ address: Int, length: Int) -> [UInt8] + func writeAndRead(_ address: Int, write: [UInt8], readLength: UInt) -> [UInt8] + + func tryReadByte(_ address: Int) throws -> UInt8 + func tryReadByte(_ address: Int, command: UInt8) throws -> UInt8 + func tryReadWord(_ address: Int, command: UInt8) throws -> UInt16 + func tryReadData(_ address: Int, command: UInt8) throws -> [UInt8] + func tryReadI2CData(_ address: Int, command: UInt8, length: UInt8) throws -> [UInt8] + func tryReadRaw(_ address: Int, length: Int) throws -> [UInt8] + func tryWriteAndRead(_ address: Int, write: [UInt8], readLength: UInt) throws -> [UInt8] + + @discardableResult func writeQuick(_ address: Int) -> Bool + @discardableResult func writeByte(_ address: Int, value: UInt8) -> Bool + @discardableResult func writeByte(_ address: Int, command: UInt8, value: UInt8) -> Bool + @discardableResult func writeWord(_ address: Int, command: UInt8, value: UInt16) -> Bool + @discardableResult func writeData(_ address: Int, command: UInt8, values: [UInt8]) -> Bool + @discardableResult func writeI2CData(_ address: Int, command: UInt8, values: [UInt8]) -> Bool // One-shot rd/wr not provided } @@ -118,6 +131,20 @@ public final class SysFSI2C: I2CInterface { #endif } + public func tryReadByte(_ address: Int) throws -> UInt8 { + setSlaveAddress(address) + + let r = i2c_smbus_read_byte() + + guard r >= 0 else { throw POSIXError( POSIXError.Code( rawValue: errno ) ?? POSIXError.EIO ) } + + #if swift(>=4.0) + return UInt8(truncatingIfNeeded: r) + #else + return UInt8(truncatingBitPattern: r) + #endif + } + public func readByte(_ address: Int, command: UInt8) -> UInt8 { setSlaveAddress(address) @@ -134,6 +161,20 @@ public final class SysFSI2C: I2CInterface { #endif } + public func tryReadByte(_ address: Int, command: UInt8) throws -> UInt8 { + setSlaveAddress(address) + + let r = i2c_smbus_read_byte_data(command: command) + + guard r >= 0 else { throw POSIXError( POSIXError.Code( rawValue: errno ) ?? POSIXError.EIO ) } + + #if swift(>=4.0) + return UInt8(truncatingIfNeeded: r) + #else + return UInt8(truncatingBitPattern: r) + #endif + } + public func readWord(_ address: Int, command: UInt8) -> UInt16 { setSlaveAddress(address) @@ -150,6 +191,20 @@ public final class SysFSI2C: I2CInterface { #endif } + public func tryReadWord(_ address: Int, command: UInt8) throws -> UInt16 { + setSlaveAddress(address) + + let r = i2c_smbus_read_word_data(command: command) + + guard r >= 0 else { throw POSIXError( POSIXError.Code( rawValue: errno ) ?? POSIXError.EIO ) } + + #if swift(>=4.0) + return UInt16(truncatingIfNeeded: r) + #else + return UInt16(truncatingBitPattern: r) + #endif + } + public func readData(_ address: Int, command: UInt8) -> [UInt8] { var buf: [UInt8] = [UInt8](repeating:0, count: 32) @@ -164,12 +219,25 @@ public final class SysFSI2C: I2CInterface { return buf } - public func readI2CData(_ address: Int, command: UInt8) -> [UInt8] { + public func tryReadData(_ address: Int, command: UInt8) throws -> [UInt8] { var buf: [UInt8] = [UInt8](repeating:0, count: 32) setSlaveAddress(address) - let r = i2c_smbus_read_i2c_block_data(command: command, values: &buf) + let r = i2c_smbus_read_block_data(command: command, values: &buf) + + guard r >= 0 else { throw POSIXError( POSIXError.Code( rawValue: errno ) ?? POSIXError.EIO ) } + + return buf + } + + + public func readI2CData(_ address: Int, command: UInt8, length: UInt8) -> [UInt8] { + var buf: [UInt8] = [UInt8](repeating:0, count: Int(length)) + + setSlaveAddress(address) + + let r = i2c_smbus_read_i2c_block_data(command: command, values: &buf, length: length) if r < 0 { perror("I2C read failed") @@ -178,70 +246,123 @@ public final class SysFSI2C: I2CInterface { return buf } - public func writeQuick(_ address: Int) { + public func tryReadI2CData(_ address: Int, command: UInt8, length: UInt8) throws -> [UInt8] { + + guard length <= I2C_DEFAULT_PAYLOAD_LENGTH else { throw POSIXError( POSIXError.EINVAL ) } + + var buf: [UInt8] = [UInt8](repeating:0, count: Int(length)) + setSlaveAddress(address) - let r = i2c_smbus_write_quick(value: I2C_SMBUS_WRITE) + let r = i2c_smbus_read_i2c_block_data(command: command, values: &buf, length: length) + + guard r >= 0 else { throw POSIXError( POSIXError.Code( rawValue: errno ) ?? POSIXError.EIO ) } + + return buf + } + + public func readRaw(_ address: Int, length: Int) -> [UInt8] { + var buf: [UInt8] = [UInt8](repeating:0, count: length) + + setSlaveAddress(address) + + let r = read( fd, &buf, length ) if r < 0 { - perror("I2C write failed") + perror("I2C read failed") abort() } + return buf } - public func writeByte(_ address: Int, value: UInt8) { + public func tryReadRaw(_ address: Int, length: Int) throws -> [UInt8] { + var buf: [UInt8] = [UInt8](repeating:0, count: Int( length )) + setSlaveAddress(address) - let r = i2c_smbus_write_byte(value: value) + let r = read( fd, &buf, length ) + + guard r >= 0 else { throw POSIXError( POSIXError.Code( rawValue: errno ) ?? POSIXError.EIO ) } + + return buf + } + + + public func writeAndRead(_ address: Int, write: [UInt8], readLength: UInt) -> [UInt8] { + var readBuf: [UInt8] = [UInt8](repeating:0, count: Int( readLength ) ) + + setSlaveAddress(address) + + let r = i2c_write_and_read (write: write, read: &readBuf ) + if r < 0 { - perror("I2C write failed") + perror("I2C read failed") abort() } + return readBuf } - public func writeByte(_ address: Int, command: UInt8, value: UInt8) { + + public func tryWriteAndRead(_ address: Int, write: [UInt8], readLength: UInt) throws -> [UInt8] { + var readBuf: [UInt8] = [UInt8](repeating:0, count: Int( readLength )) + + setSlaveAddress(address) + + let r = i2c_write_and_read (write: write, read: &readBuf ) + + guard r >= 0 else { throw POSIXError( POSIXError.Code( rawValue: errno ) ?? POSIXError.EIO ) } + + return readBuf + } + + + public func writeQuick(_ address: Int) -> Bool { + setSlaveAddress(address) + + let r = i2c_smbus_write_quick(value: I2C_SMBUS_WRITE) + + return r >= 0 + } + + public func writeByte(_ address: Int, value: UInt8) -> Bool { + setSlaveAddress(address) + + let r = i2c_smbus_write_byte(value: value) + + return r >= 0 + } + + public func writeByte(_ address: Int, command: UInt8, value: UInt8) -> Bool { setSlaveAddress(address) let r = i2c_smbus_write_byte_data(command: command, value: value) - if r < 0 { - perror("I2C write failed") - abort() - } + return r >= 0 } - public func writeWord(_ address: Int, command: UInt8, value: UInt16) { + public func writeWord(_ address: Int, command: UInt8, value: UInt16) -> Bool { setSlaveAddress(address) let r = i2c_smbus_write_word_data(command: command, value: value) - if r < 0 { - perror("I2C write failed") - abort() - } + return r >= 0 } - public func writeData(_ address: Int, command: UInt8, values: [UInt8]) { + public func writeData(_ address: Int, command: UInt8, values: [UInt8]) -> Bool { setSlaveAddress(address) let r = i2c_smbus_write_block_data(command: command, values: values) - if r < 0 { - perror("I2C write failed") - abort() - } + return r >= 0 } - public func writeI2CData(_ address: Int, command: UInt8, values: [UInt8]) { + public func writeI2CData(_ address: Int, command: UInt8, values: [UInt8]) -> Bool { setSlaveAddress(address) let r = i2c_smbus_write_i2c_block_data(command: command, values: values) - if r < 0 { - perror("I2C write failed") - abort() - } + return r >= 0 } public func isReachable(_ address: Int) -> Bool { @@ -270,15 +391,12 @@ public final class SysFSI2C: I2CInterface { return true } - public func setPEC(_ address: Int, enabled: Bool) { + public func setPEC(_ address: Int, enabled: Bool) -> Bool { setSlaveAddress(address) let r = ioctl(fd, I2C_PEC, enabled ? 1 : 0) - if r != 0 { - perror("I2C communication failed") - abort() - } + return r == 0 } private func setSlaveAddress(_ to: Int) { @@ -321,6 +439,7 @@ public final class SysFSI2C: I2CInterface { var data: UnsafeMutablePointer? //union: UInt8, UInt16, [UInt8]33 } + private func smbus_ioctl(rw: UInt8, command: UInt8, size: Int32, data: UnsafeMutablePointer?) -> Int32 { if fd == -1 { openI2C() @@ -396,15 +515,17 @@ public final class SysFSI2C: I2CInterface { } } - private func i2c_smbus_read_i2c_block_data(command: UInt8, values: inout [UInt8]) -> Int32 { + private func i2c_smbus_read_i2c_block_data(command: UInt8, values: inout [UInt8], length: UInt8) -> Int32 { var data = [UInt8](repeating:0, count: I2C_DEFAULT_PAYLOAD_LENGTH) + data[0] = length; + let r = smbus_ioctl(rw: I2C_SMBUS_READ, command: command, size: I2C_SMBUS_I2C_BLOCK_DATA, data: &data) if r >= 0 { - for i in 0.. + } + + struct i2c_rdwr_ioctl_data + { + var msgs: UnsafePointer + var nmsgs: UInt32 + }; + + private func i2c_write_and_read (write: [UInt8], read: inout [UInt8] ) -> Int32 + { + guard write.count<=I2C_DEFAULT_PAYLOAD_LENGTH && read.count<=I2C_DEFAULT_PAYLOAD_LENGTH else { fatalError("Invalid data length, can't send more than \(I2C_DEFAULT_PAYLOAD_LENGTH) bytes!") } + + var writeData = write + + return writeData.withUnsafeMutableBufferPointer { writePtr in + return read.withUnsafeMutableBufferPointer { readPtr in + + let msgs = [ i2c_rw_msg( addr: UInt16( currentSlave ), + flags: 0, + len: UInt16( writePtr.count ), + buf: writePtr.baseAddress! ), + i2c_rw_msg( addr: UInt16( currentSlave ), + flags: I2C_M_RD, + len: UInt16( readPtr.count ), + buf: readPtr.baseAddress! ) + ] + + return msgs.withUnsafeBufferPointer { msgsPtr in + + var cmd = i2c_rdwr_ioctl_data( msgs: msgsPtr.baseAddress!, nmsgs: 2 ) + + return ioctl( fd, I2C_RDWR, &cmd ) + + } + } + } + } + } // MARK: - I2C/SMBUS Constants @@ -512,6 +679,9 @@ internal let I2C_SMBUS: UInt = 0x720 internal let I2C_DEFAULT_PAYLOAD_LENGTH: Int = 32 internal let I2CBASEPATH="/dev/i2c-" +internal let I2C_M_RD: UInt16 = 0x0001 + + // MARK: - Darwin / Xcode Support #if os(OSX) || os(iOS) private var O_SYNC: CInt { fatalError("Linux only") }