Welcome to PBasic for Kids!

This is a beginner’s guide for getting started on PBasic which is a programming language created by Parralax as an easy way to interact with their various robots. For new beginners it can be a little hard to grasp various concepts of programming but PBasic does a good job at being a language to power the robots while still being user friendly.

Getting Started

Before we get started here are a couple of things you should consider downloading:
(For Windows)

  1. Parralax USB Driver - USB Driver to recognize the conncetion between your computer and parralax robot
  2. BASIC Stamp Editor - A basic IDE to run your PBasic files on your robot.

We’ll be starting off with the easy concepts first as they are part of the core to build upon later on. First on the list is Variables!

Prepping your Basic Stamp Editor

Before you attempt to run any files make sure to click the top 2 buttons to create type headers at the top of your file. These type headers are required in order to for the Stamp Editor to know which version of PBasic to compile and run on your robot(s).

For this guide you will be using the Green button and PBasic 2.5

_images/buttons_to_click.png

Comments in PBasic

Comments in PBasic are ignored when the program runs. Think of it as a way to make notes of what your program does at certain situations so you can remember later. In PBasic, the way comments are denoted is by using the apostrophe symbol:

For example:

1
2
3
4
5
' This is a comment!

DEBUG "Hello World!"

' Another comment!!!

Variables

Variables are a way to temporarily store data. Think of it as the same as variables in your math class where you can define x = 5

Defining and Using Variables

Before you can use a variable in a PBASIC program you must declare it. “Declare” means letting the BASIC Stamp know that you plan to use a variable. The format for declaring variables is as follows

variable_name    VAR     VarType

VarType refers to the following 4 values: Bit, Nib, Byte, and Word. Try to think of a variable type as classifying how much space the variable has to store values.

VarType Value Size
Bit Value can be 0 or 1
Nib Value can be 0 to 15
Byte Value can be 0 to 255
Word Value can be 0 to 65535

Here’s an analogy of how the size difference works

We can arrange these 4 objects in order by how much they can store: Envelope, Shoebox, Fridge, Room

Envelope (Bit) < Shoebox (Nib) < Fridge (Byte) < Room (Word)

Some examples of declaring variables

Choose variable names that make sense to you and are not absurd like: ThisVariable_DoessomethingreallyCOOL

1
2
3
4
x               VAR     Bit
dog             VAR     WORD
is_zero         VAR     Nib
someVariable    VAR     Byte

Some Notes on Variable Types

Under certain situations you might use different variable types. However, for the programming problems that you will encounter while undergoing the competition it might be best to just stick to Byte and Word

Printing output to the Terminal

DEBUG is used to print output to the computer screen while running your program. Think of it as a way to make sure things are are running properly while your program runs.

The easiest use case is regular messages:

DEBUG   "Hello, World!"
DEBUG   "I'm learning how to program."

Using the comma seperator

We can have multiple messages added together on the same line by using the comma seperator:

DEBUG   "Wow this is a", " multi message!"

Printing on new lines

We can use the keyword (CR) to start on a new line. Think of it like pressing enter in Microsoft Word:

DEBUG   "This should be on", CR
DEBUG   "multiple lines."

Printing variables

We can also print variables:

1
2
3
4
5
6
7
8
x   VAR     Word

Init:
    x = 65

Main:
    DEBUG x
    END

Uh oh! When trying to run the above code there should be an issue. It’s printing the letter “A”?! This is because by default the BS2 model displays everything as ASCII characters. I won’t go into detail what ASCII is but you can follow the link to read more.

Anyways, in order to properly print the value of x we need to use the decimal formatter, DEC:

1
2
3
4
5
6
7
8
x   VAR     Word

Init:
    x = 65

Main:
    DEBUG DEC x
    END

Auto-printing variables

Using the keyword (?) we can auto-print the variable name and value:

1
2
3
4
5
6
7
8
x   VAR     Word

Init:
    x = 65

Main:
    DEBUG DEC ? x
    END

Example of combining everything together

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
x   VAR     Word
y   VAR     Word

