OpenCores
URL https://opencores.org/ocsvn/ssbcc/ssbcc/trunk

Subversion Repositories ssbcc

[/] [ssbcc/] [trunk/] [core/] [9x8/] [peripherals/] [servo_motor.py] - Rev 9

Compare with Previous | Blame | View Log

################################################################################
#
# Copyright 2015, Sinclair R.F., Inc.
#
################################################################################
 
import math
 
from ssbccPeripheral import SSBCCperipheral
from ssbccUtil import CeilLog2
from ssbccUtil import SSBCCException
 
class servo_motor(SSBCCperipheral):
  """
  Servo Motor driver:\n
  Creates PWM modulated signals to operate micro servos for UAVs and similar.\n
  Usage:
    PERIPHERAL servo_motor      outport=O_name                          \\
                                {outsignal|outsignaln}=o_name           \\
                                freq_hz=FREQ_HZ                         \\
                                min_width=XXX{s|ms|us|ns}               \\
                                max_width=XXX{s|ms|us|ns}               \\
                                [default_width=XXX{s|ms|us|ns}]         \\
                                {period=XXX{s|ms|us|ns}|sync=o_name}    \\
                                [inperiod=I_name]                       \\
                                [scale=C_name]                          \\
                                [scale_max=C_name]\n
  Where:
    outport=O_name
      specifies the symbol used to write the 8-bit PWM control to this
      peripheral
      Note:  The name must start with "O_".
    {outsignal|outsignaln}=o_name
      specifies the name of the output PWM signal
      Note:  outsignal creates a positive pulse for the PWM control and
             outsignaln creates an inverted pulse for the PWM control.
      Note:  The name must start with "o_".
    freq_hz=FREQ_HZ
      specifies the processor clock speed to the peripheral
    min_width=XXX{s|ms|us|ns}
      specifies the minimum pulse width
      Note:  XXX may be an integer or a real number
      Note:  The minimum width must be a realizable positive value (since a
             pulse width of zero means no control is being given to the servo).
    max_width=XXX{s|ms|us|ns}
      specifies the maximum pulse width
      Note:  XXX may be an integer or a real number
    default_width=XXX{s|ms|us|ns}
      optionally specifies the default width of the PWM before it is set by the
      processor
      Note:  If the default width is not specified then the minimum width is
             used as the default width.
    {period=XXX{s|ms|us|ns}|sync=o_name}
      either specifies the rate at which the PWM is generated or synchronize the
      PWM generation to a preceding servo_motor peripheral with the output
      signal o_name
      Note:  XXX may be an integer or a real number
      Note:  When sync is specified the leading edges of the PWMs will coincide.
    inperiod=I_name
      optionally specifies an input port to receive the strobe generated by the
      associated period
      Note:  This optional parameter requires that period be specified.  It is
             not compatible with the sync parameter.
    scale=C_name
      optionally creates a constant named "C_name" which states how many clock
      cycles are used for each count in the 8-bit PWM control
      Note:  The name must start with "C_".
      Example:  A PWM range of 1000 to 1500 usec and a clock frequency of 8 MHz
                produces a value of ceil((1500-1000)*8/(256-1)) = 16 clock
                cycles per 8-bit control value count.
    scale_max=C_name
      optionally creates a constant named "C_name" wich states the upper limit
      of the continuous control range
      Note:  The name must start with "C_".
      Example:  For the example in "scale=C_name" the upper limit would be
                ceil((1500-1000)*8/16) = 250.  I.e., control values of 251
                through 255 inclusive will produce the same PWM width as the
                control value 250.
  Example:
    A micro servo that responds to positive PWM pulses between 1000 usec and
    1500 usec once every 20 msec and the processor clock is 8 MHz.  The
    architecture file would include the following:\n
      CONSTANT          C_FREQ_HZ       8_000_000
      PORTCOMMENT       servo motor control
      PERIPHERAL        servo_motor    outport=O_SERVO                  \\
                                       outsignal=o_servo                \\
                                       freq_hz=C_FREQ_HZ                \\
                                       min_width=1000us                 \\
                                       max_width=1500us                 \\
                                       period=20ms                      \\
                                       scale=C_servo_scale              \\
                                       scale_max=C_servo_scale_max\n
    will create a peripheral generating the desired PWM signal.\n
    The constants C_servo_scale and C_servo_scale_max could be reported by the
    micro controller to a controlling application to specify the sensitivity and
    upper limit for the servo motor controller.\n
  Example:
    Synchronize a second servo motor with PWM pulses between 1000 usec and 2500
    usec to the preceding servo motor controller:\n
      PERIPHERAL        servo_motor     outport=O_SERVO_2               \\
                                        outsignal=o_servo2              \\
                                        freq_hz=C_FREQ_HZ               \\
                                        min_width=1.0ms                 \\
                                        max_width=2.5ms                 \\
                                        sync=o_servo\n
  """
 
  def __init__(self,peripheralFile,config,param_list,loc):
    # Use the externally provided file name for the peripheral
    self.peripheralFile = peripheralFile;
    # Get the parameters.
    allowables = (
      ( 'default_width',        r'\S+',         lambda v : self.TimeMethod(config,v), ),
      ( 'freq_hz',              r'\S+$',        lambda v : self.IntMethod(config,v), ),
      ( 'inperiod',             r'I_\w+$',      None,   ),
      ( 'max_width',            r'\S+$',        lambda v : self.TimeMethod(config,v), ),
      ( 'min_width',            r'\S+$',        lambda v : self.TimeMethod(config,v), ),
      ( 'outport',              r'O_\w+$',      None,   ),
      ( 'outsignal',            r'o_\w+$',      None,   ),
      ( 'outsignaln',           r'o_\w+$',      None,   ),
      ( 'period',               r'\S+$',        lambda v : self.TimeMethod(config,v), ),
      ( 'scale',                r'C_\w+$',      None,   ),
      ( 'scale_max',            r'C_\w+$',      None,   ),
      ( 'sync',                 r'o_\w+$',      None,   ),
    )
    names = [a[0] for a in allowables];
    for param_tuple in param_list:
      param = param_tuple[0];
      if param not in names:
        raise SSBCCException('Unrecognized parameter "%s" at %s' % (param,loc,));
      param_test = allowables[names.index(param)];
      self.AddAttr(config,param,param_tuple[1],param_test[1],loc,param_test[2]);
    # Ensure the required parameters are provided.
    for paramname in (
      'outport',
      'freq_hz',
      'min_width',
      'max_width',
    ):
      if not hasattr(self,paramname):
        raise SSBCCException('Required parameter "%s" is missing at %s' % (paramname,loc,));
    # Ensure exactly one of mandatory exclusive pairs are specified.
    for exclusivepair in (
      ( 'outsignal',    'outsignaln',   ),
      ( 'period',       'sync',         ),
    ):
      if not hasattr(self,exclusivepair[0]) and not hasattr(self,exclusivepair[1]):
        raise SSBCCException('One of %s or %s must be specified at %s', (exclusivepair[0], exclusivepair[1], loc, ));
      if hasattr(self,exclusivepair[0]) and hasattr(self,exclusivepair[1]):
        raise SSBCCException('Only one of %s or %s may be specified at %s', (exclusivepair[0], exclusivepair[1], loc, ));
    # Set optional signals
    if not hasattr(self,'default_width'):
      self.default_width = self.min_width;
    # Ensure signal values are reasonable.
    if self.min_width >= self.max_width:
      raise SSBCCException('min_width must be smaller than max_width at %s' % loc);
    if not self.min_width <= self.default_width <= self.max_width:
      raise SSBCCException('default_width is not between min_width and max_width at %s' % loc);
    # Ensure the optionally provided "sync" servo_motor peripheral has been specified.
    if hasattr(self,'sync'):
      for p in config.peripheral:
        if (str(p.__class__) == str(self.__class__)) and (p.outsignal == self.sync):
          break;
      else:
        raise SSBCCException('Can\'t find preceding servo_motor peripheral with outsignal=%s at %s ' % (self.sync,loc,));
      if not hasattr(p,'period'):
        raise SSBCCException('servo_motor peripherial with outsignal=%s must have period specified to be used at %s' % (self.sync,loc,));
    # Translate the outsignal specification into a single member for the signal
    # name and a specification as to whether or not the signal is inverted.
    if hasattr(self,'outsignaln'):
      self.outsignal = self.outsignaln;
      self.invertOutsignal = True;
    else:
      self.invertOutsignal = False;
    # Set the string used to identify signals associated with this peripheral.
    self.namestring = self.outsignal;
    # Calculate the name of the signal to start the PWM.
    self.periodSignal = 's__%s__period_done' % (self.namestring if hasattr(self,'period') else self.sync)
    # Calculate the scaling and set the optionally specified constants.
    # TODO -- ensure the realizable min_width is positive
    self.scaleValue = int(math.ceil((self.max_width-self.min_width)*self.freq_hz/2**config.Get('data_width')));
    self.scale_maxValue = int(math.ceil((self.max_width-self.min_width)*self.freq_hz/self.scaleValue));
    for scalingPair in (
      ( 'scaling',      'scaleValue',           ),
      ( 'scale_max',    'scale_maxValue',       ),
    ):
      if hasattr(self,scalingPair[0]):
        config.AddConstant(scalingPair[1], getAttr(self,scalingPair[1]));
    # Add the I/O port, internal signals, and the INPORT and OUTPORT symbols for this peripheral.
    config.AddIO(self.outsignal,1,'output',loc);
    if hasattr(self,'period'):
      config.AddSignal(self.periodSignal, 1, loc);
    self.ix_outport = config.NOutports();
    config.AddOutport((self.outport,
                       False,
                       # empty list
                      ),loc);
    if hasattr(self,'inperiod'):
      config.AddSignal('s_SETRESET_%s' % self.periodSignal,1,loc);
      config.AddInport((self.inperiod,
                        (self.periodSignal, 1, 'set-reset',),
                       ),loc);
 
  def GenVerilog(self,fp,config):
    body = self.LoadCore(self.peripheralFile,'.v');
    if hasattr(self,'period'):
      body = re.sub(r'@PERIOD_BEGIN@\n','',body);
      body = re.sub(r'@PERIOD_END@\n','',body);
    else:
      body = re.sub(r'@PERIOD_BEGIN@.*?@PERIOD_END@\n','',body,flags=re.DOTALL);
    nbits_scale = CeilLog2(self.scaleValue);
    if nbits_scale == 0:
      body = re.sub(r'@SCALE_0_BEGIN@\n','',body);
      body = re.sub(r'@SCALE_0_ELSE@.*?@SCALE_0_END@\n','',body,flags=re.DOTALL);
    else:
      body = re.sub(r'@SCALE_0_BEGIN@.*?@SCALE_0_ELSE@\n','',body,flags=re.DOTALL);
      body = re.sub(r'@SCALE_0_END@\n','',body);
    scaled_min_width = int(math.floor(self.min_width*self.freq_hz/self.scaleValue));
    scaled_default_width = int(math.floor(self.default_width*self.freq_hz/self.scaleValue));
    scaled_max_width = int(math.floor(self.max_width*self.freq_hz/self.scaleValue));
    nbits_pwm = max(config.Get('data_width')+1,CeilLog2(scaled_max_width));
    pwm_formula = "%d'd%d + { %d'd0, s_N }" % (nbits_pwm,scaled_min_width-1,nbits_pwm-8,);
    if hasattr(self,'period'):
      period = self.period * self.freq_hz / self.scaleValue;
      nbits_period = CeilLog2(period);
    else:
      period = 1;
      nbits_period = 0;
    for subpair in (
      ( r'@DEFAULT_PWM@',       "%d'd%d" % (nbits_pwm,scaled_default_width-1,), ),
      ( r'@INVERT@',            '!' if self.invertOutsignal else '',            ),
      ( r'@IX_OUTPORT@',        "8'd%d" % self.ix_outport,                      ),
      ( r'@NAME@',              self.namestring,                                ),
      ( r'@NBITS_PERIOD@',      str(nbits_period),                              ),
      ( r'@NBITS_PWM@',         str(nbits_pwm),                                 ),
      ( r'@NBITS_SCALE@',       str(nbits_scale),                               ),
      ( r'@ONE_PERIOD@',        "%d'd1" % nbits_period,                         ),
      ( r'@ONE_PWM@',           "%d'd1" % nbits_pwm,                            ),
      ( r'@ONE_SCALE@',         "%d'd1" % nbits_scale,                          ),
      ( r'@OUTSIGNAL@',         self.outsignal,                                 ),
      ( r'@PERIOD_MINUS_ONE@',  "%d'd%d" % (nbits_period,period-1,),            ),
      ( r'@PWM_FORMULA@',       pwm_formula,                                    ),
      ( r'@SCALE_MINUS_ONE@',   "%d'd%d" % (nbits_scale,self.scaleValue-1,),    ),
      ( r'\bgen__',             'gen__%s__' % self.namestring,                  ),
      ( r'\bs__',               's__%s__' % self.namestring,                    ),
      ( r'@PERIOD_SIGNAL@',     self.periodSignal,                              ), # must be after ( r'\bs__', ...
    ):
      body = re.sub(subpair[0],subpair[1],body);
    body = self.GenVerilogFinal(config,body);
    fp.write(body);
 

Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.