Comparing PBASIC 2.5 control structures to the old PBASIC

<home> <index>


(c) 2003 Tracy Allen, EME Systems
modified 3/26/2004 (added PIN declaration)

Caveat: Information presented here is not sponsored or supported by Parallax Inc. It is solely the guesswork of the author, and there is no warrantee implied for any purpose. I do hope you find helpful insights here, toward better PBASIC programming. I really like the new PBASIC, and this is my exploration of the new commands. You are on your own, but please do let me know if you find mistakes. (PBASIC and BASIC Stamp are trademarks of Parallax Corporation)

The PBASIC 2.5 language is implemented in the "BASIC Stamp Windows Editor, version 2.0 beta 2.1", which you can download from Look in the downloads section. Don't let the mishmash of numbers throw you off, as PBASIC 2.5 is part of the version 2.0 beta 2.1 Editor! This is also referred to as the IDE (Integrated Development Environment).

With PBASIC 2.5, Parallax introduced several new control structures that can make PBASIC programming easier and more fun. And also much clearer to read for third parties, and for the programmer trying to figure out what he or she did last year. These structures make sushi out of what might have been spaghetti. Comments are always helpful to document what a program is doing, but the new structures can make the job of documentation easier. The new structures include,

It is important to realize that the new PBASIC 2.5 control structures do not make any new tokens inside the Stamp chips. Programs created with PBASIC 2.5 work on the original BASIC Stamp II just as they do on the newest BASIC Stamps 2p and 2pe. When you or I write a program in PBASIC 2.5 using a DO-LOOP, for example, the compiler translates it into a program in the old PBASIC. The compiler translates it into GOTOs, IFs and labels that we do not ever see. (Thank goodness!). The spaghetti can be truly daunting if it involves several nested DO loops with several exit conditions and other structures nested inside. You don't want to see it! The resulting program is easier to write, easier to read, and easier to maintain, but underneath the hood the compiler takes care of the messy details.

I have been curious exactly how the compiler does this translation of the core commands. There are good reasons to know how it is done under the hood...

  1. Understand how the commands really work. Avoid bugs and surprises that come from misunderstanding the syntax.
  2. Know when optimization will help. PBASIC 2.5 code can be much clearer than the alternative, however, it is often possible to optimize the code substantially when speed and size must take precedence over clarity.

PBASIC programs in the above listings have at least two versions. First is PBASIC 2.5 version, say DO:LOOP, and then after that the version using vintage PBASIC code that I determined from trial and error generates exactly the same tokens. You can check this yourself by looking at the memory map screen that is called up when you press the CTRL-M hot key. Once you see the memory map screen, scroll down to view the red area at the bottom of the memory map window. Compare the tokens that show up as you compiler first the PBASIC 2.5 case, and then its old-fashioned companion. Those HEX values in red are exactly the program code bytes that will be loaded into the Stamp eeprom, so, when two alternatives produce exactly the same tokens, we can say the two are equivalent. In some cases I have shown additional versions of the programs to illustrate some point or variation on the theme.

Don't expect the programs to do anything useful. They are merely study guides for the structure. The labels I have written down for these demo programs are generic and are only reference points. The PBASIC compiler translates them ultimately into numerical addresses in the Stamp eeprom, and I don't know what intermediate steps might be involved.

The translation was fairly easy to guess after a few trials. The compiler does not attempt any optimization, and that is probably a good thing, because optimization could lead to surprises when the programmer really wants to do numerical tricks with the True and False conditions. The ON x GOSUB command appears to be the only structure that does not have an exact equivalent in the old PBASIC, and the departure is minor.

Some observations:

The new variable type, PIN, is found in the new PBASIC 2.5, part of the version 2.1 beta development environment available on the Parallax web site.

PIN, the new type declaration in PBASIC 2.5

The new type declaration, PIN, is found in the new PBASIC 2.5. (part of the version 2.1 beta development environment available on the Parallax web site.)

In previous versions of PBASIC, a pin sometimes had to be declared in two or three ways:

Quite often in programming, a pin serves many different purposes and needs to be declared in all these different manners. It can be hard to keep track of which form is which and the number of names seems to be more that should be necessary.

The new PIN declaration rolls all of those into one, and the compiler intelligently figures out the intended usage.

MyPin PIN 3

