Source code for biorobotics._encoder

"""
Encoder class
"""

from pyb import Timer
from math import floor

from ._timer_channels import PINS_TO_TIMERS
from ._pins import make_pin


[docs]class Encoder: """Class to wrap around hardware timers used for motor encoders The encoder takes two digital-in pins, A and B, which need to be connected to the same hardware timer. The encoder is always type X4. The class also takes care of unwrapping the value. The returned value will continue indefinitely, ignoring the integer overflow. """ def __init__(self, pin_a: str, pin_b: str): """Constructor :param pin_a: Encoder pin A (name or Pin object) :param pin_b: Encoder pin B (name or Pin object) :raise ValueError: If any of the selected pins do not have a registered timer :raise ValueError: If the pins do not share the same timer """ self.pin_a = make_pin(pin_a) self.pin_b = make_pin(pin_b) for pin in [self.pin_a.name(), self.pin_b.name()]: if pin not in PINS_TO_TIMERS: raise ValueError('Pin `P{}` it not connected to a hardware ' 'timer'.format(pin)) timer_a, channel_a = PINS_TO_TIMERS[self.pin_a.name()] timer_b, channel_b = PINS_TO_TIMERS[self.pin_b.name()] if timer_a != timer_b: raise ValueError('The pins `P{}` and `P{}` are not connected to ' 'the same hardware timer and cannot be used for ' 'an encoder together'.format(self.pin_a.name(), self.pin_b.name())) self._period = 0xFFFF # 65535 self.timer = Timer(timer_a, prescaler=0, period=self._period) # Note: the hardware timer has 16 bits, so after 65535 the counter # will fall back to 0. self.timer.channel(channel_a, Timer.ENC_AB, pin=self.pin_a) self.timer.channel(channel_b, Timer.ENC_AB, pin=self.pin_b) self._loop_count = 0 # Number of integer overflows (not the number # of axis revolutions! self._last_counter = None # Value of the counter on the last check # Start with None, so the first call to counter() can be distinguished
[docs] def counter(self) -> int: """Return pulse count Pulse count has already been unwrapped, it will start at 0 and increase and decrease indefinitely (including going negative). Note: the unwrap happens when calling this read function, so it is possible to miss an encoder wrap if the read function is called only very rarely. """ counter = self.timer.counter() if self._last_counter is not None: # Don't try to unwrap on the # first call, in case timer.counter() is non-zero diff = counter - self._last_counter # If diff is to large we # probably wrapped if 2 * abs(diff) > self._period: if diff < 0: self._loop_count += 1 # If counter is suddenly low else: self._loop_count -= 1 # If counter is suddenly high self._last_counter = counter # Update history # (period + 1) because counter = 0 with 1 loop should be output 65536 return counter + self._loop_count * (self._period + 1)
[docs] def set_counter(self, new_counter: int): """Reset the counter to a specified value""" pulses = new_counter % (self._period + 1) # Always positive self.timer.counter(pulses) # Reset timer counter self._last_counter = pulses # Reset history to prevent unwrapping self._loop_count = floor(new_counter / (self._period + 1)) # Set loops