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

Handouts

A Not Quite C Workshop for Robofest
(With a Little Not eXactly C)

The programming language we will use this morning is Not Quite C. NQC was invented by Dave Baum for his students at the University of Utrecht. Our practice sessions will be on the playing field for Robofest 2007. If you attended Dr. Chung's session earlier this year, you will notice that we are going to attempt many of the same missions in NQC that you tried previously in RCX code.

Why NQC? Of course, one important reason is that NQC is a powerful language that can help your team do well in Robofest 2007. NQC allows the most fine grained control of your robot when using the RCX Mindstorms firmware. Later we will touch on NXC for the Lego NXT robot. To fully use the newer platform, you are going to need something like NXC. A second very important reason extends beyond Robofest. Programs written in text based languages are easy to document.

In a world where a long term job commitment might be 1 year, and teamwork is much more common than solo work, becoming literate programmers is very important. We need to be able to communicate clearly what our programs do and how they do it. Otherwise they will be not much help to our team and useless, as soon as we move to our next job. Using #define's and // comments in NQC make programs easier to read. Learn to use them!

In related pages, reached via the Handouts link, I have recommended several books. If you have time for just one book, it should be the Definitive Guide to LEGO Mindstorms, second edition, by Dave Baum.

This page probably has more missions than we will get to today. I hope the rest will give you ideas to try along the road to Robofest.

For coaches that may have used C before, you may be interested in some comments on NQC compared to C near the bottom of this page.

Mission 1: Open your programming environment and check your battery level.

Today I am assuming that you have either Windows or OS X on your laptops. If Windows, you will be using the Bricx Command Center. If OS X, you can use MacNQC. In the Bricx Command Center, as your tower tries to connect to your robot, you probably want to select USB1; and you definitely want to select RCX2 (to be able to use all of the power of NQC)!

If your team uses Windows, OS X and Linux, you might want to look at Emacs with Lego robots toward the bottom of this page.

Once connected, use the Tools | Diagnostics menu to see if your robot and batteries are alive. (In OS X the menu choices are RCX | Information.) If your battery level is less than 7500, or 7.5 volts, it is time to change your batteries. If your battery level is Ok, use the same menu to open the piano keyboard and tap out a little victory tune.

Mission 2: Go forward for 2 seconds and come back.

For Mission 2 we will concentrate on the RCX outputs. As you know from RCX code or Robolab code, outputs are usually connected to motors. The outputs are named OUT_A, OUT_B and OUT_C in NQC. Note: NQC is case sensitive -- not like Basic! Our robots will have the left wheel driven by a motor wired to OUT_A and the right wheel driven by a motor wired to OUT_C. To make our programs easy to read, and revise if we change the motor wiring later, we will use NQC #define directives to give nicknames to OUT_A and OUT_C. Motors have 3 separate attributes

  1. Direction (OUT_FWD or OUT_REV or OUT_TOGGLE),
  2. Power (0 : OUT_LOW, 1, 2, 3 : OUT_HALF, 4, 5, 6, 7 : OUT_FULL), and
  3. Mode or state (On : OUT_ON, Off with brake : OUT_OFF, Off without brake : OUT_FLOAT).
Time in NQC is in 1/100th's of a second. (Except in the confusing case of the built in timers, which like in RCX code, are in 1/10th's of a second. Using the function FastTimer() instead of Timer() most of the time is a way to avoid forgetting which is which.)

A NQC program is divided into tasks and functions. These divisions are marked like chapters and sections in a book: tasks begin with the word "task" followed by the name of the task and functions begin with the word "void" followed by the name of the function. Each task or function has at least 1 block, like a paragraph of text. Instead of beginning with indentation like a paragraph, a block begins with a "{" and ends with a "}". Each statement in a block ends with a ";" instead of a "." like a sentence does.

Let's try this program. As you look at the program notice the way capitalization is used in NQC. Also notice the way the Bricx editor window has different colors for different parts of the NQC language. These things will help you get the spelling right. Explore using the handy Function Template.

