"""
tic/toc function and a Ticker class
"""
from utime import ticks_us
import micropython
from pyb import Timer
import gc
from ._timer_channels import TICKER_TIMERS
class Timing:
"""Class wrapper around tic and toc functions
tic() and toc() are supposed to resemble their MATLAB equivalent.
This is a class just to avoid using a global variable for t0.
tic();toc() itself takes around 5 us.
"""
t0 = 0
[docs]def tic() -> None:
"""Start time measurement"""
Timing.t0 = ticks_us()
[docs]def toc() -> int:
"""End time measurement, print and return results in microseconds"""
t1 = ticks_us()
time_taken = t1 - Timing.t0
print('Time elapsed: %d us' % time_taken)
return time_taken
# Set mem for error messages while in Interrupt Service Routines (ISR)
# Without setting this memory exceptions cannot be mentioned properly
micropython.alloc_emergency_exception_buf(100)
[docs]class Ticker:
"""Timed callback manager
BioRobotics hardware timer ISR abstraction. Compared to pyb.Timer:
* Removing the need of explicit `tim.callback(micropython.schedule())`
* Timely garbage collection set on a ticker with `GC=Enable`
* Other timer related functions
Note: the ticker is not started on initialization!
"""
def __init__(self, timer: int, freq: float, callback: callable,
enable_gc: bool = False, use_hardware_id: bool = False):
"""Construct Ticker
:param timer: Number of the on-board hardware timer to use (start at 0)
:param freq: Frequency (in Hz) of the callback
:param callback: Callback to be attached to the timer
:param enable_gc: Set to True to disable general automatic garbage
collection and instead run GC at the end of the user callback -
This is recommended when the function is the `loop()` of the
program
:param use_hardware_id: When set to True, the `timer` id is used as
the id of an on-board timer. When set to False (default),
the id is used to count over general purpose timers. The latter
is useful to avoid conflicts with attached pins
"""
if use_hardware_id:
self.timer_number = timer
else:
if timer >= len(TICKER_TIMERS):
raise ValueError('Timer {} exceeds the {} available ticker '
'timers'.format(timer, len(TICKER_TIMERS)))
self.timer_number = TICKER_TIMERS[timer]
self.freq = freq
self.callback = callback
self.enable_gc = enable_gc
if self.enable_gc:
gc.disable()
# Create Timer, but leave callback for until it is started
self.timer = Timer(self.timer_number, freq=self.freq)
# Create a reference to the callback wrapper
# This is necessary to force correct allocation, see ISR documentation
self._callback_wrap_ref = self._callback_wrap
self.is_started = False
def _callback_wrap(self, _=None):
"""Wrapper around the user callback
The schedule function passes a single argument, so we have to
capture it in this method definition.
"""
self.callback()
# Collect garbage if this is the "main" loop
if self.enable_gc:
gc.collect()
[docs] def start(self):
"""Start ticker"""
# Attach callback function wrapper
# callback() passes a single argument, which is not used now
self.timer.callback(
lambda _=None: micropython.schedule(self._callback_wrap_ref, 0)
)
# We use the scheduler function instead of calling the function
# directly because it prevents ticker overloading and improves
# memory management. schedule() will run the target "very soon". See
# the micropython docs entry for more information.
self.is_started = True
[docs] def stop(self):
"""Stop ticker"""
self.timer.deinit() # Clears all callbacks
self.is_started = False