Esempio n. 1
0
class UltraSonicMeter(object):

    def __init__(self):
        self.tmp = self.time = 0
        self.cnt = 0
        self.fr = 0
        self.trig = Pin('X12', Pin.OUT_PP, Pin.PULL_NONE)
        echoR = Pin('X1', Pin.IN, Pin.PULL_NONE)
        echoF = Pin('X2', Pin.IN, Pin.PULL_NONE)
        self.micros = pyb.Timer(5, prescaler=83, period=0x3fffffff)
        self.timer = Timer(2, freq=1000)
        self.timer.period(3600)
        self.timer.prescaler(1375)
        self.timer.callback(lambda e: self.run_trig())
        extR = ExtInt(echoR, ExtInt.IRQ_RISING, Pin.PULL_NONE, self.start_count)
        extF = ExtInt(echoF, ExtInt.IRQ_FALLING, Pin.PULL_NONE, self.read_dist)

    def run_trig(self):
        self.trig.high()
        pyb.udelay(1)
        self.trig.low()

    def start_count(self, line):
        self.micros.counter(0)
        self.time = self.micros.counter()
        self.timer.counter(0)

    def read_dist(self, line):
        end = self.micros.counter()
        micros = end-self.time
        distP1 = micros//5
        distP2 = micros//6
        distP3 = (distP1-distP2)//10*2
        dist = distP2+distP3

        if dist != 0:
            self.cnt += 1
            self.fr += dist

        if self.cnt == 15:
            tmp = self.tmp
            dist = self.fr//self.cnt
            if tmp != dist:
                print(dist, 'mm')
                self.tmp = dist
            self.cnt = 0
            self.fr  = 0
Esempio n. 2
0
class LedController():
    def __init__(self):
        self.red_led = pyb.LED(1)
        self.green_led = pyb.LED(2)
        self.blue_led = pyb.LED(3)

        self.led_timer = Timer(LED_TIMER_ID, freq=50, callback=self.leds_on)

        self.red_channel = self.led_timer.channel(1,
                                                  Timer.PWM,
                                                  callback=self.red_led_off,
                                                  pulse_width_percent=0)
        self.green_channel = self.led_timer.channel(
            2, Timer.PWM, callback=self.green_led_off, pulse_width_percent=0)
        self.blue_channel = self.led_timer.channel(3,
                                                   Timer.PWM,
                                                   callback=self.blue_led_off,
                                                   pulse_width_percent=0)

    def leds_on(self, timer):
        self.red_led.on()
        self.green_led.on()
        self.blue_led.on()

    def red_led_off(self, timer):
        self.red_led.off()

    def green_led_off(self, timer):
        self.green_led.off()

    def blue_led_off(self, timer):
        self.blue_led.off()

    def set_pw_colors(self, i, red_channel, green_channel, blue_channel):
        red_on = max(0, math.sin(i) * RBG_MAX)
        green_on = max(0, math.sin(i - DEG_120) * RBG_MAX)
        blue_on = max(0, math.sin(i - DEG_240) * RBG_MAX)

        debug("%f %f %f" % (red_on, blue_on, green_on))

        self.red_channel.pulse_width_percent(red_on)
        self.green_channel.pulse_width_percent(green_on)
        self.blue_channel.pulse_width_percent(blue_on)

    def pulse(self, duration_ms):
        startTime = pyb.millis()
        while (duration_ms < 0 or pyb.elapsed_millis(startTime) < duration_ms):
            self.set_pw_colors(pyb.millis() / 1000, self.red_channel,
                               self.green_channel, self.blue_channel)
            pyb.udelay(self.led_timer.period())

        self.led_timer.deinit()
        self.red_led_off(None)
        self.green_led_off(None)
        self.blue_led_off(None)
Esempio n. 3
0
class LedController():
    def __init__(self):
        self.led_timer = Timer(LED_TIMER_ID, freq=50, callback=leds_on)

        self.red_channel = self.led_timer.channel(1,
                                                  Timer.PWM,
                                                  callback=red_led_off,
                                                  pulse_width_percent=0)
        self.green_channel = self.led_timer.channel(2,
                                                    Timer.PWM,
                                                    callback=green_led_off,
                                                    pulse_width_percent=0)
        self.blue_channel = self.led_timer.channel(3,
                                                   Timer.PWM,
                                                   callback=blue_led_off,
                                                   pulse_width_percent=0)

    def pulse(self):
        startTime = pyb.millis()
        while (pyb.elapsed_millis(startTime) < 12000):
            set_pw_colors(pyb.millis() / 1000, self.red_channel,
                          self.green_channel, self.blue_channel)
            pyb.udelay(self.led_timer.period())
Esempio n. 4
0
# check basic functionality of the timer class

import pyb
from pyb import Timer

tim = Timer(4)
tim = Timer(4, prescaler=100, period=200)
print(tim.prescaler())
print(tim.period())
tim.prescaler(300)
print(tim.prescaler())
tim.period(400)
print(tim.period())