Init:
    x = 65
    y = 99
Main:
    DEBUG DEC "Our value of x is: ", x, CR
    DEBUG DEC ? y
    END

Conditional Statements

Conditional statement are used as a way to direct the way things operate. For example, if I say “Please go to the store to buy milk. If they don’t have milk then buy apple juice”.

Notice how If there isn’t milk then we buy apple juice. However if there IS milk then we buy milk.

These types of conditional statements are ordered like this in PBasic:

1
2
3
IF (condition) THEN
    statement(s)
ENDIF

A condition is made up of comparison symbols

Comparison Operator Symbol Definition
= Equal
<> Not Equal
> Greater Than
< Less Than
>= Greater Than or Equal To
<= Less Than or Equal To

Here are some examples:

1
2
3
4
5
6
7
IF (4 = 5) THEN
    DEBUG "4 equals 5"
ENDIF

IF (10 <= 100) THEN
    DEBUG "10 is less than or equal to 100"
ENDIF

Chaining mutliple IF.. THEN statements together

You can also call chain multiple IF.. THEN statements together through the use of IF.. ELSEIF.. and/or ELSE..

Structure for Multiple If statements:

1
2
3
4
5
6
7
IF (condition) THEN
    statement(s)
ELSEIF (condition) THEN
    statement(s)
ELSE
    statement(s)
ENDIF

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
x   VAR     WORD

Main:
    x = 100

    IF (x < 200) THEN
        DEBUG DEC ? x
    ELSEIF (x < 50) THEN
        DEBUG DEC ? x
    ELSE
        DEBUG DEC ? x
    ENDIF

Notes about Mutliple If statements

It’s not necessary to have an ELSE statement at the end. If it’s omitted then the statement will stop at the last ELSEIF statement instead.

Which means that this is also a valid IF Statement:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
x   VAR     WORD

Main:
    x = 100

    IF (x < 200) THEN
        DEBUG DEC ? x
    ELSEIF (x < 50) THEN
        DEBUG DEC ? x
    ENDIF

Conditional Logic Operators

1
2
3
IF (condition) THEN
    statement(s)
ENDIF

A condition is also made up of logic operators:

  1. NOT
  2. AND
  3. OR

Logic operators are a little more confusing. The reason to use logic operators is to do multiple comparisons in one IF statement. Take for example:

1
2
3
4
5
IF (5 < 10) AND (1 < 5) THEN
    DEBUG "Hello there!"
ELSE
    DEBUG "Goodbye!"
ENDIF

Here we have two conditions that we test inside one IF statement AND only if they are both true will you see “Hello there!” printed.

The following tables and examples may help make clear how logic operators work together:

Logic Operator: NOT

1
2
3
4
5
6
7
IF NOT (1 > 10) THEN
    DEBUG "Hello World!"
ELSE
    DEBUG "Goodbye"
ENDIF

' Result: True
Condition A NOT A
False True
True False

Logic Operator: AND

1
2
3
4
5
6
7
IF (1 > 10) AND (4 = 4) THEN
    DEBUG "Hello World!"
ELSE
    DEBUG "Goodbye"
ENDIF

' Result: False
Condition A Condition B A AND B
False False False
False True False
True False False
True True True

Logic Operator: OR

1
2
3
4
5
6
7
IF (1 > 10) OR (4 = 4) THEN
    DEBUG "Hello World!"
ELSE
    DEBUG "Goodbye"
ENDIF

' Result: True
Condition A Condition B A OR B
False False False
False True True
True False True
True True True

Nesting IF Statements

You also have the ability to nest IF statements inside of each other like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
x   VAR     WORD

Main:
    x = 7

    IF (x < 10) THEN
        IF (x > 5) THEN
            DEBUG "x is between 5 and 10"
            DEBUG DEC ? x
        ENDIF
    ENDIF

Try to think of nesting as asking another question once you received an answer to your previous question. For example:

1
2
3
4
5
6
7
8
9
IF (joe went to the store)
    IF (he did buy chocolate)
        "Joe bough chocolate at the store"
    ELSEIF (he did buy milk)
        "Joe bought milk at the store"
    ELSE
        "Joe bought apple juice at the store"
