@@ -235,14 +235,8 @@ class Analog_input(IO_object):
235
235
# streams data to computer. Optionally can generate framework events when voltage
236
236
# goes above / below specified value theshold.
237
237
238
- def __init__ (self , pin , name , sampling_rate , threshold = None , rising_event = None , falling_event = None , data_type = "H" ):
239
- if rising_event or 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 )
243
- else :
244
- self .threshold_watcher = False
245
-
238
+ def __init__ (self , pin , name , sampling_rate , triggers = None , data_type = "H" ):
239
+ self .triggers = triggers
246
240
self .timer = pyb .Timer (available_timers .pop ())
247
241
if pin : # pin argument can be None when Analog_input subclassed.
248
242
self .ADC = pyb .ADC (pin )
@@ -255,8 +249,6 @@ def _run_start(self):
255
249
# Start sampling timer, initialise threshold, aquire first sample.
256
250
self .timer .init (freq = self .channel .sampling_rate )
257
251
self .timer .callback (self ._timer_ISR )
258
- if self .threshold_watcher :
259
- self .threshold_watcher .run_start (self .read_sample ())
260
252
self ._timer_ISR (0 )
261
253
262
254
def _run_stop (self ):
@@ -267,11 +259,9 @@ def _timer_ISR(self, t):
267
259
# Read a sample to the buffer, update write index.
268
260
sample = self .read_sample ()
269
261
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 )
262
+ if self .triggers :
263
+ for trigger in self .triggers :
264
+ trigger .check (sample )
275
265
276
266
def record (self ): # For backward compatibility.
277
267
pass
@@ -356,33 +346,89 @@ def send_buffer(self, run_stop=False):
356
346
fw .usb_serial .send (self .buffers [buffer_n ])
357
347
358
348
349
+ class ValueTrigger (IO_object ):
350
+ # Generates framework events when an analog signal goes above or below specified threshold value.
351
+
352
+ def __init__ (self , threshold , rising_event = None , falling_event = None ):
353
+ if rising_event is None and falling_event is None :
354
+ raise ValueError ("Either rising_event or falling_event or both must be specified." )
355
+ self .rising_event = rising_event
356
+ self .falling_event = falling_event
357
+ self .set_threshold (threshold )
358
+ self .timestamp = 0
359
+ self .crossing_direction = False
360
+ assign_ID (self )
361
+
362
+ def _initialise (self ):
363
+ # Set event codes for rising and falling events.
364
+ self .rising_event_ID = sm .events [self .rising_event ] if self .rising_event in sm .events else False
365
+ self .falling_event_ID = sm .events [self .falling_event ] if self .falling_event in sm .events else False
366
+ self .threshold_active = self .rising_event_ID or self .falling_event_ID
367
+
368
+ def _process_interrupt (self ):
369
+ # Put event generated by threshold crossing in event queue.
370
+ if self .crossing_direction :
371
+ fw .event_queue .put (fw .Datatuple (self .timestamp , fw .EVENT_TYP , "i" , self .rising_event_ID ))
372
+ else :
373
+ fw .event_queue .put (fw .Datatuple (self .timestamp , fw .EVENT_TYP , "i" , self .falling_event_ID ))
374
+
375
+ @micropython .native
376
+ def check (self , sample ):
377
+ if self .reset_above_threshold :
378
+ # this gets run when the first sample is taken and whenever the threshold is changed
379
+ self .reset_above_threshold = False
380
+ self .above_threshold = sample > self .threshold
381
+ return
382
+ new_above_threshold = sample > self .threshold
383
+ if new_above_threshold != self .above_threshold : # Threshold crossing.
384
+ self .above_threshold = new_above_threshold
385
+ if (self .above_threshold and self .rising_event_ID ) or (not self .above_threshold and self .falling_event_ID ):
386
+ self .timestamp = fw .current_time
387
+ self .crossing_direction = self .above_threshold
388
+
389
+ interrupt_queue .put (self .ID )
390
+
391
+ def set_threshold (self , threshold ):
392
+ if not isinstance (threshold , int ):
393
+ raise ValueError (f"Threshold must be an integer, got { type (threshold ).__name__ } ." )
394
+ self .threshold = threshold
395
+ self .reset_above_threshold = True
396
+
397
+
359
398
class Crossing :
360
399
above = "above"
361
400
below = "below"
362
401
none = "none"
363
402
364
403
365
- class Analog_threshold_watcher (IO_object ):
404
+ class SchmittTrigger (IO_object ):
366
405
"""
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.
406
+ Generates framework events when an analog signal goes above an upper threshold and/or below a lower threshold.
407
+ The rising event is triggered when signal > upper bound, falling event is triggered when signal < lower bound.
408
+
409
+ This trigger implements hysteresis, which is a technique to prevent rapid oscillations or "bouncing" of events:
410
+ - Hysteresis creates a "dead zone" between the upper and lower thresholds
411
+ - Once a rising event is triggered (when signal crosses above the upper bound),
412
+ it cannot be triggered again until the signal has fallen below the lower bound
413
+ - Similarly, once a falling event is triggered (when signal crosses below the lower bound),
414
+ it cannot be triggered again until the signal has risen above the upper bound
415
+
416
+ This behavior is particularly useful for noisy signals that might otherwise rapidly cross a single threshold
417
+ multiple times, generating unwanted repeated events.
370
418
"""
371
419
372
- def __init__ (self , threshold , rising_event = None , falling_event = None ):
373
- self .set_threshold (threshold )
420
+ def __init__ (self , bounds , rising_event = None , falling_event = None ):
421
+ if rising_event is None and falling_event is None :
422
+ raise ValueError ("Either rising_event or falling_event or both must be specified." )
423
+ self .set_bounds (bounds )
374
424
self .rising_event = rising_event
375
425
self .falling_event = falling_event
376
426
self .timestamp = 0
377
- self .last_crossing = Crossing .none
378
427
assign_ID (self )
379
428
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."
429
+ def set_bounds (self , threshold ):
430
+ if isinstance (threshold , tuple ):
431
+ threshold_requirements_str = "The threshold must be a tuple of two integers (lower_bound, upper_bound) where lower_bound <= upper_bound."
386
432
if len (threshold ) != 2 :
387
433
raise ValueError ("{} is not a valid threshold. {}" .format (threshold , threshold_requirements_str ))
388
434
lower , upper = threshold
@@ -396,17 +442,14 @@ def set_threshold(self, threshold):
396
442
self .lower_threshold = lower
397
443
else :
398
444
raise ValueError ("{} is not a valid threshold. {}" .format (threshold , threshold_requirements_str ))
445
+ self .reset_crossing = True
399
446
400
447
def _initialise (self ):
401
448
# Set event codes for rising and falling events.
402
449
self .rising_event_ID = sm .events [self .rising_event ] if self .rising_event in sm .events else False
403
450
self .falling_event_ID = sm .events [self .falling_event ] if self .falling_event in sm .events else False
404
451
self .threshold_active = self .rising_event_ID or self .falling_event_ID
405
452
406
- def run_start (self , sample ):
407
- self .was_above = sample > self .upper_threshold
408
- self .was_below = sample < self .lower_threshold
409
-
410
453
def _process_interrupt (self ):
411
454
# Put event generated by threshold crossing in event queue.
412
455
if self .was_above :
@@ -416,6 +459,13 @@ def _process_interrupt(self):
416
459
417
460
@micropython .native
418
461
def check (self , sample ):
462
+ if self .reset_crossing :
463
+ # this gets run when the first sample is taken and whenever the threshold is changed
464
+ self .reset_crossing = False
465
+ self .was_above = sample > self .upper_threshold
466
+ self .was_below = sample < self .lower_threshold
467
+ self .last_crossing = Crossing .none
468
+ return
419
469
is_above_threshold = sample > self .upper_threshold
420
470
is_below_threshold = sample < self .lower_threshold
421
471
0 commit comments