# Setting and printing frequency
tim = Timer(2, freq=100)
print(tim.freq())
tim.freq(0.001)
print("{:.3f}".format(tim.freq()))
Esempio n. 5
0
class RomiMotor :
  """
  We reuse the same timer for all instances of the class, so this is the callback
  of the class, which calls the handlers in each instance.
  """
  @classmethod
  def class_rpm_handler(cls, tim) :
    for h in cls.rpm_handlers :
      h(tim)
  
  # List of the rpm handlers of the instances, necessary because we share a single timer.
  rpm_handlers = []
  # The shared timer
  rpmtimer = None
  
  """
  Initialize a RomiMotor, connected either to the 'X' side or the 'Y' side of the Pyboard.
  On either side (? is 'X' or 'Y'):
    - PWM is on pin ?1
    - DIR is on pin ?2
    - SLEEP is on pin ?3
    - ENCA is on pin ?4
    - ENCB is on pin ?5
  """
  def __init__(self, X=True) :
    if X :
      self.pwmpin = Pin('X6', Pin.OUT_PP)
      self.pwmtim = Timer(2, freq=5000)
      self.pwm = self.pwmtim.channel(1, mode=Timer.PWM, pin=self.pwmpin)
      self.pwm.pulse_width(0)
      self.dir = Pin('X2', Pin.OUT_PP)
      self.dir.off()          # O = forward, 1 = reverse
      self.sleep = Pin('X3', Pin.OUT_PP)
      self.sleep.value(0)        # 0 = sleep, 1 = active
      self.enca = Pin('X4', Pin.IN, Pin.PULL_UP)
      self.encb = Pin('X5', Pin.IN, Pin.PULL_UP)
    else :
      self.pwmpin = Pin('Y1', Pin.OUT_PP)
      self.pwmtim = Timer(8, freq=5000)
      self.pwm = self.pwmtim.channel(1, mode=Timer.PWM, pin=self.pwmpin)
      self.pwm.pulse_width(0)
      self.dir = Pin('Y2', Pin.OUT_PP)
      self.dir.off()          # O = forward, 1 = reverse
      self.sleep = Pin('Y3', Pin.OUT_PP)
      self.sleep.value(0)        # 0 = sleep, 1 = active
      self.enca = Pin('Y4', Pin.IN, Pin.PULL_UP)
      self.encb = Pin('Y5', Pin.IN, Pin.PULL_UP)
    self.pwmscale = (self.pwmtim.period() + 1) // 10000 # scale factot for permyriad(10000 as it allows to get more distinct points and avoid divisions) power
    self.count_a = 0      # counter for impulses on the A output of the encoder
    self.target_a = 0     # target value for the A counter (for controlled rotation)
    self.count_b = 0      # counter for impulses on the B output of the encoder
    self.time_a = 0       # last time we got an impulse on the A output of the encoder
    self.time_b = 0       # last time we got an impulse on the B output of the encoder
    self.elapsed_a_b = 0  # time elapsed between an impulse on A and an impulse on B
    self.dirsensed = 0    # direction sensed through the phase of the A and B outputs
    self.rpm = 0          # current speed in rotations per second
    self.rpm_last_a = 0   # value of the A counter when we last computed the rpms
    self.cruise_rpm = 0   # target value for the rpms
    self.walking = False  # Boolean that indicates if the robot is walking or stationary
    self.desiredDir = True# Boolean that indecates the desired direction of the motor for move function


    self._control = PID() # PID control for the rotation of the wheels
    self._control.setTunings(KP, KI, KD);
    self._control.setSampleTime(1);
    self._control.setOutputLimits(-10000, 10000)

    self._control_distance = PID() # PID control for the cascade of the distance
    self._control_distance.setTunings(KP_DISTANCE, KI_DISTANCE, KD_DISTANCE);
    self._control_distance.setSampleTime(1);
    self._control_distance.setOutputLimits(-MAXIMUM_VELOCITY, MAXIMUM_VELOCITY)


    ExtInt(self.enca, ExtInt.IRQ_RISING, Pin.PULL_UP, self.enca_handler)
    ExtInt(self.encb, ExtInt.IRQ_RISING, Pin.PULL_UP, self.encb_handler)
    if RomiMotor.rpmtimer is None :   # create only one shared timer for all instances
      RomiMotor.rpmtimer = Timer(4)
      RomiMotor.rpmtimer.init(freq=100, callback=RomiMotor.class_rpm_handler)
    RomiMotor.rpm_handlers.append(self.rpm_handler) # register the handler for this instance
  
  """
  Handler for interrupts caused by impulses on the A output of the encoder.
  This is where we sense the rotation direction and adjust the throttle to 
  reach a target number of rotations of the wheel.
  """
  def enca_handler(self, pin) :
    self.count_a += 1
    if self.encb.value():
      self.dirsensed = -1   # A occurs before B
    else :
      self.dirsensed = 1    # B occurs before A
    if self.target_a > 0 :  # If we have a target rotation      

      if self.count_a >= self.target_a :
        self.cruise_rpm = 0
        self.pwm.pulse_width(0)   # If we reached of exceeded the rotation, stop the motor
        self.target_a = 0         # remove the target
        self.walking = False

      else:                       # The cascade control for the distance
        self._control_distance.setSetPoint(self.target_a)
        self.cruise_rpm = self._control_distance.compute(self.count_a)
        self.walking = True

  
        

  """
  Handler for interrupts caused by impulses on the B output of the encoder.
  """
  def encb_handler(self, pin) :
    self.count_b += 1


  """
  This is the handler of the timer interrupts to compute the rpm
  """
  def rpm_handler(self, tim) :

    self.rpm =  100*(self.count_a - self.rpm_last_a)   # The timer is at 100Hz
    self.rpm_last_a = self.count_a      # Memorize the number of impulses on A
    
    if self.cruise_rpm != 0 :           # If we have an rpm target

      self._control.setSetPoint(self.cruise_rpm)
      output = self._control.compute(self.rpm)
      if output < 0 or self.desiredDir == False:  # Corrects the control output for the desired direction
        if output < 0:
          output = -output
        self.dir.off()
      else :
        self.dir.on()

      self.pwm.pulse_width(output * self.pwmscale) 
      self.walking = True
      self.sleep.on()
    else:
      self.walking = False
  
  """
  Set the power of the motor in percents.
  Positive values go forward, negative values go backward.
  """
  def throttle(self, pct) :
    if pct is None :
      return
    if pct < 0 :
      self.dir.off()
      pct = -pct
    else :
      self.dir.on()
    self.pwm.pulse_width(100 * pct * self.pwmscale)
    self.walking = True
    self.sleep.on()
  
  """
  Get the current power as a percentage of the max power.
  The result is positive if the motor runs forward, negative if it runs backward.
  """
  def getThrottle(self) :
    thr = self.pwm.pulse_width() // self.pwmscale
    if self.dir.value() > 0 :
      thr = -thr
    return thr
  
  """
  Release the motor to let it rotate freely.
  """
  def release(self, release=True) :
    if release :
      self.sleep.off()
    else :
      self.sleep.on()

  """
  Perform 'tics' 1/360 rotation of the wheel at 'power' percents of the max power.
  If 'turns' is positive, the wheel turns forward, if it is negative, it turns backward.
  """
  def rotatewheel(self, tics, power=20):
    if tics < 0 :
      self.desiredDir = False
      tics = -tics
    else :
      self.desiredDir = True
    self.count_a = 0
    self.count_b = 0
    self._control_distance.setOutputLimits(-power*100, power*100)
    self.target_a = int(tics)
  
  """
  Wait for the rotations requested by 'rotatewheel' to be done.
  """
  def wait(self) :
    while self.target_a !=0 :
      pass

  """
  Set a target rpms. The wheel turns in its current rotation direction,
  'rpm' should be non negative.
  """
  def cruise(self, rpm) :
    if rpm < 0 :
      self.desiredDir = False
      rpm = -rpm
    else :
      self.desiredDir = True    
    self.cruise_rpm = int(rpm * 6)
  
  """
  Get the current rpms. This is always non negative, regardless of the rotation direction.
  """
  def get_rpms(self) :
    return self.rpm / 60
  
  """
  Cancel all targets of rotation and rpm
  """
  def clear(self) :
    self.target_a = 0
    self.cruise_rpm = 0
    self.desiredDir = True
    self.rpm = 0
    self.rpm_last_a = 0
    self.count_a = 0
    self.count_b = 0
    self.pwm.pulse_width(0)

  """
  Stop the motor.
  """
  def stop(self) :
    self.clear()
    self.throttle(0)
