The first article in this series HowTo Make An RCX Monitor - Part 1 established the groundwork for "hooking" into the idle CPU cycles that are executed while the computer is waiting for input. The second article HowTo Make An RCX Monitor - Part 2 deals with reading pushbuttons and debouncing them. This article will discuss how to hook everything up so it runs when the RCX is waiting for input from the IR tower. We will be able to choose one of 10 programs to execute.
I'm assuming that you have at least a basic grasp of programming in Forth and are familiar with the concept of pointers or indirection in at least one programming language. You should also know how to upload scripts to the RCX. You might also review pbForth RCX Extension Scripts if you're unfamiliar with pbForth on the RCX.
To get the most up to date version of pbForth firmware and the example scripts, you can get the pbForth Scripts as a ziparchive or the pbForth Scripts as a tar.gzarchive.
The first thing we need to do is figure out exactly what our simple monitor is going to do. We'll model its operation after the standard firmware in the RCX. When you press the Prgm button, the number on the far right side of the LCD should increment. When you press the Run button, the program corresponding to the number in the LCD should run. Optionally, when the Run button is pressed again, the program should stop.
This simple description is enough to go on. If you remember the discussion on vectored execution, you'll realize we need some kind of an array that we can use to hold our vectors. But there is no intrinsic support for arrays in Forth - we'll have to do it ourselves. Fortunately, it's pretty easy to do, as the following code fragment shows:
\ -----------------------------------------------------------------------------
\ vectorArray.txt - a simple array of execution vectors
\ -----------------------------------------------------------------------------
\ Revision History
\
\ R. Hempel 2002-04-12 - Original
\ -----------------------------------------------------------------------------
BASE @
DECIMAL
CREATE vector_array \ Private array of 10 ptrs to NoOp
' NoOp DUP , DUP , DUP , DUP , DUP ,
DUP , DUP , DUP , DUP , ,
0 VALUE vector_idx \ Private counter
: VECTOR_PUT ( xt idx -- ) \ Put xt into array
CELLS vector_array + ! ;
: VECTOR_GET ( idx -- xt ) \ Get xt from array
CELLS vector_array + @ ;
: VECTOR_EXECUTE ( -- )
vector_idx VECTOR_GET EXECUTE ;
: VECTOR_DISPLAY ( -- )
0 vector_idx 12311 LCD_NUMBER LCD_REFRESH ;
: VECTOR_NEXT ( -- )
vector_idx 1+ 10 MOD
TO vector_idx
VECTOR_DISPLAY ;
BASE !
\ -----------------------------------------------------------------------------
Now that we have a simple way of getting, setting, executing, displaying, and incrementing the execution vector contents, we can plug these parts in with a keypad scanner that can run during idle periods. The key scanner won't do any bufering. If we happen to catch a keypress of the Prgm button, we'll just increment to the next vector index.
Before we go any further, let's load up the button scan words from Part 2of this article if they are not already loaded. You'll need:
Next we'll test our first pass at integrating the words for vectored execution and button scanning. If the Prgm buttom is pressed, call VECTOR_NEXT.
\ -----------------------------------------------------------------------------
\ monitor1.txt - first pass at RCX keypad monitor
\
\ Requires: rcxScripts/checkButtons.txt
\ rcxScripts/getButtons.txt
\ vectorExecute/vectorArray.txt
\
\ This routine just loks for the Prgm switch to change state to pressed, and
\ then it calls VECTOR_NEXT. The mask for the Prgm button is 0x04
\ -----------------------------------------------------------------------------
\ Revision History
\
\ R. Hempel 2002-04-20 - Original
\ -----------------------------------------------------------------------------
BASE @
DECIMAL
: MONITOR1 ( -- )
b_scan 4 AND \ Did the Prgm button change state?
IF 4 AND \ Is it pressed?
IF VECTOR_NEXT \ If so, increment the vetor index
THEN
ELSE DROP
THEN ;
' MONITOR1 TO 'UserIdle
BASE !
\ -----------------------------------------------------------------------------
Not too complicated, is it? Recall that b_scan returns two values. The first (on the top of the stack) is the bits corresponding to switches that changed state, the second is the actual state of those bits. In the course of debugging this word, I first ran MONITOR1 from the console to make sure it left the stack unchanged and that it actually debounced the switch presses. Sure enough it worked, so then I made it the default idle handler by moving the execution token to 'UserIdle
We have a handler that deals with the Prgm switch, so it should be trivial to add processing for the Run switch. It should call VECTOR_EXECUTE whenever the Run switch is pressed. I've added some test code that makes sounds for the first 5 switch presses, but you could put any other words for your robot into the table. Here is the result:
\ -----------------------------------------------------------------------------
\ monitor2.txt - second pass at RCX keypad monitor
\
\ Requires: rcxScripts/checkButtons.txt
\ rcxScripts/getButtons.txt
\ vectorExecute/vectorArray.txt
\
\ This routine just loks for the Prgm switch to change state to pressed, and
\ then it calls VECTOR_NEXT. If the Run switch is pressed, it calls
\ VECTOR_EXECUTE.
\
\ The mask for the Prgm button is 0x04
\ The mask for the Run button is 0x01
\ -----------------------------------------------------------------------------
\ Revision History
\
\ R. Hempel 2002-04-20 - Original
\ -----------------------------------------------------------------------------
BASE @
DECIMAL
: MONITOR2 ( -- )
b_scan 2DUP \ Make a copy for both tests...
4 AND \ Did the Prgm button change state?
IF 4 AND \ Is it pressed?
IF VECTOR_NEXT \ If so, increment the vector index
THEN
ELSE DROP
THEN
1 AND \ Did the Run button change state?
IF 1 AND \ Is it pressed?
IF VECTOR_EXECUTE \ If so, run the vector
THEN
ELSE DROP
THEN ;
' MONITOR2 TO 'UserIdle
\ Add a few simple test words to make sure it all works. The test words
\ just make different system sounds...
HEX
: SOUND0 0 4003 SOUND_PLAY ;
: SOUND1 1 4003 SOUND_PLAY ;
: SOUND2 2 4003 SOUND_PLAY ;
: SOUND3 3 4003 SOUND_PLAY ;
: SOUND4 4 4003 SOUND_PLAY ;
: SOUND5 5 4003 SOUND_PLAY ;
: SOUND6 6 4003 SOUND_PLAY ;
' SOUND0 0 VECTOR_PUT
' SOUND1 1 VECTOR_PUT
' SOUND2 2 VECTOR_PUT
' SOUND3 3 VECTOR_PUT
' SOUND4 4 VECTOR_PUT
' SOUND5 5 VECTOR_PUT
' SOUND6 6 VECTOR_PUT
BASE !
\ -----------------------------------------------------------------------------
That's all there is to it. It is very easy to add features like On/Off switch monitoring to turn off the RCX. Another example of using this feature would be for a robot competition like a line-follower. If you needed to set up your bot to get good values white and black, you could set up calibration words and have them execute automatically. You would press the Prgm switch until it got to 0, then place the light detector over the balck line, then press Run to take a reading and store it. Next, you increment the Prgm indicator to 1 and repeat the process for the white background. Now your robot is optimized for the actual field conditions!
There are many other possibilities, including scanning for switches all the time, not just when the system is idle, but that's another article!