You are on page 1of 14

Language Document Nate Bates 4-13-00

pbForth
Introduction
When I was first introduced to pbForth, I thought it to be an outrageously complicated language. Seeing Ralph Hempel (creator of pbForth) at Mindfest using a terminal program on his laptop to communicate with the RCX and a bunch of assembly looking code everywhere made me shy away from it a little. So I avoided it as much as I could, but eventually it was tossed my way. I was in for a surprise, it wasnt going to be as bad as I had thought.

About pbForth
pbForth is based on Forth, an embedded system programming language from the 70s. It is a low-level language used frequently with robotics. pbForth is a very unique language with respect to programming the RCX. The code is written in a text file and passed to the RCX using a terminal application like HyperTerminal, CRT, or QModem. This is where pbForth differs from other programming languages. In other programming languages, you write/construct your code, compile it, and download it to the RCX. In the case of pbForth, the code is written and downloaded, and when the RCX receives it, it takes it upon itself to compile the code. This means that the programming is totally platform independent. As long as you have a terminal application for whatever operating system you are running, you can program the RCX using pbForth. Another difference between pbForth and other programming languages for the Brick is its use of a stack. The stack is used for parameter passing. These parameters are not limited to variables to or from functions. Example: In order to perform the addition 3+4, you must implement it using reverse polish notation. This looks like 3 4 +. The two parameters 3 and 4 are pushed onto the stack, and the + operator pulls them off, performs the addition operation, and returns the value on the stack. You must be careful not to place things on the stack without removing them. If you have a function that leaves unwanted residue on the stack, the program may pull the garbage off the stack instead of the expected parameter. Make sure your functions leave the stack with only the desired parameters on top.

pbForth Programs
I. Power Button
The first program I wrote in pbForth was a program to react to the OnOff button being pressed. pbForth doesnt react to the physical OnOff button. The only way to turn off the RCX when running pbForth is by programming it to turn off. The following program makes the RCX respond to the OnOff button. : INIT RCX_INIT BUTTON_INIT 3007 LCD_SHOW LCD_REFRESH 1 4004 SOUND_PLAY ; : OFF LCD_CLEAR LCD_REFRESH POWER_OFF ; : GETBUTTON POWER_INIT RCX_POWER 4000 POWER_GET RCX_POWER @ ; (*WAKE UP RCX) (INITIALIZE RCX) (INITIALIZE BUTTON READING) (DISPLAY WALKING MAN) (BEEP) (*SLEEP THE RCX) (CLEAR THE LCD) (TURN RCX OFF) (*RETURNS VALUE OF THE ONOFF BUTTON) (ENABLE POWER MODE) (GET VALUE OF BUTTON 0=PUSHED, 2=NOT) (TAKE THE VALUE AT MEMORY LOCATION-) (-RCX_POWER AND PUT IT ON THE TOP OF-) (-THE STACK) : CHECKBUTTON GETBUTTON 0 = IF OFF INIT BEGIN GETBUTTON 2 = UNTIL ELSE THEN ; : MAIN BEGIN 1 CHECKBUTTON WHILE REPEAT ; (*PROCESS STATE OF ONOFF BUTTON) (IF PUSHED,) (TURN OFF THEN ON) (STAY HERE UNTIL THE BUTTON IS NOT-) (-PUSHED, OTHERWISE ON OFF ON OFF, ETC) (IF NOT PUSHED, DO NOTHING) (DENOTES END OF IF STATEMENT) (**MAIN PROGRAM**) (REPEAT WHILE 1=REPEAT FOREVER. DO-) (CHECKBUTTON EVERY LOOP)