ELSE
    "Joe never went to the store"

Do Loops

Lets say you want to do something forever... in programming you would use a do-loop to perform this action!

Here’s a basic example that constantly prints to the terminal:

1
2
3
DO
    DEBUG "Hi there!", CR
LOOP

Here’s another example that prints the value of x and increases its value:

1
2
3
4
5
6
7
8
9
x   VAR     WORD

Init:
    x = 1
Main:
    DO
        DEBUG DEC ? x, CR
        x = x + 1
    LOOP

DO-WHILE loop

However, more often than not you will want to test some condition to determine whether the loop code should run or continue to run.

To do this we use a DO-WHILE loop like so:

1
2
3
4
5
6
7
8
9
x   VAR     WORD

Init:
    x = 1
Main:
    DO WHILE (x <= 5) ' condition to test before entering loop statements
        DEBUG "#", CR
        x = x + 1
    LOOP

Conclusion

DO loops are useful when you need to run something forever or until some special condition breaks.

For an imaginary example:

1
2
3
4
Main:
    DO WHILE (some_special_condition = 1)
        ' Do some calculations
    LOOP

FOR Loops

For loops are a little different than do-loops. For loops were created with the purpose in mind of having a program execute between a range. That range is defined by you!

Here’s an example that counts from 0 to 10:

1
2
3
4
5
6
7
x   VAR     WORD

Main:
    FOR x = 0 TO 10
        DEBUG DEC ? x, CR
    NEXT
    END

By default, a FOR loop will step through 1 by 1. We can change this behavior by adding a specific value for STEP.

Here the example counts from 0 to 10 but increasing by STEPS of 2:

1
2
3
4
5
6
7
x   VAR     WORD

Main:
    FOR x = 0 TO 10 STEP 2
        DEBUG DEC ? x, CR
    NEXT
    END

Notice how only even numbers are being displayed!

We can also make a FOR loop that decreases in range. Here’s what I mean:

1
2
3
4
5
Main:
    FOR 10 TO 5
        DEBUG "Hello!"
    NEXT
    END

Conclusion

FOR loops are very useful when you know there should be a range where a program should run. If we need to run something 10 times then it would be useful to use a FOR loop as its easy to create.

Take this for example, printing 1 to 10 by hand:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Main:
    DEBUG "1"
    DEBUG "2"
    DEBUG "3"
    DEBUG "4"
    DEBUG "5"
    DEBUG "6"
    DEBUG "7"
    DEBUG "8"
    DEBUG "9"
    DEBUG "10"
    END

VS

Printing 1 to 10 using a for loop:

1
2
3
4
5
6
7
x   VAR     WORD

Main:
    FOR x = 1 to 10
        DEBUG DEC x
    NEXT
    END

Movement

Moving the wheels of the robot is fairly simple. We will use the PULSOUT keyword to send a signal to the wheels to turn. Each wheel has a unique ID and takes in a range of “power values” for how fast the wheel spins and in what direction.

Wheel ID Power Value
Right 12 650 <=> 850
Left 13 650 <=> 850

The power values dictate how fast the wheel spins in a certain direction. Think of a number line where 650 and 850 are at the ends and 750 is the center.

_images/power_range_number_line.png

Consider 750 to be the neutral value. This means if you set a wheel to a value of 750 it shouldnt move.

If you set a wheel to either 650 or 850 then it will move at full power in a certain direction.

Power Value Direction
650 Clockwise
750 None
850 Counter-Clockwise

Moving Forward

In order to move the robot forward we need to spin each wheel either counter-clockwise or clockwise but not the same. Running this code below will make the wheels move in a very short burst.

PULSOUT 13, 850
PULSOUT 12, 650

To continuously go forwards for a small time we program it like so:

1
2
3
4
5
6
i   VAR     WORD

FOR i=1 TO 100
    PULSOUT 13, 850
    PULSOUT 12, 650
NEXT

