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. This article will go into more detail and describe how to read the pushbuttons on the RCX and perform actions based on their state.
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 zip archive or the pbForth Scripts as a tar.gz archive.
The RCX has a number of routines that are used to read the pushbuttons on the front panel. They may be found in the pbForth RCX Word List document under the section called Button and Sound Control Words. We want to end end up with a monitor that behaves more or less like the default firmware in the RCX. To accomplish this we'll need to know when a button is released and when it is pressed. We'll also make the assumption that we may not be able to process the key state changes as quickly as possible, so we will buffer the key events in a queue.
Before we get into the meat of the button processing, we need to mention something about the operation of switches in the real world. When you press one of the rubber keys on your RCX, or when you type text on a computer, the switches do not open and close cleanly. It sounds like the click on and off once per press, but in reality, there is usually 5 to 20 msec of bounce in the key signal. The computer will interpret these bouncy signals as multiple presses if we just read the switch as fast as we can. So how can we avoid the problem?
Just reading the switch once in a while (say 10 times a second) won't fix the problem. Even though the readings are about 100 msec apart, there is no way to avoid the fact that we may be reading the switch in the middle of the bounce. We will need debounce the signal somehow, and that's where a bit of logic can help.
When devising a debounce scheme, it is useful to have some design guidelines to work with. Here are my assumptions for the RCX pushbuttons:
A common theme you'll see in good Forth code is factorization. This is the art of breaking down the task into small, understandable, and testable chunks. To some extent, you can't get good at factorization until you have written a lot of code, so it's a Catch-22 situation. I'll go into some detail on the design decisions for a pushbutton interface and how they are affected by factorization.
The first thing we'll need to do is figure out what we need from our pushbutton processor. Besides the obvious requirement for some way of reading the state of the buttons, we'll also need to know when they change state. In general, we may want to do one action when a button is pressed, and another when a button is released.
We also want to avoid responding to false transitions, so some kind of debouncing is in order. Finally, we'll also need to buffer the switch presses, so that we can deal with the switch press functions whenever we have some spare CPU cycles.
One way of getting all this functionality is to detect state changes whenever the switch stabilizes into a position that is different from the last known stable state. Normally, I would deal with each bit in the switch state table individually, but because all of the switches fit into one byte, I'll deal with them all at once.
The first step is to be able to read the switches. The RCX has one routine that is used to read the Run, View, and Prgm switches and another routine to read the On switch. This is because the On switch is hooked to a line that can generate an interrupt to "wake up" the RCX.
The two routines return overlapping values, so I'll make things easy and just shift the result for the On switch over a few bits. Here is the code to read the switches and return a value for other routines that might need it.
\ ----------------------------------------------------------------------------- \ getButtons.txt - return the state of all the RCX buttons \ ----------------------------------------------------------------------------- \ Revision History \ \ R. Hempel 2002-04-16 - Original \ ----------------------------------------------------------------------------- \ \ The GET_BUTTONS word returns the state of the View, Prgm, and Run buttons \ in the lower three bits. The POWER_GET word returns the state of the On \ button as 0 if pressed. The logic after just sets the 4th bit high if the \ On button was pressed. \ ----------------------------------------------------------------------------- BASE @ HEX : GET_BUTTONS ( -- u ) RCX_BUTTON DUP BUTTON_GET @ RCX_POWER DUP 4000 POWER_GET @ 0= IF 8 OR THEN ; BASE ! \ -----------------------------------------------------------------------------
The next step in getting the switches debounced and detecting state changes is to "play computer". Try to describe in words how you would figure out that the switch state has changed and is stable. Here's my attempt:
The state of the switches has changed when two consecutive samples are the same and they are different than the last known stable state.
This tells us everything we need to know to implement the algorithm! We need some way of savng the previous sample along with the last known state. The following code block will read the buttons and return the debounced state.
\ -----------------------------------------------------------------------------
\ checkButtons.txt - return the debounced state of all the RCX buttons
\ -----------------------------------------------------------------------------
\ Revision History
\
\ R. Hempel 2002-05-17 - Added #include for testing
\ R. Hempel 2002-04-16 - Original
\ -----------------------------------------------------------------------------
\ NOTE: This code assumes getButtons.txt has already been loaded...
\
\ The state of the switches has changed when two consecutive samples are the
\ same and they are different than the last known stable state.
\ -----------------------------------------------------------------------------
\ #include getButtons.txt
BASE @
HEX
\ The button event processing uses a typical ring-buffer. If empty, the head
\ index is equal to the tail, if full, the head is one less than tail.
CREATE b_buf \ A ring buffer with 8 bytes
8 ALLOT
0 VALUE b_head \ Indexes into the b_buf structure
0 VALUE b_tail
0 VALUE b_stable \ The debounced state
0 VALUE b_prev \ The previous scan
\ Routines to put and get entries into buffer. NOTE the use of 8 as a magic
\ number! By ANDing with 7 we get the MOD function to be very fast...
: b_full? ( -- f ) \ Return non-zero if full
b_head 1+ 7 AND b_tail = ;
: b_put ( u -- )
b_head DUP 1+ 7 AND DUP
b_tail = \ Check if full
IF 2DROP DROP \ If yes, ignore change
ELSE TO b_head \ If not, update head ptr
b_buf + C! \ and store the item
THEN ;
: b_empty? ( -- u )
b_tail b_head = ; \ Return non-zero for empty
: b_get ( -- u )
b_tail DUP b_head = \ Check if empty
IF DROP 0 \ If yes, return 0
ELSE DUP 1+ 7 AND TO b_tail \ If not, update tail pointer
b_buf + C@ \ and get the item
THEN ;
: b_scan ( -- new chg ) \ Function to check for changed button
GET_BUTTONS \ Get a new scan
b_stable \ Get the last stable state and check equality
OVER XOR DUP \ Is new sample different than last stable state?
\ ( new chg f -- )
IF OVER b_prev \ If yes, check for stable change
XOR \ Determine change
\ ( new chg f -- )
IF DROP 0 \ If not stable, indicate no change
ELSE OVER TO b_stable \ If stable, save as new stable state
THEN
THEN \ ( new chg -- )
OVER TO b_prev ; \ Save this sample for next time
\ -----------------------------------------------------------------------------
These are all the words we need to check the pushbuttons for state changes and possibly put them in a buffer to deal with later. The final step is Part 3, where we put together everything we've learned in Part 1 and Part 2 to make a simple monitor that executes one of 10 programs.