'        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