-
Notifications
You must be signed in to change notification settings - Fork 39
/
hwio.go
511 lines (434 loc) · 13.3 KB
/
hwio.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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
/*
Package hwio implements a simple Arduino-like interface for controlling
hardware I/O, with configurable backends depending on the device.
*/
package hwio
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
type BitShiftOrder byte
const (
LSBFIRST BitShiftOrder = iota
MSBFIRST
)
// Reference to driver we're using
var driver HardwareDriver
// Retrieved from the driver, this is the map of the hardware pins supported by
// the driver and their capabilities
var definedPins HardwarePinMap
// A private type for associating a pin's definition with the current IO mode
// and any other dynamic properties of the pin.
type assignedPin struct {
pin Pin // pin being assigned
module Module // module that has assigned this pin
// pinIOMode PinIOMode // mode that was assigned to this pin
}
// A map of pin numbers to the assigned dynamic properties of the pin. This is
// set by PinMode when errorChecking is on, and can be used by other functions
// to determine if the request is valid given the assigned properties of the pin.
var assignedPins map[Pin]*assignedPin
// If set to true, functions should test that their constraints are met.
// e.g. test that the pin is capable of doing what is asked. This can be set
// with SetErrorChecking(). Setting to false bypasses checks for performance.
// By default turned on, which is a better default for beginners.
var errorChecking bool = true
// init() attempts to determine from the environment what the driver is. The
// intent is that the consumer of the library would not generally have to worry
// about it, it would just work. If it cannot determine the driver, it doesn't
// set the driver to anything.
func init() {
assignedPins = make(map[Pin]*assignedPin)
determineDriver()
}
func fileExists(name string) bool {
_, err := os.Stat(name)
if err != nil {
return false
}
return true
}
// Work out the driver from environment if we can. If we have any problems,
// don't generate an error, just return with the driver not set.
func determineDriver() {
drivers := [...]HardwareDriver{NewBeagleboneBlackDTDriver(), NewRaspPiDTDriver(), NewOdroidC1Driver()}
for _, d := range drivers {
if d.MatchesHardwareConfig() {
SetDriver(d)
return
}
}
fmt.Printf("Unable to select a suitable driver for this board.\n")
}
// Check if the driver is assigned. If not, return an error to indicate that,
// otherwise return no error.
func assertDriver() error {
if driver == nil {
return errors.New("hwio has no configured driver")
}
return nil
}
// Set the driver. Also calls Init on the driver, and loads the capabilities
// of the device.
func SetDriver(d HardwareDriver) {
driver = d
e := driver.Init()
if e != nil {
fmt.Printf("Could not initialise driver: %s", e)
}
definedPins = driver.PinMap()
}
// Retrieve the current hardware driver.
func GetDriver() HardwareDriver {
return driver
}
// Returns a map of the hardware pins. This will only work once the driver is
// set.
func GetDefinedPins() HardwarePinMap {
return definedPins
}
// Ensure that any resources external to the program that have been allocated are tidied up.
func CloseAll() {
if driver == nil {
return
}
driver.Close()
}
// Returns a Pin given a canonical name for the pin.
// e.g. to get the pin number of P8.13 on a beaglebone,
// pin := hwio.GetPin("P8.13")
// Order of search is:
// - search hwRefs in the pin map in order.
// This function should not generally be relied on for performance. For max speed, call this
// for each pin you use once on init, and use the returned Pin values thereafter.
// Search is case sensitive at the moment
// @todo GetPin: consider making it case-insensitive on name
// @todo GetPin: consider allowing an int or int as string to identify logical pin directly
func GetPin(pinName string) (Pin, error) {
pl := strings.ToLower(pinName)
for pin, pinDef := range definedPins {
for _, name := range pinDef.names {
if strings.ToLower(name) == pl {
return pin, nil
}
}
}
return Pin(0), fmt.Errorf("Could not find a pin called %s", pinName)
}
// Shortcut for calling GetPin and then PinMode.
func GetPinWithMode(cname string, mode PinIOMode) (pin Pin, e error) {
p, e := GetPin(cname)
if e != nil {
return
}
e = PinMode(p, mode)
return p, e
}
// Set error checking. This should be called before pin assignments.
func SetErrorChecking(check bool) {
errorChecking = check
}
// Helper function to get GPIO module
func GetGPIOModule() (GPIOModule, error) {
m, e := GetModule("gpio")
if e != nil {
return nil, e
}
if m == nil {
return nil, errors.New("Driver does not support GPIO")
}
return m.(GPIOModule), nil
}
// Given an internal pin number, return the canonical name for the pin, as defined by the driver. If the pin
// is not to the driver, return "".
func PinName(pin Pin) string {
p := definedPins[pin]
if p == nil {
return ""
}
return p.names[0]
}
// Set the mode of a pin. Analogous to Arduino pin mode.
func PinMode(pin Pin, mode PinIOMode) error {
gpio, e := GetGPIOModule()
if e != nil {
return e
}
return gpio.PinMode(pin, mode)
}
// Close a specific pin that has been assigned as GPIO by PinMode
func ClosePin(pin Pin) error {
gpio, e := GetGPIOModule()
if e != nil {
return e
}
return gpio.ClosePin(pin)
}
// Assign a pin to a module. This is typically called by modules when they allocate pins. If the pin is already assigned,
// an error is generated. ethod is public in case it is needed to hack around default driver settings.
func AssignPin(pin Pin, module Module) error {
if a := assignedPins[pin]; a != nil {
return fmt.Errorf("Pin %d is already assigned to module %s", pin, a.module.GetName())
}
assignedPins[pin] = &assignedPin{pin, module}
return nil
}
// Assign a set of pins. Method is public in case it is needed to hack around default driver settings.
func AssignPins(pins PinList, module Module) error {
for _, pin := range pins {
e := AssignPin(pin, module)
if e != nil {
return e
}
}
return nil
}
// Unassign a pin. Method is public in case it is needed to hack around default driver settings.
func UnassignPin(pin Pin) error {
delete(assignedPins, pin)
return nil
}
// Unassign a set of pins. Method is public in case it is needed to hack around default driver settings.
func UnassignPins(pins PinList) (er error) {
er = nil
for _, pin := range pins {
e := UnassignPin(pin)
if e != nil {
er = e
}
}
return
}
// Write a value to a digital pin
func DigitalWrite(pin Pin, value int) (e error) {
gpio, e := GetGPIOModule()
if e != nil {
return e
}
return gpio.DigitalWrite(pin, value)
}
// Read a value from a digital pin
func DigitalRead(pin Pin) (result int, e error) {
// @todo consider memoizing
gpio, e := GetGPIOModule()
if e != nil {
return 0, e
}
return gpio.DigitalRead(pin)
}
// given a logic level of HIGH or LOW, return the opposite. Invalid values returned as LOW.
func Negate(logicLevel int) int {
if logicLevel == LOW {
return HIGH
}
return LOW
}
// Helper function to pulse a pin, which must have been set as GPIO.
// 'active' is LOW or HIGH. Pulse sets pin to inactive, then active for
// 'durationMicroseconds' and the back to inactive.
func Pulse(pin Pin, active int, durationMicroseconds int) error {
// set to inactive state, in case it wasn't already
e := DigitalWrite(pin, Negate(active))
if e != nil {
return e
}
// set to active state
e = DigitalWrite(pin, active)
if e != nil {
return e
}
DelayMicroseconds(durationMicroseconds)
// finally reset to inactive state
return DigitalWrite(pin, Negate(active))
}
// Helper function to get GPIO module
func GetAnalogModule() (AnalogModule, error) {
m, e := GetModule("analog")
if e != nil {
return nil, e
}
if m == nil {
return nil, errors.New("Driver does not support analog")
}
return m.(AnalogModule), nil
}
// Read an analog value from a pin. The range of values is hardware driver dependent.
func AnalogRead(pin Pin) (int, error) {
analog, e := GetAnalogModule()
if e != nil {
return 0, e
}
return analog.AnalogRead(pin)
}
// Helper to turn an on-board LED on or off. Uses LED module
func Led(name string, on bool) error {
m, e := GetModule("leds")
if e != nil {
return e
}
leds := m.(LEDModule)
led, e := leds.GetLED(name)
if e != nil {
return e
}
e = led.SetTrigger("none")
if e != nil {
return e
}
return led.SetOn(on)
}
// Delay execution by the specified number of milliseconds. This is a helper
// function for similarity with Arduino. It is implemented using standard go
// time package.
func Delay(duration int) {
time.Sleep(time.Duration(duration) * time.Millisecond)
}
// Delay execution by the specified number of microseconds. This is a helper
// function for similarity with Arduino. It is implemented using standard go
// time package
func DelayMicroseconds(duration int) {
time.Sleep(time.Duration(duration) * time.Microsecond)
}
// @todo DebugPinMap: sort
func DebugPinMap() {
fmt.Println("HardwarePinMap:")
for key, val := range definedPins {
fmt.Printf("Pin %d: %s\n", key, val.String())
}
fmt.Printf("\n")
}
// The approximate mapping of Arduino shiftOut, this shifts a byte out on the
// data pin, pulsing the clock pin high and then low.
func ShiftOut(dataPin Pin, clockPin Pin, value uint, order BitShiftOrder) error {
return ShiftOutSize(dataPin, clockPin, value, order, 8)
}
// More generic version of ShiftOut which shifts out n of data from value. The
// value shifted out is always the lowest n bits of the value, but 'order'
// determines whether the msb or lsb from that value are shifted first
func ShiftOutSize(dataPin Pin, clockPin Pin, value uint, order BitShiftOrder, n uint) error {
bit := uint(0)
v := value
mask := uint(1) << (n - 1)
for i := uint(0); i < n; i++ {
// get the next bit
if order == LSBFIRST {
bit = v & 1
v = v >> 1
} else {
bit = v & mask
if bit != 0 {
bit = 1
}
v = v << 1
}
// write to data pin
e := DigitalWrite(dataPin, int(bit))
if e != nil {
return e
}
// pulse clock high and then low
e = DigitalWrite(clockPin, HIGH)
if e != nil {
return e
}
DigitalWrite(clockPin, LOW)
}
return nil
}
// Given an integer and a list of GPIO pins (that must have been set up as outputs), write the integer across
// the pins. The number of bits is determined by the length of the pins. The most-significant output pin is first.
// Bits are written MSB first.
// Maximum number of bits that can be shifted is 32.
// Note that the bits are not written out instantaneously, although very quickly. If you need instantaneous changing of
// all pins, you need to consider an output buffer.
func WriteUIntToPins(value uint32, pins []Pin) error {
if len(pins) > 31 {
return errors.New("WriteUIntToPins only supports up to 32 bits")
}
bit := uint32(0)
v := value
mask := uint32(1) << uint32((len(pins) - 1))
for i := uint32(0); i < uint32(len(pins)); i++ {
bit = v & mask
if bit != 0 {
bit = 1
}
v = v << 1
// write to data pin
// fmt.Printf("Writing %s to pin %s\n", bit, pins[i])
e := DigitalWrite(pins[i], int(bit))
if e != nil {
return e
}
}
return nil
}
// Write a string to a file and close it again.
func WriteStringToFile(filename string, value string) error {
// fmt.Printf("writing %s to file %s\n", value, filename)
f, e := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC, 0666)
if e != nil {
return e
}
defer f.Close()
_, e = f.WriteString(value)
return e
}
// Given a glob pattern, return the full path of the first matching file
func findFirstMatchingFile(glob string) (string, error) {
matches, e := filepath.Glob(glob)
if e != nil {
return "", e
}
if len(matches) >= 1 {
return matches[0], nil
}
return "", nil
}
func Map(value int, fromLow int, fromHigh int, toLow int, toHigh int) int {
return (value-fromLow)*(toHigh-toLow)/(fromHigh-fromLow) + toLow
}
// Given a high and low byte, combine to form a single 16-bit value
func UInt16FromUInt8(highByte byte, lowByte byte) uint16 {
return uint16(uint16(highByte)<<8) | uint16(lowByte)
}
func ReverseBytes16(value uint16) uint16 {
// @todo implement ReverseBytes16()
return 0
}
func ReverseBytes32(value uint32) uint32 {
// @todo implement ReverseBytes32()
return 0
}
// Get a module by name. If driver is not set, it will return an error. If the driver does not support that module,
//
func GetModule(name string) (Module, error) {
driver := GetDriver()
if driver == nil {
return nil, errors.New("GetModule: Driver is not set")
}
modules := driver.GetModules()
return modules[name], nil
}
// This is the interface that hardware drivers implement. Generally all drivers are created
// but not initialised. If MatchesHardwareConfig() is true and the driver is selected, Init()
// will be called.
type HardwareDriver interface {
// Each driver is responsible for evaluating whether it applies to the current hardware
// configuration or not. If this function returns false, the driver will not be used and Init
// will not be called. If this function returns true, the driver may be called, in which case
// Init will then be called
MatchesHardwareConfig() bool
// Initialise the driver.
Init() (e error)
// Return a module by name, or nil if undefined. The module names can be different between types of boards.
GetModules() map[string]Module
// Return the pin map for the driver, listing all supported pins and their capabilities
PinMap() (pinMap HardwarePinMap)
// Close the driver before destruction
Close()
}