'        RegulatedPerReverseDriver.bas
'        Version 1.0
'        20 Jan 2014
'        Copyright Wendell Wiggins, 2013,2014
'        This is a complete driver for a Chaotron kinetic sculpture that has a primary mover that oscillates back
'         and forth.
'        The energy pumped into the sculpture is fixed at a value per reversal of the motion.
'        This program is intended to be used as an example for writing driver programs for many other types
'         of kinetic sculptures.
'        The author makes no claims as to the functions of this code.  It must be used only in noncommercial
'        applications, and the user takes full responsibility for its use and any consequences.
'        21 Nov 2013
'        Modified to change use of confusing slowness to velocity
'        Modified to handle heavier sculptures with a low-torque motor
'        20 Jan 2014
'        Commented out the code used to compute a period since it is currently not used        
'        Name the IO channels that are used by the motor controller
#define        CHANNEL_A        15
#define        CHANNEL_B        11
#define        INDEX                10
#define        MOTOR_CW        0
#define        MOTOR_CCW        1

'        Name the motor directions
#define CW                0
#define CCW                -1

'        Set some constants
'        A value for the motor torque in some strange units.  Not really needed for a fixed torque motor
#define MOTOR_TORQUE 1

'        Number of seconds the motor is allowed to run continuously in the same direction.
'        Used to prevent runaway
#define MAX_MOTOR_RUN_TIME        15

'        The amount of work that is added to the work quota for each reverse of the prmary mover
#define PER_REVERSE_WORK 100

'        The maximum and minimum angular velocities at which the motor is allowed to run
'        The velocity is in units of steps (1/400 revolution) per second
#define CUT_ON_VELOCITY 30
#define CUT_OFF_VELOCITY 400        '400 = 1 rev/sec

DIM thisDirection AS INTEGER
DIM lastDirection AS INTEGER
DIM currentAngle AS INTEGER
DIM motorDirection AS INTEGER
'DIM thisTransit AS INTEGER
'DIM lastTransit AS INTEGER
'DIM secondLastTransit AS INTEGER
DIM notifyReversal AS INTEGER

'This function executes command on the induction motor
FUNCTION MotorControl (command)

      IF command = 1 THEN                'On CW
              OUT(MOTOR_CCW) = 0
              OUT(MOTOR_CW) = 1
              motorCutOnTime = TIMER
      ELSEIF command = -1 THEN        'On CCW
              OUT(MOTOR_CW) = 0
              OUT(MOTOR_CCW) = 1
              motorCutOnTime = TIMER
      ELSE                                'Off
              OUT(MOTOR_CW) = 0
              OUT(MOTOR_CCW) = 0
      motorDirection = command
      RETURN motorDirection

'        This code is executed each time the position sensor passes an angle mark.
'        It determines which way the pendulum has moved since the last mark and updates currentAngle
'        Using the time interval since the last execution, it computes the angular velocity.
'        It also notes each time the direction of motion reverses as a measure of how much the primary mover has
'         After each reversal it increments workToDo so that the motor will run and replace energy lost to friction and
air resistance.
SUB HandleInterrupt
      DIM thisInterval AS INTEGER
      lastTime = thisTime
      thisTime = TIMER
      thisInterval = thisTime - lastTime
      lastDirection = thisDirection
      thisDirection = IN(CHANNEL_B)
      stepCode = thisDirection - lastDirection        '0 if same direction, -1 if reversed to CW, 1 if reversed to CCW
      '  The step size is 1/400th revolution
      IF stepCode = 0 THEN
              IF thisDirection = CW THEN
                      thisStep = 1
                      thisStep = -1
              'PRINT "stepCode = "; stepCode; ", slowness = "; slowness
              thisStep = 0
      currentAngle = currentAngle + thisStep
      '  compute the instantaneous velocity
      '  Since thisInterval is in microsec., thisStep is multiplied by 1,000,000 to get units of 1/400th rev/sec.
      velocity = (1000000 * thisStep) / thisInterval
      '  Compute the work done this interval and subtract it from workToDo
      workToDo = workToDo - MOTOR_TORQUE * motorDirection * thisStep

      '  At each reversal of direction:
      '  Add PER_REVERSE_WORK to the accumulator variable workToDo
      '  Calculate data for calculating the period.  This is a diagnostic function.  It should be commented out when
