Mechanical control of the RCX.

In this section, I will describe the functions used to control the basic mechanical functions of the RCX. I have attempted to list the files in which the relevant functions can be found for all the functions I have described, as well as links to the relevant pages in the API documentation at the LegOS website. If the API docs are lacking info you need, dig into the LegOS code itself. It is not very complicated stuff (for the most part) and someone with reasonable C skill should be able to figure out what is going on (not to mention figure out the tons of features I've left undocumented!)

Demo Code

In order to show you code that uses features throughout the rest of the text, I've added the Section called Sample Code to the text. It has two parts: the first part, the Section called Code Fragments, which is linked to from multiple places in the main body of the HOWTO, is a set of code fragments that demonstrate functions relevant to the sections they are linked from. The second part, the Section called Demo.c is a single program, demo.c, which can be compiled and run. Basically, it allows you to actually see the various code fragments in action. I know this isn't the prettiest way to organize this, but it works, which is a start.

Motor control.

The motors on the RCX are controlled through functions in include/dmotor.h. For more details, try: kernel/dmotor.c

I have found it very useful in my programs to define a MIN_ENV_SPEED, in which I store the speed at which the motor gains traction on whatever surface I am working on. Since this is so dependent on flooring and the wheels in use, I often have to change it, which is why I define it at the beginning. I use a simple program to test the speed at which the bot gets traction, and then program the value I obtain on a particular flooring surface into my other programs. Alternately, with a rotation sensor, it should be possible to measure exactly when the robot starts moving and use that value dynamically in a program.

The code in the Section called Motor Demo Code is pretty self-explanatory. It will slowly increment the motor speed, and then decrement one wheel, so that the robot spins in place. It will then put the engines into "brake" and "stop" so that you can attempt to move them with your fingers, and note the difference between the two.

If you've compiled the code in the Section called Demo.c, one press of the RUN button should run the motor demo. If the motor behavior isn't as described in the previous paragraph, you may want to check two things. First, motor direction depends on the placement of the motor leads on the RCX. This means that you can reverse motor direction by removing the leads and rotating them. So, if your motors are running "backwards," or in opposite directions, you might try switching the wiring around. Second, if one motor is not functioning properly, you may want to check which outputs the motors are connected to. Remember, demo.c expects motors attached to output ports A and C. Attaching motors and sensors to the wrong ports is a common mistake, which can be hard to notice if you have buried the leads under a substantial number of Lego bricks.

Light Sensors

The light sensor on the RCX is controlled by kernel/dsensor.c, andinclude/dsensor.h.

The light sensors are used to read the difference between light and dark areas. For our purposes, this can be used to detect dark objects against a light background, or follow a dark line on a bright floor. There is sufficient detail in the reading to allow your robot to choose between different colors, but be aware that you may need to strictly control light conditions and experiment with placement of the sensor to get consistent and usable values.

Remember: if you are having inexplicable problems with the light sensors (i.e., as far as you can tell not in your code) then remember to check and make sure that they are plugged in where your code expects them to be.

Setting the Light Sensor Modes

The light sensors have two modes, active and passive. To change modes in the code, use these function calls:

What are these modes? Well, as you can see by looking at the front of the light-sensor brick, there are two components in the brick. The first is the actual light detector, and the second is a small light source. The idea is that the light is turned on if you want to find something reasonably close which will have a big difference in reflectivity from the surrounding. This will amplify the difference between the light and dark (much like shining a flashlight on something.) If you want to judge the environment (say, finding a white spot on the wall a distance away) the built in light may interfere, so we turn it off. When the light is on, the sensor is in "active" mode, and when the light is off, it is in "passive" mode. An easy way to check which mode you are in (besides looking at the code) is to see if a small red spot appears in front of the light-sensor.

In active mode, the light sensor currently returns values between roughly 50 and 300. However, this may be fixed soon, so that light scales more linearly between 0 and 100. In passive mode, the sensor itself is unpowered, and so different values (between 220 and 280, roughly speaking) will occur. They are also not as stable as when the sensor is in active mode. It is quite possible that your range will be different than this for any number of reasons: as I write, my robot seems convinced that the world never gets darker than about 60 and goes up to 300. This is probably because of my batteries running low, and is not the normal situation.

In active mode, when the sensor is particularly close to an object, LIGHT_X becomes less a measure of darkness/brightness than it does of reflectivity. Even my jeans record a value of roughly 150 when the sensor is pressed against them. Given sufficient separation (roughly 4-5 inches in my brief experimentation) the reflected light should give a reasonably consistent value.

Be aware that in either mode, the response of the light sensor is not strictly linear (i.e. the graph of actual light intensity vs. the value read from the sensor.) Michael Gasperi has a graph of light response and discussion of the light sensor internals here.

The light sensor demo fragment is in the Section called Light Sensor Demo Code. Basically, the code should make the LCD read either "dark" or "light," depending on where it is pointed. Despite the fact that I implemented the example in this fairly simplistic manner, please note that it can (with experimentation and appropriate values placed in the code) be fairly nuanced, reading far more values than the simple binary that I have demonstrated here.

In the Section called Demo.c, two presses of RUN will run the light sensor demo. Just like in the motor example, if the light is not attached to the correct port (input port 2 in this case) the behavior will be unusual.

Touch Sensors

Like the light-sensors, control functions for the touch sensors are in kernel/dsensor.c, and include/dsensor.h.

To access the touch sensors, legOS 0.2.x has TOUCH_X, which should return either a 1 (pressed) or a 0 (not pressed). Just use it as a variable, and it'll contain the proper value.

Be aware that the sensors don't spring back very well after having been touched. This allows them to be more sensitive, but also means that they tend to get stuck and remain pressed after the initial pressure is removed. Therefore, if you attach a Lego mechanism to the sensor, make sure that it is weighted such that it will spring back on its own if your design requires the sensor to be pressed and then released.

The touch sensor demo code can be found in the Section called Touch Sensor Demo Code. This demo should run the motors forward until the touch sensor is pressed. At that event, it will back up the motors briefly and then go forward again. A slightly more sophisticated version of this (for example, if the robot turned while backing up) could easily be used to implement a robot that runs around until hitting an object, and then backs up and avoids the object in the next pass.

In the Section called Demo.c, three presses of RUN will run the touch sensor demo. It assumes that the touch sensor is connected to input port 1.

Angle or Rotation Sensors

LegOS includes the capability for the measurement of rotations using the Lego Group's angle sensors. These sensors are designed to indicate when an axle passing through the sensor rotates 1/16 of a rotation. In order to use the rotation sensors in LegOS, three functions must be used:

To access the rotation sensors, use ROTATION_X, where X is 1, 2, or 3. The value returned should change every 1/16 of a turn. This value should start at the position set by ds_rotation_set, and then increment when turned in one direction and decrement when turned in the other.

There is currently no demo code to show the use of the rotation sensors. Perhaps in my next life :)