Esempio n. 6
0
# check basic functionality of the timer class

import pyb
from pyb import Timer

tim = Timer(4)
tim = Timer(4, prescaler=100, period=200)
print(tim.prescaler())
print(tim.period())
tim.prescaler(300)
print(tim.prescaler())
tim.period(400)
print(tim.period())
Esempio n. 7
0
class Encoder():
    """
    Abstracts a quadrature encoder to give a tick count.
    The count is a signed integer starting at zero. It wraps the count from the
    attached timer to give a seamless and continuous tick count. Overflows of
    the internal timer counter register should be adequatly handled. If
    overflows or weird behaviour occurs around the overflow points, try
    increasing Encoder.HYSTERESIS.
    Note: Only works on pin pairs 'X1' & 'X2', 'X9' & 'X10', or 'Y1', 'Y2'. The
    timer will be automatically selected. Both Timer 2 and 5 work for 'X1' &
    'X2', but Timer 5 is preferred because Timer 2 is used for LED PWM but both
    can be used by changing the values of Encoder.AF_MAP.
    """
    # Constant for decoding in single line mode
    SINGLE_MODE = Timer.ENC_A
    # Constant for decoding in quad mode
    DUAL_MODE = Timer.ENC_AB
    # Maps alternate pin function descriptions to the required timer number
    TIMER_MAP = {'AF1_TIM2': 2, 'AF2_TIM4': 4, 'AF2_TIM5': 5, 'AF3_TIM8': 8}
    # Maps pin names to the alternate function to use
    AF_MAP = {
        'X1': 'AF2_TIM5',
        'X2': 'AF2_TIM5',
        'X9': 'AF2_TIM4',
        'X10': 'AF2_TIM4',
        'Y1': 'AF3_TIM8',
        'Y2': 'AF3_TIM8'
    }
    # Defines the pin pairs that must be used
    PIN_PAIRS = [['X1', 'X9', 'Y1'], ['X2', 'X10', 'Y2']]
    # Hysteresis value to overflow detection
    HYSTERESIS = 12  # One full rotation of encoder

    def __init__(self, pinA, pinB, mode=DUAL_MODE):
        """
        Instantiate an Encoder object.
        Initalises the Pins, Timer and TimerChannel for use as a quadrature
        decoder. Registers an overflow callback to elegantly handle counter
        register overflows.
        pinA: Any valid value taken by pyb.Pin constructor
        pinB: Any valid value taken by pyb.Pin constructor
        mode: Mode to use for decoding (Encoder.SINGLE_MODE or
                Encoder.DUAL_MODE)
        raises: Any exception thrown by pyb.Pin, pyb.Timer, pyb.Timer.channel
                or Exception if pins are not compatible pairs
        """
        self._chA = Pin(pinA)
        self._chB = Pin(pinB)
        self._ticks = 0

        # Check pins are compatible
        self._checkPins(pinA, pinB)

        # init pins for alternate encoder function
        af = self.AF_MAP[self._chA.names()[1]]
        channel = self.TIMER_MAP[af]
        af = getattr(Pin, af)
        self._chA.init(Pin.AF_PP, pull=Pin.PULL_NONE, af=af)
        self._chB.init(Pin.AF_PP, pull=Pin.PULL_NONE, af=af)
        # init timer
        self._timer = Timer(channel, prescaler=0, period=100000)
        # init encoder mode
        # self._channel = self._timer.channel(1, mode)
        self._timer.channel(1, mode)
        # setup overflow callback
        self._timer.callback(self._overflow)
        # init count register to middle of count
        self._timer.counter(self._timer.period() // 2)
        self._lastRead = self._timer.counter()

    def _checkPins(self, pinA, pinB):
        """
        Check that two pins can be used for a decoding and are on the same
        timer.
        """
        try:
            if pinA in self.PIN_PAIRS[0]:
                if self.PIN_PAIRS[0].index(pinA) != self.PIN_PAIRS[1].index(
                        pinB):
                    raise Exception()
            elif pinA in self.PIN_PAIRS[1]:
                if self.PIN_PAIRS[0].index(pinB) != self.PIN_PAIRS[1].index(
                        pinA):
                    raise Exception()
            else:
                raise Exception()
        except:
            raise Exception(pinA + ' & ' + pinB + ' are not on the same Timer')

    def ticks(self, ticks=None):
        """
        Get or set the current tick count.
        Ticks is a signed integer.
        """
        if ticks is not None:  # set ticks to desired value
            self._ticks = ticks
        else:  # retrieve latest count and update internals
            count = self._timer.counter()
            self._ticks = self._ticks + (count - self._lastRead)
            self._lastRead = count
        return self._ticks

    def _overflow(self, timer):
        """
        Timer overflow callback to gracefully handle overflow events. If
        weird things are occurring, try increasing the HYSTERESIS value.
        """
        count = timer.counter()
        if 0 <= count <= self.HYSTERESIS:  # overflow
            self._ticks = self._ticks + (timer.period() - self._lastRead +
                                         count)
            self._lastRead = count
        elif (timer.period() -
              self.HYSTERESIS) <= count <= timer.period():  # underflow
            self._ticks = self._ticks - (self._lastRead + timer.period() -
                                         count)
            self._lastRead = count
        else:  # hysteresis not big enough
            #LED(1).on() # turn on inbuilt red led to show error
            pass
Esempio n. 8
0
class Encoder():
    """
    Abstracts a quadrature encoder to give a tick count.
    The count is a signed integer starting at zero. It wraps the count from the
    attached timer to give a seamless and continuous tick count. Overflows of
    the internal timer counter register should be adequatly handled. If
    overflows or weird behaviour occurs around the overflow points, try
    increasing Encoder.HYSTERESIS.
    Note: Only works on pin pairs 'X1' & 'X2', 'X9' & 'X10', or 'Y1', 'Y2'. The
    timer will be automatically selected. Both Timer 2 and 5 work for 'X1' &
    'X2', but Timer 5 is preferred because Timer 2 is used for LED PWM but both
    can be used by changing the values of Encoder.AF_MAP.
    """
    # Constant for decoding in single line mode
    SINGLE_MODE = Timer.ENC_A
    # Constant for decoding in quad mode
    DUAL_MODE = Timer.ENC_AB
    # Maps alternate pin function descriptions to the required timer number
    TIMER_MAP = {'AF1_TIM2': 2, 'AF2_TIM4': 4, 'AF2_TIM5': 5, 'AF3_TIM8': 8}
    # Maps pin names to the alternate function to use
    AF_MAP = {'X1' : 'AF2_TIM5', 'X2': 'AF2_TIM5', 'X9': 'AF2_TIM4',
              'X10': 'AF2_TIM4', 'Y1': 'AF3_TIM8', 'Y2': 'AF3_TIM8'}
    # Defines the pin pairs that must be used
    PIN_PAIRS = [['X1','X9','Y1'],['X2','X10','Y2']]
    # Hysteresis value to overflow detection
    HYSTERESIS = 12 # One full rotation of encoder

    def __init__(self, pinA, pinB, mode = DUAL_MODE):
        """
        Instantiate an Encoder object.
        Initalises the Pins, Timer and TimerChannel for use as a quadrature
        decoder. Registers an overflow callback to elegantly handle counter
        register overflows.
        pinA: Any valid value taken by pyb.Pin constructor
        pinB: Any valid value taken by pyb.Pin constructor
        mode: Mode to use for decoding (Encoder.SINGLE_MODE or
                Encoder.DUAL_MODE)
        raises: Any exception thrown by pyb.Pin, pyb.Timer, pyb.Timer.channel
                or Exception if pins are not compatible pairs
        """
        self._chA = Pin(pinA)
        self._chB = Pin(pinB)
        self._ticks = 0

        # Check pins are compatible
        self._checkPins(pinA, pinB)

        # init pins for alternate encoder function
        af = self.AF_MAP[self._chA.names()[1]]
        channel = self.TIMER_MAP[af]
        af = getattr(Pin, af)
        self._chA.init(Pin.AF_PP, pull = Pin.PULL_NONE, af = af)
        self._chB.init(Pin.AF_PP, pull = Pin.PULL_NONE, af = af)
        # init timer
        self._timer = Timer(channel, prescaler = 0, period = 100000)
        # init encoder mode
        # self._channel = self._timer.channel(1, mode)
        self._timer.channel(1, mode)
        # setup overflow callback
        self._timer.callback(self._overflow)
        # init count register to middle of count
        self._timer.counter(self._timer.period()//2)
        self._lastRead = self._timer.counter()

    def _checkPins(self, pinA, pinB):
        """
        Check that two pins can be used for a decoding and are on the same
        timer.
        """
        try:
            if pinA in self.PIN_PAIRS[0]:
                if self.PIN_PAIRS[0].index(pinA) != self.PIN_PAIRS[1].index(pinB):
                    raise Exception()
            elif pinA in self.PIN_PAIRS[1]:
                if self.PIN_PAIRS[0].index(pinB) != self.PIN_PAIRS[1].index(pinA):
                    raise Exception()
            else:
                raise Exception()
        except:
            raise Exception(pinA + ' & ' + pinB + ' are not on the same Timer')

    def ticks(self, ticks = None):
        """
        Get or set the current tick count.
        Ticks is a signed integer.
        """
        if ticks is not None: # set ticks to desired value
            self._ticks = ticks
        else: # retrieve latest count and update internals
            count = self._timer.counter()
            self._ticks = self._ticks + (count - self._lastRead)
            self._lastRead = count
        return self._ticks

    def _overflow(self, timer):
        """
        Timer overflow callback to gracefully handle overflow events. If
        weird things are occurring, try increasing the HYSTERESIS value.
        """
        count = timer.counter()
        if 0 <= count <= self.HYSTERESIS: # overflow
            self._ticks = self._ticks + (timer.period() - self._lastRead + count)
            self._lastRead = count
        elif (timer.period() - self.HYSTERESIS) <= count <= timer.period(): # underflow
            self._ticks = self._ticks - (self._lastRead + timer.period() - count)
            self._lastRead = count
        else: # hysteresis not big enough
            #LED(1).on() # turn on inbuilt red led to show error
            pass
Esempio n. 9
0
class uPyBot:
    """This class represent uPyBot robot"""

    #define various useful constants used by uPyBot programs
    LOW_SPEED = const(85)
    MID_SPEED = const(95)
    HIGH_SPEED = const(100)

    TURN_90 = const(6)
    TURN_180 = const(17)
    TURN_360 = const(34)

    WHEEL_TURN_90 = const(17)
    WHEEL_TURN_180 = const(35)
    WHEEL_TURN_360 = const(70)

    STEP_BACK_DISTANCE = const(7)

    LOW_DISTANCE = const(10)

    STALL_CHECK_TIME_MS = const(100)

    MOVE_OK = const(0)
    MOVE_OBSTACLE = const(1)
    MOVE_STALL = const(2)

    MOVE_MIN = const(10)
    MOVE_STEP = const(10)
    MOVE_MAX = const(100)

    SYMMETRIC_TURN = const(1)
    ASYMMETRIC_TURN = const(2)
    LEFT_TURN = const(1)
    RIGHT_TURN = const(2)

    def __init__(self):
        self._tmr2 = Timer(2, freq=20000,
                           mode=Timer.CENTER)  #used for PWM for motor control
        self._ch1 = self._tmr2.channel(1,
                                       Timer.PWM,
                                       pin=Pin.board.X1,
                                       pulse_width=(self._tmr2.period() + 1) //
                                       2)
        self._ch4 = self._tmr2.channel(4,
                                       Timer.PWM,
                                       pin=Pin.board.X4,
                                       pulse_width=(self._tmr2.period() + 1) //
                                       2)
        self._us_tmr = Timer(
            3, prescaler=83,
            period=0x3fffffff)  #used to measure US sensor pulse duration
        self._pin_in1 = Pin(Pin.board.X3, Pin.OUT_PP)  #motor control pins
        self._pin_in2 = Pin(Pin.board.X2, Pin.OUT_PP)
        self._pin_in3 = Pin(Pin.board.X5, Pin.OUT_PP)
        self._pin_in4 = Pin(Pin.board.X6, Pin.OUT_PP)
        self._pin_int7 = Pin(Pin.board.X7, Pin.IN)  #encoder interrupt pins
        self._pin_int8 = Pin(Pin.board.X8, Pin.IN)
        self._pin_trigger = Pin(Pin.board.X11, Pin.OUT_PP)  #US trig pin
        self._pin_echo = Pin(Pin.board.X12, Pin.IN)  #US echo pin
        self._pin_beep = Pin(Pin.board.X19, Pin.OUT_PP)  #beeper ctrl pin
        self._beeper = Beeper(self._pin_beep)
        self._left_motor = Motor(self._pin_in1, self._pin_in2, self._ch1)
        self._left_encoder = Encoder(self, self._pin_int7)
        self._right_motor = Motor(self._pin_in4, self._pin_in3, self._ch4)
        self._right_encoder = Encoder(self, self._pin_int8)
        self._us = UltraSonicSensor(self._pin_trigger, self._pin_echo,
                                    self._us_tmr)

    def beep(self, duration=Beeper.BEEP_DEFAULT_DURATION_MS):
        self._beeper.beep(duration)

    def stop(self):
        self._left_motor.stop()
        self._right_motor.stop()
        self._left_encoder.force_stop_cnt()
        self._right_encoder.force_stop_cnt()

    def forward(
        self, pulses, speed
    ):  #move forward specified number of the encoder pulses with specified speed
        self._left_encoder.set_stop_pulses(pulses)
        self._right_encoder.set_stop_pulses(pulses)
        self._left_motor.set_speed(speed)
        self._right_motor.set_speed(speed)
        self._left_motor.set_forward()
        self._right_motor.set_forward()

    def backward(
        self, pulses, speed
    ):  #move backward specified number of the encoder pulses with specified speed
        self._left_encoder.set_stop_pulses(pulses)
        self._right_encoder.set_stop_pulses(pulses)
        self._left_motor.set_speed(speed)
        self._right_motor.set_speed(speed)
        self._left_motor.set_backward()
        self._right_motor.set_backward()

    def turn_left(self, pulses):  #turn specified number of pulses
        self._left_encoder.set_stop_pulses(pulses)
        self._right_encoder.set_stop_pulses(pulses)
        self._left_motor.set_speed(uPyBot.HIGH_SPEED)
        self._right_motor.set_speed(uPyBot.HIGH_SPEED)
        self._left_motor.set_backward()
        self._right_motor.set_forward()

    def turn_right(self, pulses):
        self._left_encoder.set_stop_pulses(pulses)
        self._right_encoder.set_stop_pulses(pulses)
        self._left_motor.set_speed(uPyBot.HIGH_SPEED)
        self._right_motor.set_speed(uPyBot.HIGH_SPEED)
        self._left_motor.set_forward()
        self._right_motor.set_backward()

    def left_wheel_forward(self, pulses):
        self._left_encoder.set_stop_pulses(pulses)
        self._right_encoder.set_stop_pulses(0)
        self._left_motor.set_speed(uPyBot.HIGH_SPEED)
        self._right_motor.set_speed(0)
        self._left_motor.set_forward()
        self._right_motor.stop()

    def left_wheel_backward(self, pulses):
        self._left_encoder.set_stop_pulses(pulses)
        self._right_encoder.set_stop_pulses(0)
        self._left_motor.set_speed(uPyBot.HIGH_SPEED)
        self._right_motor.set_speed(0)
        self._left_motor.set_backward()
        self._right_motor.stop()

    def right_wheel_forward(self, pulses):
        self._left_encoder.set_stop_pulses(0)
        self._right_encoder.set_stop_pulses(pulses)
        self._left_motor.set_speed(0)
        self._right_motor.set_speed(uPyBot.HIGH_SPEED)
        self._left_motor.stop()
        self._right_motor.set_forward()

    def right_wheel_backward(self, pulses):
        self._left_encoder.set_stop_pulses(0)
        self._right_encoder.set_stop_pulses(pulses)
        self._left_motor.set_speed(0)
        self._right_motor.set_speed(uPyBot.HIGH_SPEED)
        self._left_motor.stop()
        self._right_motor.set_backward()

    def is_stopped(self):
        return self._left_encoder.is_stopped(
        ) and self._right_encoder.is_stopped()

    def step_back(self):
        self.backward(uPyBot.STEP_BACK_DISTANCE, uPyBot.MID_SPEED)

    def is_stall(
        self
    ):  #check if any encoder is not moving since last check what means motor stall if run
        if self._left_encoder.is_moving():
            if not self._left_encoder.is_changing():
                return True
        if self._right_encoder.is_moving():
            if not self._right_encoder.is_changing():
                return True
        return False

    def wait_until_move_done(
        self
    ):  #when movement is triggered and ongoing check for obstacle and motor stall
        start = time.ticks_ms()
        while not self.is_stopped():
            if self._us.distance_to_obstacle_in_cm(
            ) < uPyBot.LOW_DISTANCE:  #detect obstacle
                self.stop()
                return uPyBot.MOVE_OBSTACLE
            if time.ticks_diff(
                    time.ticks_ms(), start
            ) > uPyBot.STALL_CHECK_TIME_MS:  #detect motor blockage (encoder counted pulses during STALL_CHECK_TIME_MS should change)
                start = time.ticks_ms()
                if self.is_stall():
                    return uPyBot.MOVE_STALL
        return uPyBot.MOVE_OK

    def rnd_move(self):  #make rundom distance move forward
        distance = random.randint(uPyBot.MOVE_MIN, uPyBot.MOVE_MAX)
        self.forward(distance, uPyBot.HIGH_SPEED)
        result = self.wait_until_move_done()
        if result != uPyBot.MOVE_OK:
            self.step_back()
        return result

    def symmetric_turn(self, direction, angle):
        if direction == uPyBot.LEFT_TURN:
            self.turn_left(angle)
        else:
            self.turn_right(angle)
        result = self.wait_until_move_done()
        if result != uPyBot.MOVE_OK:
            self.step_back()

    def asymmetric_turn(self, direction, angle):
        #turn around one of the wheels
        if direction == uPyBot.LEFT_TURN:
            self.right_wheel_forward(angle)
        else:
            self.left_wheel_forward(angle)
        result = self.wait_until_move_done()
        if result != uPyBot.MOVE_OK:
            self.step_back()

    def rnd_turn(
        self, move_result
    ):  #make rundom turn of random type but only if not an obstacle detected for which 90 deg turn is executed always
        turn_type = random.choice(
            [uPyBot.SYMMETRIC_TURN, uPyBot.ASYMMETRIC_TURN])
        turn_direction = random.choice([uPyBot.LEFT_TURN, uPyBot.RIGHT_TURN])
        turn_angle = random.choice([uPyBot.TURN_90, uPyBot.TURN_180])
        wheel_turn_angle = random.choice(
            [uPyBot.WHEEL_TURN_90, uPyBot.WHEEL_TURN_180])
        if move_result != uPyBot.MOVE_OK:
            self.symmetric_turn(turn_direction, uPyBot.TURN_90)
        else:
            if turn_type == uPyBot.SYMMETRIC_TURN:
                self.symmetric_turn(turn_direction, turn_angle)
            else:
                self.asymmetric_turn(turn_direction, wheel_turn_angle)
Esempio n. 10
0
class RomiMotor:
    """
  We reuse the same timer for all instances of the class, so this is the callback
  of the class, which calls the handlers in each instance.
  """
    @classmethod
    def class_rpm_handler(cls, tim):
        for h in cls.rpm_handlers:
            h(tim)

    # List of the rpm handlers of the instances, necessary because we share a single timer.
    rpm_handlers = []
    # The shared timer
    rpmtimer = None
    """
  Initialize a RomiMotor, connected either to the 'X' side or the 'Y' side of the Pyboard.
  On either side (? is 'X' or 'Y'):
    - PWM is on pin ?1
    - DIR is on pin ?2
    - SLEEP is on pin ?3
    - ENCA is on pin ?4
    - ENCB is on pin ?5
  """

    def __init__(self, X=True):
        if X:
            self.pwmpin = Pin('X1', Pin.OUT_PP)
            self.pwmtim = Timer(2, freq=5000)
            self.pwm = self.pwmtim.channel(1, mode=Timer.PWM, pin=self.pwmpin)
            self.pwm.pulse_width(0)
            self.dir = Pin('X2', Pin.OUT_PP)
            self.dir.off()  # O = forward, 1 = reverse
            self.sleep = Pin('X3', Pin.OUT_PP)
            self.sleep.value(0)  # 0 = sleep, 1 = active
            self.enca = Pin('X4', Pin.IN, Pin.PULL_UP)
            self.encb = Pin('X5', Pin.IN, Pin.PULL_UP)
        else:
            self.pwmpin = Pin('Y1', Pin.OUT_PP)
            self.pwmtim = Timer(8, freq=5000)
            self.pwm = self.pwmtim.channel(1, mode=Timer.PWM, pin=self.pwmpin)
            self.pwm.pulse_width(0)
            self.dir = Pin('Y2', Pin.OUT_PP)
            self.dir.off()  # O = forward, 1 = reverse
            self.sleep = Pin('Y3', Pin.OUT_PP)
            self.sleep.value(0)  # 0 = sleep, 1 = active
            self.enca = Pin('Y4', Pin.IN, Pin.PULL_UP)
            self.encb = Pin('Y5', Pin.IN, Pin.PULL_UP)
        self.pwmscale = (self.pwmtim.period() +
                         1) // 100  # scale factor for percent power
        self.count_a = 0  # counter for impulses on the A output of the encoder
        self.target_a = 0  # target value for the A counter (for controlled rotation)
        self.count_b = 0  # counter for impulses on the B output of the encoder
        self.time_a = 0  # last time we got an impulse on the A output of the encoder
        self.time_b = 0  # last time we got an impulse on the B output of the encoder
        self.elapsed_a_b = 0  # time elapsed between an impulse on A and an impulse on B
        self.dirsensed = 0  # direction sensed through the phase of the A and B outputs
        self.rpm = 0  # current speed in rotations per second
        self.rpm_last_a = 0  # value of the A counter when we last computed the rpms
        self.cruise_rpm = 0  # target value for the rpms
        ExtInt(self.enca, ExtInt.IRQ_RISING, Pin.PULL_UP, self.enca_handler)
        ExtInt(self.encb, ExtInt.IRQ_RISING, Pin.PULL_UP, self.encb_handler)
        if RomiMotor.rpmtimer is None:  # create only one shared timer for all instances
            RomiMotor.rpmtimer = Timer(4)
            RomiMotor.rpmtimer.init(freq=4,
                                    callback=RomiMotor.class_rpm_handler)
        RomiMotor.rpm_handlers.append(
            self.rpm_handler)  # register the handler for this instance

    """
  Handler for interrupts caused by impulses on the A output of the encoder.
  This is where we sense the rotation direction and adjust the throttle to 
  reach a target number of rotations of the wheel.
  """

    def enca_handler(self, pin):
        self.count_a += 1
        self.time_a = pyb.millis()
        if pyb.elapsed_millis(self.time_b) > self.elapsed_a_b:
            self.dirsensed = -1  # A occurs before B
        else:
            self.dirsensed = 1  # B occurs before A
        if self.target_a > 0:  # If we have a target rotation
            if self.count_a >= self.target_a:
                self.pwm.pulse_width(
                    0
                )  # If we reached of exceeded the rotation, stop the motor
                self.target_a = 0  # remove the target
            elif (self.target_a - self.count_a) < 30:
                self.pwm.pulse_width(
                    7 * self.pwmscale
                )  # If we are very close to the target, slow down a lot
            elif (self.target_a - self.count_a) < 60:
                self.pwm.pulse_width(
                    15 *
                    self.pwmscale)  # If we are close to the target, slow down

    """
  Handler for interrupts caused by impulses on the B output of the encoder.
  """

    def encb_handler(self, pin):
        self.count_b += 1
        self.elapsed_a_b = pyb.elapsed_millis(
            self.time_a)  # Memorize the duration since the last A impulse

    """
  This is the handler of the timer interrupts to compute the rpms
  """

    def rpm_handler(self, tim):
        self.rpm = 4 * (self.count_a - self.rpm_last_a)  # The timer is at 4Hz
        self.rpm_last_a = self.count_a  # Memorize the number of impulses on A
        if self.cruise_rpm != 0:  # If we have an RPM target
            # Add a correction to the PWM according to the difference in RPMs
            delta = abs(self.rpm - self.cruise_rpm)
            if delta < 100:
                corr = delta // 40
            elif delta < 500:
                corr = delta // 20
            else:
                corr = delta // 10
            if self.cruise_rpm < self.rpm:
                self.pwm.pulse_width(
                    max(5 * self.pwmscale,
                        self.pwm.pulse_width() - self.pwmscale * corr))
            else:
                self.pwm.pulse_width(
                    min(100 * self.pwmscale,
                        self.pwm.pulse_width() + self.pwmscale * corr))

    """
  Set the power of the motor in percents.
  Positive values go forward, negative values go backward.
  """

    def throttle(self, pct):
        if pct is None:
            return
        if pct < 0:
            self.dir.on()
            pct = -pct
        else:
            self.dir.off()
        self.pwm.pulse_width(pct * self.pwmscale)
        self.sleep.on()

    """
  Get the current power as a percentage of the max power.
  The result is positive if the motor runs forward, negative if it runs backward.
  """

    def getThrottle(self):
        thr = self.pwm.pulse_width() // self.pwmscale
        if self.dir.value() > 0:
            thr = -thr
        return thr

    """
  Release the motor to let it rotate freely.
  """

    def release(self, release=True):
        if release:
            self.sleep.off()
        else:
            self.sleep.on()

    """
  Perform 'turns' rotations of the wheel at 'power' percents of the max power.
  If 'turns' is positive, the wheel turns forward, if it is negative, it turns backward.
  """

    def rotatewheel(self, turns, power=20):
        if turns < 0:
            sign = -1
            turns = -turns
        else:
            sign = 1
        self.count_a = 0
        self.count_b = 0
        self.target_a = int(360 * turns)
        self.throttle(sign * power)

    """
  Wait for the rotations requested by 'rotatewheel' to be done.
  """

    def wait(self):
        while self.count_a < self.target_a:
            pass

    """
  Set a target RPMs. The wheel turns in its current rotation direction,
  'rpm' should be non negative.
  """

    def cruise(self, rpm):
        self.cruise_rpm = int(rpm * 60)

    """
  Get the current RPMs. This is always non negative, regardless of the rotation direction.
  """

    def get_rpms(self):
        return self.rpm / 60

    """
  Cancel all targets of rotation and RPM
  """

    def clear(self):
        self.target_a = 0
        self.cruise_rpm = 0

    """
  Stop the motor.
  """

    def stop(self):
        self.clear()
        self.throttle(0)
Esempio n. 11
0
class tv:
    def __init__(self,hres=64,progressive=False,lines=None,linetime=64,buf=None):
        if lines == None:
            lines = 262 if progressive else 525
        self.lines = lines
        if buf == None:
            if progressive:
                self.buf = bytearray(262*hres)
            else:
                self.buf = bytearray(525*hres)
        else:
            self.buf = buf
        self.hres = hres
        self.line_time = linetime
        self.progressive = progressive
        
        self.sync_level = 0
        self.blanking_level = 56
        self.black_level = 58
        self.white_level = 73

        self.phase = 0
        self.buffer_dac = True
        self.reinit()
    def redac(self):
        self.dac = DAC(Pin('X5'),buffering=self.buffer_dac)
        self.bmv = memoryview(self.buf)[:len(self)]
        self.dac_tim = Timer(6, freq=int(self.hres*1000000/self.line_time))
        self.dac.write_timed(self.bmv,self.dac_tim,mode=DAC.CIRCULAR)
        self.frame_tim = Timer(5, prescaler=self.dac_tim.prescaler(),period=self.dac_tim.period()*len(self))
        self.frame_tim.counter(self.phase)
    def reinit(self):
        self.calc_porch_magic_numbers()
        self.init()
    def set_progressive(self,prog=True):
        self.progressive = prog
        self.calc_porch_magic_numbers()
        self.init()
    
    def __len__(self):
        return self.hres*self.lines
    def calc_porch_magic_numbers(self):
        br = self.hres/self.line_time

        w = round(4.7*br)
        t = int(10.9*br+0.9)#round mostly up
        s = round(1.5*br)
        self.h_blank = [s,s+w,t]
        self.v_blank_e = [6,12,18]
        self.v_blank_o = [6,12,19]

        hsl = round(18*br)
        hsh = round(58*br)
        
        self.h_safe = [hsl-t,hsh-t]
        self.v_safe = [32*(2-self.progressive),208*(2-self.progressive)]
        
    def init(self):
        self.carrier = Pin('X1') 
        self.tim = Timer(2, prescaler=1,period=1)
        self.ch = self.tim.channel(1, Timer.PWM, pin=self.carrier)
        self.ch.pulse_width(1)

        self.redac()
        
        self.be = self.bmv
        if not self.progressive:
            self.bo = self.be[len(self)//2:]
        self.fb = framebuf.FrameBuffer(self.buf,self.hres,self.lines,framebuf.GS8)
        self.fbe_mv = self.be[self.hres*21+self.h_blank[2]:]
        if not self.progressive:
            self.fbo_mv = self.bo[self.hres*43//2+self.h_blank[2]:]
        h = self.y_dim()//(2-self.progressive)
        self.fbe = framebuf.FrameBuffer(self.fbe_mv,self.hres-self.h_blank[2],h,framebuf.GS8,self.hres)
        if not self.progressive:
            self.fbo = framebuf.FrameBuffer(self.fbo_mv,self.hres-self.h_blank[2],h,framebuf.GS8,self.hres)
        self.clear()

    def set_pixel(self,x,y,v):
        if self.progressive:
            self.fbe.pixel(x,y,int(v*(self.white_level-self.black_level)+self.black_level))
        else:
            [self.fbe,self.fbo][y&1].pixel(x,y//2,int(v*(self.white_level-self.black_level)+self.black_level))
    def get_pixel(self,x,y):
        if self.progressive:
            return (self.fbe_mv[x+y*self.hres]-self.black_level)/(self.white_level-self.black_level)
        else:
            return ([self.fbe_mv,self.fbo_mv][y&1][x+(y//2)*self.hres]-self.black_level)/(self.white_level-self.black_level)
    def set_carrier(self,pre=1,per=1,w=1):
        self.tim.init(prescaler=pre,period=per)
        self.ch.pulse_width(w)
    def clear(self):
        self.fb.fill(self.black_level)
        self.syncs()
    def syncs(self):
        self.fb.fill_rect(0,0,self.h_blank[2],self.lines,self.blanking_level)
        self.fb.fill_rect(self.h_blank[0],0,self.h_blank[1]-self.h_blank[0],self.lines,self.sync_level)
        for y in range(self.v_blank_e[2]):
            inv = self.v_blank_e[0] <= y < self.v_blank_e[1]
            for x in range(self.hres//2):
                val = self.blanking_level
                if (self.h_blank[0] <= x < self.h_blank[1]) ^ inv:
                    val = self.sync_level
                self.buf[y*self.hres//2+x] = val
        if not self.progressive:
            for y in range(self.v_blank_o[2]):
                inv = self.v_blank_o[0] <= y < self.v_blank_o[1]
                for x in range(self.hres//2):
                    val = self.blanking_level
                    if (self.h_blank[0] <= x < self.h_blank[1]) ^ inv:
                        val = self.sync_level
                    self.bo[y*self.hres//2+x] = val
    def x_dim(self):
        return self.hres-self.h_blank[2]
    def y_dim(self):
        return self.lines-(21 if self.progressive else 43)
    
    def mandelbrot(self,imax=8,p=0,s=2,julia=False,il=0,x0=None,y0=None,x1=None,y1=None,asm=True,julia_seed=0):
        
        x0 = self.h_safe[0] if x0 == None else x0
        x1 = self.h_safe[1] if x1 == None else x1
        y0 = self.v_safe[0] if y0 == None else y0
        y1 = self.v_safe[1] if y1 == None else y1
        
        for x in range(x0,x1):
            for y in range(y0,y1):
                c = (((x-x0)/(x1-x0-1)-.5)*2 + ((y-y0)/(y1-y0-1)-.5)*2j)*s+p
                z = c
                if julia:
                    c = julia_seed
                if asm:
                    i = a_mandelbrot(z,c,imax)
                else:
                    for i in range(imax):
                        if z.real*z.real+z.imag*z.imag > 4:
                            break
                        z = z*z+c
                    else:
                        self.set_pixel(x,y,il)
                        continue
                if i == -1:
                    self.set_pixel(x,y,il)
                else:
                    self.set_pixel(x,y,i/imax)
    def demo(self,x0=None,y0=None,x1=None,y1=None):

        x0 = self.h_safe[0] if x0 == None else x0
        x1 = self.h_safe[1] if x1 == None else x1
        w = x1-x0
        y0 = self.v_safe[0] if y0 == None else y0
        y1 = self.v_safe[1] if y1 == None else y1
        h = y1-y0
        
        mx = x0
        my = y0
        import pyb
        import time
        acc = pyb.Accel()
        btn = pyb.Switch()
        p = self.get_pixel(int(mx),int(my))
        pos = 0
        zoom = 2
        it = 16
        julia = False
        jp = 0
        self.mandelbrot(it,pos,zoom,julia,0,x0,y0,x1,y1,julia_seed=jp)

        def paddles(c):
            x = int(mx-.125*w)
            xw = w//4
            y = int(my-.125*h)
            yw = h//4
            y_0 = y0
            y_1 = y1
            if not self.progressive:
                y //= 2
                yw //= 2
                y_0 //= 2
                y_1 //= 2
            self.fbe.hline(x,y_0,xw,c)
            self.fbe.vline(x0,y,yw,c)
            self.fbe.hline(x,y_1,xw,c)
            self.fbe.vline(x1,y,yw,c)
            if not self.progressive:
                self.fbo.hline(x,y_0,xw,c)
                self.fbo.vline(x0,y,yw,c)
                self.fbo.hline(x,y_1,xw,c)
                self.fbo.vline(x1,y,yw,c)
        
        while 1:
            paddles(self.black_level)
            mx = min(x1-2,max(x0,mx*.98+(-acc.x()/24+.5)*w*.02))
            my = min(y1-2,max(y0,my*.98+(acc.y()/24+.5)*h*.02))
            paddles(self.white_level)
            p = self.get_pixel(int(mx),int(my))
            self.set_pixel(int(mx),int(my),(p+.5)%1)
            pyb.delay(10)
            self.set_pixel(int(mx),int(my),p)
            if btn():
                st = time.ticks_ms()
                nit = it*2
                while btn():
                    if time.ticks_diff(time.ticks_ms(),st) > 1000:
                        if acc.z()>0:
                            nit = it*2
                        else:
                            nit = "Julia"
                        self.fbe.fill_rect(x0,y0,w,10,self.black_level)
                        if not self.progressive:
                            self.fbo.fill_rect(x0,y0,w,10,self.black_level)
                        self.fbe.text(str(nit),x0+1,y0+1,self.white_level)
                        if not self.progressive:
                            self.fbo.text(str(nit),x0+1,y0+1,self.white_level)
                cp = (((mx-x0)/w-.5)*2+2j*((my-y0)/h-.5))*zoom
                if time.ticks_diff(time.ticks_ms(),st) > 1000:
                    if nit == "Julia":
                        julia ^= 1
                        jp = pos + cp
                        pos = 0
                        zoom = 2
                        it = 16
                    else:
                        it = nit
                else:
                    pos += cp
                    zoom *= .25
                self.mandelbrot(it,pos,zoom,julia,0,x0,y0,x1,y1,julia_seed=jp)
Esempio n. 12
0
class PWM:
    """A PWM output on the given pin.
    
    This uses the pins_af.py file created by the micropython build.

    The timer argument is the name of the timer to use, as a string in
    the form 'TIM#'.  If it is not set, the first timer available for
    the pin will be used. Either length or freq can be used to specify
    the pulse length of the pwm signal. Length is the total pulse
    length in microseconds, and frequency is Hz, so length=20000
    implies freq=50, which is the default. If both are specified, freq
    is ignored.

    self.timer & self.channel are made available if you want to read or
    adjust the settings after initialization."""
    def __init__(self, pin, timer=None, length=None, freq=None):
        timers = af_map['P' + pin.name()]
        if not timers:
            raise PwmError("Pin does not support PWM.")

        if not timer:
            timer = 'TIM'
        for af, name in timers:
            if name.startswith(timer):
                timer_af, timer_name = af, name
                timer_full, channel = timer_name.split('_')
                if channel.startswith('CH'):
                    break
        else:
            raise PwmError("Pin does not support timer %s" % timer)

        if length:
            freq = 1000000 / length
        elif not freq:
            freq = 50
        pin.init(Pin.OUT, alt=af)
        self.timer = Timer(int(timer_full[3:]), freq=freq)
        self.channel = self.timer.channel(
            int(channel[2:3]),
            Timer.PWM_INVERTED if channel.endswith('N') else Timer.PWM,
            pin=pin)

        self.length = 1000000 / self.timer.freq()
        self.duty(0)

    def duty(self, percentage=None):
        """Get/Set the duty cycle as a percentage.
        
        The duty cycle is the time the output signal is on.
        Returns the last set value if called with no arguments."""

        if percentage is None:
            return round(100 * self.channel.pulse_width() /
                         self.timer.period())
        self.channel.pulse_width_percent(max(0, min(percentage, 100)))

    def pulse_width(self, width=None, time=0):
        """Get/Set the pulse width in microseconds.
        
        The width is the length of time the signal is on.  Returns the
        current value rounded to the nearest int if called with no
        arguments.
        
        Time is the number of milliseconds to take to get to the new
        width.  At least that many milliseconds will pass; if it's 0,
        the change will happen now."""

        if width is None:
            return round(self.length * self.channel.pulse_width() /
                         self.timer.period())
        target = self.timer.period() * min(width, self.length) / self.length
        self.target = round(target)
        if time == 0:
            self.channel.pulse_width(self.target)
        else:
            self.delta = int((target - self.channel.pulse_width()) *
                             self.length / (time * 1000))
            self.channel.callback(self.timed_change)
            # No initial change so we get minimum length.

    def timed_change(self, timer):
        """Make a timed change in width."""

        old = self.channel.pulse_width()
        if abs(old - self.target) > abs(self.delta):
            self.channel.pulse_width(old + self.delta)
        else:
            self.channel.callback(None)
            self.channel.pulse_width(self.target)