-
Notifications
You must be signed in to change notification settings - Fork 40
/
module_dtgpio.go
263 lines (222 loc) · 6.94 KB
/
module_dtgpio.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
// A GPIO module that uses Linux 3.7+ file system drivers with device tree. This module is intended to work for any 3.7+ configuration,
// including BeagleBone Black and Raspberry Pi's with new kernels. The actual pin configuration is passed through on SetOptions.
package hwio
import (
"errors"
"fmt"
"os"
"strconv"
)
type DTGPIOModule struct {
name string
definedPins DTGPIOModulePinDefMap
openPins map[Pin]*DTGPIOModuleOpenPin
}
// Represents the definition of a GPIO pin, which should contain all the info required to open, close, read and write the pin
// using FS drivers.
type DTGPIOModulePinDef struct {
pin Pin
gpioLogical int
}
// A map of GPIO pin definitions.
type DTGPIOModulePinDefMap map[Pin]*DTGPIOModulePinDef
type DTGPIOModuleOpenPin struct {
pin Pin
gpioLogical int
gpioBaseName string
valueFile *os.File
}
func NewDTGPIOModule(name string) (result *DTGPIOModule) {
result = &DTGPIOModule{name: name}
result.openPins = make(map[Pin]*DTGPIOModuleOpenPin)
return result
}
// Set options of the module. Parameters we look for include:
// - "pins" - an object of type DTGPIOModulePinDefMap
func (module *DTGPIOModule) SetOptions(options map[string]interface{}) error {
v := options["pins"]
if v == nil {
return fmt.Errorf("Module '%s' SetOptions() did not get 'pins' values", module.GetName())
}
module.definedPins = v.(DTGPIOModulePinDefMap)
return nil
}
// enable GPIO module. It doesn't allocate any pins immediately.
func (module *DTGPIOModule) Enable() error {
return nil
}
// disables module and release any pins assigned.
func (module *DTGPIOModule) Disable() error {
for _, openPin := range module.openPins {
openPin.gpioUnexport()
}
return nil
}
func (module *DTGPIOModule) GetName() string {
return module.name
}
func (module *DTGPIOModule) PinMode(pin Pin, mode PinIOMode) error {
if module.definedPins[pin] == nil {
return fmt.Errorf("Pin %d is not known as a GPIO pin", pin)
}
// attempt to assign this pin for this module.
e := AssignPin(pin, module)
if e != nil {
return e
}
// Create an open pin object
openPin, e := module.makeOpenGPIOPin(pin)
if e != nil {
return e
}
e = openPin.gpioExport()
if e != nil {
return e
}
if mode == OUTPUT {
fmt.Printf("about to set pin %d to output\n", pin)
e = openPin.gpioDirection("out")
if e != nil {
return e
}
} else {
e = openPin.gpioDirection("in")
// @todo implement pull up and pull down support
// pull := BB_CONF_PULL_DISABLE
// // note: pull up/down modes assume that CONF_PULLDOWN resets the pull disable bit
// if mode == INPUT_PULLUP {
// pull = BB_CONF_PULLUP
// } else if mode == INPUT_PULLDOWN {
// pull = BB_CONF_PULLDOWN
// }
if e != nil {
return e
}
}
return nil
}
func (module *DTGPIOModule) DigitalWrite(pin Pin, value int) (e error) {
openPin := module.openPins[pin]
if openPin == nil {
return errors.New("Pin is being written but has not been opened. Have you called PinMode?")
}
// if a.pinIOMode != OUTPUT {
// return errors.New(fmt.Sprintf("DigitalWrite: pin %d mode is not set for output", pin))
// }
openPin.gpioSetValue(value)
return nil
}
func (module *DTGPIOModule) DigitalRead(pin Pin) (value int, e error) {
openPin := module.openPins[pin]
if openPin == nil {
return 0, errors.New("Pin is being read from but has not been opened. Have you called PinMode?")
}
// if a.pinIOMode != INPUT && a.pinIOMode != INPUT_PULLUP && a.pinIOMode != INPUT_PULLDOWN {
// e = errors.New(fmt.Sprintf("DigitalRead: pin %d mode not set for input", pin))
// return
// }
return openPin.gpioGetValue()
}
func (module *DTGPIOModule) ClosePin(pin Pin) error {
openPin := module.openPins[pin]
if openPin == nil {
return errors.New("Pin is being closed but has not been opened. Have you called PinMode?")
}
e := openPin.gpioUnexport()
if e != nil {
return e
}
return UnassignPin(pin)
}
// create an openPin object and put it in the map.
func (module *DTGPIOModule) makeOpenGPIOPin(pin Pin) (*DTGPIOModuleOpenPin, error) {
p := module.definedPins[pin]
if p == nil {
return nil, fmt.Errorf("Pin %d is not known to GPIO module", pin)
}
result := &DTGPIOModuleOpenPin{pin: pin, gpioLogical: p.gpioLogical}
module.openPins[pin] = result
return result, nil
}
// For GPIO:
// - write GPIO pin to /sys/class/gpio/export. This is the port number plus pin on that port. Ports 0, 32, 64, 96. In our case, gpioLogical
// contains this value.
// - write direction to /sys/class/gpio/gpio{nn}/direction. Values are 'in' and 'out'
// Needs to be called to allocate the GPIO pin
func (op *DTGPIOModuleOpenPin) gpioExport() error {
bn := "/sys/class/gpio/gpio" + strconv.Itoa(op.gpioLogical)
if !fileExists(bn) {
s := strconv.FormatInt(int64(op.gpioLogical), 10)
e := WriteStringToFile("/sys/class/gpio/export", s)
if e != nil {
return e
}
}
// calculate the base name for the gpio pin
op.gpioBaseName = bn
return nil
}
// Needs to be called to allocate the GPIO pin
func (op *DTGPIOModuleOpenPin) gpioUnexport() error {
s := strconv.FormatInt(int64(op.gpioLogical), 10)
e := WriteStringToFile("/sys/class/gpio/unexport", s)
if e != nil {
return e
}
return nil
}
// Once exported, the direction of a GPIO can be set
func (op *DTGPIOModuleOpenPin) gpioDirection(dir string) error {
if dir != "in" && dir != "out" {
return errors.New("direction must be in or out")
}
f := op.gpioBaseName + "/direction"
e := WriteStringToFile(f, dir)
mode := os.O_WRONLY | os.O_TRUNC
if dir == "in" {
mode = os.O_RDONLY
}
// open the value file with the correct mode. Put that file in 'op'. Note that we keep this file open
// continuously for performance.
// Preliminary tests on 200,000 DigitalWrites indicate an order of magnitude improvement when we don't have
// to re-open the file each time. Re-seeking and writing a new value suffices.
op.valueFile, e = os.OpenFile(op.gpioBaseName+"/value", mode, 0666)
return e
}
// Get the value. Will return HIGH or LOW
func (op *DTGPIOModuleOpenPin) gpioGetValue() (int, error) {
var b []byte
b = make([]byte, 1)
n, e := op.valueFile.ReadAt(b, 0)
value := 0
if n > 0 {
if b[0] == '1' {
value = HIGH
} else {
value = LOW
}
}
return value, e
}
// Set the value, Expects HIGH or LOW
func (op *DTGPIOModuleOpenPin) gpioSetValue(value int) error {
if op.valueFile == nil {
fmt.Printf("value file no set\n")
return errors.New("value file is not defined")
}
// Seek the start of the value file before writing. This is sufficient for the driver to accept a new value.
_, e := op.valueFile.Seek(0, 0)
if e != nil {
return e
}
// Write a 1 or 0.
// @todo investigate if we'd get better performance if we have precalculated []byte values with 0 and 1, and
// use write directly instead of WriteString. Probably only marginal.
// @todo also check out http://hackaday.com/2013/12/07/speeding-up-beaglebone-black-gpio-a-thousand-times/
if value == 0 {
op.valueFile.WriteString("0")
} else {
op.valueFile.WriteString("1")
}
return nil
}