You must note that in the CHECKBUTTON routine, if the OnOff button is pressed then the code executed is OFF INIT. When the RCX is turned off, it remembers where it left off in the code. This would be directly after the POWER_OFF command in the OFF routine. When the RCX wakes up at the second push of the OnOff button, it starts directly after this command, namely at the call to INIT. This gets the RCX up and ready to go. There are some things that confused me during my writing of this program. First, the OFF subroutine clears the LCD, refreshes it, and then turns the RCX off. It is possible to turn the RCX off without turning off the LCD. This doesnt sit well in my mind. If something is off, it should be in no way active. When you turn the power back on, it should whir and come to life before your eyes. In pbForth, turning off the RCX means putting it in a sleep-like state, from which it can only be awakened using the OnOff button. The RCX may be sleeping and totally dead to the world, unable to receive commands from the IR tower, and yet still have its display active. This is entirely counter-intuitive to me. Having the RCX unresponsive while the LCD remains active serves no purpose but to drain the batteries. I am unsure if there is any reasoning behind this or if it merely mirrors the op-codes on the RCX. It makes no sense to me, however Another thing that perplexes me is the special routine for obtaining the state of the OnOff button. pbForth has specific button control words, but they are only for the View, Program, and Run buttons. For some reason the OnOff button is a special case. I wont dispute that this is the most powerful button on the RCX, but why is there a special variable (address given by RCX_POWER) to store the state of this button? POWER_GET, the function called to obtain the button state can also return the battery voltage, something which clearly demands its own variable. But pbForth provides special functions for dealing with buttons on the RCX, why is the OnOff button excluded? I can only assume that this might be a result of the underlying structure of the RCX, for it poses no practical use. All in all, this program did not accomplish what I had hoped it would. I wanted a program to make the Brick with pbForth firmware function like a Brick with any other firmware, at least with respect to the OnOff switch. I made a program that would respond to the button being pushed, but does not allow the functionality I had hoped. I was looking for a program that would let me turn on the Brick with the button, program it a little with my terminal application, and then turn the Brick off using the button again. This was not possible, however, because communication privileges with the Brick are suspended when a program is running on it. The only way I can make any use of the powerbutton program is by calling it with the terminal when I want to use it. This is the whole reason I created this programto detach the control of the Bricks power from the computer. My attempt was not totally pointless, I did learn something along the way.

II. Edge-Avoider
The second robot I programmed using pbForth was an edge-avoider. The algorithm for this robot is simple: 1: go forward until a bumper on the front is triggered 2: back up while turning for a specified amount of time 3: repeat

Physical Appearance of Edge-Avoider The coding of the robot was not too complicated. There are a few value-returning functions, as well as a couple behaviors. All in all, it is a rather simple program. : INIT RCX_INIT BUTTON_INIT 3007 LCD_SHOW LCD_REFRESH 1 4004 SOUND_PLAY ; : SENSOR_IN HEX RCX_INIT SENSOR_INIT 0 SENSOR_PASSIVE 1 0 SENSOR_TYPE 00 0 SENSOR_MODE ; (REFRESH THE RCX) (INITIALIZE RCX AND BUTTON FEATURES) (DISPLAY WALKING MAN) (BEEP) (DEFINE AND INITIALIZE SENSOR) (INITIALIZE THE RCX AND SENSORS) (SET SENSOR 1 TO PASSIVE NO POWER) (SET SENSOR 1 TO TOUCH, 0 OR 1) (SET SENSOR 1 TO RAW READINGS)

: SENSOR_VAL 0 SENSOR_READ DROP 0 SENSOR_BOOL ; : BUTTON

(GET SENSOR1 READING) (INITIALIZE THE SENSOR FOR READING) (GET RID OF GARBAGE ON STACK) (OBTAIN READING FROM TOUCH SENSOR) (OBTAIN VALUE OF BUTTON PUSHED ON RCX, IF ANY)

