|
| 1 | +//=== GT911.swift ---------------------------------------------------------===// |
| 2 | +// |
| 3 | +// Copyright (c) MadMachine Limited |
| 4 | +// Licensed under MIT License |
| 5 | +// |
| 6 | +// Authors: Andy Liu |
| 7 | +// Created: 12/19/2024 |
| 8 | +// |
| 9 | +// See https://madmachine.io for more information |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | + |
| 13 | +import SwiftIO |
| 14 | + |
| 15 | +/// A driver for the GT911 capacitive touch sensor, communicating via I2C. |
| 16 | +public final class GT911 { |
| 17 | + private let i2c: I2C |
| 18 | + private let address: UInt8 |
| 19 | + private var readBuffer = [UInt8](repeating: 0, count: 40) |
| 20 | + |
| 21 | + // MARK: - Public Types |
| 22 | + |
| 23 | + /// Enum representing the interrupt modes for the GT911 touch sensor. |
| 24 | + public enum InterruptMode: UInt8 { |
| 25 | + case rising = 0x00 |
| 26 | + case falling = 0x01 |
| 27 | + case lowLevel = 0x02 |
| 28 | + case highLevel = 0x03 |
| 29 | + } |
| 30 | + |
| 31 | + /// Initializes the GT911 touch sensor with the specified I2C instance and address. |
| 32 | + /// |
| 33 | + /// - Parameters: |
| 34 | + /// - i2c: The I2C instance for communication. |
| 35 | + /// - address: The I2C address of the GT911 sensor. Default value is `0xBA`. |
| 36 | + /// - Precondition: The I2C speed must be `.standard` (100kHz) or `.fast` (400kHz). |
| 37 | + public init(_ i2c: I2C, address: UInt8 = 0xBA) { |
| 38 | + let speed = i2c.getSpeed() |
| 39 | + guard speed == .standard || speed == .fast else { |
| 40 | + print(#function + ": GT911 only supports 100kHz (standard) and 400kHz (fast) I2C speed") |
| 41 | + fatalError() |
| 42 | + } |
| 43 | + |
| 44 | + self.i2c = i2c |
| 45 | + self.address = address >> 1 |
| 46 | + |
| 47 | + softReset() |
| 48 | + sleep(ms: 20) |
| 49 | + clearStatus() |
| 50 | + } |
| 51 | + |
| 52 | + // MARK: - Public Methods |
| 53 | + |
| 54 | + /// Reads the product ID of the GT911 touch sensor. |
| 55 | + /// |
| 56 | + /// - Returns: A string representing the product ID, or `nil` if reading fails. |
| 57 | + public func readProductID() -> String? { |
| 58 | + var str: String? |
| 59 | + |
| 60 | + do { |
| 61 | + try readRegister(.productID, into: &readBuffer, count: 4) |
| 62 | + readBuffer.withUnsafeBufferPointer { buffer in |
| 63 | + str = String(cString: buffer.baseAddress!) |
| 64 | + } |
| 65 | + } catch { |
| 66 | + return nil |
| 67 | + } |
| 68 | + |
| 69 | + return str |
| 70 | + } |
| 71 | + |
| 72 | + /// Resets the touch sensor by issuing a soft reset command. |
| 73 | + public func softReset() { |
| 74 | + let data: UInt8 = 0x04 |
| 75 | + |
| 76 | + try? writeRegister(.command, data) |
| 77 | + } |
| 78 | + |
| 79 | + /// Sets the number of touch points the sensor can detect. |
| 80 | + /// |
| 81 | + /// - Parameter number: The number of touch points (1 to 5). |
| 82 | + public func setTouchNumber(_ number: UInt8) { |
| 83 | + guard number >= 1, number <= 5 else { |
| 84 | + return |
| 85 | + } |
| 86 | + |
| 87 | + try? writeRegister(.touchNumber, number) |
| 88 | + } |
| 89 | + |
| 90 | + /// Reads the maximum X and Y output resolution supported by the touch sensor. |
| 91 | + /// |
| 92 | + /// - Returns: A tuple containing the X and Y maximum values. |
| 93 | + public func readOutputMax() -> (x: UInt16, y: UInt16) { |
| 94 | + var x: UInt16 = 0, y: UInt16 = 0 |
| 95 | + |
| 96 | + try? readRegister(.xOutputMax, into: &x) |
| 97 | + try? readRegister(.yOutputMax, into: &y) |
| 98 | + |
| 99 | + return (x, y) |
| 100 | + } |
| 101 | + |
| 102 | + /// Sets the maximum X and Y output resolution. |
| 103 | + /// |
| 104 | + /// - Parameters: |
| 105 | + /// - x: The maximum X value to set (optional). |
| 106 | + /// - y: The maximum Y value to set (optional). |
| 107 | + public func setOutputMax(x: UInt16? = nil, y: UInt16? = nil) { |
| 108 | + if let x { |
| 109 | + try? writeRegister(.xOutputMax, x) |
| 110 | + } |
| 111 | + if let y { |
| 112 | + try? writeRegister(.yOutputMax, y) |
| 113 | + } |
| 114 | + } |
| 115 | + |
| 116 | + /// Reverses the X and Y axes for the touch coordinates. |
| 117 | + public func reverseXY() { |
| 118 | + var oldValue: UInt8 = 0 |
| 119 | + try? readRegister(.moduleSwitch1, into: &oldValue) |
| 120 | + |
| 121 | + oldValue ^= 0x08 |
| 122 | + try? writeRegister(.moduleSwitch1, oldValue) |
| 123 | + } |
| 124 | + |
| 125 | + /// Sets the interrupt mode for the touch sensor. |
| 126 | + /// |
| 127 | + /// - Parameter mode: The interrupt mode to set (`InterruptMode` enum). |
| 128 | + public func setInterruptMode(_ mode: InterruptMode) { |
| 129 | + var oldValue: UInt8 = 0 |
| 130 | + try? readRegister(.moduleSwitch1, into: &oldValue) |
| 131 | + |
| 132 | + oldValue &= 0xFC |
| 133 | + oldValue |= mode.rawValue |
| 134 | + try? writeRegister(.moduleSwitch1, oldValue) |
| 135 | + } |
| 136 | + |
| 137 | + /// Reads the status register of the touch sensor. |
| 138 | + /// |
| 139 | + /// - Returns: The status register value. |
| 140 | + public func readStatus() -> UInt8 { |
| 141 | + var value: UInt8 = 0 |
| 142 | + try? readRegister(.status, into: &value) |
| 143 | + |
| 144 | + return value |
| 145 | + } |
| 146 | + |
| 147 | + /// Clears the status register of the touch sensor. |
| 148 | + public func clearStatus() { |
| 149 | + try? writeRegister(.status, UInt8(0)) |
| 150 | + } |
| 151 | + |
| 152 | + /// Reads the touch information and returns an array of touch points. |
| 153 | + /// |
| 154 | + /// - Returns: An array of `TouchInfo` structures containing touch point data. |
| 155 | + public func readTouchInfo() -> [TouchInfo] { |
| 156 | + var touchInfo = [TouchInfo]() |
| 157 | + |
| 158 | + let status = readStatus() |
| 159 | + |
| 160 | + if (status & 0x80) == 0 { |
| 161 | + return touchInfo |
| 162 | + } else if (status & 0x0F) == 0 { |
| 163 | + clearStatus() |
| 164 | + return touchInfo |
| 165 | + } |
| 166 | + |
| 167 | + let touchPointCount = Int(status & 0x0F) |
| 168 | + try! readRegister(.bufferStart, into: &readBuffer, count: touchPointCount * 8) |
| 169 | + |
| 170 | + for i in 0 ..< touchPointCount { |
| 171 | + touchInfo.append(getTouchPosition(at: i)) |
| 172 | + } |
| 173 | + |
| 174 | + clearStatus() |
| 175 | + |
| 176 | + return touchInfo |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +extension GT911 { |
| 181 | + private enum Register: UInt16 { |
| 182 | + case command = 0x8040 |
| 183 | + case xOutputMax = 0x8048 |
| 184 | + case yOutputMax = 0x804A |
| 185 | + case touchNumber = 0x804C |
| 186 | + case moduleSwitch1 = 0x804D |
| 187 | + case touchLevel = 0x8053 |
| 188 | + case leaveLevel = 0x8054 |
| 189 | + |
| 190 | + case productID = 0x8140 |
| 191 | + case firmwareVer = 0x8144 |
| 192 | + case status = 0x814E |
| 193 | + case bufferStart = 0x814F |
| 194 | + |
| 195 | + func getRawData() -> [UInt8] { |
| 196 | + let highByte = UInt8(rawValue >> 8) |
| 197 | + let lowByte = UInt8(rawValue & 0xFF) |
| 198 | + |
| 199 | + return [highByte, lowByte] |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + private func getTouchPosition(at index: Int) -> TouchInfo { |
| 204 | + var index = index * 8 |
| 205 | + |
| 206 | + let id = readBuffer[index] |
| 207 | + |
| 208 | + index += 1 |
| 209 | + let x = 320 - (UInt16(readBuffer[index]) | (UInt16(readBuffer[index + 1]) << 8)) |
| 210 | + |
| 211 | + index += 2 |
| 212 | + let y = UInt16(readBuffer[index]) | (UInt16(readBuffer[index + 1]) << 8) |
| 213 | + |
| 214 | + index += 2 |
| 215 | + let size = UInt16(readBuffer[index]) | (UInt16(readBuffer[index + 1]) << 8) |
| 216 | + |
| 217 | + return TouchInfo(id: id, x: x, y: y, size: size) |
| 218 | + } |
| 219 | + |
| 220 | + private func writeRegister(_ register: Register, _ value: UInt8) throws(Errno) { |
| 221 | + var data = register.getRawData() |
| 222 | + data.append(value) |
| 223 | + |
| 224 | + let result = i2c.write(data, to: address) |
| 225 | + if case let .failure(err) = result { |
| 226 | + throw err |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + private func writeRegister(_ register: Register, _ value: UInt16) throws(Errno) { |
| 231 | + var data = register.getRawData() |
| 232 | + |
| 233 | + let lowByte = UInt8(value & 0xFF) |
| 234 | + let highByte = UInt8(value >> 8) |
| 235 | + data.append(lowByte) |
| 236 | + data.append(highByte) |
| 237 | + |
| 238 | + let result = i2c.write(data, to: address) |
| 239 | + if case let .failure(err) = result { |
| 240 | + throw err |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + private func readRegister( |
| 245 | + _ register: Register, into byte: inout UInt8 |
| 246 | + ) throws(Errno) { |
| 247 | + var result = i2c.write(register.getRawData(), to: address) |
| 248 | + if case let .failure(err) = result { |
| 249 | + throw err |
| 250 | + } |
| 251 | + |
| 252 | + result = i2c.read(into: &byte, from: address) |
| 253 | + if case let .failure(err) = result { |
| 254 | + throw err |
| 255 | + } |
| 256 | + } |
| 257 | + |
| 258 | + private func readRegister( |
| 259 | + _ register: Register, into value: inout UInt16 |
| 260 | + ) throws(Errno) { |
| 261 | + for i in readBuffer.indices { |
| 262 | + readBuffer[i] = 0 |
| 263 | + } |
| 264 | + |
| 265 | + var result = i2c.write(register.getRawData(), to: address) |
| 266 | + if case let .failure(err) = result { |
| 267 | + throw err |
| 268 | + } |
| 269 | + |
| 270 | + result = i2c.read(into: &readBuffer, count: 2, from: address) |
| 271 | + if case let .failure(err) = result { |
| 272 | + throw err |
| 273 | + } |
| 274 | + |
| 275 | + value = UInt16(readBuffer[0]) | (UInt16(readBuffer[1]) << 8) |
| 276 | + } |
| 277 | + |
| 278 | + private func readRegister( |
| 279 | + _ register: Register, into buffer: inout [UInt8], count: Int |
| 280 | + ) throws(Errno) { |
| 281 | + for i in buffer.indices { |
| 282 | + buffer[i] = 0 |
| 283 | + } |
| 284 | + |
| 285 | + var result = i2c.write(register.getRawData(), to: address) |
| 286 | + if case let .failure(err) = result { |
| 287 | + throw err |
| 288 | + } |
| 289 | + |
| 290 | + result = i2c.read(into: &buffer, count: count, from: address) |
| 291 | + if case let .failure(err) = result { |
| 292 | + throw err |
| 293 | + } |
| 294 | + } |
| 295 | +} |
0 commit comments