Skip to content

Commit df3c8f0

Browse files
committed
analog threshold improvements
- can now provide a upper and lower threshold - raise an error if a rising or falling event is provided without a threshold - raise errors if threshold(s) not provided correctly - add ability to change threshold(s) after task has started
1 parent a54bdca commit df3c8f0

File tree

1 file changed

+77
-29
lines changed

1 file changed

+77
-29
lines changed

source/pyControl/hardware.py

+77-29
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,26 @@ class Analog_input(IO_object):
237237

238238
def __init__(self, pin, name, sampling_rate, threshold=None, rising_event=None, falling_event=None, data_type="H"):
239239
if rising_event or falling_event:
240-
self.threshold = Analog_threshold(threshold, rising_event, falling_event)
240+
if threshold is None:
241+
raise ValueError("A threshold must be specified if rising or falling events are defined.")
242+
self.threshold_watcher = Analog_threshold_watcher(threshold, rising_event, falling_event)
241243
else:
242-
self.threshold = False
244+
self.threshold_watcher = False
245+
243246
self.timer = pyb.Timer(available_timers.pop())
244247
if pin: # pin argument can be None when Analog_input subclassed.
245248
self.ADC = pyb.ADC(pin)
246249
self.read_sample = self.ADC.read
247250
self.name = name
248-
self.Analog_channel = Analog_channel(name, sampling_rate, data_type)
251+
self.channel = Analog_channel(name, sampling_rate, data_type)
249252
assign_ID(self)
250253

251254
def _run_start(self):
252255
# Start sampling timer, initialise threshold, aquire first sample.
253-
self.timer.init(freq=self.Analog_channel.sampling_rate)
256+
self.timer.init(freq=self.channel.sampling_rate)
254257
self.timer.callback(self._timer_ISR)
255-
if self.threshold:
256-
self.threshold.run_start(self.read_sample())
258+
if self.threshold_watcher:
259+
self.threshold_watcher.run_start(self.read_sample())
257260
self._timer_ISR(0)
258261

259262
def _run_stop(self):
@@ -263,9 +266,12 @@ def _run_stop(self):
263266
def _timer_ISR(self, t):
264267
# Read a sample to the buffer, update write index.
265268
sample = self.read_sample()
266-
self.Analog_channel.put(sample)
267-
if self.threshold:
268-
self.threshold.check(sample)
269+
self.channel.put(sample)
270+
if self.threshold_watcher:
271+
self.threshold_watcher.check(sample)
272+
273+
def change_threshold(self, new_threshold):
274+
self.threshold_watcher.set_threshold(new_threshold)
269275

270276
def record(self): # For backward compatibility.
271277
pass
@@ -286,15 +292,21 @@ class Analog_channel(IO_object):
286292
# data array bytes (variable)
287293

