Skip to content

Commit 083df2b

Browse files
committed
Use libwinter for factory setup
1 parent 2a0561c commit 083df2b

File tree

9 files changed

+965
-105
lines changed

9 files changed

+965
-105
lines changed

factory/.cache/firmware.uf2.bin

207 KB
Binary file not shown.
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2019 Alethea Flowers for Winterbloom
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
23+
__version__ = "0.0.0-auto.0"
24+
__repo__ = "https://github.com/theacodes/Winterbloom_VoltageIO.git"
25+
26+
"""A helper library to setting a DACs to voltage values and reading voltage
27+
values from ADCs.
28+
29+
That is, instead of setting a 16-bit integer value you can set the DAC to a
30+
floating-point voltage value. See `VoltageOut` for more details. Same for
31+
ADCs: instead of reading a 16-bit integer value you can read the voltage
32+
value directly.
33+
"""
34+
35+
import analogio
36+
37+
38+
def _take_nearest_pair(values, target):
39+
"""Given a sorted, monotonic list of values and a target value,
40+
returns the closest two pairs of numbers in the list
41+
to the given target. The first being the closest number
42+
less than the target, the second being the closest number
43+
greater than the target.
44+
45+
For example::
46+
47+
>>> _take_nearest_pair([1, 2, 3], 2.5)
48+
(2, 3)
49+
50+
If the target is not part of the continuous range of the
51+
values, then both numbers will either be the minimum or
52+
maximum value in the list.
53+
54+
For example::
55+
56+
>>> _take_nearest_pair([1, 2, 3], 10)
57+
(3, 3)
58+
59+
"""
60+
low = values[0]
61+
high = values[0]
62+
63+
for value in values:
64+
if value <= target and value >= low:
65+
low = value
66+
if value > target:
67+
high = value
68+
break
69+
else:
70+
# If we never found a value higher than
71+
# the target, the the target is outside
72+
# of the range of the list. Therefore,
73+
# the highest close number is also the
74+
# lowest close number.
75+
high = low
76+
77+
return low, high
78+
79+
80+
class VoltageOut:
81+
"""Wraps an AnalogOut instance and allows you to set the voltage instead
82+
of specifying the 16-bit output value.
83+
84+
Example::
85+
86+
vout = winterbloom_voltageio.VoltageOut.from_pin(board.A1)
87+
vout.linear_calibration(3.3)
88+
vout.voltage = 1.23
89+
90+
With multiple calibration points, this class can help counteract any
91+
non-linearity present in the DAC. See `direct_calibration` for more
92+
info.
93+
"""
94+
95+
def __init__(self, analog_out):
96+
self._analog_out = analog_out
97+
self._calibration = {}
98+
self._voltage = 0
99+
100+
@classmethod
101+
def from_pin(cls, pin):
102+
return cls(analogio.AnalogOut(pin))
103+
104+
def linear_calibration(self, min_voltage, max_voltage):
105+
"""Determines intermediate calibration values using the given
106+
a minimum and maximum output voltage. This is the
107+
simplest way to calibrate the output. It assumes that the DAC
108+
and any output scaling op amps have an exactly linear response.
109+
110+
Example::
111+
112+
# Output range is 0v to 10.26v.
113+
vout.linear_calibration(0.0, 10.26)
114+
115+
"""
116+
self._calibration[min_voltage] = 0
117+
self._calibration[max_voltage] = 65535
118+
119+
self._calibration_keys = sorted(self._calibration.keys())
120+
121+
def direct_calibration(self, calibration):
122+
"""Allows you to set the calibration values directly.
123+
124+
A common case for this is to set the DAC's value to the point
125+
where it outputs 0%, 25%, 50%, 75%, and 100% of your output range
126+
and record the values at each point. You'd then pass the voltage
127+
and DAC values as the keys and values to this method.
128+
129+
For example if your DAC outputs from 0v-3.3v your calibration might
130+
look something like this::
131+
132+
vout.direct_calibration({
133+
0: 0,
134+
0.825: 16000,
135+
1.65: 32723,
136+
2.475: 49230,
137+
3.3, 65535,
138+
})
139+
140+
You can keep adding more calibration points as needed to counteract
141+
any non-linearity in your DAC. You could even specify a calibration point
142+
for every output value of your DAC if your processor has enough RAM, though
143+
it's very likely overkill.
144+
"""
145+
self._calibration.update(calibration)
146+
self._calibration_keys = sorted(self._calibration.keys())
147+
148+
def _calibrated_value_for_voltage(self, voltage):
149+
if voltage in self._calibration:
150+
return self._calibration[voltage]
151+
152+
low, high = _take_nearest_pair(self._calibration_keys, voltage)
153+
154+
if high == low:
155+
normalized_offset = 0
156+
else:
157+
normalized_offset = (voltage - low) / (high - low)
158+
159+
low_val = self._calibration[low]
160+
high_val = self._calibration[high]
161+
162+
lerped = round(low_val + ((high_val - low_val) * normalized_offset))
163+
164+
return min(lerped, 65535)
165+
166+
def _get_voltage(self):
167+
return self._voltage
168+
169+
def _set_voltage(self, voltage):
170+
self._voltage = voltage
171+
value = self._calibrated_value_for_voltage(voltage)
172+
self._analog_out.value = value
173+
174+
voltage = property(_get_voltage, _set_voltage)
175+
176+
177+
class VoltageIn:
178+
"""Wraps an AnalogIn instance and allows you to read an ADC's measured voltage
179+
instead of the 16-bit input value.
180+
181+
Example::
182+
183+
vin = winterbloom_voltageio.VoltageIn.from_pin(board.A1)
184+
vin.linear_calibration(3.3)
185+
print(vin.voltage)
186+
187+
With multiple calibration points, this class can help counteract any
188+
non-linearity present in the ADC. See `direct_calibration` for more
189+
info.
190+
"""
191+
192+
def __init__(self, analog_in):
193+
self._analog_in = analog_in
194+
self._calibration = {}
195+
196+
@classmethod
197+
def from_pin(cls, pin):
198+
return cls(analogio.AnalogIn(pin))
199+
200+
def linear_calibration(self, min_voltage, max_voltage):
201+
"""Determines intermediate calibration values using the given
202+
a minimum and maximum output voltage. This is the
203+
simplest way to calibrate the input. It assumes that the ADC
204+
and any output scaling op amps have an exactly linear response.
205+
206+
Example::
207+
208+
# Input range is 0v to 10.26v.
209+
vin.linear_calibration(0, 10.26)
210+
211+
"""
212+
self._calibration[0] = 0
213+
self._calibration[65535] = max_voltage
214+
215+
self._calibration_keys = sorted(self._calibration.keys())
216+
217+
def direct_calibration(self, calibration):
218+
"""Allows you to set the calibration values directly.
219+
220+
A common case for this is to set known, stable input voltages
221+
into your ADC and record the measurement value. These values can
222+
be passed as the calibration data.
223+
224+
For example if your ADC has a range of 0v to 4v and you measure
225+
the value at each integral voltage your calibration would look
226+
something like like this::
227+
228+
vin.direct_calibration({
229+
0: 0,
230+
16000: 1.0,
231+
32723: 2.0,
232+
49230: 3.0,
233+
65535: 4.0,
234+
})
235+
"""
236+
self._calibration.update(calibration)
237+
self._calibration_keys = sorted(self._calibration.keys())
238+
239+
def _calibrated_voltage_for_value(self, value):
240+
if value in self._calibration:
241+
return self._calibration[value]
242+
243+
low, high = _take_nearest_pair(self._calibration_keys, value)
244+
245+
if high == low:
246+
normalized_offset = 0
247+
else:
248+
normalized_offset = (value - low) / (high - low)
249+
250+
low_volt = self._calibration[low]
251+
high_volt = self._calibration[high]
252+
253+
lerped = low_volt + ((high_volt - low_volt) * normalized_offset)
254+
255+
return lerped
256+
257+
def _get_voltage(self):
258+
return self._calibrated_voltage_for_value(self._analog_in.value)
259+
260+
voltage = property(_get_voltage, None)

0 commit comments

Comments
 (0)