The LCD

The LCD on the front of the RCX is controlled by functions in include/dlcd.h, include/rom/lcd.h,kernel/lcd.c include/conio.h, and kernel/conio.c. It is surprisingly versatile, allowing the display either of individual LCD sections or pre-defined characters.

Writing to the LCD is pretty straightforward: just call one of the functions listed below. In legOS 0.1.x, a call to refresh() was required, but that in legOS 0.2.x this is done automatically by the OS every 100 ms.

There are examples of LCD usage in each of the code fragments in the Section called Sample Code. Note that basically all of them have sleep() or msleep() calls after them, in order to prevent overwriting. Some of these aren't obvious, though- they may come after an if/else statement, for example.

Buttons.

In 0.2.x, buttons are accessed through dbutton.h and dkey.h. Unfortunately, unlike legOS 0.1.x, button access is no longer fully arbitrary. On/Off and Program are fixed so that On/Off always turns the robot off and Program always stops the running program. This is nice in some ways and not so nice in others. Because of that linkage, only the view and run buttons are now accessible all the time.

PRESSED(button_state(), NAME) or RELEASED(button_state(),NAME) allow access to the remaining buttons. Each function returns true if the button that has been named is in the appropriate state (pressed or released) and false otherwise. NAME must be one of the following: BUTTON_RUN or BUTTON_VIEW. This function is not debounced: i.e., if you don't put msleep() statements into your code, and have several PRESSED() statements in a row, they may all be triggered by a single press! Under certain circumstances (usually while loops that repeatedly look for button input) this can actually cause the program to freeze. Alternately, you can just tell your loop to wait until the button is released before accepting any other input or attempting to run any other code. task_swapper() in the Section called Button Demo Code makes use of this method.

There is also another way to get get button presses. The function getchar(), when called, will wait until a button is pressed, and then immediately return a value signaling the first button that has been pressed. If VIEW is pressed, it will return 4, and if RUN is pressed, it will return 8. To use this, #include dkey.h.

The IR unit

As of 0.2.x, legOS now uses the LegOS Network Protocol (LNP) to communicate with the PC. Under Linux, the LNP Daemon can be used to communicate with the RCX from the PC, and under Windows, WinLNP allows for the use of the various Windows programming languages to communicate with a legOS robot. Unfortunately, neither of these are completely well documented as of yet. However, if you want to find the most up-to-date information, you should be able to check out the sourceforge pages for each tool: http://legOS.sourceforge.net/files/linux/LNPD/ and http://legOS.sourceforge.net/files/windows/winLNP/ Neither of these are the "official" pages, but they should be kept up to date and point you to the right place.

Sound

LegOS now has very thorough support for sound. For details, check out include/dsound.h. There is also an example in the demo directory, demo/robots.c, which uses this driver.

The basic structure of the driver is pretty straightforward. Music is defined as a series of note_t structs. Each note_t contains a pitch and a duration. Once you've defined an array of note_t's that you want to play, pass it to dsound_play() and the robot will play the music. dsound.h contains a list of #defines that are useful as definitions of pitches and durations, as well as a few functions (beyond dsound_play()) which may be helpful but are not necessary for the playing of sounds.