Low-Level RCX Reference

Note: This reference is an incomplete draft.

Perhaps if somebody bugs me to finish this document, I will. Most of the info is documented already, just not in one place. See also: ROM Image, Librcx. In particular, rom.h from Librcx contains many details currently missing below.

Introduction [top]

The RCX is programmable, microcontroller-based brick which contains a Hitachi H8 microcontroller with 16K of on-board ROM. The ROM stores a set of low-level routines for controlling the RCX, and can be extended by downloading up to 28K of firmware from a PC sitting across an infrared link. Once loaded, the firmware is given complete control over the RCX, with the ROM operating in the background, providing low-level services to the firmware.

This document describes the low-level interfaces of the RCX. In particular, it describes ROM Version 3.1 and the hardware configuration that this version of the ROM manages. The intent of this document is to facilitate the creation of replacement firmware that accesses the RCX at its lowest levels.

The document is organized into the following sections:

Please note that this not an official LEGO® document or web site. LEGO® is a trademark of the LEGO Group, which does not sponsor, authorize, or endorse this document.

In addition, note that this document describes RCX internals as the author understands them. While every effort has been made to ensure that the contents of this document are accurate, the author cannot guarantee that any part of this document is correct. The author cannot be held responsible for any consequences of the use or misuse of the information contained in this document.

Also note that the interfaces described in this document are not (as far as I know) supported by the LEGO Group, and are therefore subject to change.

This low-level reference is part of RCX Internals.

Copyright © 1998, 1999 Kekoa Proudfoot. All rights reserved.

Overview [top]

Firmware Basics [top]

The standard RCX firmware makes a good starting point for understanding the operation of the RCX and the routines made available to replacement firmware. This section describes the basic operation of the standard firmware and the some of interactions between the standard firmware and the routines in ROM.

When the RCX is first powered up, the ROM enters an event loop whose primary purpose is to receive firmware. A minimal set of opcodes (alive, get versions, delete firmware, start firmware download, transfer data, and unlock firmware) is available for this purpose. Once the firmware has been transferred to the RCX and unlocked, the ROM executes it.

[Also: Start firmware download clears RAM from 0x8000-0xcc00. Unlock firmware requires "Do you byte, when I knock?" string to be located in firmware image starting before cc00; anything after cc00 is ignored, so the string does not have to be complete (!). Firmware execution begins at the address specified by start firmware download.]

Once started, the firmware calls a number of initialization routines, which in turn call initialization routines in ROM.

The first ROM routine called is init_timer, which sets up an interrupt handler that runs once every millisecond. This routine also stores pointers to two data structures used by the ROM for asynchronous communication with the firmware. The first data structure is a dispatch array whose elements are associated with different aspects of the system (sensors, motors, buttons, power, interpreter). The ROM sets flags in the array, and the firmware responds by clearing the flags and running the appropriate handler functions. The second data structure contains a set of counters that serve a number of purposes; the ROM periodically increments and decrements these counters. [Details are in the description for init_timer.]