// mission2.nqc
// your-team-name
// This mission is to go forward for 2 seconds and then come back.
// The program assumes a robot with output A connected to a motor
// driving the left wheel and output C, the right wheel.
#define LEFT OUT_A
#define RIGHT OUT_C
// every NQC program has at least 1 task, that task is called
task main()
{ // this is the start of task main
  SetDirection(LEFT+RIGHT,OUT_FWD);
  SetPower(LEFT+RIGHT,OUT_FULL);
  SetOutput(LEFT+RIGHT,OUT_ON);
  Wait(200); // a 2 second wait
  SetDirection(LEFT+RIGHT,OUT_REV);
  SetOutput(LEFT+RIGHT,OUT_ON);
  Wait(200); // a 2 second wait
  SetOutput(LEFT+RIGHT,OUT_OFF);
} // this is the end of task main

Download and run your program. How far did the robot go? Try changing OUT_OFF to OUT_FLOAT. Is there any difference?

Of course NQC is a descendant of C, so there are lots of shortcuts. Remember your program will begin running with the direction set to forward and the power to full. Try these variations.

// just 5 statements
OnFwd(LEFT+RIGHT);
Wait(200);
OnRev(LEFT+RIGHT);
Wait(200);
Off(LEFT+RIGHT);
or
// just 4 statements
Fwd(LEFT+RIGHT);
OnFor(LEFT+RIGHT,200);
Rev(LEFT+RIGHT);
OnFor(LEFT+RIGHT,200);

Mission 3: Go forward 1.5 seconds and come back by spinning left.

Here are 3 ways to turn, each makes a little sharper turn.

  1. Have 1 wheel turn faster than the other, or
  2. stop 1 wheel while the other still turns forward, or
  3. reverse 1 wheel while the other still turns forward.
// mission3.nqc
// your-team-name
// This mission is to go forward for 1.5 seconds, turn left 180 degrees,
// and stop.
// The program assumes a robot with output A connected to a motor
// driving the left wheel and output C, the right wheel.
#define LEFT OUT_A
#define RIGHT OUT_C
// Sometimes it is nice to have all your run-time adjustable constants up at
// the top of your program to make them easy to find in the confusion
// of game day.
#define TURN_TIME 200
task main()
{
  OnFwd(LEFT+RIGHT);
  Wait(150);
  Off(LEFT); // begin a turn to the left
  Wait(TURN_TIME);
  Off(RIGHT);  
}
Adjust your turn until it is about 180 degrees. Try using
Rev(LEFT);
Wait(TURN_TIME);
Off(LEFT+RIGHT);
Do you need to readjust TURN_TIME?

Mission 4: Wait to start. Using the touch sensor.

In order to complete this mission we will need to know how to count and compare numbers in NQC, so our robot can decide when to start. We also need to learn to use the touch sensor. We will also learn a different way to wait, using "until" instead of "Wait". until looks like this

until (some-condition-is-true) {
  do-something;
}
// or
until (some-condition-is-true) {
  // do-nothing;
}
// the latter is usually abbreviated:
until (some-condition-is-true)  ;

NQC has two similar looking operators, "=" and "==". The single equal sign is called the assignment operator. It assigns or gives the value on its right to the variable on its left. The double equal sign compares 2 numbers for equality. == yields the answer "true" (1) if the numbers are the same and "false" (0) otherwise. The other comparison operators like >, <, >=, <= are not so confusing and do what you would expect.

myNumber = 1 + 2;       // puts the value 3 in the variable myNumber
answer = myNumber == 3; // puts the value "true" in the variable answer

This mission is also important because we will try to solve a more complex problem by breaking it into smaller pieces.