With that established, you can use MyPin in commands and in most cases it will do what you would expect. But it takes a bit of study to know when to know you are on the edge of doing something where the compiler will second guess you wrong. An email from Jeff Martin reproduced below lays out the rules.


HIGH MyPin ' same as  HIGH 3
INPUT MyPin  ' same as INPUT 3
MyPin=1 ' sets output latch  OUT3=1 
OUTPUT MyPIN  ' same as OUTPUT 3

Note that the third command above does not automatically make the pin an output, so the fourth command may also be needed to assert the contents of the output latch onto the output pin. The same is true for some commands that read the input from a pin; it may be necessary to turn the pin into an input before executing the command that reads it.

Here are more examples:

IF MyPin=1 THEN ...  '  same as IF in3=1 THEN ...
x = MyPin ' same as x = in3
PULSOUT MyPin,1000 ' same as PULSOUT 3,1000
PULSIN MyPin,x ' same as PULSIN 3,x

note that the last one could be ambiguous. You might guess that it would use the input state of MyPIN (in3=0 or in3=1) as the PULSOUT or PULSIN pin. But the compiler's rule is, when MyPin is in a position that expects a pin argument, it is a constant.

x = MyPin        ' same as x = in3
x = MyPin+1      ' same as x = in3 + 1   
x = out0(MyPin)  ' same as x = out3, or as out0(3)

This last one above illustrates another rule. The compiler treats MyPin as a constant when it is an array index or index computation.

How about this one, where you look at the different positions MyPin could appear in a LOOKUP command?...

LOOKUP MyPin,[0,10,20,30,40],x ' same as LOOKUP in3,[0,10,20,30,40],x

The compiler does in fact treat MyPin as an input with a value of 0 or 1, and the lookup will return either x=0 or x=10. How about MyPin as one of the value arguments...

LOOKUP idx,[0,10+MyPin,20,30,40],x ' same as LOOKUP idx,[0,10+in3,20,30,40],x

If idx happens to be 1, then the result will depend on the state of in3, and the result will be either x=10 or x=11. And finally

LOOKUP idx,[0,1,1,0,1],MyPin ' same as LOOKUP idx,[0,10,20,30,40],out3

This will put the result into the output latch, out3. The compiler expects to write something into a variable in that spot. Note that MyPin is always just one bit.

The compiler figures out from the context whether to treat is as a variable reflecting the input state of the pin, or as a constant referring to the pin number, or as a reference to the output latch. But you have to know the rules! Especially if you are doing anything out of the ordinary, like indirect addressing or tricky shortcuts.

There could be weird situations where you are not doing the obvious. Examples:

TOGGLE MyPin    ' same as TOGGLE 3, but maybe you really want...
TOGGLE in3    ' reads the state of in3 and toggles either p0 or p1

Why on earth would you want to use the second form, the weird one? Say you have a switch attached to p3, and you want one led attached to either p0 or to p1 to flash depending on the state of the switch. The TOGGLE in3 command accomplishes that in a concise way, but not in a way that the compiler would parse it. 

More examples...

PULSOUT 5+MyPin,1000  ' same as PULSOUT 5+3,1000
'the pulse will appear on p8

The pulse will appear on pin p8, because the compiler in this position will treat myPin as a constant, 3. If you really want the pulse to be output on a pin that depends on the state of a switch attached to MyPin, then do this:

INPUT MyPin     ' it is an input
x =MyPin       ' either 0 or 1 depending on input level
PULSOUT 5+x,1000       ' outputs the pulse on either p5 or p6

or simply force it to use in3...

PULSOUT 5+in3,1000 ' put the pulse out on either p5 or p6.

The compiler rule is to use the PIN as a constant in locations where a pin reference is clearly called for by the syntax. So you have to work around if you are being tricky and really do need a pin number that depends on the state of another pin.

Similarly, if you have an array of variables, what happens when a PIN reference is used as the index?

x var byte(8)
MyPin PIN 3
x(MyPin)=124    ' puts 124 into x(3)
x(MyPin+2)=124  ' puts 124 into x(5)

The compilter treats the PIN as a CONstant when used as the array index. If instead you really want a program to select an array element depending on the state of a pin, you have to do something like this:

INPUT MyPin     ' it is an input
idx = MyPin       ' reads the state of the pin
x(idx+2)=124  ' puts 124 into either x(2) or x(3) 

