Chapter 3
Discovering More PBASIC Programming Tricks
In This Chapter
Connecting pushbuttons
Generating random numbers
Using a potentiometer as input
Creating subroutines with the GOSUB
command
In this chapter, we describe some more PBASIC programming techniques, to add to the fundamental ones we introduce in Chapter 2 of this minibook. These tips are sure to become invaluable in your BASIC Stamp projects. Specifically, you discover handling input data in the form of pushbuttons, generating random numbers (that make your programs more interesting by adding a degree of randomness), reading the value of a potentiometer and using the GOSUB
command to organise your program into subroutines.
Pushing Buttons with a BASIC Stamp
In Chapter 2 of this minibook, we discuss connecting a light-emitting diode (LED) to a BASIC Stamp I/O pin and turning the LED on or off by using the HIGH
and LOW
commands in a PBASIC program. Those commands are designed to use BASIC Stamp I/O pins as output pins by setting the status of an I/O pin to high or low so that external circuitry (such as an LED) can react to the pin's status.
But what if you want to use an I/O pin as an input instead of an output? In other words, you want the BASIC Stamp to react to the status of an external circuit instead of the other way around. The easiest way to do that is to connect a pushbutton to an I/O pin. Then, you can add commands to your PBASIC program to detect whether the pushbutton is pressed.
Active-high: This connection places +5 V on the I/O pin when the pushbutton is pressed. When the button is released, the I/O pin sees 0 V.
Active-low: This connection sees +5 V when the pushbutton isn’t pressed. When you press the pushbutton, the +5 V is removed and the I/O pin sees no voltage.
Figure 3-1 shows examples of active-high and active-low pushbuttons. In the active-high circuit, the I/O pin is connected to ground through R1 and R2 when the pushbutton isn’t pressed. Thus, the voltage at the I/O pin is 0. When the pushbutton is pressed, the I/O pin is connected to Vdd (+5 V) through R1, causing the I/O pin to see +5 V. As a result, the I/O pin is low when the button isn’t pressed and high when the button is pressed.
Figure 3-1: Active-high and active-low input circuits.
In the active-low circuit, the I/O pin is connected to Vdd (+5 V) through R1 and R2, causing the I/O pin to go high. But when the button is pressed, the current from Vdd is connected to ground through R2, causing the voltage at the I/O pin to drop to zero. Thus, the I/O pin is high when the button isn’t pressed and low when the button is pressed.
Checking the Status of a Switch in PBASIC
When you’ve connected a switch to a BASIC Stamp I/O pin, you need to know how to determine whether the switch is open or closed from a PBASIC program. The easiest way to do so is to first assign a name to the pin you want to test using the PIN directive. For example, if an active-high input button (see the preceding section for an explanation) is connected to pin 14, you can assign it a name such as this:
Button1 PIN 14
Here, the name Button1
is assigned to pin 14.
To determine whether the pushbutton is pressed, you can use an IF
statement as follows:
IF Button1 = 1 THEN
HIGH Led1
ENDIF
Here, the output pin designated as Led1
is made high when the button is pressed.
If you want Led1
to be high only when Button1
is pressed, use this code:
IF Button1 = 1 THEN
HIGH Led1
ELSE
LOW Led1
ENDIF
Here, Led1
is made high if the button is pressed and low if the button isn't pressed.
DO
IF Button1 = 1 THEN
HIGH Led1
ELSE
LOW Led1
ENDIF
LOOP
Listing 3-1 shows an interesting program that works with a BASIC Stamp that has a pushbutton switch connected to pin 14 and LEDs connected to pins 0 and 2. The program flashes the LED connected to pin 2 on and off at half-second intervals until the pushbutton switch is depressed. Then, it flashes the LED on pin 0.
Listing 3-1: The Pushbutton Program
' Pushbutton Program
' Doug Lowe
' July 13, 2011
' {$STAMP BS2}
' {$PBASIC 2.5}
Led1 PIN 0
Led2 PIN 2
BUTTON1 PIN 14
DO
IF BUTTON1 = 1 THEN
LOW Led2
HIGH Led1
PAUSE 100
LOW Led1
PAUSE 100
ELSE
LOW Led1
HIGH Led2
PAUSE 100
LOW Led2
PAUSE 100
ENDIF
PAUSE 100
LOOP
Project 3-1 shows how to build a simple circuit you can use to test the program in Listing 3-1.
Randomising Your Programs
Many computer-controlled applications require a degree of randomness to their operation. A classic example is the Indiana Jones ride at Disneyland and Disney World, in which the adventure is slightly different every time. At the start, your vehicle can enter through one of three doors; the exact door chosen for your ride is determined randomly. Many other details of the ride are randomly varied in an effort to make the adventure slightly different every time you climb board.
You can add a bit of randomness to your own BASIC Stamp programs by using the RANDOM
command. This command scrambles the bits of a variable (check out this minibook's Chapter 2 for a definition).
Byte
: The RANDOM
command generates a random number between 0 and 255.
Word
: The random value is between 0 and 65,535.
Here's an example of a simple way to use the RANDOM
command to add a random pause:
Result VAR Word
RANDOM Result
PAUSE Result
This sequence of code creates a Word
variable named Result
, randomises the variable and then pauses for the number of milliseconds (ms) indicated by the Result
variable.
In most cases, the value returned by the RANDOM
command isn't really the random number you're looking for. Usually, you want to determine a random number that falls within a range of numbers. For example, if you're writing a dice program and want to simulate the roll of a single die, you need to generate a random number between 1 and 6. You can easily reduce the value of a full Word
variable to a number between 1 and 6 by using a simple mathematical calculation that involves a special type of division operation called modulus division.
As we explain in Chapter 2 of this minibook, when PBASIC does division, it keeps the integer portion of the quotient and discards the remainder. For example, 10 / 3 = 3; the remainder (1) is simply discarded.
Modulus division, which is represented by two slashes (//
) instead of one, throws away the quotient and keeps only the remainder. Thus, 10 // 3 = 1, because the remainder of 10 / 3 = 1.
You can put modulus division to good use when working with random numbers. For example:
Result VAR Word
Die VAR Byte
RANDOM Result
Die = Result // 6
To find a random number that falls between 1 and 6, you need to calculate the modulus division by 5, not by 6, and then add 1 to the result. For example:
Result VAR Word
Die VAR Byte
RANDOM Result
Die = Result // 5 + 1
The preceding calculation returns a random number between 1 and 6.
Listing 3-2 shows a sample program that lights Led1
(pin 0) until the pushbutton on pin 14 is pressed. Then, it lights Led2
(pin 2) for a random period of time between 1 and 10 seconds. It uses the following equation to calculate the number of seconds to pause:
Seconds = Result // 9 + 1
The program then multiplies the seconds by 1,000 to convert to milliseconds as required by the PAUSE
command.
Listing 3-2: The Random Program
' Random Program
' Doug Lowe
' July 10, 2011
'
' This program turns on the LED at pin 2 for a random number
' of seconds between 1 and 10 when the pushbutton on pin 14
' is pressed.
' {$STAMP BS2}
' {$PBASIC 2.5}
Result VAR Word
Seconds VAR Byte
Button1 PIN 14
Led1 PIN 0
Led2 PIN 2
DO
HIGH Led1
IF Button1 = 1 THEN
RANDOM Result
Seconds = Result // 9 + 1
LOW Led1
HIGH Led2
PAUSE Seconds * 1000
HIGH Led1
LOW Led2
ENDIF
LOOP
Each time you press the pushbutton in this program, Led2
lights up for a different length of time, between 1 and 10 seconds.
For example, if you use a Word
variable whose initial value is 0, the RANDOM
command always changes the variable's value to 64,992. If you apply the RANDOM
command to the same variable again, the result is always 9,072. The sequence of numbers generated from a given initial value is distributed randomly across the entire range of possible values (for example, 0 to 65,535), but the sequence is the same every time.
Thus, the program in Listing 3-2 always generates the same sequence of random delays. In particular, the sequence for the first ten button presses is always as follows:
First press: 4 seconds
Second press: 1 second
Third press: 4 seconds
Fourth press: 1 second
Fifth press: 3 seconds
Sixth press: 5 seconds
Seventh press: 4 seconds
Eighth press: 1 second
Ninth press: 1 second
Tenth press: 4 seconds
This sequence appears fairly random, but every time you reset the program and start over, the sequence is identical.
The easiest way around this problem is to make the initial value of the variable fed to the RANDOM
command dependent on some external event, such as the press of a button. You can easily do that by creating a loop that counts while it waits for the user to press the button. Because your program has no way to determine exactly when the user is going to press the button, the number used to start the random number generator is truly random.
Listing 3-3 shows an improved version of the random program that uses this technique to create a truly random delay. Only one additional line of code is needed to randomise the delay properly:
Result = Result + 1
By adding 1 to the Result
variable each pass through the loop, the seed value for the RANDOM
command is unpredictable, and so a true random value is generated.
Listing 3-3: An Improved Version of the Random Program
' Improved Random Program
' Doug Lowe
' July 10, 2011
'
' This program turns on the LED at pin 2 for a random number
' of seconds between 1 and 10 when the pushbutton on pin 14
' is pressed.
'
' This version of the program uses a counter to create a truly random number.
' {$STAMP BS2}
' {$PBASIC 2.5}
Result VAR Word
Seconds VAR Byte
Button1 PIN 14
Led1 PIN 0
Led2 PIN 2
DO
HIGH Led1
Result = Result + 1
IF Button1 = 1 THEN
RANDOM Result
Seconds = Result // 9 + 1
LOW Led1
HIGH Led2
PAUSE Seconds * 1000
HIGH Led1
LOW Led2
ENDIF
LOOP
Reading a Value from a Potentiometer
A potentiometer (often called a pot) is simply a variable resistor with a knob you can turn to vary the resistance (check out Book II, Chapter 2 for more about potentiometers). Pots of various types are often used as input devices for BASIC Stamp projects. For example, you can use a simple pot to control the speed of a pair of flashing LEDs: as you turn the pot’s knob, the rate at which the LEDs flash changes.
Although the most common type of pot has a mechanical knob to vary its resistance, many pots use some other means to vary their resistance. For example, a joystick uses pots that are connected to a moveable stick. One of the pots measures the motion of the stick in the x-axis; the other measures the motion of the stick in the y-axis. You can also connect a pot to the hinge on a door, in which case the resistance of the pot indicates not only whether the door is opened or closed, but also the door’s angle if it’s partly opened.
The BASIC Stamp doesn’t have the ability to read directly the value of a pot connected to one of its I/O pins. However, by cleverly combining the pot with a small capacitor, you can measure how long the capacitor takes to discharge. With this knowledge, you can calculate the resistance of the pot by using the resistor-capacitor (RC) time calculations that we present in Book II, Chapter 3 (flip to that chapter too to find out how RC circuits work and how to do the time calculations). For the purposes of this chapter, you don’t need to perform the time calculations yourself, but brushing up on how RC circuits work isn’t going to hurt.
Figure 3-2 shows a typical RC circuit connected to a pin on a BASIC Stamp. Here, a 10 kΩ pot is placed in parallel with a 0.1 μF capacitor. In addition, a 220 Ω resistor is placed in series with the pot to protect the BASIC Stamp from damage that may be caused by excess current if you turn the pot’s knob so that the resistance of the pot drops to zero.
The capacitor in this circuit is small enough (0.1 μF) that the circuit charges and discharges very quickly – within about a millisecond or so, depending on where the pot knob is set. Thus, your program isn’t delayed significantly while it waits for the capacitor to discharge so that it can determine the resistance of the pot.
Figure 3-2: Connecting a pot to a BASIC Stamp I/O pin.
Given the circuit in Figure 3-2, how do you go about measuring the resistance of the pot? The answer requires a clever bit of programming. You set pin 13 to high, which charges the capacitor. Then, you set up a loop to monitor the input status of pin 13, which stops driving P13 high and allows the capacitor to discharge through the R2. Each time you check the status of pin 13, you add 1 to a counter. When the capacitor has discharged, pin 13 goes low. When pin 13 is low, the loop ends and the counter indicates how long discharging the capacitor took. Knowing the size of the capacitor and the length of time required to discharge the capacitor allows you to calculate the resistance of the pot.
Fortunately, PBASIC includes a command called RCTIME
that does this process automatically. All you have to do is tell the RCTIME
command what pin the RC circuit is on, whether you want to measure how long the RC circuit takes to charge or discharge, and the name of a variable in which to store the resulting time calculation.
Here's how to use the RCTIME
command to determine how long an RC circuit on pin 13 takes to discharge, storing the answer in a variable named Timer
:
RCTIME 13, 1, Timer
This RCTIME
command sets the variable named Timer
to a value that indicates how long it took the RC circuit to discharge. Immediately before this command, you need to set the I/O pin (in this case, pin 13) to high to charge the capacitor. You also need to pause for a short time (usually, 1 ms is enough) to allow the circuit to charge.
For the circuit shown in Figure 3-2, the RCTIME
command calculates time values ranging from about 12 when the resistance of the pot is near 0 to about 54 when the resistance of the pot is at its maximum (10 kΩ).
Listing 3-4 shows a simple program that alternately flashes LEDs connected to pins 0 and 2. The rate at which the LEDs flash is set by a pot in an RC circuit on pin 13. As you can see, the program simply multiples the time value calculated by the RCTIME
command by 10 to determine how long the program is to pause between flashes. As you turn the pot's knob to decrease the resistance of the pot, the LEDs flash at a faster rate.
Listing 3-4: An LED Flashing Program That Uses a Pot
' Potentiometer LED Flashing Program
' Doug Lowe
' July 10, 2011
'
' This program flashes LEDs connected to pins 0 and 2
' at a rate determined by an RC circuit on pin 13.
' {$STAMP BS2}
' {$PBASIC 2.5}
Time VAR Word
Led1 PIN 0
Led2 PIN 2
Pot PIN 13
DO
HIGH Pot
RCTIME Pot, 1, Time
HIGH Led1
LOW Led2
PAUSE Time * 10
LOW Led1
HIGH Led2
PAUSE Time * 10
LOOP
Project 3-2 shows you how to build a circuit that includes a 10 kΩ potentiometer and a capacitor so that you can test the code in Listing 3-4. Figure 3-3 shows the completed circuit.
Figure 3-3: A circuit that uses a potentiometer to control flashing LEDs (Project 3-2).
Using Subroutines and the GOSUB Command
A subroutine is a section of a program that can be called upon from any location in the program. When the subroutine finishes, control of the program jumps back to the location from which the subroutine was called. Subroutines are useful because they let you separate long portions of your program from the program’s main loop, which simplifies the main program loop to make it easier to understand.
Another benefit of subroutines is that they can make your program shorter. Suppose that you’re writing a program that needs to perform some complicated calculation several times. If you place the complicated calculation in a subroutine, you can call the subroutine from several places in the program. That way, you have to write the code that performs the complicated calculation only once. Without subroutines, you’d have to duplicate the complicated code each time you need to perform the calculation, which would be a real drag.
To create and use subroutines, you make use of two PBASIC commands:
GOSUB
: Calls the subroutine. You typically use the GOSUB
command within your program's main loop whenever you want to call the subroutine.
RETURN
: Always the last command in the subroutine. RETURN
jumps back to the command that immediately follows the GOSUB
command.
To create a subroutine, you start with a label and end with a RETURN
command. Between them, you write whatever commands you want to execute when the subroutine is called.
Here's an example of a subroutine that generates a random number between 1 and 999 and saves it in a variable named Rnd
:
GetRandom:
RANDOM Rnd
Rnd = Rnd // 999 + 1
RETURN
To call this subroutine, you simply use a GOSUB
command as follows:
GOSUB GetRandom
This GOSUB
command transfers control to the GetRandom
label. Then, when the GetRandom
subroutine reaches its RETURN
command, control jumps back to the command immediately following the GOSUB
command.
Listing 3-5 shows a complete program that uses a subroutine to get a random number between 1 and 1,000 and uses the random number to cause the LED on pin 0 to blink at random intervals. You can run this program on any BASIC Stamp circuit that has an LED on pin 0, including the circuits you build for Projects 3-1 and 3-2 earlier in this chapter.
Listing 3-5: Using a Subroutine to Blink an LED
' LED Blinker Program
' Doug Lowe
' July 10, 2011
'
' This program blinks the LED on pin 0 randomly.
' {$STAMP BS2}
' {$PBASIC 2.5}
Rnd VAR Word
Led1 PIN 0
DO
GOSUB GetRandom
HIGH Led1
PAUSE Rnd
LOW Led1
PAUSE 100
LOOP
GetRandom:
RANDOM Rnd
Rnd = Rnd // 999 + 1
RETURN
FOR Counter = 1 TO 100
GOSUB GetRandom
HIGH Led1
PAUSE Rnd
LOW Led1
PAUSE 100
NEXT
GetRandom:
RANDOM Rnd
Rnd = Rnd // 999 + 1
RETURN
We hope you can see the problem: after the FOR-NEXT
loop blinks the LED 100 times, the program continues with the next command after the FOR-NEXT
loop, which is the subroutine!
To prevent that from happening, you can use another PBASIC command, END
, which simply tells the BASIC Stamp that you've reached the end of your program and it should stop executing commands. You place the END
command after the NEXT
command, as follows:
FOR Counter = 1 TO 100
GOSUB GetRandom
HIGH Led1
PAUSE Rnd
LOW Led1
PAUSE 100
NEXT
END
GetRandom:
RANDOM Rnd
Rnd = Rnd // 999 + 1
RETURN
The program stops after the FOR-NEXT
loop finishes.