// mission4.nqc
// your-team-name
// This mission is to wait until the touch sensor is pressed and released
// 5 times and then to go forward for 3 seconds, turn left 90 degrees, and
// stop.
// This program assumes a touch sensor is wired to input 1
#define LEFT OUT_A
#define RIGHT OUT_C
#define TOUCH SENSOR_1
#define TURN_TIME 100
#define START_COUNT 5
// Variables
int count;
task main()
{
  // Inputs have to be initialized before the robot can use them..
  SetSensor(TOUCH,SENSOR_TOUCH);
  wait_touch_start(); // Task waits here until wait_touch_start is done.
  OnFwd(LEFT+RIGHT);
  Wait(300);
  Off(RIGHT); // begin a turn to the right
  Wait(TURN_TIME);
  Off(LEFT);  
}

void wait_touch_start() // A subroutine is like a My Block in RCX code.
{
  count = 0;  // Always remember to initialize your variables.
  SetUserDisplay(count,0);  // A trick so you can watch the count on the LED
  until (count == START_COUNT) {
    until (TOUCH == 1) ;
    count = count + 1;
    // If this does not work, why doesn't it?
    // Would adding a line like this help?
    // until (TOUCH == 0) ; // Keep the RCX from counting too fast.
  }
}

Mission 5: Stop at the edge of the table.

// mission5.nqc
// your-team-name
// This mission is to go forward until you detect the edge of
// the table and then stop.
// This program assumes a light sensor is pointed down and wired to input 2.
#define LEFT OUT_A
#define RIGHT OUT_C
#define LIGHT SENSOR_2
// constants that may change with game conditions
#define OFF_TABLE 40
// Variables
task main()
{
  // Inputs have to be initialized before the robot can use them..
  SetSensor(LIGHT,SENSOR_LIGHT);
  OnFwd(RIGHT+LEFT);
  until(LIGHT <= OFF_TABLE) ;
  Off(RIGHT+LEFT);  
}

Mission 6: Stop on the second black line on the playing field.

For this mission, we will detect the lines by using a light sensor pointed downwards.

// mission6.nqc
// your-team-name
// This mission is to wait until the touch sensor is pressed and released
// 2 times and then to go to the second black line and stop.
// This program assumes a touch sensor is wired to input 1 and
// a light sensor is pointed down and wired to input 2.
#define LEFT OUT_A
#define RIGHT OUT_C
#define TOUCH SENSOR_1
#define LIGHT SENSOR_2
// constants that may change with game conditions
#define TOO_DARK 44
#define TOO_BRIGHT 48
#define TURN_TIME 100
#define START_COUNT 2
// Variables
int count;
task main()
{
  // Inputs have to be initialized before the robot can use them..
  SetSensor(TOUCH,SENSOR_TOUCH);
  SetSensor(LIGHT,SENSOR_LIGHT);
  wait_touch_start(); // Task waits here until wait_touch_start is done.
  go_to_line(count);     // Then waits here until the second line is found.
}

void wait_touch_start()
{
  count = 0;  // Always remember to initialize your variables.
  SetUserDisplay(count,0);  // A trick so you can watch the count on the LED
  until (count == START_COUNT) {
    until (TOUCH == 1) ; // Wait for press.
    count = count + 1;
    until (TOUCH == 0) ;    // Wait for release.
  }
}

void go_to_line(int & count) // The & here saves a temporary variable.
                             // An NQC program only has 35-45 variables.
                             // In some programs (not this one) conservation
                             // is important.
                             // Really, since count is a global variable
                             // There could be no arguments at all.
{
   // Go forward...
   // Your code goes here.
   until (count == 0) { 
     until (LIGHT < TOO_DARK) ;   // Just keep going
     until (LIGHT > TOO_BRIGHT) ; // Continue until off the line. Why???
     count--; // shorthand for count = count - 1;
   }
   Off(LEFT+RIGHT);           // then stop.
}

Mission 7: Following a line while watching for the foil.

For this mission, something new, doing two things at once or "multitasking". Before our robot took 1 step at a time. This time we will start as usual. When we get to the line and are ready to follow it, task main will ask 2 helper tasks to work at the same time. task follow_line will follow the line and task find_foil will keep watch for the shiny patch of foil where the straight line meets the loop.

