November 21, 2024, 11:09:07 PM

News:

Be sure to checkout our Vixen interfaces in the Library forum -- if you want PC automation at near zero cost, EFX-TEK and Vixen is a great combination of tools.


Interrupts

Started by GregH, August 26, 2008, 01:05:05 PM

Previous topic - Next topic

GregH

Hi Jon!

It's that time of year again when I blow the dust off last years haunted house props and fire them up!  All is good so far!!

I have a question about interrupting a program on any of the Prop 1, 2, 2SX controllers.  For safety reasons, I think it would be nice to have the ability to halt a program based on a monitored input.  We have one prop that moves toward people during it's show.  We take precautions but if you really wanted to, you could get in the way (especially some of the folks we see on a Saturday night after one too many).

Since the program is basically linear, I'm not sure how to continually monitor an input other than looking prior to each action. 

Thanks!

JonnyMac

August 26, 2008, 04:51:54 PM #1 Last Edit: August 26, 2008, 04:56:02 PM by JonnyMac
The only controller that we sell that is actually capable of using interrupts is the Prop-SX, but for your stated goal the Prop-1 and Prop-2 can be programmed using a style called Finite State Machine (FSM).  Of course, you could just put a switch in the V+ line of your controller to break power to the valves (sometimes the simplest approach is the best).

In the event you want to go with the software approach I've written a program that is designed as an FSM.  The valid states are 0 to 2; if, for some reason, the value of "state" is set outside this range the program will fix it.  Let's break down the program section by section.

Reset:
  PINS = %00000000                              ' clear all outputs
  DIRS = %00011111                              ' make P0-P4 outputs

  SEROUT Sio, Baud, ("!!!!!!AP8", %00, "X")     ' shutdown the AP-8
  PAUSE 25                                      ' let AP-8 reset
  state = 0                                     ' reset FSM
  timer = 0


Nothing mysterious here; we setup the IOs, silence the AP-8 if it's playing and reset the state machine (pointer and timer).

Main:
  IF ESwitch = IsOn THEN Reset                  ' hold for E-Switch
  PAUSE 20                                      ' base loop timing
  timer = timer + 20                            ' update global timer value
  RANDOM lottery                                ' stir all the time


Okay, here's where we check your emergency switch.  Note that it's at the very top of the program which means we need to come back to that section (Main) frequently.  The FSM design allows this.  Let's say we have three states.  The way the Prop-1 FSM will work is that they will run in this order:

Main
State 0
Main
State 1
Main
State 2

... and everything repeats at this point.  See how the code at Main is interleaved between each of the program states?  For this to work well we cannot have any appreciable delays in any state, hence the use of the global timer that is updated every time we pass through Main.  In PBASIC we can use BRANCH to get us to the current state.

Process_State:
  BRANCH state, (PS0, PS1, PS2)                 ' branch to Program State

Fix_Bad_State:
  state = 0
  timer = 0
  GOTO Reset


The single line BRANCH above is the equivalent to these three lines:

  IF state = 0 THEN PS0
  IF state = 1 THEN PS1
  IF state = 2 THEN PS2

What happens if state is greater than 2?  BRANCH will fail and the code with drop through to the Fix_Bad_State section which will get things back on track.  If you're careful with your design the program should not ever fall through.

The first state (state 0) of the demo program debounces the Trigger input. 

PS0:
  timer = timer * Trigger                       ' trigger active?
  IF timer < 100 THEN Main                      ' valid trigger?
    state = 1                                   ' point to next state
    timer = 0                                   ' reset timer
    holdOff = lottery // 2501 + 500             ' random, 500 to 3000
    GOTO Main


Do you see what's going on?  The timer variable is being updated with every pass through Main.  By using an active-high input all we have to do is multiply the timer by the value of the trigger pin.  No trigger and the timer is reset.  If the trigger is active long enough then the program will drop through the IF-THEN check.

Note that when this happens we have to setup the program for the next state; in this case we set the state to 1, clear the timer, then set another variable (holdoff) to a random value.

Here's the state that processes the random hold-off:

PS1:                                            ' variable hold-off
  IF timer < holdOff THEN Main
    state = 2
    timer = 0
    subState = 0                                ' for LEDs
    subTimer = 0
    sfx = 0
    GOSUB Play_SFX
    GOTO Main


Nothing to it, right?  All we have to do is compare the timer to our hold-off setting and when we get there we drop through for the next state.  You can see that we have a call to Play_SFX; since SEROUT uses time we have to account for it, like this:

Play_SFX:
  SEROUT Sio, Baud, ("!AP8", %00, "P", sfx)
  timer = timer + 29                            ' account for command
  RETURN


For your reference, each character transmitted at 2400 baud takes about 4.2 milliseconds.  Multiply this by the number of bytes you're sending (there are 7 in the command above) and round to the nearest whole number.