depending on the input from MyPin. Or, simply use in3 instead of MyPin:

x(in3+2)=124  ' puts 124 into either x(2) or x(3) 

How about this?...

switchA PIN 4
switchB PIN 5
switchC PIN 6
ledA PIN 7
ledB PIN 8
ledC PIN 9
FOR i=0 to 2
  INPUT switchA+i           ' same as INPUT 4+i, makes each pin 4,5 & 6 an input
  DEBUG DEC switchA(i),CR   ' same as DEBUG DEC in4(i),CR, displays each state
  OUTPUT ledA+i             ' makes each of the pins 7,8 and 9 and output
  ledA(i)=switchA(i)        ' same as out7(i)=in4(i)
                            ' steps through out7=in4, out8=in5, and out9=in6

Not quite tired of examples? How about this, suppose you have mypin defined, and your program needs to display the pin number. You can't do it this way:

' the compiler way
myPin  PIN  3 
x = MyPin                ' same as x = in3
DEBUG DEC MyPin          ' same as DEBUG DEC in3 

because the the compiler will display the value 0 or 1 found on the pin, not the constant pin number. Of course you could define another constant, myPin3 CON 3 and display that, but that kind of defeats the simplicity of the PIN type. Here is get it to display the pin number:

FOR i=0 to 15   ' define an array of nibbles, 0,1,2,...,15
x = nibs(MyPin)          ' same as x=3
DEBUG DEC x              ' same as DEBUG DEC 3

That works because the compiler always treats myPin is always a constant when it appears as an array argument. No, it is not pretty! If anyone has an easier way to do that, let me know! Why would you want to do it? Maybe a program has to scan several pins and report to the serial port, which one is high at any given time. In that case, the program can report the index used to scan the pins.

What if you want to read the output latch associated with pin p3? This is quite often useful. For one thing, if a pin is always defined as an input, then the state of its output latch becomes a "free" variable that can be used for other purposes. You can't just say x=myPin, because that will always return the input state of the pin. You can do it simply like this:

x = out3                

or like this,

     x = out0(3)              ' implied index


x = out0(MyPin)          ' same as x = out3 or x=out0(3)

Enough. This should be easy. And most of the time it is. I really like the new PIN type. Just recognize when you are about to do some tricky thing that the compiler treats in a different way.



The following is a summary from Jeff Martin, Parallax engineer, who kindly sent me an email on the subject.

Here are the rules regarding PIN type symbols, the best way I can describe them at the moment:

In this description, a "read" context is that in which the compiler/stamp expects to read a value from a variable or expression, and a "write" context is that in which the compiler/stamp expects to write a value to a variable.

  1. If specified as the "pin" argument of a command, it is always treated as a constant.
  2. If specified as a non-pin argument of a command, it is always treated as a variable, and:
    1. If it is a "read" context argument, it is always treated as an input pin (INx)
    2. If it is a "write" context argument, it is always treated as an output latch (OUTx)
  3. If specified as an index to an array, it is always treated as a constant.

 Other notable examples:

  • Ex1: If the PIN variable is the only symbol to the left of an = sign in an assignment statement, it is a "write" context argument and thus follows the rule 2b.
  • Ex2: If the PIN variable is to the right on an = sign in an assignment statement, it is a "read" context argument and thus follows rule 2a.
  • Ex3: If the PIN variable appears by itself or in an expression as an index to an array, it is a constant, following rule 3. This is because it is usually unintended and impractical to use an input pin as the index of an array.
    (editor's note--It might in fact be the intention, a trick when a selection depends on the state of a pin)
  • Ex4: Examples of "read" context arguments: LOOKUP's Index and ValueX arguments, an argum ent in DEBUG, POT's scale argument. LOOKUP MyIndex,[MyValue, MyValue1,...],MyVariable
  • Ex5: Examples of "write" context arguments: LOOKUP's Variable argument, POT's Variable argument. and note that a PIN variable is always one bit.

I hope this helps. If there are examples that seem to defy these rules, please let me know.

--Jeff Martin
Engineering Manager
Parallax, Inc.


OWL2pe data logger core

OWL data logger with BS2pe, large programs benefit from structured programming in PBASIC 2.5

 .<top> <index> <home> logo <>