Moving Backwards

We have the same idea as moving forwards except the values are flipped.

PULSOUT 13, 650
PULSOUT 12, 850

And again to continuously go backwards for a small time we program it like so:

1
2
3
4
5
6
i   VAR     WORD

FOR i=1 TO 100
    PULSOUT 13, 650
    PULSOUT 12, 850
NEXT

Turning

There are 2 approaches to turning your robot.

  1. Pivot Turn
  2. Spin Turn

This diagram helps to explain the key differences:

_images/pivot_vs_turn.jpg

Ultimately what type of turns you want to perform is up to you. Just make sure you’re consistent with the type of turns you perform.

Pivot Turn

Depending on the wheel you want to pivot about influences what code to use.

Pivot about Left Wheel:

1
2
3
4
5
i   VAR     WORD

FOR i=1 TO 100
    PULSOUT 12, 650
NEXT

Pivot about Right Wheel:

1
2
3
4
5
i   VAR     WORD

FOR i=1 TO 100
    PULSOUT 13, 650
NEXT

Spin Turn

Spin turns move both wheels in the same direction either clockwise or counter-clockwise.

Spinning in Clockwise direction:

1
2
3
4
5
6
i   VAR     WORD

FOR i=1 TO 100
  PULSOUT 13, 650
  PULSOUT 12, 650
NEXT

Spinning in Counter-Clockwise direction:

1
2
3
4
5
6
i   VAR     WORD

FOR i=1 TO 100
  PULSOUT 13, 850
  PULSOUT 12, 850
NEXT

Practice

I’d like to challenge you to program your robot to move forward, spin in some direction, and then backup with what you’ve learned so far. In addition, you should try to practice more by programming your own little movement sequence.

Subroutines

Imagine you have a “special piece of code” that’s 10 lines long. And you have to use it 7 times in your program. Now, it’s not too hard to copy and paste but one can imagine that having to paste 70 lines of the same code can be repetitive and ultimately “ugly”. Ugly in the sense that you shouldn’t have to repeat yourself.

There is a rule in programming that goes: DON’T REPEAT YOURSELF (DRY)

With subroutines, you can use the same piece of code without copy and pasting.

The structure of a subroutine is as follows:

1
2
3
YourSubroutineName:
    (Code)
RETURN

Example

1
2
3
4
MySubroutine:
    DEBUG "Hello there!", CR
    DEBUG "This is a subroutine", CR
RETURN

To call and execute a subroutine you use the GOSUB keyword:

1
2
3
4
5
6
7
8
9
Main:
    DEBUG "We're inside Main... Calling MySubroutine", CR
    GOSUB MySubroutine
    END

MySubroutine:
    DEBUG "Hello there!", CR
    DEBUG "This is a subroutine", CR
RETURN

Calculating the area of a square

We can use variables within subroutines like so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
sideLength  VAR     WORD
result      VAR     WORD

Main:
    sideLength = 50
    GOSUB calcSquareArea

    sideLength = 75
    GOSUB calcSquareArea

    sideLength = 100
    GOSUB calcSquareArea

    END

calcSquareArea:
    DEBUG DEC "Calculating Area with side legnth: ", sideLength, CR
    result = sideLength * sideLength ' area = l x w
    DEBUG DEC "Result: ", result, CR
RETURN

Conclusion

Subroutines are a good way to organize and cleanup your code. If you have parts where you need to constantly repeat yourself than put it into a subroutine! There is no limit to how many times you can call a subroutine.

Practice

Create a movement sequence again but this time using subroutines. You should notice you’re code should look a lot cleaner this time around.

Whiskers

Whiskers are one of the components included in the robot kit. Whisker sensors allow the robot to detect obstacles when it bumps into them.

_images/concept_whiskers.jpg

Whisker values are accessed via IN5 and IN7

IN id Whisker
IN5 Left Whisker
IN7 Right Whisker
Whisker State Value
Unpressed 1
Pressed 0

Example: Outputting values when pressed

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
left_whisker    VAR     Bit
right_whisker   VAR     Bit