find_foil will have a way that it can say to the other tasks, "Hey, I found the foil!" (In programming this is called raising a flag or signaling with a semaphore.) For times when things are going wrong, adding a little sound can help the programmer figure out what is going on.

// mission7.nqc
// your-team-name
// This mission is to wait until the touch sensor is pressed and released
// 2 times, and then follow the line while looking out for the foil patch,
// and stop on the foil patch.
// This program assumes a touch sensor is wired to input 1 and
// a light sensor is pointed down and wired to input 2.
#define LEFT OUT_A
#define RIGHT OUT_C
#define TOUCH SENSOR_1
#define LIGHT SENSOR_2
// constants that may change with game conditions
#define TOO_DARK 44
#define TOO_BRIGHT 48
#define FOIL 60
#define START_COUNT 2
// Variables
int count;
int foil; // If foil == false, robot is still looking for the foil.
          // If foil == true, robot has found the foil.
task main()
{
  // Initialize inputs
  SetSensor(TOUCH,SENSOR_TOUCH);
  SetSensor(LIGHT,SENSOR_LIGHT);
  wait_touch_start(); // Task waits here until wait_touch_start is done.
  // get ready to watch for foil
  foil = false;
  start follow_line;  // Get started following the line.
  start find_foil;    // At the same time watch out for the foil patch.
  until (foil == true) ; // Wait here until find_foil reports success.
  stop follow_line;
  stop find_foil;
  Off(LEFT+RIGHT);
}

void wait_touch_start()
{
  count = 0;  // Always remember to initialize your variables.
  SetUserDisplay(count,0);  // A trick so you can watch the count on the LED
  until (count == START_COUNT) {
    until (TOUCH == 1) ; // Wait for press.
    count = count + 1;
    until (TOUCH == 0) ;    // Wait for release.
  }
}

task find_foil()
{
   until (LIGHT >= FOIL) ;
   foil = true; // let task main know what happened
   PlaySound(SOUND_UP); // let person know what happened
}

task follow_line() // This is the simple algorithm from Dave Baum's book
                   // Definitive Guide to LEGO Mindstorms, 2nd. ed.
                   // This assumes starting and following along the
                   // LEFT side of the line.
{ 
  OnFwd(LEFT+RIGHT);
  while(true) {
    if (LIGHT <= TOO_DARK) {
      Off(LEFT);
      On(RIGHT);
    } else if (LIGHT >= TOO_BRIGHT) {
      Off(RIGHT);
      On(LEFT);
    } else {
      On(LEFT+RIGHT);
    }
  }
}

Try taking your robot with the line following program and see how it works

  1. starting on the black line is Ok,
  2. starting just to the left of the black line is Ok,
  3. starting just to the right of the black line does not work. The robot gets lost because it only knows how to turn right looking to get back on the line. If there is enough space to turn the robot will turn 180 degrees and start following the line the wrong way.

Mission 8: Signaling another robot.

Mission 8 is simply Mission 7 with wait_touch_start() changed to wait_message_start(). Here we will try out IR communication from robot to robot. You might want to make a wait_light_start() subroutine for practice.

// mission8.nqc
// your-team-name
// This mission is to wait until the proper start message is received.
// and then follow the line while looking out for the foil patch,
// and stop on the foil patch.
// This program assumes a light sensor is pointed down and wired to input 2.
#define LEFT OUT_A
#define RIGHT OUT_C
#define LIGHT SENSOR_2
// constants that may change with game conditions
#define TOO_DARK 44
#define TOO_BRIGHT 48
#define FOIL 60
#define START_MESSAGE 3
// Variables
int count;
int foil; // If foil == false, robot is still looking for the foil.
          // If foil == true, robot has found the foil.
