An Ada Interface to Lego
Mindstorms
US Air Force Academy
Colorado Springs, CO
719-333-3340
barry.fagin@usafa.af.mil
1.0
Introduction
This article presents an Ada interface to the Lego Mindstorms™ RCX “brick”, the core element of a commercial product that emerged from a collaboration between the Lego Corporation and MIT’s Media Laboratory. Since its arrival on the market in 1998, it has considerable interest in the computing community: a variety of operating systems and language interfaces are available for it, all provided free of charge by people who simply wanted to find out how the RCX worked.
The interface described here is in use at the Air Force Academy as part of an experiment in computer science education. Students with no prior programming background are given a pre-built Mindstorms robot and a series of programming challenges, which they then attempt to implement using Ada. Our hope is that the experience of programming robots will provide an effective, efficient, and enjoyable method for conveying essential computer science concepts. We are currently attempting to assess its effectiveness, and hope to present our results at a future date.
2.0 The Lego RCX Platform
The RCX brick is shown below:
It contains a Hitachi microcontroller, three input ports numbered 1, 2, and 3, and three output ports labeled A, B, and C. Inputs can be connected to a variety of sources, including touch sensors (that detect whether or not a button has been pressed), light sensors (that measure the intensity of reflected light) and even temperature sensors. Outputs are usually connected to motors, to provide mobility, a grabber arm, or a similar moving component. The sensors, motors, and other Lego components are included with the Mindstorms kit, which is used to build a device around the brick itself. One example is shown below:
3.0
The Ada/Mindstorms Programming Environment
Ada/Mindstorms 1.0 consists of two files, lego.ads and lego.adb, and a translator known as ada2nqc. It also requires access to an Ada compiler, which must be run on the source program before the translator is run. The translator performs some semantic checks that a regular compiler cannot catch (many features of Ada, for example, cannot be supported in the RCX hardware). It then converts the Ada program to a language known as NQC, for “Not Quite C”.
NQC was developed by Dave Baum, and is a functioning high level language execution environment for Lego Mindstorms [Ba99]. While NQC is well suited for experienced programmers and curious hackers, it not an attractive language for teaching programming, particularly for students with no programming background. By providing an Ada interface, we are able to use a more controlled, rigorous, and forgiving programming environment, one we believe to be better suited to computer science education.
The complete Ada/Mindstorms programming process is shown below:
Note that Ada/Mindstorms 1.0 is bundled into the latest release of AdaGide, which permits the building of executables for multiple targets. The base code for the ada2nqc translator was produced using the Ada Generator of Object-Oriented Parsers (ADAGOOP), developed by Carlisle [Ca00].
4.0
The Ada Subset Supported by Ada/Mindstorms
Many of the more elaborate features of Ada cannot be executed on the RCX hardware. Others would add substantial complexity to the translation software, and are planned for later releases. Thus Ada/Mindstorms 1.0 programs are currently subject to the following restrictions:
1) All identifiers and literals must be integers
2) Procedures may only appear inside the main procedure
3) No separate compilation
4) No standard packages
5) No user-defined types
More detailed restrictions are described in the Ada/Mindstorms 1.0 User’s Manual, available at http://www.usafa.af.mil/dfcs/adamindstorms.htm. Some of the material in this paper is also available there.
5.0
A Sample Program
Below is a simple Ada/Mindstorms program, written for a robot with two motors connected to outputs A and C and a bumper with a touch sensor connected to input A. This program will make the robot go forward until it bumps into something, then go back for three seconds, turn, and go forward again. The direction of the turn alternates each time the bumper is pressed.
with Lego;
use Lego;
procedure Test is
Left
: constant
Integer
:= 0;
Right : constant Integer := 1;
procedure
Go_Forward
is
begin
Output_On_Forward(Output => Output_A);
Output_On_Forward(Output => Output_C);
end
Go_Forward;
procedure
Go_Back
(Tenths_Of_A_Second : in Integer ) is
begin
Output_On_Reverse(Output
=> Output_A);
Output_On_Reverse(Output => Output_C);
Wait(Hundredths_Of_A_Second => Tenths_Of_A_Second * 10);
Output_Off(Output => Output_A);
Output_Off(Output => Output_C);
end
Go_Back;
procedure
Turn
(Direction : in Integer ) is
begin
if Direction
= Left then
Output_Off(Output => Output_A);
Output_Power(Power
=> Power_Low, Output => Output_C);
Output_On_Forward(Output => Output_C);
Wait(Hundredths_Of_A_Second => 100);
Output_Off(Output => Output_C);
else
--right
Output_Off(Output => Output_C);
Output_Power(Output => Output_A,Power => Power_Low);
Output_On_Forward(Output => Output_A);
Wait(Hundredths_Of_A_Second => 100);
Output_Off(Output => Output_A);
end
if;
end
Turn;
procedure
Initialize_Robot
is
begin
Config_Sensor(Sensor
=> Sensor_1, Config => Config_Touch);
Output_Power(Output => Output_A,Power => Power_Low);
Output_Power(Output
=> Output_C,Power => Power_Low);
end
Initialize_Robot;
counter : integer := 1;
begin
Initialize_Robot;
Go_Forward;
loop
--touch
sensor pressed?
if
Get_Sensor_Value(Sensor
=> Sensor_1) = 1 then
Go_Back(Tenths_Of_A_Second => 30);
if counter = 0 then
Turn(Direction => Right);
counter := 1;
else
Turn(Direction => Left);
counter := 0;
end if;
Go_Forward;
end
if;
end loop;
6.0
Where to Obtain Ada/Mindstorms 1.0, and Future Work
Ada/Mindstorms 1.0 is available for PC’s running Windows; see http://www.usafa.af.mil/dfcs/adamindstorms.htm for details. Source code for the ada2nqc translator is available from the author.
We are currently experimenting with efforts to use Ada/Mindstorms 1.0 and RCX projects to teach computer science. Work in progress includes an investigation into which topics are best suited for robotics-based instruction, and an assessment of the effectiveness of robotics-based instruction for teaching computer science.
Future plans for Ada/Mindstorms include support for arrays, separate compilation, and tasks. We also plan to construct a simulator, by replacing the stubs for the API procedures in lego.adb with actual code.
7.0
Acknowledgements
This work was funded by a grant from the Institute for Information Technology Applications, whose support is gratefully acknowledged. Readers with questions about Ada/Mindstorms 1.0 are invited to visit the Ada/Mindstorms web site, review other work on this topic [Fa00], or to contact the author.
8.0
References
[Ba99]
Baum, D., The NQC web site (http:// www.enteract.com/~dbaum/nqc/)
[Ca00]
Carlisle, M., An Automatic Object-Oriented Parser Generator for Ada, Ada Letters, Vol 20 No 2, June 2000, pp 57-63.
[Fa00]
Fagin, B., Using Ada-Based Robotics to Teach Computer Science, Proceedings
of the 5th Annual Conference on Innovation and Technology in
Computer Science Education, Helsinki, Finland, July 2000, pp 148-151.
-----------------------------------------------------------------------
--
-- File:
Lego.ads
-- Description: Package to implement LEGO commands
--
--
--
-----------------------------------------------------------------------
package Lego is
type Ada_Mindstorms_Procedure is (
Set_Sensor,
Set_Sensor_Mode,
Set_Sensor_Type,
Clear_Sensor,
Get_Sensor_Value,
Output_On,
Output_Off,
Output_Float,
Output_Forward,
Output_Reverse,
Output_Toggle,
Output_Power,
Wait,
Play_Sound,
Play_Tone,
Stop_All_Tasks,
Send_Message,
Clear_Message,
Config_Sensor,
Output_On_Forward,
Output_On_Reverse,
Output_On_For,
Set_Output_Mode,
Set_Output_Direction
);
-- type used to specify a sensor port
(1..3) on the RCX.
type Sensor_Port is (Sensor_1, Sensor_2, Sensor_3);
-- type used to specify an RCX output port (A
B or C).
type Output_Port is (Output_A, Output_B, Output_C);
-- type used to specify how the sensor input
is handled.
type Configuration is
(Config_Touch,
Config_Light,
Config_Pulse,
Config_Rotation,
Config_Celsius,
Config_Fahrenheit,
Config_Edge);
-- type used to specify an RCX output mode
type Output_Mode is
(Output_Mode_On,
Output_Mode_Off,
Output_Mode_Float);
-- type used to specify an RCX output
direction
type Output_Direction is
(Output_Direction_Forward,
Output_Direction_Reverse,
Output_Direction_Toggle);
-- type used to specify the actual sensor
type (hardware).
type Sensor_Type is
(Type_Touch,
Type_Temperature,
Type_Light,
Type_Rotation);
-- type used to specify the power level of an
output.
type Power_Type is range 0..7;
-- type used to specify the type of sound
desired.
type Sound is
(Click,
Double_Beep,
Down,
Up,
Low_Beep,
Fast_Up);
-- type used to specify a message number.
type Message is range 1 .. 255;
-- type used to specify a duration in 10ths
of a second.
type Duration is new Positive;
-- type used to specify a frequency.
type Frequency is new Positive;
-- type used to hold the value of the sensor.
type Sensor_Value is new Integer;
--type for sensor modes
type Sensor_Mode is (
Mode_Raw,
Mode_Bool,
Mode_Edge,
Mode_Pulse,
Mode_Percent,
Mode_Celsius,
Mode_Fahrenheit,
Mode_Rotation);
--power level definitions, supplied as a
convenience
Power_Low : constant Power_Type := 1;
Power_Half : constant Power_Type := 4;
Power_High : constant Power_Type := 7;
--------------------
-- Procedures --
--------------------
--PROCEDURES FOR
CONTROLLING AND READING THE INPUT PORTS
-- Name : ClearSensor
-- Description :
-- This procedure takes in a sensor port
and clears the
-- current value of that sensor to
'0'. This only needs to
-- performed on sensors configured as
Rotation, Edge, or Pulse,
-- since they are the only sensors that
maintain a value.
-- Example: Clear_Sensor(Sensor =>
Sensor_1);
procedure Clear_Sensor (
Sensor : in Sensor_Port );
-- Name : Config_Sensor
-- Description :
-- This procedure takes in a sensor type
and a config type.
-- It tells the RCX how to configure the
given input. It
-- differs from setsensortype because
setsensortype deals
-- with the attached hardware and not the
output. For
-- example, a touch sensor (hardware) can
be used as a touch
-- sensor giving a value of 0 or 1, or as a
pulse sensor
-- that keeps a count of presses, or as an
edge sensor that
-- keeps a count of state transitions. Depending on how you
-- want to use the sensor, is how you
configure it.
-- Example: Config_Sensor(Sensor => Sensor_2,
-- Config => Touch);
procedure Config_Sensor (
Sensor : in Sensor_Port;
Config : in Configuration );
-- Name : Get_Sensor_Value
-- Description :
-- Takes in a sensor port and returns the
current value.
-- Example: if (Get_Sensor_Value(Sensor => Sensor_1) = 0) then ...
function Get_Sensor_Value (Sensor : in Sensor_Port )
return sensor_value;
-- Name : Set_Sensor_Mode
-- Description:
-- This procedure takes in a sensor port
and a mode, setting
-- the sensor appropriately. Normally not used, Config_Sensor is better.
-- Example: Set_Sensor_Mode(Sensor => Sensor_1, Mode => Pulse);
procedure Set_Sensor_Mode (
Sensor : in Sensor_Port;
Mode : in sensor_mode );
-- Name : Set_Sensor_Type
-- Description:
-- This procedure sets the type of a
sensor. Normally not used,
Config_Sensor is better.
-- Example: Set_Sensor_Type(Sensor => Sensor_1, Kind => Type_Touch);
procedure Set_Sensor_Type(
Sensor : in Sensor_Port;
Kind : in Sensor_Type );
-- PROCEDURES FOR CONTROLLING THE OUTPUT
PORTS
-- Name : Output_float
-- Description :
-- This procedure takes in an output port
(the ports most
-- commonly used to run motors) and turns
it off, but in a
-- way that allows it to glide to a stop.
-- Example: Output_Float(Output => Output_1);
procedure Output_Float (Output : in Output_Port );
-- Name : Output_Forward
-- Description :
-- This procedure takes in an output port
and sets it in the
-- forward direction. The port must be on for this to work.
-- Example: Output_Forward(Output =>
Output_1);
procedure Output_Forward (Output : in Output_Port );
-- Name : Output_Off
-- Description :
-- This procedure takes in an output port
(the ports most
-- commonly used to run motors) and turns
it off.
-- Example: Output_Off(Output => Output_1);
procedure Output_Off (Output : in Output_Port );
-- Name : Output_On
-- Description :
-- This procedure takes in an output port
(the ports most
-- commonly used to run motors) and turns
it on.
-- Example: Output_On(Output => Output_1);
procedure Output_On (Output : in Output_Port );
-- Name : Output_on_for
-- Description :
-- This procedure takes in an output port
and a time in hundredths
-- of a second. It turns the output port on for the specified
-- time duration.
-- Example: Output_On_For(Output => Output_1,
-- Hundredths_Of_A_Second => 200);
procedure Output_On_For (
Output : in Output_Port;
Hundredths_Of_A_Second : in Natural );
-- Name : Output_on_forward
-- Description :
-- This procedure takes in an output
port. It simply turns
-- on the port AND sets it forward.
-- Example: Output_On_Forward(Output => Output_1);
procedure Output_On_Forward (
Output : in Output_Port );
-- Name : Output_on_reverse
-- Description :
-- This procedure takes in an output
port. It simply turns
-- on the port AND sets it to reverse.
-- Example: Output_On_Reverse(Output => Output_1)
procedure Output_On_Reverse (
Output : in Output_Port );
-- Name : Ouput_power
-- Description :
-- This procedure takes in an output port
and a power value.
-- It applies this power to the
output. The minimum power
-- is zero (Power_Low) and the max is seven
(Power_High).
-- Example: Output_Power(Output => Output_1, Power => Power_Low);
-- Output_Power(Output => Output_1, Power => 3);
procedure Output_Power (
Output : in Output_Port;
Power : in Power_Type );
-- Name : Output_Reverse
-- Description :
-- This procedure takes in an output port
and sets it in the
-- reverse direction. The port must be on for this to work.
-- Example: Output_Reverse(Output => Output_1);
procedure Output_Reverse (Output : in Output_Port );
-- Name : Output_toggle
-- Description :
-- This procedure takes in an output port
and flips the
-- direction of the output. The port must be on for this to
--
work.
-- Example: Output_Toggle(Output => Output_1);
procedure Output_Toggle (Output : in Output_Port );
-- Name : Set_Output_Direction
-- Description:
-- Sets the direction of a given output.
-- Example: Set_Output_Direction(Output => Output_1,
-- Direction => Output_Direction_Forward);
procedure Set_Output_Direction(
Output : in Output_Port;
Direction : in Output_Direction);
-- Name : Set_Output_Mode
-- Description:
-- Turns a given output on or off.
-- Example: Set_Output_Mode(Output => Output_1, Mode =>
Output_Mode_On);
procedure Set_Output_Mode(
Output : in Output_Port;
Mode : in Output_Mode);
--OTHER PROCEDURES
-- Name : Clear_Message
-- Description :
-- This procedure clears the IR message
buffer. For example,
-- if an RCX receives a message from
another RCX, this
-- message is retained until cleared.
-- Example: Clear_Message;
procedure Clear_Message;
-- Name : Play_Sound
-- Description : This procedure takes in a sound type and plays it.
-- Example: Play_Sound(Sound_To_Play => Double_Beep);
procedure Play_Sound (
Sound_To_Play : in sound );
-- Name : Play_Tone
-- Description :
-- This procedure takes in a frequency and
a duration and
-- plays the frequency for that
duration. The frequency
-- can be any in the range of the human
ear, and the duration
-- is given in 10ths of a second.
-- Example: Play_Tone(Frequency_In_Hertz => 440,
-- Tenths_Of_A_Second => 20);
procedure Play_Tone (
Frequency_In_Hertz : in frequency;
Tenths_Of_A_Second : in Natural );
-- Name : Send_Message
-- Description :
-- This procedure takes in a message
type. RCX will send the
-- message (a number from 1 to 255) through
its IR port. This is
-- commonly used for communicating between
RCX's.
--
Example: Send_Message(Message =>
124);
procedure Send_Message (
Message_To_Send : in Message );
-- Name : Stop_All_Tasks
-- Description : This procedure will stop everything running on the RCX.
-- Example: Stop_All_Tasks;
procedure Stop_All_Tasks;
-- Name : Wait
-- Description :
-- This procedure takes in a time duration
and waits for that
-- amount of time before moving on to the
next command. The
-- duration is in 100ths of a second.
-- Example: Wait(Hundredths_Of_A_Second => 50);
procedure Wait (Hundredths_Of_A_Second : in Natural );
end Lego;