This repository was archived by the owner on Feb 15, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathshifty.py
More file actions
191 lines (133 loc) · 4.84 KB
/
shifty.py
File metadata and controls
191 lines (133 loc) · 4.84 KB
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
"""Interface to shift register (SN74HC595)."""
from gpiozero import DigitalOutputDevice, Device, OutputDeviceError
from time import sleep, perf_counter
from threading import Thread
def pulse(pin):
pin.on()
pin.off()
class ShiftRegister(Device):
def __init__(self, SER, SRCLK, RCLK, outputs=8, SRCLR=None, OE=None, pin_factory=None):
super().__init__(pin_factory=pin_factory)
self.SER = DigitalOutputDevice(SER, pin_factory=pin_factory)
self.SRCLK = DigitalOutputDevice(SRCLK, pin_factory=pin_factory)
self.RCLK = DigitalOutputDevice(RCLK, pin_factory=pin_factory)
self.SRCLR = DigitalOutputDevice(SRCLR, active_high=False, pin_factory=pin_factory) if SRCLR else None
self.OE = DigitalOutputDevice(OE, active_high=False, pin_factory=pin_factory) if OE else None
self._closed = False
if not isinstance(outputs, int):
raise TypeError("Number of outputs must be an int.")
self.outputs = outputs
self._state = [False] * outputs
self.clear()
def close(self):
if self._closed:
return
self.clear()
self.SER.close()
self.SRCLK.close()
self.RCLK.close()
self.SRCLR is None or self.SRCLR.close()
self.OE is None or self.OE.close()
self._closed = True
def clear(self):
# If we connected the clearing pin we can just pulse it.
if self.SRCLR is not None:
pulse(self.SRCLR)
pulse(self.RCLK)
self._state = [False] * self.outputs
# Nope, pipe in false signals for as many outputs as we have.
else:
self.shiftin([False]*self.outputs)
def shiftin(self, bits):
bits = [bool(b) for b in bits]
for bit in bits:
self.SER.value = bit
pulse(self.SRCLK)
pulse(self.RCLK)
# Shift the previous state to the right and plug these bits in.
self._state = bits[self.outputs-1::-1] + self._state[:-len(bits)]
def write(self, bits):
if len(bits) != self.outputs:
raise OutputDeviceError(
"Device initialized with {} outputs, but {} passed in.".format(
self.outputs, len(bits)))
self.shiftin(list(reversed(bits)))
@property
def value(self):
return self._state
@value.setter
def value(self, bits):
self.write(bits)
class PWMShiftRegister(Device):
def __init__(
self,
SER, SRCLK, RCLK,
outputs=8,
frequency=100,
SRCLR=None, OE=None, pin_factory=None,
):
"""Control shift register pins using pulse width modulation.
:param float frequency:
Frequency in Hz at which to pulse the pins.
"""
super().__init__(pin_factory=pin_factory)
self._closed = False
self._underlying = ShiftRegister(SER, SRCLK, RCLK, outputs=outputs, SRCLR=SRCLR, OE=OE, pin_factory=pin_factory)
if not isinstance(frequency, (float, int)):
raise TypeError("Frequency must be a number.")
if frequency < 10:
raise Warning("Frequency is too low.")
self._pulse_duration = 1 / frequency
if not isinstance(outputs, int):
raise TypeError("Number of outputs must be an int.")
self.outputs = outputs
self._state = [0.0] * outputs
self._running = True
self._pwm_thread = Thread(target=self._pwm_runner, daemon=True)
self._pwm_thread.start()
def _pwm_runner(self):
# Localise some variables.
outputs = self.outputs
pulse = self._pulse_duration
underlying = self._underlying
last_toggled = [perf_counter()] * outputs
state = [False] * outputs
while self._running:
values = self._state
now = perf_counter()
# Turn off pins that have been on for more than fraction of pulse,
# and turn on pins that have been off for more than (1-fraction) of pulse.
next_state = [
min(now-lt, pulse-1e-9) < pulse*v if s else min(now-lt+1e-9, pulse) > pulse*(1.0-v)
for s, lt, v
in zip(state, last_toggled, values)]
underlying.write(next_state)
# Takes a while to write it, get time again.
now = perf_counter()
last_toggled = [lt if s == ns else now for s, ns, lt in zip(state, next_state, last_toggled)]
state = next_state
def write(self, values):
# Make sure values are numbers in [0, 1].
values = [float(v) for v in values]
if not all(0.0 <= v <= 1.0 for v in values):
raise ValueError("Input values must be in the closed interval [0, 1]; outliers found: {}".format(values))
if len(values) != self.outputs:
raise OutputDeviceError(
"Device initialized with {} outputs, but {} passed in.".format(
self.outputs, len(values)))
self._state = values
def clear(self):
self._state = [0.0] * self.outputs
def close(self):
if self._closed:
return
self._running = False
self._pwm_thread.join()
self._underlying.close()
self._closed = True
@property
def value(self):
return self._state
@value.setter
def value(self, bits):
self.write(bits)