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