'        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