' 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