Programming with Robots by Albert W. Schueller - HTML preview

PLEASE NOTE: This is an HTML preview only and some elements such as links or page numbers may be incorrect.
Download the book in PDF, ePub, Kindle for a complete version.

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