task main()
{
  // Initialize inputs
  SetSensor(LIGHT,SENSOR_LIGHT);
  wait_message_start();  // Task waits here until wait_mesage_start is done.
  foil = false;          // Get ready to watch for foil.
  start follow_line;     // Get started following the line.
  start find_foil;       // At the same time watch out for the foil patch.
  until (foil == true) ; // Wait here until find_foil reports success.
  stop follow_line;
  stop find_foil;
  Off(LEFT+RIGHT);       // Stop.
}

int mesg; // a variable only used by wait-message-start
void wait-message-start()
{
  ClearMessage(); // This line is very important in a room with other robots.
  mesg = 1000;             // A number bigger than any real message.
  SetUserDisplay(mesg,0);  // Display received mesg for debugging.
  until (mesg == START_MESSAGE) {
    mesg = Message();      // Keep checking for new messages.
  }
  PlaySound(SOUND_UP);     // Noise to help with debugging.
}

task find_foil()
{
   until (LIGHT >= FOIL) ;
   foil = true; // let task main know what happened
   PlaySound(SOUND_UP); // let person know what happened
}

task follow_line() // From Dave Baum's book
{ 
  OnFwd(LEFT+RIGHT);
  while(true) {
    if (LIGHT <= TOO_DARK) {
      Off(LEFT);
      On(RIGHT);
    } else if (LIGHT >= TOO_BRIGHT) {
      Off(RIGHT);
      On(LEFT);
    } else {
      On(LEFT+RIGHT);
    }
  }
}

You could get a start message from a remote control. You could also team up with another robot. The other robot would run a message sending program. The sending program will use a new function: SendMessage() and a new control statement: repeat (number-of-times) { }. Your robots have to use the same START_MESSAGE for the message.

// sender.nqc
// by your-names
// A program to send a start message
#define START_MESSAGE 3
task main()
{
  repeat (5) {                   // Send message several times to be sure
    SendMessage(START_MESSAGE);  // it is received in a room crowded with
    Wait(15);                    // other robots.
  }
}

Mission 9: Finding a ball with a light sensor.

This mission will probably be a take home mission.

Our challenge is to detect a tennis ball wrapped with foil using a second light sensor. We need to learn another concept: responding to events.

Remember the clear colored area at the end of the light sensor detects both infra-red light like that which comes from the IR port and the visible red light from the red colored area at the end of the light sensor. Here we make use of the fact that the infra-red light is really much brighter than the visible red light even though a human cannot see it. It does not make much difference what number we use as a message to send from the IR port. Baum's suggestion of using 85 as the message is as good as any. The threshold of 52 for using the light sensor as a proximity sensor as suggested in Baum's book is likely to work for our robots as well.

Now write a program that heads toward a ball and stops when the following event is detected: The light sensor reading is at or above the threshold.

// mission9.nqc
// by your-names
// A program to travel toward a ball and stop when the proximity
// sensor detects the bottle.
// Similar to a program in Chapter 13 of Baum's book.
#define LEFT OUT_A
#define RIGHT OUT_C
#define PS SENSOR_3
#define PS_EVENT 1
#define PS_THRESHOLD 52
#define PS_MESSAGE 85
#define FOREVER 0
task main()
{
  SetSensor(PS,SENSOR_LIGHT);
  // Set our event to "monitor" or watch for, to be when the light sensor,
  // used as a proximity sensor, reads above the threshold.
  SetEvent(PS_EVENT,PS,EVENT_TYPE_HIGH);
  SetUpperLimit(PS_EVENT,PS_THRESHOLD);
  OnFwd(LEFT+RIGHT); // go forward
  SetTxPower(TX_POWER_HI); // use high power for IR port
  monitor (EVENT_MASK(PS_EVENT)) { // Start watching for a
    until (FOREVER) {              // proximity-sensor-high event.
      SendMessage(PS_MESSAGE);
    }
  } catch {                       // When a proximity-sensor-high event is
    PlaySound(SOUND_DOUBLE_BEEP); // noticed or caught, stop sending messages
    Off(LEFT+RIGHT);              // forever and play a tone and stop the
  }                               // motors.
}

