the loop so that each light reading is assigned to a different array element. Notice also that
the break statement occurs after the counter increment, but before the wait10Msec() calls.
An array bound overrun occurs if you attempt to use an out of range index to access an
array element. This common error can be difficult to find because the compiler cannot detect
it. Such errors can lead to unpredictable behavior in the same way that uninitialized variables
can. Correcting array bound overruns involves careful inspection of all array operations and
clearly determining the value of any index variables. For example, in the program in Listing
6.6, if we simply swap the order of the counter increment and the break statement, we will introduce an array bound overrun. Checking the index before incrementing will lead to an
attempt to use the illegal value 96 as an array index the next time through the loop.
44
CHAPTER 6. LOOPS AND ARRAYS
✞
☎
# p r a g m a c o n f i g ( Sensor , S1 , l i g h t s e n s o r , s e n s o r L i g h t I n a c t i v e ) task main () {
int c o u n t =0;
int l i g h t _ r e a d i n g s [ 9 6 ] ;
w h i l e ( true ) {
l i g h t _ r e a d i n g s [ c o u n t ] = S e n s o r V a l u e [ l i g h t s e n s o r ];
c o u n t ++;
if ( c o u n t == 95) { b r e a k ; }
w a i t 1 0 M s e c ( 3 0 0 0 0 ) ;
w a i t 1 0 M s e c ( 3 0 0 0 0 ) ;
w a i t 1 0 M s e c ( 3 0 0 0 0 ) ;
}
//
}
✝
✆
Listing 6.6: This program uses a while-loop to collect light sensor readings every 15 min-
utes for 24 hours and store the data in the array light_readings. The three calls to
wait10Msec(30000) are required because the wait10Msec() function can only accept argu-
ments that are less than 32767. To wait 15 minutes requires an argument of 90000 which is
too large.
6.4
For-loops
For-loops are syntactically designed to be used with arrays. Though the behavior of a for-
loop may be duplicated by a while-loop, for readability, programmers typically use for-loops
when the intention of the loop is to manage an array.
✞
☎
for ( [ i n i t i a l i z a t i o n ]; [ c o n d i t i o n ]; [ i n c r e m e n t ]
) {
// block of i n s t r u c t i o n
}
✝
✆
Listing 6.7: The syntax of a for-loop. The block of instruction is executed as long as the
condition is true.
When a for-loop is first encountered, the [initialization] instruction is executed and
immediately thereafter the [condition] statement. If the [condition] statement is true,
the for-loop body block is executed. At the end of the block, program execution returns
to the [increment] statement and immediately thereafter the [condition] statement. If
the [condition] statement is true, the block is executed again and the program execution
returns to the [increment] statement. The [initialization] statement is only executed
once when the program execution first encounters the for-loop. Thereafter, the [increment]
and [condition] statements are executed until the [condition] statement becomes false.
At first, the for-loop operation seems overly complicated, but consider the code in Listing
6.8 which shows a typical application of for-loop syntax.
6.5. TWO-DIMENSIONAL ARRAYS
45
✞
☎
task main () {
int i ;
int p e r f e c t _ s q u a r e s [ 1 0 ] ;
for ( i =0; i <=9; i ++) {
p e r f e c t _ s q u a r e s [ i ] = i * i ;
}
//
}
✝
✆
Listing 6.8:
A simple for-loop that stores the first 10 perfect squares in the array
perfect_squares.
In this case, the for-loop controls the value of the the array index, i, by initializing it to
0 and incrementing it by 1 through the values 0 through 9. In the loop body, we assign i*i
to the ith array element. After the loop is finished, the perfect_squares array contains
(0, 1, 4, 9, . . . , 81).
Listing 6.9 shows a program that will scroll through a list of the lower-case letters of the alphabet with a quarter-second delay between each letter.
✞
☎
task main () {
int i ;
char a l p h a b e t [ 2 6 ] ;
for ( i =0; i < = 2 5 ; i ++) {
a l p h a b e t [ i ] = ’ a ’ + i ;
}
for ( i =0; i < = 2 5 ; i ++) {
n x t S c r o l l T e x t ( " % c " , a l p h a b e t [ i ]);
w a i t 1 0 M s e c ( 2 5 ) ;
}
}
✝
✆
Listing 6.9: Scrolls through the letters of the alphabet. The first for-loop fills an array with
the letters of the alphabet. The second displays them to the screen with a quarter-second
delay between each new letter.
Notice the unusual addition of an integer and a character assigned to the alphabet array.
This is a useful method of manipulating characters via their positions in the alphabet. It
works via the notion of casting.
6.5
Two-Dimensional Arrays
A two-dimensional (2D) array is a “grid” of elements. While there is very little that a 2D
array can do that an ordinary array cannot, sometimes information is conceptually easier to
46
CHAPTER 6. LOOPS AND ARRAYS
represent as a 2D array. For example, the NXT display in Figure 3.1 is naturally suited to a 2D array representation. The syntax for declaring a 2D array is
✞
☎
[ d a t a t y p e ] [ v a r i a b l e name ][[ S I Z E 1 ]][[ S I Z E 2 ]];
✝
✆
More specifically, an array that might represent the NXT display would be
✞
☎
bool s c r e e n [ 1 0 0 ] [ 6 4 ] ;
✝
✆
Elements that are true indicate pixels that that are on. Elements that are false indicate
pixels that are off. To represent a blank screen, we set all of the elements to false
✞
☎
bool s c r e e n [ 1 0 0 ] [ 6 4 ] ;
for ( i =0; i < 1 0 0 ; i ++) {
for ( j =0; j <64; j ++) {
s c r e e n [ i ][ j ]= f a l s e ;
}
}
✝
✆
To represent a display with a horizontal line across the middle of the display
✞
☎
bool s c r e e n [ 1 0 0 ] [ 6 4 ] ;
for ( i =0; i < 1 0 0 ; i ++) {
s c r e e n [ i ] [ 3 2 ] = true ;
}
✝
✆
Notice that indices start at zero, just like with ordinary arrays. We see a nice correspondence
between the pixel coordinate, (i,j), and the 2D array element [i][j]. A programmer can
make a whole assortment of changes to the screen array and then use it to “paint” the screen
all at once by “visiting” every array element and, if true, turning on the corresponding pixel.
✞
☎
for ( i =0; i < 1 0 0 ; i ++) {
for ( j =0; j <64; j ++) {
if ( s c r e e n [ i ][ j ]) { n x t S e t P i x e l ( i , j ); }
}
}
✝
✆
6.6. EXERCISES
47
6.6
Exercises
1. Under what circumstances might the program in Listing 6.4 miss a trigger count?
2. Consider the alternative below to the trigger count program in Listing 6.4.
✞
☎
# p r a g m a c o n f i g ( Sensor , S1 , trigger , s e n s o r T o u c h )
task main () {
int c o u n t =0;
n x t D i s p l a y C e n t e r e d B i g T e x t L i n e (3 , " % d " , c o u n t );
w h i l e (1) {
if ( S e n s o r V a l u e [ t r i g g e r ]) { c o u n t ++; }
e r a s e D i s p l a y ();
n x t D i s p l a y C e n t e r e d B i g T e x t L i n e (3 , " % d " , c o u n t );
}
}
✝
✆
Will it give an accurate trigger count? Why or why not?
3. Write a program that uses the sound sensor to briefly display (1 second) a message (or
graphic) when a loud noise is detected.
4. Use the PlayImmediateTone(), as detailed in the RobotC On-line Support on the left side-bar under the NXT Functions → Sounds section, to play a tone whose frequency
is proportional to the intensity of light being measured by the light sensor. (For added
information, display the light measurement on the screen. Also, a small loop delay
may be required for this to work well.)
5. Write a snippet of code that declares a 10-element integer array and fills it with random
integers between -100 and 100 inclusively.
6. In computer science, a queue is a data structure that allows data to be added at the
end of the array and removed from the front. Queues are usually stored in arrays.
To add a value to the queue simply assign it to the next open array element. This
assumes that you, the programmer, are keeping track of how many elements are in the
queue with a counter variable like QueueSize that you increment when an element is
added and decrement when an element is removed. To remove an element, copy all the
elements forward in the array, e.g. assign element 1 to element 0, element 2 to element
1, etc.
Write a for-loop that shifts n elements in an array forward, effectively deleting element
0 from the queue.
7. Write a snippet of code that swaps the values in the ith and jth elements of an array.
48
CHAPTER 6. LOOPS AND ARRAYS
8. Write a program that uses the sonar sensor to count the number of times a hand is
waved before it in close proximity. Include a break statement that terminates the
loop after 20 “waves”. (Hint: For this program, you will have to do some preliminary
testing to see how the values of the sonar change when a hand is waved in front of it.
You will then need to use those explicit values to construct predicates for the holding
while-loops.)
9. In Listing 6.6, why is it important that the break statement be placed precisely where it is placed? In your response, consider other placements, e.g. before count++; or after
the calls to wait10Msec().
10. Write a program that displays a scrolling bar graph of the light sensor values sampled
at 100 millisecond intervals.
11. Write a program that tests the reaction time of the user. Users are instructed that
when the test starts they are to wait for some visual cue to appear (after some random
length of time between 0.5s and 10.0s) and to press the trigger as soon as they see it.
The program then reports the reaction time in seconds (to 2 significant digits) along
with some (perhaps snarky) comment on the time.
Chapter 7
Motors and Motion
Up to this point, we have only discussed the sensors and the display. Sensors provide infor-
mation to the robot the environment. The screen provides information to the environment.
So far everything has been quite passive. Now it is time to get proactive! The NXT kit
contains 3 motors. The motors allow the robot to change its environment–to go from a
passive observer to an active participant.
7.1
Motors
The NXT motors are sophisticated devices that not only provide a way to apply force, but
also act as rotational sensors–able to measure rotation to the nearest of 360◦. Most of the
motor control is done through the use of a collection of special reserved arrays.
7.1.1
Motor Arrays
Motors are connected to the NXT brick through the A, B, or C ports only (not the numbered
ports). Motors are connected and identified to your program in the same way that sensors
are using the “Motors and Sensors Setup” window. It is important that the programmer
know which motor is connected to which lettered port.
When motors are connected to the NXT brick and identified to the program, the program
maintains two important arrays associated with the motors summarized in Table 7.1.
Array
Description
motor[]
speed array, each integer element (one for each motor)
ranges from -100 to 100, negative values reverse direction
nMotorEncoder[]
encoder array, each integer element (one for each motor)
indicates the number of degrees of rotation relative to
some zero that the programmer sets
Table 7.1: Two important motor arrays and their purposes.
49
50
CHAPTER 7. MOTORS AND MOTION
The motor[] array is a 3-element integer array with motor[0] corresponding to Port A,
motor[1] to Port B, and motor[2] to Port C. The “Motors and Sensors Setup” window,
among other things, sets up meaningful aliases for the array indices 0, 1, and 2, to make
your program more readable.
At the start of the program, the motor array elements are all 0 (motors off). Assigning a
non-zero value between -100 and 100 to a motor array element instantly turns on the motor
to that power and in the direction specified by the sign. The motor will remain on, at that
power, and in that direction for the duration of the program or until the programmer changes
the value.
The nMotorEncoder[] array elements have the same correspondence to Ports A, B, and
C as the motor[] array. Each integer element indicates the number of degrees of rotation of
the motor since the beginning of the program or since the programmer last set the element
to zero. For example, a value of 360◦ indicates that the motor has completed one full turn,
720◦ indicates two turns, 765 indicate two and a quarter turns.
Other motor arrays, as described in the RobotC On-line Support on the left side-bar under the NXT Functions → Motors section, allow the programmer to control more subtle
aspects of the motors.
7.1.2
Basic Motor Control
The timing and control of motor actions can be tricky. For example, consider the snippet
✞
☎
# p r a g m a c o n f i g ( Motor , motorC , Left , t m o t o r N o r m a l , P I D C o n t r o l )
task main () {
m o t o r [ Left ] = 50;
}
✝
✆
This program will exit immediately leaving no time for the motor to turn. Adding a wait
command,
✞
☎
# p r a g m a c o n f i g ( Motor , motorC , Left , t m o t o r N o r m a l , P I D C o n t r o l )
task main () {
m o t o r [ Left ] = 50;
w a i t 1 M s e c ( 1 0 0 0 ) ;
}
✝
✆
will cause the motor to run for 1 second at half power.
7.2
Turning and Motor Synchronization
A robot with two drive wheels and a trailing coast wheel (a tri-bot) can be made to turn
by driving the two drive wheels at different rates. Consider the circular track in Figure
7.2. TURNING AND MOTOR SYNCHRONIZATION
51
w
r
Figure 7.1: A two-wheel drive circular path of radius r and trackwidth w.
7.1 traced out by the two drive wheels of the the tri-bot. A simple geometric calculation determines the relative rates at which the two drive wheels should turn in order to move
along this path (the dotted blue line). We assume that the radius of the path is r and the
trackwidth (the distance from center of the point of contact of the left wheel with the ground
and the right wheel with the ground) is w. Assume that the tri-bot travels around the track
in t seconds. The left wheel (inner circle) travels a distance of 2π(r − w/2)–the circumference
of the inner circle, while the right wheel travels a distance of 2π(r + w/2)–the circumference
of the outer circle. The speeds of the left wheel, sL, and the right wheel, sR are given by
2π(r − w/2)
sL =
t
and
2π(r + w/2)
sR =
.
t
Consider the ratio of these two speeds
sL
r − w/2
=
.
(7.1)
sR
r + w/2
As long as this ratio is preserved, the tri-bot will move along the circular track of radius r.
For example, suppose a tri-bot has a trackwidth of w = 10cm and we would like it to
move along a circular track of radius r = 20cm. Our formula suggests that we run the
52
CHAPTER 7. MOTORS AND MOTION
motors with a speed ratio of 15 = 3 . Of course there are many different power settings for
25
5
the motors that yield this ratio. In fact, any pair of left and right motor power settings that
reduce to this fraction will cause the tri-bot to move along this track of radius 20cm. The
only difference will be in how fast the robot moves along the track. A ratio of 15 : 25 will
move only half as fast as a ratio of 30 : 50.
RobotC provides convenient commands for controlling a pair of motors. Listing 7.1 shows how to control the relative speeds of the two motors.
✞
☎
# p r a g m a c o n f i g ( Motor , motorC , Left , t m o t o r N o r m a l , P I D C o n t r o l )
# p r a g m a c o n f i g ( Motor , motorA , Right , t m o t o r N o r m a l , P I D C o n t r o l )
task main () {
n S y n c e d M o t o r s = s y n c h A C ; // Left motor slaved to Right motor
n S y n c e d T u r n R a t i o = +60; // Left motor turns 60% of right motor
m o t o r [ R i g h t ] = 50; // Right motor moves at 50% power
// Left motor a u t o m a t i c a l l y moves at 30%
// because of synch and synch ratio .
w a i t 1 M s e c ( 1 0 0 0 ) ;
}
✝
✆
Listing 7.1: This program will drive a tri-bot with trackwidth 10cm around a circle of radius
20cm.
The left motor is synchronized to the right motor with the instruction, nSyncedMotors
= synchAC;. The relative speed of the left motor to the right motor is set at +60% (roughly
3 : 5) with the instruction nSyncedTurnRatio = +60;. Subsequently, when the right motor
is activated with the power 50, the left motor is automatically activated with a power that
is 60% of the right’s–in this case, roughly 30. With the speed ratio at 3 : 5, the tri-bot will
move around a track of radius 20cm for 1 second.
In the previous example, we assumed that the center of the circle about which the robot
turned was to the left (or right) of the robot. Suppose the center of the circle is between the
drive wheels (under the robot) as in Figure 7.2. This situation occurs when r < w/2.
Notice that in this case, the numerator of equation (7.1) is negative indicating that the left wheel turns in the opposite direction of the right wheel. The path of the left wheel is
indicated by the inside black circle. The path of the right wheel is indicated by the outside
black circle. The arrows show the direction of travel and starting point of each wheel.
The dotted blue line shows the path of the midpoint of the distance between the wheels.
Additional information on motor synchronization is available here.1
7.3
Distance and Motor Encoders
Through synchronization, we now have a method of executing accurate and precise turns,
but lack a method of traveling accurate and precise distances. In this section, we continue to
1http://carrot.whitman.edu/Robots/PDF/Synching%20Motors.pdf
7.3. DISTANCE AND MOTOR ENCODERS
53
r
w
2
Figure 7.2: A two-wheel drive circular path of radius r and trackwidth w in which r < w/2.
In this case, the wheels turn at different rates in opposite directions and the blue dot tracks
the circle. The center of rotation, the black dot, is between the drive wheels.
use the tri-bot model. Controlling distance requires precise information about the effective
circumference of the drive wheels and the ability to specify the angle of rotation of the
wheels.
7.3.1
Circumference
To determine the circumference of a drive wheel, we could simply remove it, roll it on a piece
of scrap paper through one complete rotation, marking the start and end, and measure the
distance between them. Or, we could wrap a piece of thread around the wheel and measure
its length. Or, we could measure the radius, r, of the wheel and use the circumference
formula
C = 2πr.
(7.2)
One problem with these approaches is that the wheel has been removed from the context
in which it will be used. For example, a weight bearing wheel will not travel as far in
one rotation as an unloaded wheel. A more accurate method would be to measure the
circumference of the wheel in context. The effective circumference is the circumference
of the wheel in the context in which it will be used.
To measure the effective circumference, we will use the motor encoders to drive the wheels
in a straight line through a set number of rotations and measure the distance traveled. The
motor encoders measure the number of degrees of rotation of a motor to the nearest of 360◦.
As discussed in Section 7.1.1, the 3-element array, nMotorEncoder[], always contains the current number of degrees the corresponding motor has turned starting since the program
started or since the value was last reset to zero. The value is a signed 16-bit integer with the
sign indicating the direction of rotation. For example, if the wheel goes forward for one full
rotation and then reverses for one full rotation, the motor encoder value for that wheel in
the end will be unchanged. Note also that the range of allowable encoder values goes from
54
CHAPTER 7. MOTORS AND MOTION
-32768 to 32767 degrees. This corresponds to about 91 rotations in either the forward or
reverse directions. In long running programs, the programmer should periodically reset the
encoder values to zero to avoid an overflow.
Consider the instructions in Listing 7.2. This program will cause the robot to move forward in a straight line through exactly to rotations of the drive wheels. The first two
✞
☎
# p r a g m a c o n f i g ( Motor , motorC , Left , t m o t o r N o r m a l , P I D C o n t r o l )
# p r a g m a c o n f i g ( Motor , motorA , Right , t m o t o r N o r m a l , P I D C o n t r o l )
task main () {
n S y n c e d M o t o r s = s y n c h A C ; // Left motor slaved to right motor
n S y n c e d T u r n R a t i o = + 1 0 0 ; // Left right motors same rate
n M o t o r E n c o d e r [ R i g h t ]=0; // Reset right motor encoder to 0
n M o t o r E n c o d e r T a r g e t [ R i g h t ] = 720; // Stops after 720 degs
m o t o r [ R i g h t ] = 50; // Right motor moves at 50% power
w h i l e ( n M o t o r R u n S t a t e [ R i g h t ]== r u n S t a t e R u n n i n g ){} // Hold
}
✝
✆
Listing 7.2: This program will cause the tri-bot to move forward in a straight line through
exactly two rotations of the drive wheels.
instructions synchronize the motors and cause them to run at the same speed. Next, we set
the motor encoder value for the right motor to zero and use the nMotorEncoderTarget[]
array to set a precise stopping point at 720◦. Setting the target does not start the motor,
but it does cause the motor to stop when the motor encoder value reaches 720◦. We then
start the motor at 50% speed and use a while-loop to hold the program execution until the
rotations are complete.
It is reasonable to wonder why, in Listing 7.2, we do not omit the encoder target instruction and simply stop the motors by setting the motor power to 0 after the while-loop exits.
Indeed, this approach does work. However, particularly at high speeds, stopping in this
manner can be quite rough causing