Cycle Tutorial
Cycle is a simple Java (or C/C++) programming language for producing
programs for Cybot from Ultimate Real Robots. It is a direct alternative to
using the graphical programming "language" supplied on the CDs.
Hello World
The simplest program possible in any programming language is usually Hello
World! Cycle is no exception, but as Cybot has no graphical display, we will
make do with just making his antennae LEDs flash. The code for this is as
follows:
include "cybot.cyc"
proc main()
{
antennae.toggle();
delay.wait( 5 );
}
|
That's it! Not very long is it! In fact it could be shorter still, as
line 1 is simply a comment telling you what the program is or does and the
blank lines are simply there to make the code more readable.
The code proper begins at line 3, whereby we tell the Cycle compiler that we
wish to use the definitions for Cybot. These simple tell the compiler what
inputs (sensors we can interrogate) and outputs (things we can control) our
robot has. These are stored in the file cybot.cyc. The reason these
are in a separate file is because they are shared between all Cycle programs -
if they need to be changed, then you simply update that file, you don't
have to update all your programs individually.
All Cycle programs contain a procedure called main . The
program starts with the first statement inside main . The start of
the procedure is at line 5 and takes the form proc followed by the
procedure name, followed by a set of parenthesis (round brackets). The body of
the procedure (and because this is main , the program) is enclosed
in braces (curly brackets). So in our Hello World example the body of
main runs from line 6 to line 9.
The body of Hello World consists of a single statement,
antennae.toggle() . All statements in Cycle are followed by a
semicolon (;), unless the use braces ({}). This helps the compiler work out
where one statement ends and the next begins. antennae.toggle()
simply sets the antennae LEDs flashing (toggle is the name used in the
graphical programmer for the flashing state).
The second statement delay.wait( 5 ) simply pauses for 5
seconds, after which the program terminates. Without the delay, the program
would exit immediately without ever flashing the LEDs once!
To compile this program, simply type it into a text editor (e.g. Notepad)
and save it as hello.cyc in the same folder as you installed the cycle
compiler. Note that you should not type the line numbers, they are just to
make the code to read. Having saved the file, open a DOS or Command Prompt
and compile it with:
cycle -ocycle.03p hello.cyc
|
This will produce a new file called hello.03p . This file can
be opened by Programmer 03 on CD2. You will need to copy it into
<path_to_real_robots_2>\program files\Real Robots\Robot
Programs . You should then be able to open it in Programmer 03 and you
will see a graphical representation of our code and run it in the simulator.
Windows users can alternatively use the following command, where the
-i switch causes the output to be installed into the Robot
Programs folder directly:
cycle -i -ocycle.03p hello.cyc
|
Be careful using this, as like most compilers, any file with the same name
will be overwritten without warning.
Loops
Most Cybot programs need to do things in a loop. For example following a line
repeatedly reads the line following sensor and adjusts the speed of the motors
to turn it to the left or the right. We'll start with the simplest form of
loop, the infinite loop. Here the program repeats a set of actions over and
over, for ever. The program will have the same function as Hello World, but
will make the LEDs flash more slowly. Here's the code:
include "cybot.cyc"
proc main()
{
while( true )
{
antennae.on();
delay.wait( 1 );
antennae.off();
delay.wait( 1 );
}
}
|
Save this as flash.cyc, compile it and run it. You should see that
Cybot's LEDs flash on and off as 1 second intervals. So how does it
work?
As before we include the Cybot definitions from cycbot.cyc and start
the program with a main procedure. This time however the body of
the program contains a while loop (line 7). This executes
whatever is in the body of the loop while the condition is true. In our
case the condition is always true, as it is the constant true , so
the loop will continue forever.
The body of the loop (lines 9 to 12) is executed from top to bottom each
time the loop repeats. The first statement, antennae.on() , at
line 9 turns Cybot's antennae LEDs on, and the third statement,
antennae.off() at line 11, turns them off again. The second and
fourth statements, as lines 10 and 12 respectively, cause the program to wait.
The 1 between the parenthesis is a parameter indicating how many seconds to
wait for, in this case it is for 1 second each time.
Variables
Cycle programs can contain variables. These are "boxes" into which a number
can be placed and later retrieved. Each "box" is denoted by a letter from 'a'
to 'l' (lowercase 'L'). Variables can be assigned a new value, which
can be either a number, or the contents of another variable. They can also be
assigned a value calculated using a simple expression. An expression
can be a simple addition e.g. a + 1 , or subtraction,
multiplication and division.
OK, so we can put a number in a box, very useful that! Well, there's
slightly more to it than that - we can test the value in the box to see if some
condition is true. We can test to see if the value is equal to another
value, or greater than, less than, not equal to, etc. If we look back to the
while loop example, we can see that the loop is executed while the
condition is true, or to put it another way, the loop will stop executing when
the condition becomes false.
Well, that's the theory, let' try an example. Say we want to flash
Cybot's LEDs four times. We could use a variable to count the number of
times we have flashed and stop when this reaches 4. In Cycle this can be
written:
include "cybot.cyc"
proc main()
{
a = 0;
while( a < 4 )
{
antennae.on();
delay.wait( 1 );
antennae.off();
delay.wait( 1 );
a = a + 1;
}
}
|
This looks a little more complicated, but we have really only added three
lines, and changed one, from the previous example. At line 7 we put the number
zero in the box marked 'a'. In our while statement our condition
now tests to see that the contents of variable 'a' are less than four. This
will be true, as 'a' initially contains zero (from the previous statement). We
therefore execute the body of the while statement, which will
flash the LEDs once. At the end of the loop body we add one to the contents of
'a' and place the result back in box 'a' (line 16). This as the net effect of
incrementing 'a' by one. We then go back to the beginning of the loop and test
to see whether 'a' is still less than four - it is, so we flash the LEDs again
and increment 'a' again. This happens until the value in 'a' reaches four.
The condition of the while loop then evaluates to false and
execution jumps to the next statement after the end of the body of the
while , i.e. line 18. In this case, this is the end of the
procedure, so it simply causes the program to terminate.
For Loops
Our while loop example is all well and good, but is a little long
winded. Instead we can re-write this as a for loop, which does exactly
the same thing, but in less code. A for statement consists of
an initial assignment, followed by a condition, followed by an increment
assignment, all of which are written at the start of the loop. So, our
above example now becomes:
include "cybot.cyc"
proc main()
{
for( a = 0; a < 4; a++ )
{
antennae.on();
delay.wait( 1 );
antennae.off();
delay.wait( 1 );
}
}
|
I'm sure you'll agree this is much more compact way of writing
things. You may have spotted that the increment has been written
a++ instead of a = a + 1 - this is an common
abbreviation inherited from C.
If Statement
Sticking with our flash program, we could re-write the code again to use an
if statement to control the proceedings. The for example is
about as compact as we can make it, but to illustrate the use of
if , we can write the same program as:
include "cybot.cyc"
proc main()
{
a = 0;
while( true )
{
if( a == 4 )
{
cybot.stop();
}
antennae.on();
delay.wait( 1 );
antennae.off();
delay.wait( 1 );
a = a + 1;
}
}
|
The only difference here is that we are testing the inverse of the
while condition using if and that we exit the loop with
cybot.stop() , which simple terminates the program. Programmers
of other languages should note that the braces ({}) are mandatory on
if , for and while statements in Cycle.
Controlling Outputs
Cybot has other outputs that it's antennae LEDs, the most important being
the two motors. These are controlled using motors.setSpeed(
left, right ) . Here is a simple
program to drive forward for 1 second, turn left slowly for 1 second and
finally turn sharp right for 1 second before stopping:
include "cybot.cyc"
proc main()
{
motors.setSpeed( 2, 2 );
delay.wait( 1 );
motors.setSpeed( 1, 2 );
delay.wait( 1 );
motors.setSpeed( 2, 0 );
delay.wait( 1 );
}
|
Line 7 simply sets both the left and right motors to speed 2. If you
remember from CD1, in Programmer 02 the motors can have speeds from -4 (full
speed backwards) to 4 (full speed forwards). These same values apply in Cycle,
so we are setting both motors to about half speed forwards. Line 8 waits for
1 second before line 9 sets the left motor to run slightly slower than the
right, resulting in a slow turn to the left. After another wait at line 10,
we set the left-hand motor to run at half speed and stop the right-hand motor,
resulting in a tight turn to the right. Note that we need another wait at line
12, otherwise we would immediately exit the program!
The motor speeds are quite easy to remember, but if you prefer, you can use
names for the numbers -4 to 4. These are Motors (with a capital
'M') followed by FFAST (4), FMED (3),
FSLOW (2), FVSLOW (1) and STOP (0). To
go backwards the names are the same, but prefixed by a 'B' instead of an 'F'.
So the above program becomes:
include "cybot.cyc"
proc main()
{
motors.setSpeed( Motors.FSLOW, Motors.FSLOW );
delay.wait( 1 );
motors.setSpeed( Motors.FVSLOW, Motors.FSLOW );
delay.wait( 1 );
motors.setSpeed( Motors.FSLOW, Motors.STOP );
delay.wait( 1 );
}
|
For controlling the motors, this is not as readable, but names can be used
in place of values almost anywhere, and are often a good idea to improve the
readability of your programs.
Reading Inputs
Having mastered simple control of the robot, we would now like our programs to
be able to react to the various sensors (light, sonar and line following)
on the robot. Lets start with a rather unsophisticated program which steers
towards light:
include "cybot.cyc"
proc main()
{
while( true )
{
switch( light.getStatus() )
{
case Light.LEFT:
motors.setSpeed( -2, 4 );
break;
case Light.SAME:
motors.setSpeed( 4, 4 );
break;
case Light.RIGHT:
motors.setSpeed( 4, -2 );
break;
}
}
}
|
Here we use a new kind of statement, the switch statement and
its associated parts, case and break . What
switch does is take a value (in the brackets) and compare it
against each of the case clauses in turn. When it finds a match,
the code between the ':' of the case and the break
is executed. After that, execution continues at the next statement after the
closing brace (}) of the switch .
In the above example we are reading the light sensor using
light.getStatus() and depending upon its value, we decide what to
do. If the value is LEFT (1), then we turn to the left (lines 11
to 13), if it is RIGHT (3), we turn to the right (lines 17 to 19),
and if it is SAME (2), we drive straight on (lines 14 to 16). As
we do this in a loop, the program will make Cybot continually hunt for the
brightest light and drive towards it. .
The fundamental flaw with the above program is that Cybot will keep driving
even if there is an obstacle in the way. Time to look at using Cybot's
Sonar sensors to avoid objects.
Reading More Than One Input
In order to cope with Sonar, we need to be able to read more than one input at
a time, as the Sonar is organized as two separate sensors. This is not
difficult, but requires that we place one switch inside another:
include "cybot.cyc";
proc main()
{
while( true )
{
switch( sonar.getRightRange() )
{
case 1:
switch( sonar.getLeftRange() )
{
case 1: motors.setSpeed( -2, -2 ); break;
case 2: motors.setSpeed( -2, 0 ); break;
case 3: motors.setSpeed( -2, 1 ); break;
case 4: motors.setSpeed( -2, 2 ); break;
}
break;
case 2:
switch( sonar.getLeftRange() )
{
case 1: motors.setSpeed( 0, -2 ); break;
case 2: motors.setSpeed( 0, 2 ); break;
case 3: motors.setSpeed( 2, 3 ); break;
case 4: motors.setSpeed( 2, 4 ); break;
}
break;
case 3:
switch( sonar.getLeftRange() )
{
case 1: motors.setSpeed( 1, -2 ); break;
case 2: motors.setSpeed( 2, 1 ); break;
case 3: motors.setSpeed( 2, 3 ); break;
case 4: motors.setSpeed( 3, 4 ); break;
}
break;
case 4:
switch( sonar.getLeftRange() )
{
case 1: motors.setSpeed( 2, -2 ); break;
case 2: motors.setSpeed( 3, 2 ); break;
case 3: motors.setSpeed( 4, 3 ); break;
case 4: motors.setSpeed( 4, 4 ); break;
}
break;
}
}
}
|
Here we use sonar.getRightRange() in an outer
case statement, and for every value, we have an inner
switch statement which uses sonar.getLeftRange() . In
both cases the range goes from 1 (the nearest) to 4 (the furthest). From this
you should be able to see that every combination of left and right range is
accounted for, just as it was in Programmer 02 on CD1.
Well that's the basics, if you want to know more, then read the
Advanced Tutorial as well. This explains the
contents of cybot.cyc and how to write your own robot definitions.
|