tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/mcp23017/device.go (about) 1 // Package mcp23017 implements a driver for the MCP23017 2 // I2C port expander chip. See https://www.microchip.com/wwwproducts/en/MCP23017 3 // for details of the interface. 4 // 5 // It also provides a way of joining several such devices into one logical 6 // device (see the Devices type). 7 package mcp23017 8 9 import ( 10 "errors" 11 12 "tinygo.org/x/drivers" 13 "tinygo.org/x/drivers/internal/legacy" 14 ) 15 16 const ( 17 // hwAddressFixed holds the bits of the hardware address 18 // that are fixed by the chip. Bits 0-3 (those in hwAddressMask) 19 // are user-defined by the A0-A2 pins on the chip. 20 hwAddress = uint8(0b010_0000) 21 // hwAddressMask holds the bits that are significant in hwAddress. 22 hwAddressMask = uint8(0b111_1000) 23 ) 24 25 type register uint8 26 27 const ( 28 // The following registers all refer to port A (except 29 // rIOCON with is port-agnostic). 30 // ORing them with portB makes them refer to port B. 31 rIODIR = register(0x00) // I/O direction. 0=output; 1=input. 32 rIOPOL = register(0x02) // Invert input values. 0=normal; 1=inverted. 33 rGPINTEN = register(0x04) 34 rDEFVAL = register(0x06) 35 rINTCON = register(0x08) 36 rIOCON = register(0x0A) 37 rGPPU = register(0x0C) // Pull up; 0=no pull-up; 1=pull-up. 38 rINTF = register(0x0E) 39 rINTCAP = register(0x10) 40 rGPIO = register(0x12) // GPIO pin values. 41 rOLAT = register(0x14) 42 registerCount = 0x16 43 44 portB = register(0x1) 45 ) 46 47 // PinCount is the number of GPIO pins available on the chip. 48 const PinCount = 16 49 50 // PinMode represents a possible I/O mode for a pin. 51 // The zero value represents the default value 52 // after the chip is reset (input). 53 type PinMode uint8 54 55 const ( 56 // Input configures a pin as an input. 57 Input = PinMode(0) 58 // Output configures a pin as an output. 59 Output = PinMode(1) 60 61 // Direction is the bit mask of the pin mode representing 62 // the I/O direction. 63 Direction = PinMode(1) 64 65 // Pullup can be bitwise-or'd with Input 66 // to cause the pull-up resistor on the pin to 67 // be enabled. 68 Pullup = PinMode(2) 69 70 // Invert can be bitwise-or'd with Input to 71 // cause the pin value to reflect the inverted 72 // value on the pin. 73 Invert = PinMode(4) 74 ) 75 76 // ErrInvalidHWAddress is returned when the hardware address 77 // of the device is not valid (only some bits can be set by the 78 // address pins). 79 var ErrInvalidHWAddress = errors.New("invalid hardware address") 80 81 // New returns a new MCP23017 device at the given I2C address 82 // on the given bus. 83 // It returns ErrInvalidHWAddress if the address isn't possible for the device. 84 // 85 // By default all pins are configured as inputs. 86 func NewI2C(bus drivers.I2C, address uint8) (*Device, error) { 87 if address&hwAddressMask != hwAddress { 88 return nil, ErrInvalidHWAddress 89 } 90 d := &Device{ 91 bus: bus, 92 addr: address, 93 } 94 pins, err := d.GetPins() 95 if err != nil { 96 return nil, errors.New("cannot initialize mcp23017 device at " + hex(address) + ": " + err.Error()) 97 } 98 d.pins = pins 99 return d, nil 100 } 101 102 func hex(x uint8) string { 103 digits := "0123456789abcdef" 104 return "0x" + digits[x>>4:x>>4+1] + digits[x&0xf:x&0xf+1] 105 } 106 107 // Device represents an MCP23017 device. 108 type Device struct { 109 // TODO would it be good to have a mutex here so that independent goroutines 110 // could change pins without needing to do the locking themselves? 111 112 // bus holds the reference the I2C bus that the device lives on. 113 // It's an interface so that we can write tests for it. 114 bus drivers.I2C 115 addr uint8 116 // pins caches the most recent pin values that have been set. 117 // This enables us to change individual pin values without 118 // doing a read followed by a write. 119 pins Pins 120 } 121 122 // GetPins reads all 16 pins from ports A and B. 123 func (d *Device) GetPins() (Pins, error) { 124 return d.readRegisterAB(rGPIO) 125 } 126 127 // SetPins sets all the pins for which mask is high 128 // to their respective values in pins. 129 // 130 // That is, it does the equivalent of: 131 // 132 // for i := 0; i < PinCount; i++ { 133 // if mask.Get(i) { 134 // d.Pin(i).Set(pins.Get(i)) 135 // } 136 // } 137 func (d *Device) SetPins(pins, mask Pins) error { 138 if mask == 0 { 139 return nil 140 } 141 newPins := (d.pins &^ mask) | (pins & mask) 142 if newPins == d.pins { 143 return nil 144 } 145 err := d.writeRegisterAB(rGPIO, newPins) 146 if err != nil { 147 return err 148 } 149 d.pins = newPins 150 return nil 151 } 152 153 // TogglePins inverts the values on all pins for 154 // which mask is high. 155 func (d *Device) TogglePins(mask Pins) error { 156 if mask == 0 { 157 return nil 158 } 159 return d.SetPins(^d.pins, mask) 160 } 161 162 // Pin returns a Pin representing the given pin number (from 0 to 15). 163 // Pin numbers from 0 to 7 represent port A pins 0 to 7. 164 // Pin numbers from 8 to 15 represent port B pins 0 to 7. 165 func (d *Device) Pin(pin int) Pin { 166 if pin < 0 || pin >= PinCount { 167 panic("pin out of range") 168 } 169 var mask Pins 170 mask.High(pin) 171 return Pin{ 172 dev: d, 173 mask: mask, 174 pin: uint8(pin), 175 } 176 } 177 178 // SetAllModes sets the mode of all the pins in a single operation. 179 // If len(modes) is less than PinCount, all remaining pins 180 // will be set fo modes[len(modes)-1], or PinMode(0) if 181 // modes is empty. 182 // 183 // If len(modes) is greater than PinCount, the excess entries 184 // will be ignored. 185 func (d *Device) SetModes(modes []PinMode) error { 186 defaultMode := PinMode(0) 187 if len(modes) > 0 { 188 defaultMode = modes[len(modes)-1] 189 } 190 var dir, pullup, invert Pins 191 for i := 0; i < PinCount; i++ { 192 mode := defaultMode 193 if i < len(modes) { 194 mode = modes[i] 195 } 196 if mode&Direction == Input { 197 dir.High(i) 198 } 199 if mode&Pullup != 0 { 200 pullup.High(i) 201 } 202 if mode&Invert != 0 { 203 invert.High(i) 204 } 205 } 206 if err := d.writeRegisterAB(rIODIR, dir); err != nil { 207 return err 208 } 209 if err := d.writeRegisterAB(rGPPU, pullup); err != nil { 210 return err 211 } 212 if err := d.writeRegisterAB(rIOPOL, invert); err != nil { 213 return err 214 } 215 return nil 216 } 217 218 // GetModes reads the modes of all the pins into modes. 219 // It's OK if len(modes) is not PinCount - excess entries 220 // will be left unset. 221 func (d *Device) GetModes(modes []PinMode) error { 222 dir, err := d.readRegisterAB(rIODIR) 223 if err != nil { 224 return err 225 } 226 pullup, err := d.readRegisterAB(rGPPU) 227 if err != nil { 228 return err 229 } 230 invert, err := d.readRegisterAB(rIOPOL) 231 if err != nil { 232 return err 233 } 234 if len(modes) > PinCount { 235 modes = modes[:PinCount] 236 } 237 for i := range modes { 238 mode := Output 239 if dir.Get(i) { 240 mode = Input 241 } 242 if pullup.Get(i) { 243 mode |= Pullup 244 } 245 if invert.Get(i) { 246 mode |= Invert 247 } 248 modes[i] = mode 249 } 250 return nil 251 } 252 253 func (d *Device) writeRegisterAB(r register, val Pins) error { 254 // We rely on the auto-incrementing sequential write 255 // and the fact that registers alternate between A and B 256 // to write both ports in a single operation. 257 buf := [2]byte{uint8(val), uint8(val >> 8)} 258 return legacy.WriteRegister(d.bus, d.addr, uint8(r&^portB), buf[:]) 259 } 260 261 func (d *Device) readRegisterAB(r register) (Pins, error) { 262 // We rely on the auto-incrementing sequential write 263 // and the fact that registers alternate between A and B 264 // to read both ports in a single operation. 265 var buf [2]byte 266 if err := legacy.ReadRegister(d.bus, d.addr, uint8(r), buf[:]); err != nil { 267 return Pins(0), err 268 } 269 return Pins(buf[0]) | (Pins(buf[1]) << 8), nil 270 } 271 272 // Pin represents a single GPIO pin on the device. 273 type Pin struct { 274 // mask holds the mask of the pin. 275 mask Pins 276 // pin holds the actual pin number. 277 pin uint8 278 dev *Device 279 } 280 281 // Set sets the pin to the given value. 282 func (p Pin) Set(value bool) error { 283 // TODO currently this always writes both registers when 284 // technically it only needs to write one. We could potentially 285 // optimize that. 286 if value { 287 return p.dev.SetPins(^Pins(0), p.mask) 288 } else { 289 return p.dev.SetPins(0, p.mask) 290 } 291 } 292 293 // High is short for p.Set(true). 294 func (p Pin) High() error { 295 return p.Set(true) 296 } 297 298 // High is short for p.Set(false). 299 func (p Pin) Low() error { 300 return p.Set(false) 301 } 302 303 // Toggle inverts the value output on the pin. 304 func (p Pin) Toggle() error { 305 return p.dev.TogglePins(p.mask) 306 } 307 308 // Get returns the current value of the given pin. 309 func (p Pin) Get() (bool, error) { 310 // TODO this reads 2 registers when we could read just one. 311 pins, err := p.dev.GetPins() 312 if err != nil { 313 return false, err 314 } 315 return pins&p.mask != 0, nil 316 } 317 318 // SetMode configures the pin to the given mode. 319 func (p Pin) SetMode(mode PinMode) error { 320 // We could use a more efficient single-register 321 // read/write pattern but setting pin modes isn't an 322 // operation that's likely to need to be efficient, so 323 // use less code and use Get/SetModes directly. 324 modes := make([]PinMode, PinCount) 325 if err := p.dev.GetModes(modes); err != nil { 326 return err 327 } 328 modes[p.pin] = mode 329 return p.dev.SetModes(modes) 330 } 331 332 // GetMode returns the mode of the pin. 333 func (p Pin) GetMode() (PinMode, error) { 334 modes := make([]PinMode, PinCount) 335 if err := p.dev.GetModes(modes); err != nil { 336 return 0, err 337 } 338 return modes[p.pin], nil 339 } 340 341 // Pins represents a bitmask of pin values. 342 // Port A values are in bits 0-8 (numbered from least significant bit) 343 // Port B values are in bits 9-15. 344 type Pins uint16 345 346 // Set sets the value for the given pin. 347 func (p *Pins) Set(pin int, value bool) { 348 if value { 349 p.High(pin) 350 } else { 351 p.Low(pin) 352 } 353 } 354 355 // Get returns the value for the given pin. 356 func (p Pins) Get(pin int) bool { 357 return (p & pinMask(pin)) != 0 358 } 359 360 // High is short for p.Set(pin, true). 361 func (p *Pins) High(pin int) { 362 *p |= pinMask(pin) 363 } 364 365 // Low is short for p.Set(pin, false). 366 func (p *Pins) Low(pin int) { 367 *p &^= pinMask(pin) 368 } 369 370 // Toggle inverts the value of the given pin. 371 func (p *Pins) Toggle(pin int) { 372 *p ^= pinMask(pin) 373 } 374 375 func pinMask(pin int) Pins { 376 return 1 << pin 377 }