BUTTON_INIT RCX_BUTTON DUP BUTTON_GET 2 ; : FORWARD 7 1 0 MOTOR_SET 7 1 2 MOTOR_SET ; : BACKWARD 0 1 0 MOTOR_SET 7 2 2 MOTOR_SET 0 0 TIMER_SET BEGIN 0 TIMER_GET 10 = UNTIL ; : MAIN SENSOR_IN BEGIN SENSOR_VAL 1 = IF BACKWARD ELSE FORWARD THEN BUTTON 1 = UNTIL 7 3 0 MOTOR_SET 7 3 2 MOTOR_SET INIT ; (REPEAT UNTIL RUN BUTTON PUSHED) (STOP MOTOR A) (STOP MOTOR C) (RETURN RCX TO READY STATE) (IF BUMPER TRIGGERED) (BACK UP AND TURN) (OTHERWISE, PROCEED FORWARD) (**MAIN PROGRAM**) (INITIALIZE SENSOR) (MOVE ROBOT FORWARD) (MOTOR A FULL POWER FORWARD) (MOTOR C FULL POWER FORWARD) (BACK ROBOT UP, WHILE TURNING) (MOTOR A LOW POWER FORWARD) (MOTOR C FULL POWER BACKWARD) (SET FIRST TIMER TO ZERO) (DELAY FOR 1 SECOND)

One interesting thing to note is the explicit stopping of motors A and C at the end of the code. This peculiarity is similar to the LCD remaining on when the RCX is turned off. I assumed that initializing the RCX would return it to its natural, steady state. I believed in this state that no code would be executing, that all inputs and motors would be turned off. This was a false assumption, however, and the first time I ran my code the RCX stopped executing its code but the motors were left in their ON state. I guess that I shouldnt have assumed that everything would be turned off. I shouldve realized that the LCD screen is

not reset to its default 4th display, that the inputs are still powered (if they are active), much like Robotics Invention System and other programming languages for the Brick. In these other languages, however, the purpose of keeping the inputs powered and sampled is that you can view their readings and test your robot with them. With pbForth, the View button does absolutely nothing unless you have a program running to sample the inputs. Perhaps RCX_INIT should be changed to stop everything and return the RCX to its newborn state, as if the firmware had just been downloaded to it. Maybe there is some use to the RCX_INIT command, however, and another word should be created, RCX_RESET, to reset the RCX. It would not be too hard to construct your own function to accomplish the resetting of everything on the RCX, my point is that RCX_INIT should not be misunderstood to perform the function of resetting the RCX. I enjoyed constructing and programming this robot, as it was actually a robot. It read a sensor value and responded accordingly. The robot could be seen running around and performing its task (that task being to run around some more). It was nice to have the robots physical construction being more than a single RCX.

III. Slot Machine


My last programming venture with pbForth was creating a slot machine type device. One of the main advantages (some might say disadvantages) of pbForth is its allowance of the programming of the RCX buttons. I chose to utilize this capability and create a robot whose physical construction was as simple as could be. The slot machine robot was comprised of a single RCX brick. My goal was to create the equivalent of a handheld computer game. While I did not accomplish everything I had hoped (there is no running score, and its not considered a win if only 2 are the same), I believe this program to be a success. The slot machine is comprised of two components of the RCX: the View button, and the LCD. Reels are represented by numbers on the LCD, and the View button serves to stop the current reel. There are three reels displayed on the LCD that spin at a quick, but not outrageous speed. These reels count from 1 to 9 and then start over. First, the first reel is incremented, then the third, and finally the second. This is to give a more random look to the moving numbers. You stop the reels starting with the leftmost and moving right. Pressing View stops the current reel moving and leaves the remaining reels spinning. After stopping all three reels, the program determines if you have three matching numbers. If you do, it plays a victory noise. If not, there is a noise of defeat. This program is the most complicated one I programmed using pbForth. There are numerous variables as well as a large number of functions that pass variables back and forth on the stack and call multiple functions themselves. I hope that the code isnt too hard to follow.

VARIABLE REEL1 VARIABLE REEL2 VARIABLE REEL3 VARIABLE LEVEL VARIABLE VALUE