not needed.
      IF stepCode <> 0 THEN        'stepCode = 0 is a reversal of direction
              'secondLastTransit = lastTransit
              'lastTransit = thisTransit
              'thisTransit = TIMER
              '  Notify the main loop that a reversal has occurred
              notifyReversal = 1
      'PRINT "currentAngle = "; currentAngle        

'A dummy sub to assign to the interrupt when it is to do nothing
SUB DoNothing


'        The main section of the code.  Execution begins here.
      DIM motorRunTime AS INTEGER
      DIM motorRunaway AS INTEGER
      DIM thisPeriod AS INTEGER
      DIM lastPeriod AS INTEGER
      DIM cutoffWork AS INTEGER
      DIM absVelocity AS INTEGER
      '        Set A, B, and INDEX for input
      DIR(CHANNEL_A) = 0        'input
      DIR(CHANNEL_B) = 0
      DIR(INDEX) = 0
      '        Set MOTOR_CW and MOTOR_CCW for output
      DIR(MOTOR_CW) = 1
      DIR(MOTOR_CCW) = 1
      '        Initialize the main timer to zero
      TIMER = 0
      notifyReversal = 0
      workToDo = 0
      'Make sure the motor is off
      motorDirection = motorControl(0)
      '  Get the primary mover moving at least a little
      motorDirection = motorControl(1)

      '        Enable IO15 to generate interrupts
      ON EINT2 FALL HandleInterrupt
      '        Get the primary mover moving

      'PRINT "velocity = "; velocity
      WHILE ABS(currentAngle) < 200
              absVelocity = ABS(velocity)
              IF motorDirection = 0 AND absVelocity > 0 THEN
                      IF velocity > 0 THEN
                              motorDirection = motorControl(1)
                              motorDirection = motorControl(-1)
                      IF absVelocity < 1  THEN
                              motorDirection = motorControl(0)
      LOOP                'End of startup loop
      PRINT "After start run"
      motorRunaway = 0
      workToDo = 0
      '  The main run loop is endless unless the motor runs away
      WHILE motorRunaway = 0
              'PRINT "In main loop: motorDirection = "; motorDirection
              '        On each pass through this loop, the motor runs to perform only part of the work that has been
              '        allocated so that the movment of the sculpture doesn't show a perceptible speedup.
              '        Adjust the fraction in the next statement small enough to give smooth motion, but large enough
              '        to allow the work to always stay ahead of new allocations.
              cutoffWork = (9 * workToDo) / 10
              '        This loop reverses the motor as the motion reverses until the work is done.
              'PRINT "velocity = "; velocity
              WHILE workToDo > cutoffWork
                      '  Check the length of time the motor has been running continuously in the same direction.
                      '  If it has been on longer than MAX_MOTOR_RUN_TIME, shut it off and exit
                      IF motorDirection <> 0 THEN
                              motorRunTime = TIMER - motorCutOnTime
                              IF motorRunTime > (MAX_MOTOR_RUN_TIME * 1000000) THEN
                                      PRINT "Motor has been on continuously for "; (motorRunTime/1000000); " sec. Exiting."
                                      motorRunaway = 1

                      absVelocity = ABS(velocity)
                      IF motorDirection = 0 THEN
                              IF absVelocity > CUT_ON_VELOCITY AND absVelocity < CUT_OFF_VELOCITY THEN
                                      IF velocity > 0 THEN
                                              motorDirection = motorControl(1)
                                              motorDirection = motorControl(-1)
                              IF absVelocity < CUT_ON_VELOCITY OR absVelocity >< CUT_OFF_VELOCITY THEN
                                      motorDirection = motorControl(0)
              LOOP                'End of accumulated work loop
              '        Insure that the motor is off when the current work is done.
              motorDirection = motorControl(0)

              '        Calculate and display the period for diagnostics.
              '        Comment out this section for normal operation
              'lastPeriod = thisPeriod
              'thisPeriod = thisTransit - secondLastTransit
              'IF lastPeriod <> thisPeriod THEN
              '        PRINT "latest period = "; thisPeriod
              IF ABS(currentAngle) > 300 THEN
                      workToDo = 0
                      currentAngle = 0
              IF notifyReversal > 0 THEN
                      PRINT "Reversal"
                      notifyReversal = 0
                      workToDo = workToDo + PER_REVERSE_WORK
              PRINT "workToDo = "; workToDo; ", currentAngle = "; currentAngle
              '        Set the wait between motor operations here.  Use this in conjunction with the fraction of
              '        that is performed on each pass as controlled by cutoffWork
      LOOP                '  End of run loop
      ON EINT2 FALL DoNothing