Lego MindStorms RCX

Rediscovering a 20-year-old robot

Every holiday we ask our daughter what she would like to do. We create a list of things, create colorful Post-it Notes, add some hand-drawn icons for each item, and put it on her own little Kanban-isch board. Now that she is 5 years old she is becoming more interested in electronics, so on the list appeared a "Make a Robot🤖".

Together we decided to build a robot using the original Lego MindStorms set. We did it using macOS and without running any virtual machines (no Windows 98) or other bloated software. We succeeded by using a programming language called NQC and a USB infrared tower.

Lego MindStorms - beta testing

Rewind to 1997. I was surfing on and found that Lego was looking for beta testers for their new robots platform. Unfortunately, I was not a U.S. resident. However, I was able to convince Lego that they should let me participate. It might have helped that I was the Dutch national Lego building champion. They agreed! I signed an NDA and a couple of weeks later I received the beta set for testing. Later this became known to the public as Lego MindStorms. Good times!

Yes, for this Robot project I could have just bought the new Lego MindStorms Robot Inventor set that was released recently. Though why buy something new if you can dust of the old toys? It surely brings back fun memories.

Lego MindStorms RCX 2.0 - Fire truck

Lego MindStorms Robotics Invention System 2.0

When Lego released the MindStorms Robotics Invention System 2.0 (Set 3804) in 2001 I convinced my mother that I should spend my pocket money on this Lego set. The set includes a yellow RCX 2.0, which can be used to control 3 motors and attach 3 sensors. My set came with an infrared transmitter tower that uses USB.

Older versions used a serial port. I had success using the Keyspan USB Serial Adapter to use the old Serial infrared tower. Though for simplicity I used the native USB infrared tower.

Windows 98, seriously?!

Since the original software didn't receive any major updates it only runs on Microsoft Windows 98. After some reading, I found that most people installed a virtual machine running Windows 98 (or Windows XP). Digging a bit deeper I found that while the original driver only supported 32-bit, there is a 64-bit driver (Windows 7 / macOS). Though that does require the use of LabVIEW. All great, but I'm not planning to install, maintain, and secure Windows virtual machines or install and learn LabVIEW just to make a little robot go run around in circles.

macOS - Catalina

There is another way, which requires you to be comfortable with the command line, but it works great! Adrien Glitchbone did a good job of collecting a set of tools to make it easy to send firmware from the command line on macOS to the RCX. Paulo Marques even include the commands for the USB Infrared Tower in the read me (pull request).

Awesome, so this is what I was looking for and it saved me hours of fiddling around:

Instead of using the original Lego software, this approach is based on NQC. NCQ is a simple programming language for the RCX (more on that later). There is also BrickOS and LeJOS, though for now, I'd like to keep it simple and stupid, so NQC is perfect for the job.

Faster firmware

Dick Swan from Robomatter wrote custom firmware that claims to be compatible with NQC and runs 50 to 100 times faster than the original firmware from Lego.

fast0612.lgo: 50 to 100 times faster execution speed than standard LEGO firmware. Float and long variable support. Full-featured debugger support: breakpoints, suspend/resume, single-step execution, a full set of fast updated “watch” windows, etc. Expanded infrared messaging support. Should be backward compatible and work with the latest version of NQC.

After downloading the fast0612.lgo firmware we are ready to connect the USB Infrared tower, put 6 freshly charged AA batteries in the RCX, turn it on and start updating the new faster firmware.

Ok, relatively new, considering it was published in 2013.

nqc -Susb -firmware firmwares/fast0612.lgo

Sending firmware [25296 bytes]:

NQC - Not Quite C

NQC is a simple C-like syntax to program the RCX. It is really straight forward to write some simple programs. Excellent fit for today's use case: making a simple robot.

Let's start with a program called sensor.nqc to check if the touch sensor is still working:

// sensor.nqc
task main(){

Now turn your RCX on, upload it sensor.nqc to the RCX in the program slot 1, attach the touch sensor to sensor connector 1 and push the green 'run' button.

nqc -Susb -TRCX2 -d -pgm 1 sensor.nqc

Sending program [16 bytes]:

If you push the sensor you should see the 0 on the display turn into a 1. Great success!

Robot, with rubber tracks

We decided to build a robot with big rubber tracks, later it became a fire truck and we added a blue/red light bar and sirene sounds. The robot drives forward until it hits an object and then reverses a bit and makes a left turn into a (random) other direction. We had some issues with the robot driving full speed and bumping into things. We found it useful to make it accelerate slowly to reach its top speed.

// robot.nqc
// OUT_A = left motor
// OUT_B = light
// OUT_C = right motor
// SENSOR_1 = touch sensor

int random_time;
int tone;

task main(){

    start check_sensors;
    start move_forward;

void accelerate(int speed){
    tone = (speed * 100) + 1000;
    PlayTone(tone,  30);
    SetPower(OUT_A+OUT_C, speed);

task move_forward(){

    // accelerate slowly
    accelerate(1); Wait(30);
    accelerate(2); Wait(30);
    accelerate(3); Wait(30);
    accelerate(4); Wait(30);
    accelerate(5); Wait(30);
    accelerate(6); Wait(30);
    accelerate(7); Wait(30);

    start sirines;

    while (true){

task move_backward(){
    while (true){
        SetPower(OUT_A+OUT_C, 7);
        PlayTone(1200, 50); // truck reverse sound

task move_left(){
    while (true){
        SetPower(OUT_A+OUT_C, 7);
        OnFwd(OUT_A); // for a in-place turn we run the motors in opposite directions

task sirines(){
    while (true){
        PlayTone(800,  50); Wait(50);
        PlayTone(2000, 50); Wait(50);
        PlayTone(1200, 50); Wait(50);
        PlayTone(2000, 50); Wait(50);

task check_sensors(){
    while (true){
        if (SENSOR_1 == 1){
            // stop moving forward
            Off(OUT_B); // turn off the lights
            stop move_forward;
            stop sirines;

            // reverse
            start move_backward;
            random_time = Random(100) + 50;
            stop move_backward;

            // turn left
            random_time = Random(200) + 50;
            start move_left;
            stop move_left;

            // strart moving forward
            start move_forward;
            On(OUT_B); // turn the lights on

Now you can upload it and start having fun!

nqc -Susb -TRCX2 -d -pgm 1 examples/robot.nqc
Sending program [374 bytes]:

No recursion?

While programming the accelerate function I found that NQC does not support recursion). Therefore the above accelerate function in robot.nqc doesn't use recursion, which makes it a bit less clean.

nqc -Susb -TRCX2 -d -pgm 1 robot.nqc
# Error: recursive function call: 'accelerate'
File "robot.nqc" ; line 23
#         accelerate(new_speed);
#         ^^^^^^^^^^
# 1 error during compilation

If fixed that by implementing recursion is the NQC language, making my code a bit cleaner. Tough that is a bit out of scope for this article :-)

NQC further reading

The following two resources were really useful it getting started with NQC:

NQC Tutorial

NQC API documentation