OLD | NEW |
1 // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Fletch project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE.md file. | 3 // BSD-style license that can be found in the LICENSE.md file. |
4 | 4 |
5 /// GPIO support. | 5 /// GPIO support. |
6 /// | 6 /// |
7 /// Provides access to controlling GPIO pins. | 7 /// Provides access to controlling GPIO pins. |
8 /// | 8 /// |
9 /// The library provide two ways of accessing the GPIO pins: direct access or | 9 /// The library provide two ways of accessing the GPIO pins: direct access or |
10 /// access through a Sysfs driver. | 10 /// access through a Sysfs driver. |
11 /// | 11 /// |
12 /// When direct access is used, the physical memory addresses, where the | 12 /// When direct access is used, the physical memory addresses, where the |
13 /// SoC registers for the GPIO pins are mapped, are accessed directly. This | 13 /// SoC registers for the GPIO pins are mapped, are accessed directly. This |
14 /// always require root access. | 14 /// always require root access. |
15 /// | 15 /// |
16 /// When the Sysfs driver is used the filesystem under `/sys/class/gpio` is | 16 /// When the Sysfs driver is used the filesystem under `/sys/class/gpio` is |
17 /// used; root access is also required by default. However this can be changed | 17 /// used; root access is also required by default. However this can be changed |
18 /// through udev rules, e.g. by adding a file to ` /etc/udev/rules.d`. | 18 /// through udev rules, e.g. by adding a file to ` /etc/udev/rules.d`. |
19 /// In addition the Sysfs driver supports state change notifications. | 19 /// In addition the Sysfs driver supports state change notifications. |
20 /// | 20 /// |
21 /// Currently this has only been tested with a Raspberry Pi 2. | 21 /// Currently this has only been tested with a Raspberry Pi 2. |
22 library gpio; | 22 library gpio; |
23 | 23 |
24 import 'dart:fletch.ffi'; | 24 import 'dart:fletch.ffi'; |
25 import 'dart:typed_data'; | 25 import 'dart:typed_data'; |
26 | 26 |
27 import 'package:file/file.dart'; | 27 import 'package:file/file.dart'; |
28 | 28 |
29 // Foreign functions used. | 29 // Foreign functions used. |
30 final ForeignFunction _open = ForeignLibrary.main.lookup('open'); | |
31 final ForeignFunction _lseek = ForeignLibrary.main.lookup('lseek'); | 30 final ForeignFunction _lseek = ForeignLibrary.main.lookup('lseek'); |
32 final ForeignFunction _mmap = ForeignLibrary.main.lookup('mmap'); | |
33 final ForeignFunction _poll = ForeignLibrary.main.lookup('poll'); | 31 final ForeignFunction _poll = ForeignLibrary.main.lookup('poll'); |
34 | 32 |
35 /// GPIO modes. | 33 /// GPIO modes. |
36 enum Mode { | 34 enum Mode { |
37 /// GPIO pin input mode. | 35 /// GPIO pin input mode. |
38 input, | 36 input, |
39 /// GPIO pin output mode. | 37 /// GPIO pin output mode. |
40 output, | 38 output, |
41 /// GPIO has other functions than `input` and `output`. Most GPIO pins have | 39 /// GPIO has other functions than `input` and `output`. Most GPIO pins have |
42 /// several special functions, so when receiving the mode this value can be | 40 /// several special functions, so when receiving the mode this value can be |
43 /// returned. This value cannot be used when setting the mode. | 41 /// returned. This value cannot be used when setting the mode. |
44 other | 42 other |
45 } | 43 } |
46 | 44 |
47 /// Base GPIO interface supported by all GPIO implementations. | 45 /// Base GPIO interface supported by all GPIO implementations. |
48 abstract class GPIO { | 46 abstract class GPIO { |
| 47 /// The default number of pins for GPIO is 50. |
| 48 static const int defaultPins = 50; |
| 49 |
| 50 /// Number of pins exposed by this GPIO. |
| 51 int get pins; |
| 52 |
49 /// Set the mode of [pin] to [mode]. | 53 /// Set the mode of [pin] to [mode]. |
50 void setMode(int pin, Mode mode); | 54 void setMode(int pin, Mode mode); |
51 | 55 |
52 /// Get the current mode of [pin]. | 56 /// Get the current mode of [pin]. |
53 Mode getMode(int pin); | 57 Mode getMode(int pin); |
54 | 58 |
55 /// Set the value of the [pin] to [value]. The boolean value | 59 /// Set the value of the [pin] to [value]. The boolean value |
56 /// [true] represents high (1) and the boolean value [false] | 60 /// [true] represents high (1) and the boolean value [false] |
57 /// represents low (0). | 61 /// represents low (0). |
58 void setPin(int pin, bool value); | 62 void setPin(int pin, bool value); |
59 | 63 |
60 /// Get the value of the [pin]. The boolean value | 64 /// Get the value of the [pin]. The boolean value |
61 /// [true] represents high (1) and the boolean value [false] | 65 /// [true] represents high (1) and the boolean value [false] |
62 /// represents low (0). | 66 /// represents low (0). |
63 bool getPin(int pin); | 67 bool getPin(int pin); |
64 } | 68 } |
65 | 69 |
66 /// Pull-up/down resistor state. | 70 // Internal base class. |
67 enum PullUpDown { | 71 abstract class GPIOBase implements GPIO { |
68 floating, | 72 // Number of GPIO pins. |
69 pullDown, | 73 final int _pins; |
70 pullUp, | |
71 } | |
72 | 74 |
73 // Internal base class. | 75 GPIOBase(this._pins); |
74 class _GPIOBase { | |
75 // Number of GPIO pins. | |
76 final int _maxPins; | |
77 | 76 |
78 _GPIOBase(this._maxPins); | 77 get pins => _pins; |
79 | 78 |
80 void _checkPinRange(int pin) { | 79 void checkPinRange(int pin) { |
81 if (pin < 0 || _maxPins <= pin) { | 80 if (pin < 0 || _pins <= pin) { |
82 throw new RangeError.index(pin, this, 'pin', null, _maxPins); | 81 throw new RangeError.index(pin, this, 'pin', null, _pins); |
83 } | 82 } |
84 } | 83 } |
85 } | 84 } |
86 | 85 |
87 /// Provide GPIO access on Raspberry Pi using direct memory access. | |
88 /// | |
89 /// The following code shows how to turn on GPIO pin 4: | |
90 /// | |
91 /// ``` | |
92 /// PiMemoryMappedGPIO gpio = new PiMemoryMappedGPIO(); | |
93 /// gpio.setMode(4, Mode.output); | |
94 /// gpio.setPin(4, true)); | |
95 /// ``` | |
96 /// | |
97 /// The following code shows how to read GPIO pin 17: | |
98 /// | |
99 /// ``` | |
100 /// PiMemoryMappedGPIO gpio = new PiMemoryMappedGPIO(); | |
101 /// gpio.setMode(17, Mode.input); | |
102 /// print(gpio.getPin(17)); | |
103 /// ``` | |
104 class PiMemoryMappedGPIO extends _GPIOBase implements GPIO { | |
105 // See datasheet: | |
106 // https://www.raspberrypi.org/wp-content/uploads/2012/02/BCM2835-ARM-Peripher
als.pdf | |
107 | |
108 // Raspberry Pi model 1 (A/A+/B) | |
109 // BCM2708 / BCM 2835 | |
110 | |
111 // Raspberry Pi model 2 | |
112 // BCM2709 / BCM 2836 | |
113 | |
114 // Peripherals base address. | |
115 static const int _baseAddressModel1 = 0x20000000; | |
116 static const int _baseAddressModel2 = 0x3F000000; | |
117 static const int _baseAddressGPIOOffset = 0x00200000; | |
118 | |
119 // Size of the peripherals area. | |
120 static const int _blockSize = 4096; | |
121 | |
122 // Offsets (in bytes) to various areas. | |
123 static const int _gpioFunctionSelectBase = 0 << 2; | |
124 static const int _gpioOutputSetBase = 7 << 2; | |
125 static const int _gpioOutputClearBase = 10 << 2; | |
126 static const int _gpioPinLevelBase = 13 << 2; | |
127 static const int _gpioPullUpPullDown = 37 << 2; | |
128 static const int _gpioPullUpPullDownClockBase = 38 << 2; | |
129 | |
130 // All alternative functions are mapped to `Mode.other` for now. | |
131 static const _functionToMode = | |
132 [Mode.input, Mode.output, | |
133 Mode.other, Mode.other, Mode.other, Mode.other, Mode.other]; | |
134 | |
135 int _fd; // File descriptor for /dev/mem. | |
136 ForeignPointer _addr; | |
137 ForeignMemory _mem; | |
138 | |
139 PiMemoryMappedGPIO(): super(54) { | |
140 // From /usr/include/x86_64-linux-gnu/bits/fcntl-linux.h. | |
141 const int oRDWR = 02; // O_RDWR | |
142 // Found from C code 'printf("%x\n", O_SYNC);'. | |
143 const int oSync = 0x101000; // O_SYNC | |
144 | |
145 // Open /dev/mem to get to the physical memory. | |
146 var devMem = new ForeignMemory.fromStringAsUTF8('/dev/mem'); | |
147 _fd = _open.icall$2Retry(devMem, oRDWR | oSync); | |
148 if (_fd < 0) { | |
149 throw new GPIOException("Failed to open '/dev/mem'", Foreign.errno); | |
150 } | |
151 devMem.free(); | |
152 | |
153 // From /usr/include/x86_64-linux-gnu/bits/mman-linux.h. | |
154 const int protRead = 0x1; // PROT_READ. | |
155 const int protWrite = 0x2; // PROT_WRITE. | |
156 const int mapShared = 0x01; // MAP_SHARED. | |
157 | |
158 _addr = _mmap.pcall$6(0, _blockSize, protRead | protWrite, mapShared, | |
159 _fd, _baseAddressModel2 + _baseAddressGPIOOffset); | |
160 _mem = new ForeignMemory.fromAddress(_addr.address, _blockSize); | |
161 } | |
162 | |
163 void setMode(int pin, Mode mode) { | |
164 _checkPinRange(pin); | |
165 // GPIO function select registers each have 3 bits for 10 pins. | |
166 var fsel = (pin ~/ 10); | |
167 var shift = (pin % 10) * 3; | |
168 var function = mode == Mode.input ? 0 : 1; | |
169 var offset = _gpioFunctionSelectBase + (fsel << 2); | |
170 var value = _mem.getUint32(offset); | |
171 value = (value & ~(0x07 << shift)) | function << shift; | |
172 _mem.setUint32(offset, value); | |
173 } | |
174 | |
175 Mode getMode(int pin) { | |
176 _checkPinRange(pin); | |
177 // GPIO function select registers each have 3 bits for 10 pins. | |
178 var fsel = (pin ~/ 10); | |
179 var shift = (pin % 10) * 3; | |
180 var offset = _gpioFunctionSelectBase + (fsel << 2); | |
181 var function = (_mem.getUint32(offset) >> shift) & 0x07; | |
182 return _functionToMode[function]; | |
183 } | |
184 | |
185 void setPin(int pin, bool value) { | |
186 _checkPinRange(pin); | |
187 // GPIO output set and output clear registers each have 1 bits for 32 pins. | |
188 int register = pin ~/ 32; | |
189 int shift = pin % 32; | |
190 if (value) { | |
191 _mem.setUint32(_gpioOutputSetBase + (register << 2), 1 << shift); | |
192 } else { | |
193 _mem.setUint32(_gpioOutputClearBase + (register << 2), 1 << shift); | |
194 } | |
195 } | |
196 | |
197 bool getPin(int pin) { | |
198 _checkPinRange(pin); | |
199 // GPIO pin level registers each have 1 bits for 32 pins. | |
200 int register = pin ~/ 32; | |
201 int shift = pin % 32; | |
202 return | |
203 (_mem.getUint32(_gpioPinLevelBase + (register << 2)) & 1 << shift) != 0; | |
204 } | |
205 | |
206 /// Set the floating/pull-up/pull-down state of [pin]. | |
207 /// | |
208 /// Use `0` for floating, `1` for pull down and `2` for pull-up. | |
209 void setPullUpDown(int pin, PullUpDown pullUpDown) { | |
210 _checkPinRange(pin); | |
211 int register = pin ~/ 32; | |
212 int shift = pin % 32; | |
213 // First set the value in the update register. | |
214 _mem.setUint32(_gpioPullUpPullDown, pullUpDown.index); | |
215 sleep(1); // Datasheet says: "Wait for 150 cycles". | |
216 // Then set the clock bit. | |
217 _mem.setUint32(_gpioPullUpPullDownClockBase + (register << 2), 1 << shift); | |
218 sleep(1); // Datasheet says: "Wait for 150 cycles". | |
219 // Clear value and clock bit. | |
220 _mem.setUint32(_gpioPullUpPullDown, 0); | |
221 _mem.setUint32(_gpioPullUpPullDownClockBase + (register << 2), 0); | |
222 } | |
223 } | |
224 | |
225 /// Interrupt triggers. | 86 /// Interrupt triggers. |
226 enum Trigger { | 87 enum Trigger { |
227 none, | 88 none, |
228 rising, | 89 rising, |
229 falling, | 90 falling, |
230 both, | 91 both, |
231 } | 92 } |
232 | 93 |
233 /// Provide GPIO access using the Sysfs Interface for Userspace. | 94 /// Provide GPIO access using the Sysfs Interface for Userspace. |
234 /// | 95 /// |
(...skipping 25 matching lines...) Expand all Loading... |
260 /// gpio.exportPin(17); | 121 /// gpio.exportPin(17); |
261 /// gpio.setMode(17, Mode.input); | 122 /// gpio.setMode(17, Mode.input); |
262 /// gpio.setTrigger(17, Trigger.both); | 123 /// gpio.setTrigger(17, Trigger.both); |
263 /// bool value = gpio.getPin(17) | 124 /// bool value = gpio.getPin(17) |
264 /// gpio.setPin(4, value); | 125 /// gpio.setPin(4, value); |
265 /// while (true) { | 126 /// while (true) { |
266 /// var value = gpio.waitFor(17, !value, -1); | 127 /// var value = gpio.waitFor(17, !value, -1); |
267 /// gpio.setPin(4, value); | 128 /// gpio.setPin(4, value); |
268 /// } | 129 /// } |
269 /// ``` | 130 /// ``` |
270 class SysfsGPIO extends _GPIOBase implements GPIO { | 131 class SysfsGPIO extends GPIOBase { |
271 // For documentation on the GPIO Sysfs Interface for Userspace see | 132 // For documentation on the GPIO Sysfs Interface for Userspace see |
272 // https://www.kernel.org/doc/Documentation/gpio/sysfs.txt. | 133 // https://www.kernel.org/doc/Documentation/gpio/sysfs.txt. |
273 static const String _basePath = '/sys/class/gpio/'; | 134 static const String _basePath = '/sys/class/gpio/'; |
274 | 135 |
275 // Cached constants. | 136 // Cached constants. |
276 ByteBuffer _zero; | 137 ByteBuffer _zero; |
277 ByteBuffer _one; | 138 ByteBuffer _one; |
278 ByteBuffer _in; | 139 ByteBuffer _in; |
279 ByteBuffer _out; | 140 ByteBuffer _out; |
280 ByteBuffer _none; | 141 ByteBuffer _none; |
281 ByteBuffer _rising; | 142 ByteBuffer _rising; |
282 ByteBuffer _falling; | 143 ByteBuffer _falling; |
283 ByteBuffer _both; | 144 ByteBuffer _both; |
284 | 145 |
285 // Open value files for all tracked pins. Indexed by pin number. | 146 // Open value files for all tracked pins. Indexed by pin number. |
286 List<File> _tracked; | 147 List<File> _tracked; |
287 | 148 |
288 /// Create a GPIO controller using the GPIO Sysfs Interface. | 149 /// Create a GPIO controller using the GPIO Sysfs Interface. |
289 SysfsGPIO([int maxPins = 54]): super(maxPins) { | 150 SysfsGPIO([int pins = GPIO.defaultPins]): super(pins) { |
290 // Byte buffers for string constants. | 151 // Byte buffers for string constants. |
291 var data; | 152 var data; |
292 data = new Uint8List(1); | 153 data = new Uint8List(1); |
293 data.setRange(0, 1, '0'.codeUnits); | 154 data.setRange(0, 1, '0'.codeUnits); |
294 _zero = data.buffer; | 155 _zero = data.buffer; |
295 data = new Uint8List(1); | 156 data = new Uint8List(1); |
296 data.setRange(0, 1, '1'.codeUnits); | 157 data.setRange(0, 1, '1'.codeUnits); |
297 _one = data.buffer; | 158 _one = data.buffer; |
298 data = new Uint8List(2); | 159 data = new Uint8List(2); |
299 data.setRange(0, 2, 'in'.codeUnits); | 160 data.setRange(0, 2, 'in'.codeUnits); |
300 _in = data.buffer; | 161 _in = data.buffer; |
301 data = new Uint8List(3); | 162 data = new Uint8List(3); |
302 data.setRange(0, 3, 'out'.codeUnits); | 163 data.setRange(0, 3, 'out'.codeUnits); |
303 _out = data.buffer; | 164 _out = data.buffer; |
304 data = new Uint8List(4); | 165 data = new Uint8List(4); |
305 data.setRange(0, 4, 'none'.codeUnits); | 166 data.setRange(0, 4, 'none'.codeUnits); |
306 _none = data.buffer; | 167 _none = data.buffer; |
307 data = new Uint8List(6); | 168 data = new Uint8List(6); |
308 data.setRange(0, 6, 'rising'.codeUnits); | 169 data.setRange(0, 6, 'rising'.codeUnits); |
309 _rising = data.buffer; | 170 _rising = data.buffer; |
310 data = new Uint8List(7); | 171 data = new Uint8List(7); |
311 data.setRange(0, 7, 'falling'.codeUnits); | 172 data.setRange(0, 7, 'falling'.codeUnits); |
312 _falling = data.buffer; | 173 _falling = data.buffer; |
313 data = new Uint8List(4); | 174 data = new Uint8List(4); |
314 data.setRange(0, 4, 'both'.codeUnits); | 175 data.setRange(0, 4, 'both'.codeUnits); |
315 _both = data.buffer; | 176 _both = data.buffer; |
316 | 177 |
317 // Find the exported pins by just running through all trying to open | 178 // Find the exported pins by just running through all trying to open |
318 // the value file. | 179 // the value file. |
319 _tracked = new List<File>(_maxPins); | 180 _tracked = new List<File>(pins); |
320 for (int pin = 0; pin < _maxPins; pin++) { | 181 for (int pin = 0; pin < pins; pin++) { |
321 _track(pin); | 182 _track(pin); |
322 } | 183 } |
323 } | 184 } |
324 | 185 |
325 void _track(int pin) { | 186 void _track(int pin) { |
326 _untrack(pin); | 187 _untrack(pin); |
327 var f; | 188 var f; |
328 try { | 189 try { |
329 // Open the value file for this pin. | 190 // Open the value file for this pin. |
330 f = new File.open('${_basePath}gpio${pin}/value', mode: File.WRITE); | 191 f = new File.open('${_basePath}gpio${pin}/value', mode: File.WRITE); |
331 } catch (e) { | 192 } catch (e) { |
332 // Ignore pins which cannot be opened. | 193 // Ignore pins which cannot be opened. |
333 return; | 194 return; |
334 } | 195 } |
335 _tracked[pin] = f; | 196 _tracked[pin] = f; |
336 } | 197 } |
337 | 198 |
338 void _untrack(pin) { | 199 void _untrack(pin) { |
339 if (_tracked[pin] != null) { | 200 if (_tracked[pin] != null) { |
340 _tracked[pin].close(); | 201 _tracked[pin].close(); |
341 _tracked[pin] = null; | 202 _tracked[pin] = null; |
342 } | 203 } |
343 } | 204 } |
344 | 205 |
345 void _checkTracked(int pin) { | 206 void _checkTracked(int pin) { |
346 _checkPinRange(pin); | 207 checkPinRange(pin); |
347 if (!isTracked(pin)) throw 'Pin $pin is not tracked'; | 208 if (!isTracked(pin)) throw 'Pin $pin is not tracked'; |
348 } | 209 } |
349 | 210 |
350 /// Returns a list with the pins currently tracked. | 211 /// Returns a list with the pins currently tracked. |
351 List tracked() { | 212 List tracked() { |
352 var result = []; | 213 var result = []; |
353 for (int pin = 0; pin < _maxPins; pin++) { | 214 for (int pin = 0; pin < pins; pin++) { |
354 if (_tracked[pin] != null) result.add(pin); | 215 if (_tracked[pin] != null) result.add(pin); |
355 } | 216 } |
356 return result; | 217 return result; |
357 } | 218 } |
358 | 219 |
359 /// Checks if [pin] is tracked. | 220 /// Checks if [pin] is tracked. |
360 bool isTracked(int pin) { | 221 bool isTracked(int pin) { |
361 return _tracked[pin] != null; | 222 return _tracked[pin] != null; |
362 } | 223 } |
363 | 224 |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
471 var f = new File.open('${_basePath}${exportOrUnexport}', | 332 var f = new File.open('${_basePath}${exportOrUnexport}', |
472 mode: File.WRITE_ONLY); | 333 mode: File.WRITE_ONLY); |
473 var value = '$pin'.codeUnits; | 334 var value = '$pin'.codeUnits; |
474 var bytes = new Uint8List(value.length); | 335 var bytes = new Uint8List(value.length); |
475 bytes.setRange(0, value.length, value); | 336 bytes.setRange(0, value.length, value); |
476 f.write(bytes.buffer); | 337 f.write(bytes.buffer); |
477 f.close(); | 338 f.close(); |
478 } | 339 } |
479 | 340 |
480 void exportPin(int pin) { | 341 void exportPin(int pin) { |
481 _checkPinRange(pin); | 342 checkPinRange(pin); |
482 // If already exported do nothing. | 343 // If already exported do nothing. |
483 if (isTracked(pin)) return; | 344 if (isTracked(pin)) return; |
484 _exportUnexport(true, pin); | 345 _exportUnexport(true, pin); |
485 _track(pin); // This is now tracked. | 346 _track(pin); // This is now tracked. |
486 } | 347 } |
487 | 348 |
488 void unexportPin(int pin) { | 349 void unexportPin(int pin) { |
489 _checkPinRange(pin); | 350 checkPinRange(pin); |
490 // If not exported do nothing. | 351 // If not exported do nothing. |
491 if (!isTracked(pin)) return; | 352 if (!isTracked(pin)) return; |
492 _exportUnexport(false, pin); | 353 _exportUnexport(false, pin); |
493 _untrack(pin); // This is no longer tracked. | 354 _untrack(pin); // This is no longer tracked. |
494 } | 355 } |
495 } | 356 } |
496 | 357 |
497 /// Exceptions thrown by GPIO. | 358 /// Exceptions thrown by GPIO. |
498 class GPIOException implements Exception { | 359 class GPIOException implements Exception { |
499 /// Exception message. | 360 /// Exception message. |
500 final String message; | 361 final String message; |
501 /// OS error number if any. | 362 /// OS error number if any. |
502 final int errno; | 363 final int errno; |
503 const GPIOException(this.message, [this.errno]); | 364 const GPIOException(this.message, [this.errno]); |
504 String toString() { | 365 String toString() { |
505 if (errno == null) return message; | 366 if (errno == null) return message; |
506 return '$message (error ${errno})'; | 367 return '$message (error ${errno})'; |
507 } | 368 } |
508 } | 369 } |
OLD | NEW |