forked from piface/pifacedigitalio
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pifacedigitalio.py
468 lines (357 loc) · 14.1 KB
/
pifacedigitalio.py
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
#!/usr/bin/env python3
"""Provides I/O methods for interfacing with PiFace Digital (for Raspberry Pi)
Copyright (C) 2013 Thomas Preston <thomasmarkpreston@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import sys
import ctypes
import posix
import select
import subprocess
from fcntl import ioctl
from asm_generic_ioctl import _IOW
# spi stuff requires Python 3
assert sys.version_info.major >= 3, __name__ + " is only supported on Python 3."
WRITE_CMD = 0
READ_CMD = 1
# Register addresses
IODIRA = 0x0 # I/O direction A
IODIRB = 0x1 # I/O direction B
IPOLA = 0x2 # I/O polarity A
IPOLB = 0x3 # I/O polarity B
GPINTENA = 0x4 # interupt enable A
GPINTENB = 0x5 # interupt enable B
DEFVALA = 0x6 # register default value A (interupts)
DEFVALB = 0x7 # register default value B (interupts)
INTCONA = 0x8 # interupt control A
INTCONB = 0x9 # interupt control B
IOCON = 0xa # I/O config (also 0xb)
GPPUA = 0xc # port A pullups
GPPUB = 0xd # port B pullups
INTFA = 0xe # interupt flag A (where the interupt came from)
INTFB = 0xf # interupt flag B
INTCAPA = 0x10 # interupt capture A (value at interupt is saved here)
INTCAPB = 0x11 # interupt capture B
GPIOA = 0x12 # port A
GPIOB = 0x13 # port B
# some easier to remember/read values
OUTPUT_PORT = GPIOA
INPUT_PORT = GPIOB
INPUT_PULLUP = GPPUB
# I/O config
# make a config byte like so:
BANK_OFF = 0x00 # addressing mode
BANK_ON = 0x80
INT_MIRROR_ON = 0x40 # interupt mirror (INTa|INTb)
INT_MIRROR_OFF = 0x00
SEQOP_OFF = 0x20 # incrementing address pointer
SEQOP_ON = 0x20
DISSLW_ON = 0x10 # slew rate
DISSLW_OFF = 0x00
HAEN_ON = 0x08 # hardware addressing
HAEN_OFF = 0x00
ODR_ON = 0x04 # open drain for interupts
ODR_OFF = 0x00
INTPOL_HIGH = 0x02 # interupt polarity
INTPOL_LOW = 0x00
SPI_IOC_MAGIC = 107
MAX_BOARDS = 4
SPIDEV = '/dev/spidev0.0'
GPIO_INTERUPT_DEVICE = '/sys/devices/virtual/gpio/gpio25/'
spidev_fd = None
# custom exceptions
class InitError(Exception):
pass
class InputDeviceError(Exception):
pass
class RangeError(Exception):
pass
class NoPiFaceDigitalDetectedError(Exception):
pass
# classes
class Item(object):
"""An item connected to a pin on PiFace Digital"""
def __init__(self, pin_num, board_num=0):
self.pin_num = pin_num
self.board_num = board_num
@property
def handler(self):
return sys.modules[__name__]
class InputItem(Item):
"""An input connected to a pin on PiFace Digital"""
def __init__(self, pin_num, board_num=0):
super().__init__(pin_num, board_num)
@property
def value(self):
return 1 ^ self.handler.read_bit(self.pin_num, INPUT_PORT, self.board_num)
@value.setter
def value(self, data):
raise InputDeviceError("You cannot set an input's values!")
class OutputItem(Item):
"""An output connected to a pin on PiFace Digital"""
def __init__(self, pin_num, board_num=0):
super().__init__(pin_num, board_num)
@property
def value(self):
return self.handler.read_bit(self.pin_num, OUTPUT_PORT, self.board_num)
@value.setter
def value(self, data):
return self.handler.write_bit(data, self.pin_num, OUTPUT_PORT, self.board_num)
def turn_on(self):
self.value = 1
def turn_off(self):
self.value = 0
def toggle(self):
self.value = not self.value
class LED(OutputItem):
"""An LED on PiFace Digital"""
def __init__(self, led_num, board_num=0):
if led_num < 0 or led_num > 7:
raise RangeError(
"Specified LED index (%d) out of range." % led_num)
else:
super().__init__(led_num, board_num)
class Relay(OutputItem):
"""A relay on PiFace Digital"""
def __init__(self, relay_num, board_num=0):
if relay_num < 0 or relay_num > 1:
raise RangeError(
"Specified relay index (%d) out of range." % relay_num)
else:
super().__init__(relay_num, board_num)
class Switch(InputItem):
"""A switch on PiFace Digital"""
def __init__(self, switch_num, board_num=0):
if switch_num < 0 or switch_num > 3:
raise RangeError(
"Specified switch index (%d) out of range." % switch_num)
else:
super().__init__(switch_num, board_num)
class PiFaceDigital(object):
"""A single PiFace Digital board"""
def __init__(self, board_num=0):
self.board_num = board_num
self.input_pins = [InputItem(i, board_num) for i in range(8)]
self.output_pins = [OutputItem(i, board_num) for i in range(8)]
self.leds = [LED(i, board_num) for i in range(8)]
self.relays = [Relay(i, board_num) for i in range(2)]
self.switches = [Switch(i, board_num) for i in range(4)]
class _spi_ioc_transfer(ctypes.Structure):
"""SPI ioc transfer structure (from linux/spi/spidev.h)"""
_fields_ = [
("tx_buf", ctypes.c_uint64),
("rx_buf", ctypes.c_uint64),
("len", ctypes.c_uint32),
("speed_hz", ctypes.c_uint32),
("delay_usecs", ctypes.c_uint16),
("bits_per_word", ctypes.c_uint8),
("cs_change", ctypes.c_uint8),
("pad", ctypes.c_uint32)]
class InputFunctionMap(list):
"""Maps inputs pins to callback functions.
The callback function is passed a the input port as a byte
map parameters (*optional):
index - input pin number
into - direction of change (into 1 or 0, 0 is pressed, None =either)
callback - function to run when interupt is detected
board* - what PiFace digital board to check
def callback(interupted_bit, input_byte):
# input_byte = 0b11110111 <- pin 3 caused the interupt
# input_byte = 0b10110111 <- pins 6 and 3 activated
print(bin(input_byte))
"""
def register(self, input_index, into, callback, board_index=0):
self.append({
'index': input_index,
'into': into,
'callback': callback,
'board': board_index})
# functions
def init(init_board=True):
"""Initialises the PiFace Digital board"""
global spidev_fd
spidev_fd = posix.open(SPIDEV, posix.O_RDWR)
if init_board:
# set up each board
ioconfig = BANK_OFF | INT_MIRROR_OFF | SEQOP_ON | DISSLW_OFF | \
HAEN_ON | ODR_OFF | INTPOL_LOW
pfd_detected = False
for board_index in range(MAX_BOARDS):
write(ioconfig, IOCON, board_index) # configure
if not pfd_detected and read(IOCON, board_index) == ioconfig:
pfd_detected = True
write(0, GPIOA, board_index) # clear port A
write(0, IODIRA, board_index) # set port A as outputs
write(0xff, IODIRB, board_index) # set port B as inputs
write(0xff, GPPUB, board_index) # set port B pullups on
if not pfd_detected:
raise NoPiFaceDigitalDetectedError(
"There was no PiFace Digital board detected!")
def deinit():
"""Closes the spidev file descriptor"""
global spidev_fd
posix.close(spidev_fd)
def digital_read(pin_num, board_num=0):
"""Returns the status of the input pin specified.
1 is active
0 is inactive
Note: This function is for familiarality with Arduino users
"""
return read_bit(pin_num, INPUT_PORT, board_num) ^ 1 # inputs are
def digital_write(pin_num, value, board_num=0):
"""Writes the value specified to the output pin
1 is active
0 is inactive
Note: This function is for familiarality with Arduino users
"""
write_bit(value, pin_num, OUTPUT_PORT, board_num)
def digital_read_pullup(pin_num, board_num=0):
return read_bit(pin_num, INPUT_PULLUP, board_num)
def digital_write_pullup(pin_num, value, board_num=0):
write_bit(value, pin_num, INPUT_PULLUP, board_num)
def get_bit_mask(bit_num):
"""Translates a pin num to pin bit mask. First pin is pin0."""
if bit_num > 7 or bit_num < 0:
raise RangeError("Specified bit num (%d) out of range (0-7)." % bit_num)
else:
return 1 << (bit_num)
def get_bit_num(bit_pattern):
"""Returns the lowest pin num from a given bit pattern"""
bit_num = 0 # assume bit 0
while (bit_pattern & 1) == 0:
bit_pattern = bit_pattern >> 1
bit_num += 1
if bit_num > 7:
bit_num = 0
break
return bit_num
def read_bit(bit_num, address, board_num=0):
"""Returns the bit specified from the address"""
value = read(address, board_num)
bit_mask = get_bit_mask(bit_num)
return 1 if value & bit_mask else 0
def write_bit(value, bit_num, address, board_num=0):
"""Writes the value given to the bit specified"""
bit_mask = get_bit_mask(bit_num)
old_byte = read(address, board_num)
# generate the new byte
if value:
new_byte = old_byte | bit_mask
else:
new_byte = old_byte & ~bit_mask
write(new_byte, address, board_num)
def __get_device_opcode(board_num, read_write_cmd):
"""Returns the device opcode (as a byte)"""
board_addr_pattern = (board_num << 1) & 0xE # 1 -> 0b0010, 3 -> 0b0110
rw_cmd_pattern = read_write_cmd & 1 # make sure it's just 1 bit long
return 0x40 | board_addr_pattern | rw_cmd_pattern
def read(address, board_num=0):
"""Reads from the address specified"""
devopcode = __get_device_opcode(board_num, READ_CMD)
op, addr, data = spisend((devopcode, address, 0)) # data byte is not used
return data
def write(data, address, board_num=0):
"""Writes data to the address specified"""
devopcode = __get_device_opcode(board_num, WRITE_CMD)
op, addr, data = spisend((devopcode, address, data))
def spisend(bytes_to_send):
"""Sends bytes via the SPI bus"""
global spidev_fd
if spidev_fd is None:
raise InitError("Before spisend(), call init().")
# make some buffer space to store reading/writing
write_bytes = bytes(bytes_to_send)
wbuffer = ctypes.create_string_buffer(write_bytes, len(write_bytes))
rbuffer = ctypes.create_string_buffer(len(bytes_to_send))
# create the spi transfer struct
transfer = _spi_ioc_transfer(
tx_buf=ctypes.addressof(wbuffer),
rx_buf=ctypes.addressof(rbuffer),
len=ctypes.sizeof(wbuffer))
# send the spi command (with a little help from asm-generic
iomsg = _IOW(SPI_IOC_MAGIC, 0, ctypes.c_char*ctypes.sizeof(transfer))
ioctl(spidev_fd, iomsg, ctypes.addressof(transfer))
return ctypes.string_at(rbuffer, ctypes.sizeof(rbuffer))
# interupts
def wait_for_input(input_func_map=None, loop=False, timeout=None):
"""Waits for an input to be pressed (using interups rather than polling)
Paramaters:
input_func_map - An InputFunctionMap object describing callbacks
loop - If true, keep checking interupt status
timeout - How long we should wait before giving up and exiting the
function
"""
enable_interupts()
# set up epoll (can't do it in enable_interupts for some reason)
gpio25 = open(GPIO_INTERUPT_DEVICE+'value', 'r')
epoll = select.epoll()
epoll.register(gpio25, select.EPOLLIN | select.EPOLLET)
while True:
# wait here until input
try:
events = epoll.poll(timeout) if timeout else epoll.poll()
except KeyboardInterrupt:
epoll.close()
disable_interupts() # more graceful
raise
if len(events) <= 0:
epoll.close()
disable_interupts()
return
if input_func_map:
call_mapped_input_functions(input_func_map)
if not loop:
epoll.close()
disable_interupts()
return
def call_mapped_input_functions(input_func_map):
for board_i in range(MAX_BOARDS):
this_board_ifm = [m for m in input_func_map if m['board'] == board_i]
# read the interupt status of this PiFace board
int_bit = read(INTFB, board_i)
if int_bit == 0:
continue # The interupt has not been flagged on this board
int_bit_num = get_bit_num(int_bit)
int_byte = read(INTCAPB, board_i)
into = (int_bit & int_byte) >> int_bit_num # bit changed into (0/1)
# for each mapping (on this board) see if we have a callback
for mapping in this_board_ifm:
if int_bit_num == mapping['index'] and \
(mapping['into'] is None or into == mapping['into']):
mapping['callback'](int_bit, int_byte)
return # one at a time
def clear_interupts():
"""Clears the interupt flags by reading the capture register on all boards"""
for i in range(MAX_BOARDS):
read(INTCAPB, i)
def enable_interupts():
for board_index in range(MAX_BOARDS):
write(0xff, GPINTENB, board_index)
# access quick2wire-gpio-admin for gpio pin twiddling
try:
subprocess.check_call(["gpio-admin", "export", "25"])
except subprocess.CalledProcessError as e:
if e.returncode != 4: # we can ignore 4 (gpio25 is already up)
raise e
# we're only interested in the falling edges of this file (1 -> 0)
with open(GPIO_INTERUPT_DEVICE+'edge', 'w') as gpio25edge:
gpio25edge.write('falling')
def disable_interupts():
with open(GPIO_INTERUPT_DEVICE+'edge', 'w') as gpio25edge:
gpio25edge.write('none')
try:
subprocess.check_call(["gpio-admin", "unexport", "25"])
except subprocess.CalledProcessError as e:
if e.returncode != 4: # we can ignore 4 (gpio25 is already down)
raise e
for board_index in range(MAX_BOARDS):
write(0, GPINTENB, board_index) # disable the interupt