Main:
    DO
        left_whisker = IN5
        right_whisker = IN7

        IF (left_whisker = 0) AND (right_whisker = 0) THEN
            DEBUG "Both Whiskers were pressed!"
        ELSEIF (left_whisker = 0) THEN
            DEBUG "Left Whisker was pressed!"
        ELSEIF (right_whisker = 0) THEN
            DEBUG "Right Whisker was pressed!"
        ELSE
            DEBUG "No Whiskers are pressed..."
        ENDIF
    LOOP

Example: Utilizing the whiskers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
left_whisker    VAR     Bit
right_whisker   VAR     Bit
pulse_count     VAR     Byte

Main:
    left_whisker = IN5
    right_whisker = IN7

    DO
        IF (left_whisker = 0) AND (right_whisker = 0) THEN
            ' Left and Right whiskers are pressed so we back up and make a U-turn by default
            ' A U-turn is just 2 left turns
            GOSUB Back_Up
            GOSUB Spin_Turn_Left
            GOSUB Spin_Turn_Left
        ELSEIF (left_whisker = 0) THEN
            GOSUB Back_Up
            GOSUB Spin_Turn_Right
        ELSEIF (right_whisker = 0) THEN
            GOSUB Back_Up
            GOSUB Spin_Turn_Left
        ELSE
            ' here the whiskers are NOT in contact with a wall so we pulse forward
            GOSUB Pulse_Forward
        ENDIF
    LOOP


Pulse_Forward:
    PULSOUT 13,850
    PULSOUT 12,650
RETURN

Spin_Turn_Left:
    FOR pulse_count = 0 TO 50
        PULSOUT 13, 650
        PULSOUT 12, 650
    NEXT
RETURN

Spin_Turn_Right:
    FOR pulse_count = 0 TO 50
        PULSOUT 13, 850
        PULSOUT 12, 850
    NEXT
RETURN

Back_Up:
    FOR pulse_count = 0 TO 50
        PULSOUT 13, 650
        PULSOUT 12, 850
    NEXT
RETURN

Conclusion

Whiskers are a good way to detect obstacles in front of the robot. However, whiskers aren’t the best way to detect obstacles. There are some quirks of the whiskers bending in weird ways and which makes them less reliable. In the next section we will cover Infrared Sensors which offer much more in terms of depth perception and field of view (fov).

Infrared Sensors

Infrared sensors are the best sensors included in the robot kit. They offer more reliability since they don’t bend or lose shape over time like the Whiskers. They work in the same way that whiskers work in terms of whether an obstacle is detected or not detected.

IN id Sensor
IN9 Left Sensor
IN0 Right Sensor
Sensor State Value
Undetected 1
Detected 0

Example: Outputting values when detected

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
left_ir_sensor      VAR     Bit
right_ir_sensor     VAR     Bit

Main:
    DO
        FREQOUT 8, 1, 38500
        left_ir_sensor = IN9

        FREQOUT 2, 1, 38500
        right_ir_sensor = IN0

        IF (left_ir_sensor = 0) AND (right_ir_sensor = 0) THEN
            DEBUG "Both sensors detected something!"
        ELSEIF (left_ir_sensor = 0) THEN
            DEBUG "Left IR sensor detected something!"
        ELSEIF (right_ir_sensor = 0) THEN
            DEBUG "Right IR sensor detected something!"
        ELSE
            DEBUG "No detection..."
        ENDIF
    LOOP

How IR detection works

I want to explain what this block of code does inside the DO-LOOP:

1
2
3
4
5
FREQOUT 8, 1, 38500
left_ir_sensor = IN9

FREQOUT 2, 1, 38500
right_ir_sensor = IN0

FREQOUT makes the IR LED shoot a 38.5 kHz IR signal outwards. Think of it like laser blasters from star wars.

_images/star_wars_clone_blasters.jpg

Now, lets say that signal bounces off a wall like deflecting the laser in star wars.

_images/laser_deflection.jpg

The last thing to do is catch the signal in the IR Reciever. Which now makes so we can detect if there is an object ahead of us or not!