(FIRST REEL NUMBER) (SECOND REEL NUMBER) (THIRD REEL NUMBER) (DENOTES NUMBER OF REELS SPINNING) (NUMBER TO BE DISPLAYED ON LCD, AS) (YOU CANNOT DISPLAY INDIVIDUAL DIGITS) (YOU HAVE TO COMBINE THE THREE REELS)

VARIABLE LOOPNUM VARIABLE COUNTER

(INDICATES REEL TO BE INCREMENTED) (FOR USE WITH A DELAY FUNCTION)

: DOREEL1 LEVEL @ 3 = IF REEL1 @ 9 < IF REEL1 @ 1 + REEL1 ! ELSE 1 REEL1 ! THEN ELSE THEN ; : DOREEL2 LEVEL @ 1 > IF REEL2 @ 9 < IF REEL2 @ 1 + REEL2 ! ELSE 1 REEL2 ! THEN ELSE THEN ; : DOREEL3 LEVEL @ 0 > IF REEL3 @ 9 < IF REEL3 @ 1 + REEL3 ! ELSE 1 REEL3 ! THEN ELSE THEN ;

(PROCESS VALUE OF REEL1) (IF REEL1 IS STILL SPINNING) (INCREMENT IF LESS THAN 9)

(RESET TO 1 IF 9 OR ABOVE) (IF REEL IS NOT SPINNING, LEAVE THE-) (VALUE THE SAME) (PROCESS VALUE OF REEL2) (IF REEL2 IS STILL SPINNING) (INCREMENT IF LESS THAN THAN 9)

(RESET TO 1 IF 9 OR ABOVE) (IF REEL IS NOT SPINNING, LEAVE THE- ) (VALUE THE SAME) (PROCESS VALUE OF REEL3) (IF REEL3 IS STILL SPINNING) (INCREMENT IF LESS THAN 9)

(RESET TO 1 IF 9 OR ABOVE) (IF REEL IS NOT SPINNING, LEVE THE- ) (VALUE THE SAME)

: LOOPPROCESS LOOPNUM @ 1 = IF DOREEL1 ELSE THEN LOOPNUM @ 2 = IF DOREEL3 ELSE THEN LOOPNUM @ 3 = IF DOREEL2 ELSE THEN

(DETERMINE WHICH REEL IS CHANGING, AND CHANGE IT) (IF REEL1 IS UP TO BE CHANGED) (PROCESS REEL1 CHANGE)

(IF REEL3 IS UP TO BE CHANGED) (PROCESS REEL3 CHANGE)

(IF REEL2 IS UP TO BE CHANGED) (PROCESS REEL2 CHANGE)

LOOPNUM @ 1 + LOOPNUM ! LOOPNUM @ 4 = IF 1 LOOPNUM ! ELSE THEN ; : GETNUM DECIMAL LOOPPROCESS DECIMAL

(MODIFY REEL TO BE CHANGED) (IF REEL VALUE IS 4, RESET THE PROCESS- ) (ID TO 1)

(CREATES A NUMBER REPRESENTATION OF THE- ) (REELS TO BE DISPLAYED ON THE LCD) (SET THE MODE TO DECIMAL) (CHANGE THE REEL) (SET THE MODE TO DECIMAL AGAIN) (STORE THE NUMBER- ) (REPRESENTATION OF THE REELS)

REEL1 @ 100 * REEL2 @ 10 * + REEL3 @ + VALUE ! ;

: NATEWAIT 0 COUNTER ! BEGIN COUNTER @ 250 < WHILE COUNTER @ 1 + COUNTER ! REPEAT ; : BUTTON

(DELAY A LITTLE BIT) (INITIALIZE THE COUNTER) (STAY IN THE LOOP FOR 250 CYCLES)

(RETURN THE VALUE OF THE CURRENT BUTTON PUSHED)

BUTTON_INIT RCX_BUTTON DUP BUTTON_GET RCX_BUTTON @ ;

: INIT DECIMAL RCX_INIT BUTTON_INIT LCD_CLEAR LCD_REFRESH 1 REEL1 ! 4 REEL2 ! 7 REEL3 ! 3 LEVEL ! 1 LOOPNUM ! DECIMAL GETNUM ; : DISPLAY GETNUM HEX