288294
def __init__(self, name, sampling_rate, data_type, plot=True):
289-
assert data_type in ("b", "B", "h", "H", "i", "I"), "Invalid data_type."
290-
assert not any(
291-
[name == io.name for io in IO_dict.values() if isinstance(io, Analog_channel)]
292-
), "Analog signals must have unique names."
295+
if data_type not in ("b", "B", "h", "H", "i", "I"):
296+
raise ValueError("Invalid data_type.")
297+
if any([name == io.name for io in IO_dict.values() if isinstance(io, Analog_channel)]):
298+
raise ValueError(
299+
"Analog signals must have unique names.{} {}".format(
300+
name, [io.name for io in IO_dict.values() if isinstance(io, Analog_channel)]
301+
)
302+
)
303+
293304
self.name = name
294305
assign_ID(self)
295306
self.sampling_rate = sampling_rate
296307
self.data_type = data_type
297308
self.plot = plot
309+
298310
self.bytes_per_sample = {"b": 1, "B": 1, "h": 2, "H": 2, "i": 4, "I": 4}[data_type]
299311
self.buffer_size = max(4, min(256 // self.bytes_per_sample, sampling_rate // 10))
300312
self.buffers = (array(data_type, [0] * self.buffer_size), array(data_type, [0] * self.buffer_size))
@@ -344,45 +356,81 @@ def send_buffer(self, run_stop=False):
344356
fw.usb_serial.send(self.buffers[buffer_n])
345357

346358

347-
class Analog_threshold(IO_object):
348-
# Generates framework events when an analog signal goes above or below specified threshold.
359+
class Crossing:
360+
above = "above"
361+
below = "below"
362+
none = "none"
363+
364+
365+
class Analog_threshold_watcher(IO_object):
366+
"""
367+
Generates framework events when an analog signal goes above or below specified threshold.
368+
If given single threshold value, rising event is triggered when signal > threshold, falling event is triggered when signal <= threshold.
369+
If given tuple of two threshold values, rising event is triggered when signal > upper bound, falling event is triggered when signal < lower bound.
370+
"""
349371

350-
def __init__(self, threshold=None, rising_event=None, falling_event=None):
351-
assert isinstance(
352-
threshold, int
353-
), "Integer threshold must be specified if rising or falling events are defined."
354-
self.threshold = threshold
372+
def __init__(self, threshold, rising_event=None, falling_event=None):
373+
self.set_threshold(threshold)
355374
self.rising_event = rising_event
356375
self.falling_event = falling_event
357376
self.timestamp = 0
358-
self.crossing_direction = False
377+
self.last_crossing = Crossing.none
359378
assign_ID(self)
360379

380+
def set_threshold(self, threshold):
381+
if isinstance(threshold, int): # single threshold value
382+
self.upper_threshold = threshold
383+
self.lower_threshold = threshold + 1 # +1 so falling event is triggered when crossing into <= threshold
384+
elif isinstance(threshold, tuple):
385+
threshold_requirements_str = "The threshold must be a single integer or a tuple of two integers (lower_bound, upper_bound) where lower_bound <= upper_bound."
386+
if len(threshold) != 2:
387+
raise ValueError("{} is not a valid threshold. {}".format(threshold, threshold_requirements_str))
388+
lower, upper = threshold
389+
if not upper >= lower:
390+
raise ValueError(
391+
"{} is not a valid threshold because the lower bound {} is greater than the upper bound {}. {}".format(
392+
threshold, lower, upper, threshold_requirements_str
393+
)
394+
)
395+
self.upper_threshold = upper
396+
self.lower_threshold = lower
397+
else:
398+
raise ValueError("{} is not a valid threshold. {}".format(threshold, threshold_requirements_str))
399+
361400
def _initialise(self):
362401
# Set event codes for rising and falling events.
363402
self.rising_event_ID = sm.events[self.rising_event] if self.rising_event in sm.events else False
364403
self.falling_event_ID = sm.events[self.falling_event] if self.falling_event in sm.events else False
365404
self.threshold_active = self.rising_event_ID or self.falling_event_ID
366405

367406
def run_start(self, sample):
368-
self.above_threshold = sample > self.threshold
407+
self.was_above = sample > self.upper_threshold
408+
self.was_below = sample < self.lower_threshold
369409

370410
def _process_interrupt(self):
371411
# Put event generated by threshold crossing in event queue.
372-
if self.crossing_direction:
412+
if self.was_above:
373413
fw.event_queue.put(fw.Datatuple(self.timestamp, fw.EVENT_TYP, "i", self.rising_event_ID))
374414
else:
375415
fw.event_queue.put(fw.Datatuple(self.timestamp, fw.EVENT_TYP, "i", self.falling_event_ID))
376416

377417
@micropython.native
378418
def check(self, sample):
379-
new_above_threshold = sample > self.threshold
380-
if new_above_threshold != self.above_threshold: # Threshold crossing.
381-
self.above_threshold = new_above_threshold
382-
if (self.above_threshold and self.rising_event_ID) or (not self.above_threshold and self.falling_event_ID):
383-
self.timestamp = fw.current_time
384-
self.crossing_direction = self.above_threshold
419+
is_above_threshold = sample > self.upper_threshold
420+
is_below_threshold = sample < self.lower_threshold
421+
422+
if is_above_threshold and not self.was_above and self.last_crossing != Crossing.above:
423+
self.timestamp = fw.current_time
424+
self.last_crossing = Crossing.above
425+
if self.rising_event_ID:
385426
interrupt_queue.put(self.ID)
427+
elif is_below_threshold and not self.was_below and self.last_crossing != Crossing.below:
428+
self.timestamp = fw.current_time
429+
self.last_crossing = Crossing.below
430+
if self.falling_event_ID:
431+
interrupt_queue.put(self.ID)
432+
433+
self.was_above, self.was_below = is_above_threshold, is_below_threshold
386434

387435

388436
# Digital Output --------------------------------------------------------------

0 commit comments

Comments
 (0)