The next state is a bit trickier -- wanted to show you that you can do fun things.  This state is actually doing two things: 1) it's providing a 15 second delay while the AP-8 plays and, 2) every 100 milliseconds changes the LEDs on P0-P4 to create a rotating chaser pattern.  In order to handle the LEDs two more vairalbes have been added to the program: subState and subTimer (these got cleared at the end of state 1).

PS2:
  READ subState, PINS                           ' update LED pattern
  subTimer = subTimer + 20
  IF subTimer < 100 THEN PS2_Timing
    subState = subState + 1 // 5                ' set next pattern
    subTimer = 0                                ' reset the sub timer

PS2_Timing:
  IF timer < 15000 THEN Main
    state = 0
    timer = 0
    PINS = %00000000                            ' reset outputs
    GOTO Main


At the top of the state we update the LED pattern based on the value of subState.  I used READ here, but LOOKUP could have been used as well since the list is short.  Then the subTimer is updated and when it hits 100 it is cleared and we move to the next sub-state.  By using modulus (//) we can keep subState 0 to 4.  The reset of the state works as above, the difference being that we clear the pins that may have been activated before going to the top.

And there you have it.  I won't lie, it may take a day or two to wrap your mind around FSM style programming, but when you do you'll be able to do lots of things that appear to be "multi-tasking" and that can be very cool indeed.

Here's the entire listing:

' =========================================================================
'
'   File...... E-Switch.BS1
'   Purpose...
'   Author.... Jon Williams, EFX-TEK
'              Copyright (c) 2008 EFX-TEK
'              Some Rights Reserved
'              -- see http://creativecommons.org/licenses/by/3.0/
'   E-mail.... jwilliams@efx-tek.com
'   Started...
'   Updated...
'
'   {$STAMP BS1}
'   {$PBASIC 1.0}
'
' =========================================================================


' -----[ Program Description ]---------------------------------------------


' -----[ Revision History ]------------------------------------------------


' -----[ I/O Definitions ]-------------------------------------------------

SYMBOL  Sio             = 7                     ' SETUP = out; no ULN
SYMBOL  Trigger         = PIN6                  ' SETUP = DN
SYMBOL  ESwitch         = PIN5                  ' ULN acts as PULL-DN


' -----[ Constants ]-------------------------------------------------------

SYMBOL  IsOn            = 1                     ' for active-high in/out
SYMBOL  IsOff           = 0

SYMBOL  Baud            = OT2400


' -----[ Variables ]-------------------------------------------------------

SYMBOL  state           = B2
SYMBOL  subState        = B3

SYMBOL  sfx             = B4
SYMBOL  subTimer        = B5

SYMBOL  holdoff         = W3
SYMBOL  timer           = W4
SYMBOL  lottery         = W5


' -----[ Initialization ]--------------------------------------------------

Reset:
  PINS = %00000000                              ' clear all outputs
  DIRS = %00011111                              ' make P0-P4 outputs

  SEROUT Sio, Baud, ("!!!!!!AP8", %00, "X")     ' shutdown the AP-8
  PAUSE 25                                      ' let AP-8 reset
  state = 0                                     ' reset FSM
  timer = 0


' -----[ Program Code ]----------------------------------------------------

Main:
  IF ESwitch = IsOn THEN Reset                  ' hold for E-Switch
  PAUSE 20                                      ' base loop timing
  timer = timer + 20                            ' update global timer value
  RANDOM lottery                                ' stir all the time


Process_State:
  BRANCH state, (PS0, PS1, PS2)                 ' branch to Program State


Fix_Bad_State:
  state = 0
  timer = 0
  GOTO Reset


PS0:
  timer = timer * Trigger                       ' trigger active?
  IF timer < 100 THEN Main                      ' valid trigger?
    state = 1                                   ' point to next state
    timer = 0                                   ' reset timer
    holdOff = lottery // 2501 + 500             ' random, 500 to 3000
    GOTO Main


PS1:                                            ' variable hold-off
  IF timer < holdOff THEN Main
    state = 2
    timer = 0
    subState = 0                                ' for LEDs
    subTimer = 0
    sfx = 0
    GOSUB Play_SFX
    GOTO Main


PS2:
  READ subState, PINS                           ' update LED pattern
  subTimer = subTimer + 20
  IF subTimer < 100 THEN PS2_Timing
    subState = subState + 1 // 5                ' set next pattern
    subTimer = 0                                ' reset the sub timer

PS2_Timing:
  IF timer < 15000 THEN Main
    state = 0
    timer = 0
    PINS = %00000000                            ' reset outputs
    GOTO Main


' -----[ Subroutines ]-----------------------------------------------------

Play_SFX:
  SEROUT Sio, Baud, ("!AP8", %00, "P", sfx)
  timer = timer + 29                            ' account for command
  RETURN

' -------------------------------------------------------------------------


' -------------------------------------------------------------------------


' -------------------------------------------------------------------------


' -------------------------------------------------------------------------


' -------------------------------------------------------------------------


' -----[ User Data ]-------------------------------------------------------

Chaser:
  EEPROM (%00001, %00010, %00100, %01000, %10000)

Jon McPhalen
EFX-TEK Hollywood Office

JonnyMac

August 26, 2008, 07:22:22 PM #2 Last Edit: August 26, 2008, 07:27:30 PM by JonnyMac
After coming back to this thread I realized that some heads may explode, Scanners-style, with the program above (which would actually be cool for Halloween!).  I like FSMs, but they're not for everyone.  The killer, when it comes to emergency stop inputs, is looonng PAUSE instructions.  What we can do is replace PAUSE with a timer subroutine that can: 1) monitor the E-Switch input and abort the delay and, 2) report the input to the main program where it can be acted upon.

IMPORTANT NOTE: You cannot simply bail out of the delay subroutine and jump back to Reset; this will leave the subroutine stack (the memory area that saves the location to jump back to) in a big mess and could, ultimately, crash the program.  As you can see in the listing below we check the eStop bit after returning from the subroutine (see lines marked red).   In the end we replace PAUSE XXXXX with

timer = XXXXX
GOSUB Run_Timer
IF eStop = Yes THEN Reset


This program, other than the replacement of PAUSE, is constructed very much like any standard linear program.

' =========================================================================
'
'   File...... E-Switch-EZ.BS1
'   Purpose...
'   Author.... Jon Williams, EFX-TEK
'              Copyright (c) 2008 EFX-TEK
'              Some Rights Reserved
'              -- see http://creativecommons.org/licenses/by/3.0/
'   E-mail.... jwilliams@efx-tek.com
'   Started...
'   Updated...
'
'   {$STAMP BS1}
'   {$PBASIC 1.0}
'
' =========================================================================


' -----[ Program Description ]---------------------------------------------


' -----[ Revision History ]------------------------------------------------


' -----[ I/O Definitions ]-------------------------------------------------

SYMBOL  Sio             = 7                     ' SETUP = out; no ULN
SYMBOL  Trigger         = PIN6                  ' SETUP = DN
SYMBOL  ESwitch         = PIN5                  ' ULN acts as PULL-DN


' -----[ Constants ]-------------------------------------------------------

SYMBOL  IsOn            = 1                     ' for active-high in/out
SYMBOL  IsOff           = 0

SYMBOL  Yes             = 1
SYMBOL  No              = 0

SYMBOL  Baud            = OT2400


' -----[ Variables ]-------------------------------------------------------

SYMBOL  flags           = B0
SYMBOL   eStop          =  BIT5

SYMBOL  sfx             = B2
SYMBOL  idx             = B3
SYMBOL  blinks          = B4

SYMBOL  timer           = W4
SYMBOL  lottery         = W5


' -----[ Initialization ]--------------------------------------------------

Reset:
  PINS = %00000000                              ' clear all outputs
  DIRS = %00011111                              ' make P0-P4 outputs

  SEROUT Sio, Baud, ("!!!!!!AP8", %00, "X")     ' shutdown the AP-8
  PAUSE 25

  flags = %00000000


' -----[ Program Code ]----------------------------------------------------

Main:
  timer = 0


Check_Trigger:
  IF ESwitch = IsOn THEN Reset                  ' hold for E-Switch
  RANDOM lottery
  PAUSE 20                                      ' base loop timing
  timer = timer + 20 * Trigger                  ' update global timer value
  IF timer < 100 THEN Check_Trigger


Random_Holdoff:
  timer = lottery // 2501 + 500
  GOSUB Run_Timer
  IF eStop = Yes THEN Reset



Start_Audio:
  sfx = 0
  GOSUB Play_SFX
  IF ESwitch = IsOn THEN Reset


Blinky_Lights:
  FOR blinks = 1 TO 30                          ' 30 x 5 x 100 = 15000
    FOR idx = 0 TO 4
      READ idx, PINS
      timer = 100
      GOSUB Run_Timer
      IF eStop = Yes THEN Reset

    NEXT
  NEXT

  PINS = %00000000
  GOTO Main


' -----[ Subroutines ]-----------------------------------------------------

Run_Timer:
  eStop = eStop | ESwitch
  IF eStop = Yes THEN Timer_Exit
  IF timer < 10 THEN Timer_Exit
    PAUSE 10
    timer = timer - 10
    GOTO Run_Timer

Timer_Exit:
  RETURN

' -------------------------------------------------------------------------

Play_SFX:
  SEROUT Sio, Baud, ("!AP8", %00, "P", sfx)
  RETURN

' -------------------------------------------------------------------------


' -----[ User Data ]-------------------------------------------------------

Chaser:
  EEPROM (%00001, %00010, %00100, %01000, %10000)
Jon McPhalen
EFX-TEK Hollywood Office

GregH

 :D :D :D

Hi Jon!,

I finally got around to reading your response!  I can't thank you enough for the time and thought you've put into my question!!  I'll be using the information posted here over the next couple of weeks. 

Thanks so much for a great product and great support!!

Greg