[Also: Init_timer additionally initializes A/D conversion, the speaker, and the output of motor waveforms. Also, it clears the shadow registers in ROM, which is very important: if you don't call init_timer, you must make sure to clear and later maintain these registers; if you do call init_timer, you must call it before calling any other initialization functions.]

The remaining ROM initialization routines set up other aspects of the system. In the order called, they are: init_sensors, which sets up the sensor pins; init_motors, which does nothing; init_buttons, which sets up the button inputs and the display pins; init_power, which sets up the on/off button; init_serial, which sets up the serial communications interface; and init_port_6_bit_3, whose function is unknown but seemingly irrelevant.

Once initialized, the firmware starts a small state machine. The primary function of this state machine is to poll the dispatch array and execute the associated handler functions. The firmware effectively executes the first handler that can execute, running the byte code interpreter if nothing else is ready to run.

The dispatch handlers generally make calls into the ROM to control the low-level functions of the RCX, which encompass all of the brick's inputs and outputs, namely the sensors, motors, buttons, speaker, display, and serial port. In addition, they maintain any related firmware state that might be required.

The two other significant functions of the state machine are managing a power down mode and executing a system reset. The power down mode is triggered either by a power down timer or by a press of the on/off button. When this state is entered, the firmware calls a number of shutdown routines that turn off all peripherals, ending with a function that puts the microcontroller to sleep. The processor is awoken by an interrupt caused by a second press of the on/off button. The firmware reinitializes the machine by calling all of the initialization functions, in the same order as before. All the contents of memory are preserved when the procesor is put to sleep, so both the firmware and all of its data remain intact during this process.

A system reset occurs when the firmware receives a delete firmware opcode. To reset the system, the firmware first shuts down all peripherals as with power down mode. It then sets some flags in a ROM data area and calls a ROM reset vector, effectively deleting the firmware and restarting the ROM in its power up state.

[Also: (Later?) Sample firmware source code.]

ROM Entry Points [top]

Init sensors [top]

Entry point 1498.

void init_sensors (void);

Initializes the sensor output pins, which are used to supply power to the active (light/rotation) sensors. In order to initialize the sensor input pins, you must call init_timer.

Read sensor [top]

Entry point 14c0.

typedef struct {
         char type;
         char mode;
         short raw;
         short value;
         char boolean;
} sensor_t;

void read_sensor (short code, sensor_t *sensor);

short code Function opcode.
sensor_t *sensor Pointer to sensor struct.

Reads the raw value of the sensor specified by code and converts the raw value into a meaningful set of values according to the mode of the sensor, updating the raw, value, and boolean struct members. The raw struct member is set to a number in 0..1023, while value is set according to the sensor mode, as described below. The boolean value is set to either 0 or 1, with the value also dependent on the sensor mode in a manner described below.

Meaningful code values specify which sensor to read:

0x1000Sensor 1
0x1001Sensor 2
0x1002Sensor 3

All other opcodes are ignored.

The sensor mode consists of two portions. Bits 0-4 contain a slope value in 0..31, while bits 5-7 contain the mode, 0..7. The eight modes, which control how value is set, are:

0RawValue in 0..1023.
1BooleanEither 0 or 1.
2Edge countNumber of boolean transitions.
3Pulse countNumber of boolean transitions divided by two.
4PercentageRaw value scaled to 0..100.
5Temperature in °C1/10ths of a degree, -19.8..69.5.
6Temperature in °F1/10ths of a degree, -3.6..157.1.
7Angle1/16ths of a rotation, represented as a signed short.
The slope value controls 0/1 detection for the three boolean modes as well as the boolean struct member. A slope of 0 causes raw sensor values greater than 562 to cause a transition to 0 and raw sensor values less than 460 to cause a transition to 1. The hysteresis prevents bouncing between 0 and 1 near the transition point. A slope value in 1..31 causes a transition to 0 or to 1 whenever the difference between consecutive raw sensor values exceeds the slope. Increases larger than the slope result in 0 transitions, while decreases larger than the slope result in 1 transitions. Note the inversions: high raw values correspond to a boolean 0, while low raw values correspond to a boolean 1.

Because computing sensor values sometimes requires the use of a sensor's previous values, it is necessary to set value and boolean to the values returned by the most recent previous call to this function for the given sensor.

Set sensor active [top]

Entry point 1946.

void set_sensor_active (short code);

short code Function opcode.

Activates the output for sensor specifed by code, with the effect of supplying power to the sensor. Valid values for code are:

0x1000Sensor 1
0x1001Sensor 2
0x1002Sensor 3

All other values are ignored.

In order to use the light and rotation sensors, you must first call this function.

Set sensor passive [top]

Entry point 19c4.

void set_sensor_passive (short code);

short code Function opcode.

Deactivates the output for sensor specifed by code, with the effect of shutting off power to the sensor. Valid values for code are:

0x1000Sensor 1
0x1001Sensor 2
0x1002Sensor 3

All other values are ignored.

You should call this function whenever you are finished using the light or rotation sensors.

Shutdown sensors [top]

Entry point 1a22.

void shutdown_sensors (void);

Shuts down the sensor output pins, turning off power to the sensors.

Init motors [top]

Entry point 1a4a.

void init_motors (void);

Does nothing.

Initialize the motors by calling init_timer.

Control motor [top]

Entry point 1a4e.

void control_motor (short code, char mode, char power);

short code Function opcode.
char mode Motor mode. 1..4.
char power Motor power. 0..7.

Sets the mode and power of the motor specified by code. The mode controls the output sent to the motor controller, while the power sets the duty cycle of the output waveform.

Meaningful code values are:

0x2000Motor A
0x2001Motor B
0x2002Motor C

All other values are ignored.

There are four valid motor modes:


Invalid modes are ignored.

Power settings correspond to 1/8ths of a duty cycle, with a power setting of 0 corresponding to a 12.5% duty cycle and a power setting of 7 corresponding to a 100% duty cycle. Set the motor mode to float to get a 0% duty cycle. Power settings are taken modulo 8.

Shutdown motors [top]

Entry point 1ab4.

void shutdown_motors (void);

Does nothing.

Shut down the motors by calling shutdown_timer. Note that shutdown_timer shuts down more than just the motors.

Init buttons [top]

Entry point 1aba.

void init_buttons (void);

Initializes the Run, View, and Prgm buttons, and initializes the display.

Specifically, this routine sets the Run, View, and Prgm button pins to input, installs a Run button interrupt handler that does nothing, sets the LCD interface pins to output a high value, and clears some internal state relating to which LCD segments are set.

It is not strictly necessary to call this routine to initialize the display, since refreshing the display performs the proper initialization.

Play view button sound [top]

Entry point 1b32.

void play_view_button_sound (short code);

short code Function opcode.

Calls another ROM function to play system sound 1 (beep beep) unqueued. The value of code must be 0x301e, otherwise the call will be ignored.

This function is somewhat useless. It is recommended that play_system_sound be used instead.

Set lcd segment [top]

Entry point 1b62.

void set_lcd_segment (short code);

short code Function opcode.

Sets internal state to activate the LCD segments specified by code. In order to actually send the changes to the display, you must also call refresh_display.

Meaningful code values are:

0x3006Standing figure
0x3007Walking figure
0x3008Sensor 1 view selected
0x3009Sensor 1 pressed
0x300aSensor 2 view selected
0x300bSensor 2 pressed
0x300cSensor 3 view selected
0x300dSensor 3 pressed
0x300eMotor A view selected
0x300fMotor A backward arrow
0x3010Motor A forward arrow
0x3011Motor B view selected
0x3012Motor B backward arrow
0x3013Motor B forward arrow
0x3014Motor C view selected
0x3015Motor C backward arrow
0x3016Motor C forward arrow
0x3018Datalog indicator (*)
0x3019Download in progress (*)
0x301aUpload in progress (*)
0x301bBattery low
0x301cShort range indicator
0x301dLong range indicator
0x3020All segments

All other values are ignored.

Short, long range: partial segments, or complete ones?

Clear lcd segment [top]

Entry point 1e4a.

void clear_lcd_segment (short code);

short code Function opcode.

Read buttons [top]

Entry point 1fb6.

void read_buttons (short code, short *ptr);

short code Function opcode.
short *ptr Pointer to location to store button data.

Set lcd number [top]

Entry point 1ff2.

void set_lcd_number (short code, short value, short pointcode);

short code Function opcode.
short value Value to display.
short pointcode Function opcode.

Clear display [top]

Entry point 27ac.

void clear_display (void);

Refresh display [top]

Entry point 27c8.

void refresh_display (void);

Shutdown buttons [top]

Entry point 27f4.

void shutdown_buttons (void);

Does not clear the display! You have to do that manually.

Init power [top]

Entry point 2964.

void init_power (void);

Play system sound [top]

Entry point 299a.

void play_system_sound (short code, short sound);

short code Function opcode.
short sound Sound index. 0..5.

Get power status [top]

Entry point 29f2.

void get_power_status (short code, short *ptr);

short code Function opcode.
short *ptr Pointer thingy.

Shutdown power [top]

Entry point 2a62.

void shutdown_power (void);

Init serial [top]

Entry point 30d0.

void init_serial (void *ptr0, void *ptr1, short code0, short code1);

void *ptr0 Pointer thingy.
void *ptr1 Pointer thingy.
short code0 Function opcode.
short code1 Function opcode.

Set range long [top]

Entry point 3250.

void set_range_long (short code);

short code Function opcode.

Set range short [top]

Entry point 3266.

void set_range_short (short code);

short code Function opcode.

Play sound or set data pointer [top]

Entry point 327c.

void play_sound_or_set_data_pointer (short code, short param0, short param1);

short code Function opcode.
short param0 Parameter thingy.
short param1 Parameter thingy.

Reset minute timer [top]

Entry point 339a.

void reset_minute_timer (short code);

short code Function opcode.

Receive data [top]

Entry point 33b0.

void receive_data (void *data, char maxlen, char *len);

void *data Data buffer to receive data into.
char maxlen Length of data buffer.
char *len Pointer to length of data copied into buffer.

Check for data [top]

Entry point 3426.

void check_for_data (char *valid, char **nextbyte);

char *valid Pointer to byte for return value.
char **nextbyte Pointer to pointer to next transfer byte.

argument and store the result in variable index. The absolute value of the largest negative number, -32768, is defined to be the largest positive number, 32767.

Send data [top]

Entry point 343e.

char send_data (short code, short param, void *data, short len);

short code Code thing.
byte opcode Opcode to use to send.
void *data Data to send.
short len Send data length.

Shutdown serial [top]

Entry point 3636.

void shutdown_serial (void);

Init port 6 bit 3 [top]

Entry point 3692.

void init_port_6_bit_3 (void);

Sets port 6 bit 3 to output, high. Nobody seems to know what port 6 bit 3 does.

Shutdown port 6 bit 3 [top]

Entry point 36aa.

void shutdown_port_6_bit_3 (void);

Sets port 6 bit 3 to input. Nobody seems to know what port 6 bit 3 does.

Init timer [top]

Entry point 3b9a.

typedef struct {
         char sensors;
         char motors;
         char buttons;
         char power;
         char interpreter;
         char unused;
} dispatch_t;

typedef struct {
         short receiver_timeout;
         short firmware_timers[4];
         short clock_minutes;
         short shutoff_minutes;
         short task_wakeup[10];
         short motor_wakeup[3];
} counters_t;

void init_timer (void *ptr0, void *ptr1);

void *ptr0 Rom data struct.
void *ptr1 Dispatch priority table.

Get sound playing flag [top]

Entry point 3ccc.

void get_sound_playing_flag (short code, char *ptr);

short code Function opcode.
char *ptr Pointer thingy.

Control output [top]

Entry point 3de0.

void control_output (short code, short param0, short param1, short param2);

short code Function opcode.
short param0 Parameter thingy.
short param1 Parameter thingy.
short param2 Parameter thingy.

Shutdown timer [top]

Entry point 3ed4.

void shutdown_timer (void);

Data Types [top]

Function Index [top]

There are two ways to organize the ROM functions: by what inputs/outputs they control or by when they might be called.

Input/output categories: sensors, motors, buttons, sounds, display, serial, math.

Time of use: ROM bootup, init, runtime, shutdown.

Perhaps easiest to organize by time of use, and sub-organize by class. Also, list ROM bootup functions as an appendix.

Rom functions by category:

Initialization routines:

Runtime routines:






One of the biggest turn-offs with the layout of the functions in ROM is that the entry points don't entirely make sense. (Or maybe, sense hasn't been made of them.)