Not eXactly C is similar to NQC, but for the Lego NXT robot. You might want to look at this example from the NXC site. It is a line following program somewhat similar to what we tried in NQC.

#include "NXCDefs.h"

#define SpeedSlow 50
#define SpeedFast 100

int SV;
int LoopCount;

sub FollowLine(int loopTime)
{
  int Threshold1=600;
  int Threshold2=630;
  int theSpeed;
  long t;

  // set sensor type and mode
  SetSensorType(IN_3, IN_TYPE_LIGHT_ACTIVE);
  SetSensorMode(IN_3, IN_MODE_RAW);

  // start looping
  t = CurrentTick() + loopTime;
  while (t > CurrentTick())
  {
    // read the light sensor value
    SV = SensorRaw(IN_3);
    
    // set speed for motor 1
    if (SV < Threshold2)
      OnFwd(OUT_A, SpeedFast);
    else
      OnFwd(OUT_A, SpeedSlow);
      
    // set speed for motor 2
    if (SV > Threshold1)
      OnFwd(OUT_B, SpeedFast);
    else
      OnFwd(OUT_B, SpeedSlow);
      
    // display sensor value
//    NumOut(0, LCD_LINE1, false, SV);
    
    LoopCount++;
  }
  // 10 second loop is done
  return
}

task main()
{
  string lcStr;
  string svStr;
  string msg;
  
  // call subroutine
  FollowLine(10000);
  
  // output results
  lcStr = NumToStr(LoopCount);
  svStr = NumToStr(SV);
  msg = svStr + " - " + lcStr;
  TextOut(0, LCD_LINE1, false, msg);
  
  // stop both motors
  Off(OUT_AB);
  
  // let user see the last message
  Wait(10000);
}
Now again in a format more like NQC on the RCX.
#include "NXCDefs.h"

#define LEFT OUT_C
#define RIGHT OUT_B
#define BOTH OUT_BC
#define LIGHT SENSOR_3
#define TOO_DARK 50
#define TOO_BRIGHT 60

int v;
int loopCount;

void FollowLine(int loopTime) // Follow the left side of the line.
{
  long t = CurrentTick();
  SetSensorLight(S3);
  while (CurrentTick() - t < loopTime) { // start looping
    v = LIGHT; // read the light sensor value
    // set speed for left motor
    if (v > TOO_DARK)
      OnFwd(LEFT,75);
    else
      OnFwd(LEFT,50);
    // set speed for right motor
    if (v < TOO_BRIGHT)
      OnFwd(RIGHT,75);
    else
      OnFwd(RIGHT,50);
    loopCount++;
  } // done looping
}

task main()
{
  string lcStr;
  string svStr;
  string msg;
  // call subroutine
  FollowLine(5000);
  // output results
  lcStr = NumToStr(loopCount);
  svStr = NumToStr(v);
  msg = svStr + " - " + lcStr;
  TextOut(0, LCD_LINE1, false, msg);
  // stop both motors
  Off(BOTH);
  // let user see the last message
  Wait(5000);
}
There are a number of things to notice here.

Not Quite C versus C, a few comments

Using Emacs with Lego robots. Emacs works well as a programmer's editor and development environment for Lego robots. It is available for any operating system that you might want to use with these robots. The following is a fragment from my .emacs file that you might find helpful to automate downloading to the RCX. This is in e-Lisp, the Emacs dialect of Lisp, but that is a talk for another day.

(defun rcx-download (program-slot)
  "Saves, Compiles and downloads the current buffer
to program number PROGRAM-SLOT in the RCX Yellow Brick."
  (interactive "p")
  (save-buffer)
  (let ((nqc (buffer-name)))
    (shell)
    (goto-char (point-max))
    (insert (format
             "/Applications/nqc -TRCX2 -L -d -SUSB -pgm %d %s"
             program-slot nqc))
    (comint-send-input)))

Revised February 9, 2007