'        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 workToDo AS INTEGER
DIM thisTime AS INTEGER
DIM lastTime AS INTEGER
DIM thisDirection AS INTEGER
DIM lastDirection AS INTEGER
DIM stepCode AS INTEGER
DIM thisStep AS INTEGER
DIM velocity AS INTEGER
DIM currentAngle AS INTEGER
DIM command AS INTEGER
DIM motorDirection AS INTEGER
'DIM thisTransit AS INTEGER
'DIM lastTransit AS INTEGER
'DIM secondLastTransit AS INTEGER
DIM motorCutOnTime 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
      ENDIF
      motorDirection = command
      RETURN motorDirection
ENDFUNCTION


'        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
traveled.
'         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
              ELSE
                      thisStep = -1
              ENDIF
      ELSE
              'PRINT "stepCode = "; stepCode; ", slowness = "; slowness
              thisStep = 0
      ENDIF
              
      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
      ENDIF
      
      'PRINT "currentAngle = "; currentAngle        
ENDSUB

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

ENDSUB

'        The main section of the code.  Execution begins here.
MAIN:
      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)
      WAIT(2000)

      '        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)
                      ELSE
                              motorDirection = motorControl(-1)
                      ENDIF
              ELSE
                      IF absVelocity < 1  THEN
                              motorDirection = motorControl(0)
                      ENDIF
              ENDIF
      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
                                      EXIT
                              ENDIF
                      ENDIF

                      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)
                                      ELSE
                                              motorDirection = motorControl(-1)
                                      ENDIF
                              ENDIF
                      ELSE
                              IF absVelocity < CUT_ON_VELOCITY OR absVelocity >< CUT_OFF_VELOCITY THEN
                                      motorDirection = motorControl(0)
                              ENDIF
                      ENDIF
              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
              'ENDIF
              
              IF ABS(currentAngle) > 300 THEN
                      workToDo = 0
                      currentAngle = 0
              ENDIF
              
              IF notifyReversal > 0 THEN
                      PRINT "Reversal"
                      notifyReversal = 0
                      workToDo = workToDo + PER_REVERSE_WORK
              ENDIF
              PRINT "workToDo = "; workToDo; ", currentAngle = "; currentAngle
              
              '        Set the wait between motor operations here.  Use this in conjunction with the fraction of
currentWork
              '        that is performed on each pass as controlled by cutoffWork
              WAIT(200)
              
      LOOP                '  End of run loop
      
      ON EINT2 FALL DoNothing        
END