Microcontroller Interface [top]

The ROM is built on top of a low-level interface within the H8. In order to understand the ROM, it is necessary to understand the interface that the ROM is built on top of. Also, if you want to completely replace portions of the functionality implemented in ROM, you need to understand the low-level interface. A good amount of this interface is described in the H8 manuals; this section describes the specifics for the RCX.

Interrupt vectors

All interrupt handlers in ROM dispatch to interrupt vectors in RAM. The RAM interrupt vectors are as follows:

  Address  Vector
  Address  Vector

All of these vectors can be overridden simply by writing a function pointer to the proper vector's address.

Several of the ROM routines use handlers. The handlers set up by the ROM routines are as follows:

Handler  Description
IRQ0Set up but not used. Does nothing.
IRQ1Used to wake up processor after going to sleep.
TEIUsed for IR communication.
TXIUsed for IR communication.
RXIUsed for IR communication.
ERIUsed for IR communication.
OCIAAsynchronous millisecond update.
A/DStops A/D conversion after sensors have been read.

I/O port connections

I/O ports are connected as follows:

Port  Bit   DescriptionNotes
1* Address bus
2* Address bus
3* Data bus
40 Transmitter range output0 if long, 1 if short.
41 On/off button input (IRQ1)0 if pressed.
42 Run button input (IRQ0)0 if pressed.
43 Bus read
44 Bus write
45 Bus address strobe
46 System clock
47 Bus waitProbably unused.
50 Transmit data (TxD)Configure as input.
51 Receive data (RxD)
52 On/off button output
60 Sensor 3 output1 if active.
61 Sensor 2 output1 if active.
62 Sensor 1 output1 if active.
63 Unknown
64 Speaker (TMO0)
65 LCD input/output
66 LCD input/output
67 Infrared carrier (TMO1)26µs square wave.
70 Sensor 3 input (AN0)
71 Sensor 2 input (AN1)
72 Sensor 1 input (AN2)
73 Battery voltage input (AN3)
74 Unused
75 Unused
76 View button input0 if pressed.
77 Prgm button input0 if pressed.