(SET UP THE RCX AND INITIALIZE VARIABLES) (SET THE MODE TO DECIMAL) (INITIALIZE THE RCX AND BUTTON CAPABILITIES) (RESET THE LCD) (STORE INITIAL VALUES INTO REELS)

(START WITH ALL 3 REELS SPINNING) (START PROCESSING WITH LEFT REEL) (SPIN THE FIRST REEL) (DISPLAY THE CURRENT REELS ON THE LCD) (OBTAIN THE NEW VALUE OF THE REELS) (SET THE MODE TO HEX) (DISPLAY THE NUMBER)

3002 VALUE @ 3001 LCD_NUMBER LCD_REFRESH ; : STOPREEL BUTTON 2 = IF LEVEL @ 1 - LEVEL ! BEGIN DISPLAY NATEWAIT BUTTON 0 = UNTIL ELSE THEN ; : CHECKZERO LEVEL @ 0 = IF VALUE @ 6F MOD 0 = IF HEX 5 4004 SOUND_PLAY LEVEL @ 1 - LEVEL !

(PROCESS THE VALUE OF THE STOPPER, VIEW) (IF THE VIEW BUTTON IS PRESSED) (DECREMENT THE NUMBER OF SPINNING REELS) (KEEP THE REELS SPINNING AND WAIT UNTIL THE- ) (VIEW BUTTON IS UNPRESSED. IF THIS WAS NOT-) (HERE, THE VIEW BUTTON WOULD BE PROCESSED-) (A LOT AND ALL REELS WOULD BE STOPPED)

