Lawrence Technological University
College of Arts and Science
Department of Mathematics and Computer Sciences

Handouts

Migrating from NQC to NXC

This is a Web page for coaches and their teams that have Lego NXT robots and have had some experience with Not Quite C for the Lego RCX robots. Not eXactly C is a similar language, perhaps a little closer to C, that will help you take full advantage of your new robots' strengths.

As I write, NXC is still in beta, Ver.1.0.1 b25 to be exact. This page is thus also in beta. Please check back often and email suggestions and questions to me jmmiller@ltu.edu.

If you are having trouble getting started, you may want to jump down to Current problems, work-arounds and FAQs before trying the missions.

Doing the missions that are mentioned in Dr. Chung's workshops loosely based on Robofest 2007, Miner Rescue: (For these missions we will use the "Tribot" NXT setup. There is a touch sensor on port 1, a light sensor on port 3, and a ultrasonic sensor on port 4. The right motor runs from port B and the left motor runs from port C.)

    Mission 1 2 3 4 5 6 7 8 9 10

  1. Go forward for 2 seconds and come back by reversing the motors. Here we will use the RCX notion of controlling the distance traveled by setting the travel time.
    // mission1.nxc
    #include "NXCDefs.h"
    // The required #include is new for NXC
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    // In NQC this would have been #define BOTH OUT_B + OUT_C
    task main()
    {
      OnFwd(BOTH,75);
      Wait(2000); // Time in NXC is in thousandths and not hundredths of a second.
      OnRev(BOTH,75);
      Wait(2000);
      Off(BOTH);
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  2. Go forward 1.5 seconds and come back by spinning left. Experiment with controlling the motors by degrees of rotation. You can continue to figure distance as speed × time as you did with the RCX, but speed can vary during a competition as your batteries wear down.
    // mission2.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    task main()
    {
      OnFwd(BOTH,75);
      Wait(1500);
      Off(BOTH);
      RotateMotor(RIGHT,75,370); // A bit more than 1 wheel rotation.
      RotateMotor(LEFT,-75,370); // 75% power in reverse.
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  3. Stop at edge of the table. First use your View menu to take light sensor readings from the white table top, the black lines and looking down over the edge of the table. You might find numbers like 62% over white, 40% over black and 28% off the edge with a dark floor..
    // mission3.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    #define EDGE 32
    task main()
    {
      SetSensorLight(S3); // In NQC you might use SENSOR_3 here instead of S3.
      OnFwd(BOTH,75);
      until (SENSOR_3 < EDGE) ; // Here you want SENSOR_3 and not S3!
      Off(BOTH);
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  4. Stop the robot when it sees a 2nd black line on the Miner Rescue field. Use a variable to count to 2. Be careful not to count the same line many times by beginning to count during the time spent crossing the line. Remember even a small computer counts pretty quickly.
    // mission4.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    #define BLACK 42
    int count = 0;
    task main()
    {
      SetSensorLight(S3);
      OnFwd(BOTH,75);
      until (count == 2) {
        until (SENSOR_3 < BLACK) ; // As in NQC, allow a little for hysteresis.
        until (SENSOR_3 > BLACK + 3) ; // Wait until completely across the line.
        count = count + 1;
      }
      Off(BOTH);
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  5. Loop until a touch sensor is pressed. Use separate tasks. This mission, each loop will be completed before stopping. In the next mission we will try to stop the motors right away. Notice that the power and direction setting is an expression: 75 * direction. This would not have been allowed in NQC because the RCX firmware required constants for parameters like these.
    // mission5.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    int touched = false;  // A semaphore flag that signals a touch has happened.
    int direction = 1;
    task wait_touch()
    {
      until (SENSOR_1 == 1) ;
      touched = true;
    } // wait_touch stops itself when it gets here.
    task main()
    {
      SetSensorTouch(S1);
      start wait_touch; // In NXC main starts wait_touch, but cannot stop it. 
      until (touched == true) {
        OnFwd(BOTH,75);
        Wait(1500);
        Off(BOTH);
        RotateMotor(LEFT,75 * direction,370);
        RotateMotor(RIGHT,-75 * direction,370);
        direction = -direction; // Spin the opposite way each time.
      }
      Off(BOTH);
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  6. Follow a black line until the touch sensor is pressed. This time, try to stop right away so that the robot does not run into whatever pressed the touch sensor. To do this we will experiment with the if-else-if form of the decision making if statement. To follow the line, try following one edge of the line. That means to stay in the zone where the reflected light is a little brighter than over the center of the black line, but still a little darker than over the white background.
    // mission6.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    #define TOO_DARK 45
    #define TOO_BRIGHT 55
    int touched = false;  // A semaphore flag that signals a touch has happened.
    task wait_touch()
    {
      until (Sensor(S1) == 1) ; // Sensor(S1) gives the same value as SENSOR_1.
      touched = true; // Wave the semaphore flag so task main knows to stop.
    }
    task main()
    {
      int l;
      SetSensorTouch(S1);
      SetSensorLight(S3);
      start wait_touch;
      until (touched == true) {
        // Which side of the line will this algorithm follow?
        l = Sensor(S3); // read the light sensor value
        // set speed for left motor
        if (touched)
          Off(BOTH);
        else if (l > TOO_DARK)
          OnFwd(LEFT,75);
        else
          OnFwd(LEFT,50);
        // set speed for right motor
        if (touched)
          Off(BOTH);
        else if (l < TOO_BRIGHT)
          OnFwd(RIGHT,75);
        else
          OnFwd(RIGHT,50);
      } // Done following the line.
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  7. Follow the line until it detects the foil. First we use the view menu to find that the reflected light over the foil is 76%. Next we learn to use a function to set the limits for line following "dynamically." This time the robot starts with its light sensor directly over the black line. This is important because light sensors, like motor speed, change as your batteries wear down.
    // mission7.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    #define FOIL 70
    #define HYSTERESIS 3
    int too_dark,too_bright;
    int found_foil = false; // The semaphore flag.
    task find_foil()
    {
      until (Sensor(S3) > FOIL) ;
      found_foil = true; // Wave the semaphore flag so task main can see to stop.
    }
    void set_linefollowing_limits()
    {
      int black, white;
      black = Sensor(S3);
      RotateMotor(LEFT,-50,45); // Swing a little to the left;
      white = Sensor(S3);
      too_dark = black + HYSTERESIS;
      too_bright = white - HYSTERESIS;
    }
    task main()
    {
      int l;
      SetSensorLight(S3);
      start find_foil;
      set_linefollowing_limits();
      until (found_foil == true) {
        l = Sensor(S3); // read the light sensor value
        // set speed for left motor
        if (found_foil)
          Off(BOTH);
        else if (l > too_dark)
          OnFwd(LEFT,75);
        else
          OnFwd(LEFT,50);
        // set speed for right motor
        if (found_foil)
          Off(BOTH);
        else if (l < too_bright)
          OnFwd(RIGHT,75);
        else
          OnFwd(RIGHT,50);
      } // Done following the line.
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  8. Follow the line until it climbs back to the home base. This is an advanced mission where we can use the rotation sensors built into the NXT motors to tell when we have made a 90° turn. Back in Mission 2 we turned 180° by turning each wheel 370° in opposite directions. Where did our guess of 370 come from. The Tribot has 56 mm. tires and a 115 mm. wheel base. To turn exactly 180°, each wheel would have to travel 115 × π / 2 mm.; and thus, since each wheel revolution covers 56 × π mm., 115 / (56 × 2) rotations or 369.6°. The difference between the wheels' rotations is 370 - (-370) or 740° for half-turn or 370° for a quarter-turn. For this mission, as we are coming around the perimeter black line and are on the segment approaching the foil patch, we would like to know where we are before we get to the foil where we might crash into our partner robot -- killing the miner we just rescued.
    // mission8.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    #define FOIL 70
    #define HYSTERESIS 3
    int too_dark,too_bright;
    int found_foil = false; // The fall-back semaphore flag.
    int turned = false;     // The early warning semaphore flag.
    task find_foil()
    {
      until (Sensor(S3) > FOIL || turned) ; // This check of turned is because
                                            // in NXC a task stops itself.
      if (!turned)         // If actually got as far as the foil:
        found_foil = true; // Wave the semaphore flag so task main can see to stop.
    }
    task watch_4_turn()
    {
      ResetTachoCount(BOTH);
      until (GetOutput(LEFT,TachoCount) - GetOutput(RIGHT,TachoCount) > 370) ;
      turned = true; // Wave the semaphore flag so task main can see to stop.
    }
    void set_linefollowing_limits()
    {
      int black, white;
      black = Sensor(S3);
      RotateMotor(LEFT,-50,45); // Swing a little to the left;
      white = Sensor(S3);
      too_dark = black + HYSTERESIS;
      too_bright = white - HYSTERESIS;
    }
    task main()
    {
      int l;
      SetSensorLight(S3);
      start find_foil;
      set_linefollowing_limits()
      start watch_4_turn;
      until (found_foil || turned) {
        l = Sensor(S3); // read the light sensor value
        // Set speed for left motor.
        if (found_foil || turned)
          Off(BOTH);
        else if (l > too_dark)
          OnFwdEx(LEFT,75,RESET_NONE);
        else
          OnFwdEx(LEFT,25,RESET_NONE); // 25 to allow for a sharper curve.
        // set speed for right motor
        if (found_foil || turned)
          Off(BOTH);
        else if (l < too_bright)
          OnFwdEx(RIGHT,75,RESET_NONE);
        else
          OnFwdEx(RIGHT,25,RESET_NONE);
      } // Done following the line.
      // Put your code to climb the hill here...
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  9. Follow the line until the ultrasonic sensor detects something 6 inches away. This is also an advanced mission for using NXC. I am not sure if there might be problems if two robot partners are close together and using their ultrasonic sensors at the same time. Slowing down at 10-12 inches away might be a useful tactic.
    // mission9.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    #define HYSTERESIS 3
    int too_dark,too_bright;
    int nearby = false;     // The semaphore flag.
    int range = 0;
    task ping_it()
    {
      int us = 1000; // Some arbitrary large number. 
      SetSensorLowspeed(S4); // The ultrasonic sensor is one of these.
      until (us < range) {
        us = SensorUS(S4); // us = Sensor(S4) will not work.
        NumOut(0,LCD_LINE1,true,us); // Displaying the range in centimeters
      }                              // for debugging.
      nearby = true; // Wave the semaphore flag so task main can see to stop.
    }
    int set_US_range(int inches)
    {
      return (inches * 254 / 100);
    }
    void set_linefollowing_limits()
    {
      int black, white;
      black = Sensor(S3);
      RotateMotor(LEFT,-50,45); // Swing a little to the left;
      white = Sensor(S3);
      too_dark = black + HYSTERESIS;
      too_bright = white - HYSTERESIS;
    }
    task main()
    {
      int l;
      SetSensorLight(S3);
      set_linefollowing_limits()
      range = set_US_range(6);
      start ping_it
      until (nearby) {
        l = Sensor(S3); // read the light sensor value
        // Set speed for left motor.
        if (nearby)
          Off(BOTH);
        else if (l > too_dark)
          OnFwdEx(LEFT,75,RESET_NONE);
        else
          OnFwdEx(LEFT,25,RESET_NONE); // 35 to allow for a sharper curve.
        // set speed for right motor
        if (nearby)
          Off(BOTH);
        else if (l < too_bright)
          OnFwdEx(RIGHT,75,RESET_NONE);
        else
          OnFwdEx(RIGHT,25,RESET_NONE);
      } // Done following the line.
      // Your code to pick up the miner goes here...
    }
    

    Mission 1 2 3 4 5 6 7 8 9 10

  10. For this last advanced mission the robot will look for a miner that is not near a black line. These miners are a bit short, so you will have to point the ultrasonic sensor down a bit compared to the standard Tribot.
    // mission10.nxc
    #include "NXCDefs.h"
    #define LEFT OUT_C
    #define RIGHT OUT_B
    #define BOTH OUT_BC
    task approach() 
    {
       until (SensorUS(S4) < 10) ;
       Off(BOTH);
    }
    task main()
    {
      int range = 50;
      int sweep = 320;
      int step = 40;
      int minimum,angle,min_angle,us,rotations;
      SetSensorLowspeed(S4);
      until (range < 20) {
        RotateMotor(LEFT,-75,sweep / 2); // Swing to the left.
        minimum = range;
        for (angle = 0;angle < sweep;angle += step) {
          us = SensorUS(S4);
          NumOut(0,LCD_LINE1,true,us);
          if (us < minimum) {
            min_angle = angle;
            minimum = us;
          }
          RotateMotor(LEFT,75,step);
        }
        if (minimum < range) {
          range = minimum;
          RotateMotor(LEFT,-75,sweep - min_angle);
          if (sweep > 40) { // Smaller sweeps as you get closer.
             sweep /= 2;
             step /= 2;
          }
          // Go about 1/2 the remaining distance.
          RotateMotor(BOTH,75,360 * minimum * 10 / (2 * 56 * 3));
        } else {
          range = 0; // Lost!
        }
      }
      if (range > 0) {
        start approach;
        OnFwd(BOTH,25);
        RotateMotor(OUT_A,-75,90);  // Grab the miner.
      }
    }
    

Current problems, work-arounds and FAQs:

Revised February 21, 2007