Here’s a pretty good diagram of what I mean:

_images/ir_detection.jpg

Example: Utilizing the IR Sensors

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
left_ir_sensor      VAR     Bit
right_ir_sensor     VAR     Bit
pulse_count         VAR     Byte

Main:
    DO
        FREQOUT 8, 1, 38500
        left_ir_sensor = IN9

        FREQOUT 2, 1, 38500
        right_ir_sensor = IN0

        IF (left_ir_sensor = 0) AND (right_ir_sensor = 0) THEN
            ' Left and Right IR sensors detected so we back up and make a U-turn by default
            ' A U-turn is just 2 left turns
            GOSUB Back_Up
            GOSUB Spin_Turn_Left
            GOSUB Spin_Turn_Left
        ELSEIF (left_ir_sensor = 0) THEN
            GOSUB Back_Up
            GOSUB Spin_Turn_Right
        ELSEIF (right_ir_sensor = 0) THEN
            GOSUB Back_Up
            GOSUB Spin_Turn_Left
        ELSE
            ' here the IR Sensors DONT detect anything so we pulse forward
            GOSUB Pulse_Forward
        ENDIF
    LOOP


Pulse_Forward:
    PULSOUT 13,850
    PULSOUT 12,650
RETURN

Spin_Turn_Left:
    FOR pulse_count = 0 TO 50
        PULSOUT 13, 650
        PULSOUT 12, 650
    NEXT
RETURN

Spin_Turn_Right:
    FOR pulse_count = 0 TO 50
        PULSOUT 13, 850
        PULSOUT 12, 850
    NEXT
RETURN

Back_Up:
    FOR pulse_count = 0 TO 50
        PULSOUT 13, 650
        PULSOUT 12, 850
    NEXT
RETURN

Important notes about Example: Utilizing the IR Sensors

The way the subroutines are coded is that they have set amounts for how much the robot will turn or backup. This isn’t the most optimized way to navigate through a maze. You run the risk of either overshooting your turn or not turning enough. These risks should be very concerning to you even if they aren’t!

Example: Optimizing the use of IR Sensors

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
left_ir_sensor      VAR     Bit
right_ir_sensor     VAR     Bit
pulse_left          VAR     Word
pulse_right         VAR     Word

Main:
    DO
        FREQOUT 8, 1, 38500
        left_ir_sensor = IN9

        FREQOUT 2, 1, 38500
        right_ir_sensor = IN0

        IF (left_ir_sensor = 0) AND (right_ir_sensor = 0) THEN
            ' Both sensors detect something so we back up
            pulse_left = 650
            pulse_right = 850
        ELSEIF (left_ir_sensor = 0) THEN
            ' We pulse spin-turn the wheels to the right
            pulse_left = 850
            pulse_right = 850
        ELSEIF (right_ir_sensor = 0) THEN
            ' We pulse spin-turn the wheels to the left
            pulse_left = 650
            pulse_right = 650
        ELSE
            ' We pulse forward
            pulse_left = 850
            pulse_right = 650
        ENDIF

        ' Apply the pulse to the wheels
        PULSOUT 13, pulse_left
        PULSOUT 12, pulse_right
    LOOP

Notes about Example: Optimizing the use of IR Sensors

This is a much more accurate way to traverse a maze. Since changes to the direction the robot is moving is now done in single pulses. We get a much more reliable way to move throughout the maze. Now we don’t have to worry about turning too much or too little!

Conclusion

The IR sensors are reliable and are the ones I encourage you to use. One thing that I’d like to take a moment to address is that you can change the signal frequency at which the IR transmitter sends. Increasing or decreasing has effects on the distance at which an object can be detected.

_images/ir_detection_ranges.png

For example:

FREQOUT 8, 1, 40500
left_ir_sensor = IN9

Competition Files

Here you can find all the files used in the competion from previous years. Click to download the files!

Contact

If you have any questions then feel free to send me an email and I’ll try to get back to you as soon as possible:

rogelio_negrete@live.com

Indices and tables