(SEE IF ALL REELS ARE STOPPED AND IF PLAYER WON) (IF ALL REELS ARE STOPPED) (IF DIVISIBLE BY 111ALL SAME #) (PLAY HAPPY SOUND) (DECREMENT LEVEL SO SOUND-) (WONT BE PLAYED AGAIN)

ELSE HEX 4 4004 SOUND_PLAY LEVEL @ 1 - LEVEL ! THEN NATEWAIT ELSE THEN ; : MAIN BEGIN INIT BEGIN BUTTON 1 = IF RCX_SHUTDOWN ELSE THEN DISPLAY NATEWAIT STOPREEL CHECKZERO LEVEL @ 0 = UNTIL BUTTON 1 = UNTIL ;

(IF PLAYER DIDNT WIN) (PLAY UNHAPPY SOUND) (DECREMENT LEVEL SO SOUND-) (WONT BE PLAYED AGAIN) (STAY HERE FOR A LITTLE BIT)

(**MAIN PROGRAM**) (SET UP RCX AND VARIABLES) (EXPLAINED LATER)

HEX 3002 2 3001 LCD_NUMBER LCD_REFRESH

(IF RUN BUTTON IS PRESSED, STOP)

(CALCULATE AND DISPLAY REELS) (PAUSE SO NUMBERS ARE VISIBLE) (SEE IF STOPVIEWIS PRESSED) (SEE IF GAME IS OVER) (PLAY GAME UNTIL ALL REELS ARE-) (STOPPED) (KEEP PLAYING GAMES UNTIL RUN-) (BUTTON IS PRESSED)

This program has quite a few issues, and this is understandable as its a large program. Some are large, confusing problems, while others are small annoyances. My original intention was to make the values of the reels appear more random than sequential counting. I considered counting by threes and subtracting 10 if the value went above 10. This would result in a number sequence as follows. 0 3 6 9 2 5 8 1 4 7 0 3 6 9 2

The problem with this is that you cannot specify where to put a given integer. The only way to display a number on the LCD is to display one number. If you wanted to display 3 integers, you would have to do it using a single number in the hundreds. The formula for this number is: integer1*10 + integer2*10 + integer3. The problem arises when integer1 is a zero. Then you get numbers like 42 instead of 042 which is what you would like. There is a formatting for the LCD display which fills all places to the left of the number with zeros, but there are 4 places on the LCD screen and I only wanted to utilize 3 of them. Rather than having a static zero on the left of the screen, I chose to eliminate the zeros and have the reels run from 1-9 sequentially. The reason I did not count by threes was that it wouldve required yet another if statement in a block that already had two. And in case you havent noticed, if statements arent pretty in pbForth. It is certainly possible to count by threes and omit the zeros, but I didnt feel this needed to be done Another issue with the LCD was that it would not display within the DISPLAY loop until a primary call had been made to the LCD. Originally, I thought that this might have to do with the hex/decimal issues I faced (discussed later), but it did not. By making an initial call to the LCD, HEX 3002 2 3001 LCD_NUMBER LCD_REFRESH as found in MAIN, the LCD would be initialized and ready to respond to the DISPLAY function. If it had been an issue of hex/decimal base, this simple call would not have fixed it, as the base has not been modified within DISPLAY. I am actually quite perplexed as to why this initialization was needed for the LCD. The most frustrating part of my experience with pbForth was working with different bases. In order to display a number on the LCD, the number must be in decimal. The parameters that you pass to the LCD display function are expected to be in hex. This means that you cant write your whole program in one base. You must make sure that the numbers you increment and store as reels are decimals and that when you combine them that they result in a decimal value. You must change to hex where the parameters require it, and you must be aware of the fact that after calling a function from within a function the base might be changed. The LCD would display the results of incrementing the reels as hex numbers at the beginning of my hex ordeal. I attempted to work through the program and determine exactly where the numbers needed to be decimal, where they needed to be hex, and where they might change as a result of calling a function. After being unsuccessful in this attempt, I placed DECIMAL and HEX wherever there mightve been a problem. As a result, my code was overloaded with these words and made it look like I had no idea what I was doing. Also as a result, my code worked. I decided not to mess with the bases and try to clean it up, for things are temperamental within pbForth. You might download a program, run it, and it might run perfectly. If you downloaded it again and ran it, it might not work exactly the same. Also, if I messed something up and passed a decimal value to a function that was expecting a hex number, the RCX might crash and Id have to reset the firmware (something I did well over 20 times while working

with pbForth). While my code may be a bit messy with respect to my number bases, it works, and I am content with that. One thing that I noticed a bit with the other programs that became quite obvious with this code was the order in which you would download things to the RCX. I found that if you changed a function that you had to download all other functions that made use of this function. Perhaps when you send something to the RCX, it assigns the code a starting address and points all references to that code to that address. If you send the same code to the RCX, it might not realize that the code represents the same function and assign it to a new address. All old calls to the function of the same name as the new function would still be pointed to the old function. In order to change this, I believe you have to download all functions that make reference to the modified function. You have to realize that you cannot change one portion of your code, send it to the RCX and expect everything to work now. You must keep track of the functions hierarchy and download them to the RCX accordingly. I believe this was an important program to create. It shows a lot of the capabilities pbForth has which other languages do not.

Conclusion
pbForth is certainly a language like no other. It is a little difficult to work with in that you need many different applications to use pbForth, and it is lacking any sort of visual appeal. pbForth is a language for the hardcore old-school embedded systems programmers out there. It is certainly not a language for kids, and I think its a little too complicated for me as well. I dont have the proper mindset or programming technique to take advantage of everything pbForth provides. Learning to program pbForth well would be an involved and complicated task. It is simple enough to implement old programming strategies (IF statements, WHILE loops, etc) using pbForth, but if thats all youre going to use pbForth for, youre better using another language. I found pbForth to be an interesting challenge from a programming point of view, but highly impractical for programming regular robots (by regular I mean not requiring RCX button input and such). There are certainly more doors to be opened when using pbForth, but the locks to these doors become correspondingly more complex. Ultimately, pbForth is a complicated but welcome (and necessary) addition to the world of RCX programming languages.

You might also like