The ROM accesses these I/O ports as follows...

The control registers are described in the H8 documentation...

When changing some of these control registers, you must also change the corresponding shadow registers...

Shadow data direction registers

The data direction registers on the H8 are write-only. The ROM uses a set of variables to shadow the data direction registers; typically, bits in the shadow registers are read and modified, then new values are stored to both the shadow and real registers. The shadow registers provide a common area to store information about the contents of the data direction registers.

ROM functions that modify the data direction registers expect to find the correct data direction settings in the shadow registers. Therefore, when writing code that modifies the data direction registers, you should also modify the shadow registers.

The most notable shadow register is for port 6. Whenever the display is updated, the ROM uses the port 6 data direction shadow register to maintain the state of port 6 while communicating with the LCD controller. If the shadow register for port 6 does not reflect the actual state of the port, then the port state will get corrupted.

The shadow registers are as follows:

Name Description Shadow
P1DDR   Port 1 data direction register fd80 ffb0
P2DDR Port 2 data direction register fd81 ffb1
P3DDR Port 3 data direction register fd82 ffb4
P4DDR Port 4 data direction register fd83 ffb5
P5DDR Port 5 data direction register fd84 ffb8
P6DDR Port 6 data direction register fd85 ffb9

Motor control

The motors are memory mapped to addresses f000-fb7f. Any write to an address in this range affects the motors. The 0xc0 bits control motor A, the 0x0c bits control motor B, and the 0x03 bits control motor C. Within each pair of bits, 0x01=forward, 0x02=backward, 0x00=stop, 0x03=float.

[Untested] Clearing the RAME bit in the system control register (SYSCR) has no effect except to extend the range of addresses mapped to the motors.

See Also [top]

Documentation on the H8 microcontroller is available from Hitachi. Of particular interest is the H8/3297 Series Hardware Manual, which describes the microcontroller used in the RCX.

Generation of code for the H8 is made possible by two GNU utilities, the GNU binutils and GCC. Descriptions of how to obtain and install these tools are available at Mindstorms Internals.

The official LEGO® MindstormsTM web page is located at www.legomindstorms.com.

Known Issues and Missing Items [top]

There are several known issues and missing items as of Sat Dec 2 1999:

Copyright © 1998, 1999 Kekoa Proudfoot. All rights reserved. kekoa@graphics.stanford.edu