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

Handouts

A Not eXactly C Workshop for Robofest 2008

The scheduled date of this workshop is January 26th. However, this Web page will be updated all the way to World Robofest 2008. Please send me your comments and contributions at jmmiller@ltu.edu.

The programming language we will use in this workshop is Not eXactly C. NXC is like its predecessor Not Quite C. NQC was invented by Dave Baum for his students at the University of Utrecht for use with the RCX LEGO brick. NXC for the LEGO NXT brick, by John C. Hansen, continues in this tradition. Visit the Web pages for NXC.

RobotC is another dialect of C for the NXT brick. In keeping with the Robofest traditional effort to simultaneously maximize the students' learning while holding down participation costs, we will not be exploring RobotC here.

One expense worth including in your total cost of team participation is a couple of books

NXC is a powerful text-based language that can help your team excel in Robofest 2008. Text-based languages are important in a world where a long term job commitment might be 1 year, and teamwork is much more common than solo work. Donald Knuth's concept of "literate" programmers is more important than ever. 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 NXC makes programs easier to read. Learn to use them!

The ability to store intermediate results in a text file is new in the NXT. Your autonomous robot will be smarter if it can exploit this new ability to remember its failures and successes. Not surprisingly, manipulating text files is actually easier in the text-based NCX than in the icon-based NXT-G.

Today we will be writing programs to handle some missions on a laptop and "cross compiling" the programs so that the executable is suitable to run on the NXT LEGO brick. Transferring that executable file from the laptop to the brick we will refer to as downloading. For more on this whole chain of events see Appendix A below.

Our missions for today (and a few for homework)

  1. Begin by getting our laptops and NXT bricks to work together.
    // mission1.nxc
    // Since 1972, "Mission 1" has been the Hello,World! program.
    // NQC programmers will notice this new required #include for NXC.
    // C programmers will notice the quotation marks instead of angle brackets.
    #include "NXCDefs.h"
    task main() // Every program must have a main task.
    {
      TextOut(0,LCD_LINE3,"Hello, World!");
      Wait(10000); // Hold our message on the screen for 10 seconds.
    }
    
  2. The ability to multi-task is vital to robotics. By a small decrease in flexibility, the icon programming languages make multitasking more accessible to beginning programmers. We need work on two concepts today to keep our tasks from interfering with each other: ordering and synchronization. For practice at ordering tasks, consider Mission 2. The ordering of the lines of code on the page is a separate issue. With a 1-pass compiler things have to be defined or declared before they can be used or referred to. Try moving task main() to be the first task mentioned in the mission2.nxc and see how it the program compiles.
    // mission2.nxc
    // Practice in scheduling tasks.
    #include "NXCDefs.h"
    
    task two()
    {
      TextOut(0,LCD_LINE4,"A 2nd Hello!");
      Wait(3000); // A 3 second pause.
    }
    
    task three()
    {
      Follows(two);
      TextOut(0,LCD_LINE5,"A 3rd Hello!");
      Wait(10000); // Hold our messages on the screen for 10 seconds.
    }
    
    task main() // Every program must have a main task.
    {
      Precedes(two,three);
      TextOut(0,LCD_LINE3,"Hello, World!");
      Wait(3000); // A 3 second pause.
    }
    
  3. The motors that come with the NXT set have built in rotation sensors. This mission and the next will explore those new motors. In his book Hansen points out that motor control in NXT-G eats up 10 times as much executable memory space as in NXC. His next point that you cannot see the motor speed in a simple screen shot of NXT-G, is perhaps even more important for student programmers.
    // mission3.nxc
    /* Practice in using the motors (and a multi-line comment style)
       a) in the RCX manner: OUT_REGMODE_IDLE,
       b) in the regulated manner: OUT_REGMODE_SPEED,
       c) in the synchronized manner,
       d) and limited by the motors' rotation sensors.
    
         In each of the speed regulation modes try manually
       braking one wheel and watch the speed.
    
         In each of the braking modes (Off, Coast, Speed set to 0) try
       manually turning one wheel and notice the resistance and reaction
       if any.
    
    */
    #include "NXCDefs.h"
    // Follow convention of left motor is OUT_B and right motor is OUT_C.
    #define LEFT OUT_B
    #define RIGHT OUT_C
    #define BOTH OUT_BC // Not OUT_B|OUT_C or OUT_B+OUT_C as in NQC!
    task motor_speed()
    {
      while (true) {
        NumOut(0,LCD_LINE1,MotorActualSpeed(OUT_B));
        NumOut(0,LCD_LINE2,MotorActualSpeed(OUT_C));
      }
    }  
    task main()
    {
      start motor_speed;
      // a) Like in RCX NQC this could be just OnFwd(BOTH,40).
      TextOut(0,LCD_LINE4,"Idle            ");
      OnFwdReg(BOTH,40,OUT_REGMODE_IDLE);
      Wait(5000);
      OnRevReg(BOTH,40,OUT_REGMODE_IDLE);
      Wait(5000);
      Off(BOTH);
      TextOut(0,LCD_LINE4,"Off             ");
      Wait(5000);
      Coast(BOTH);
      TextOut(0,LCD_LINE4,"Coasting        ");
      Wait(5000);
      // b) Trying to keep a constant speed.
      TextOut(0,LCD_LINE4,"Constant speed  ");
      OnFwdReg(BOTH,40,OUT_REGMODE_SPEED);
      Wait(5000);
      OnRevReg(BOTH,40,OUT_REGMODE_SPEED);
      Wait(5000);
      // Braking by setting the speed to 0.
      TextOut(0,LCD_LINE4,"Speed set to 0  ");
      OnFwdReg(BOTH,0,OUT_REGMODE_SPEED);
      Wait(10000);
      // c) Trying to keep going straight by keeping both motors in sync.
      TextOut(0,LCD_LINE4,"In sync         ");
      OnFwdSync(BOTH,40,0);
      Wait(5000);
      OnRevSync(BOTH,40,0);
      Wait(5000);
      Off(BOTH);
      // d) To go 15cm on 56mm tires by rotating motors in degrees.
      // A constant is calculated on the compiling computer.
      const int cm = 15;
      const int degrees = cm * 10 * 100 * 360 / (314 * 56);
      TextOut(0,LCD_LINE4,"Moving          ");
      TextOut(0,LCD_LINE5,"   centimeters  ");
      NumOut(0,LCD_LINE5,cm);
      TextOut(0,LCD_LINE6,"    degrees  ");
      NumOut(0,LCD_LINE6,degrees);
      RotateMotor(BOTH,40,degrees);
      RotateMotor(BOTH,40,-degrees);
      Wait(5000);
      StopAllTasks();
    }
    
  4. For the next mission we can try using a subroutine. In general it helps to separate code, that is repeated in several parts of your program, and place it in a subroutine. In contrast, two pieces code that are to be done at the same time, like going forward while watching for a wall, need to be in separate tasks, not separate subroutines.
    // mission4.nxc
    /* More practice in using the motors.
    
         NQC and NXC have inherited "repeat(count)" from the LOGO
       programming language.  Here we will try a subroutine make a
       LOGO-like turn left by degrees.  My tires have a 56mm diameter
       and are set 118mm apart.
    
    */
    #include "NXCDefs.h"
    // Follow convention of left motor is OUT_B and right motor is OUT_C.
    #define LEFT OUT_B
    #define RIGHT OUT_C
    #define BOTH OUT_BC
    #define WHEEL_DIAMETER 56
    #define WHEEL_BASE 118
    
    void left(int degrees)
    {
      degrees = WHEEL_BASE * degrees / WHEEL_DIAMETER;
      RotateMotor(RIGHT,40,degrees);
      RotateMotor(LEFT,40,-degrees);
    }
    
    void right(int degrees)
    {
      degrees = WHEEL_BASE * degrees / WHEEL_DIAMETER;
      RotateMotorEx(BOTH,40,-degrees,-100,true,true);
    }
    
    task main()
    {
      left(90); // Why do these two subroutines work slightly differently?
      right(90);
    }
    
    To finish Mission 4, try making and testing a subroutine like square(int side_length_cm).
  5. For the next mission we will try to dynamically calibrate our light sensors and save the result in a file for later line following.
    // mission5.nxc
    /* Practice using the light sensor and files and the motor rotation sensors.
    
         For this mission the robot will start on the light tabletop
       and cross a black tape line and then backup to the starting point.
    
    */
    #include "NXCDefs.h"
    // Follow convention of left motor is OUT_B and right motor is OUT_C.
    #define LEFT OUT_B
    #define RIGHT OUT_C
    #define BOTH OUT_BC
    #define LIGHT IN_3
    int light = 0;
    int minimum = 100;
    int maximum = 0;
    int degrees = 0;
    int found_tape = 0; // a semaphore flag for signaling between tasks.
    unsigned int result;
    byte handle;  // The "handle" by which we refer to and opened file.
    int file_size = 20;
    
    task find_tape()
    {
      while (!found_tape) {
        light = Sensor(LIGHT);
        if (light > maximum) maximum = light;
        if (light < minimum) minimum = light;
        // See if across the line and getting lighter yet.
        if (maximum - minimum > 5 && light - minimum > 5) found_tape = 1;
        degrees = MotorRotationCount(RIGHT);
        // Display the data so far.
        NumOut(0,LCD_LINE1,light);
        NumOut(0,LCD_LINE2,maximum);
        NumOut(0,LCD_LINE3,minimum);
        NumOut(0,LCD_LINE4,degrees);
      }
    }
    
    void record_data(string file_name)
    {
      result = CreateFile(file_name,file_size,handle);
      if (result == LDR_FILEEXISTS) { // If an old copy is already there delete it.
        DeleteFile(file_name);
        result = CreateFile(file_name,file_size,handle);
      }
      if (result == LDR_SUCCESS) {
        Write(handle,maximum);
        Write(handle,minimum);
        CloseFile(handle);
      }
    }
    
    void retrieve_data(string file_name)
    {
      result = OpenFileRead(file_name,file_size,handle);
      if (result == LDR_SUCCESS) {
        Read(handle,maximum);
        Read(handle,minimum);
        CloseFile(handle);
        NumOut(0,LCD_LINE6,maximum);
        NumOut(0,LCD_LINE7,minimum);
      } else {
        TextOut(0,LCD_LINE6,"Error:");
        NumOut(0,LCD_LINE7,result);
      }
    }
    
    task main()
    {
      SetSensorLight(LIGHT);
      start find_tape;
      OnFwdEx(BOTH,40,RESET_ALL);  // Zero the rotation counters and GO.
      until (found_tape) ; // Just wait for the semaphore signal from find_tape().
      RotateMotor(BOTH,40,-degrees);
      // Here using subroutines is not about saving memory, but is a way of
      // breaking a task into smaller pieces.
      record_data("light.rdt");
      retrieve_data("light.rdt");
      Wait(10000);
      StopAllTasks();
    }
    
  6. For the next mission we would like our robot to head toward a wall and stop 15cm from it.
    // mission6.nxc
    /* Practice using the ultrasonic sensor.
    
         For this mission the robot will go forward until it is
         15 cm from the wall.
    
    */
    #include "NXCDefs.h"
    // Follow convention of left motor is OUT_B and right motor is OUT_C.
    #define LEFT OUT_B
    #define RIGHT OUT_C
    #define BOTH OUT_BC
    #define ULTRASONIC IN_4
    int distance = 100;
    
    int find_wall(int range)
    {
      while (distance > range) {
        distance = SensorUS(ULTRASONIC);
        NumOut(0,LCD_LINE1,distance);
      }
      return distance;
    }
    
    task main()
    {
      int actual;
      SetSensorLowspeed(ULTRASONIC);
      OnFwd(BOTH,40);
      actual = find_wall(15);
      Off(BOTH);
      NumOut(0,LCD_LINE2,actual);
      Wait(10000);
    }
    
  7. For the next mission our robot should head toward a line and stop on it. This mission is similar to the last one. Here is some pseudo-code:
    Mission 7:
    Open the light sensor values file for reading.
    Read the maximum and minimum from the file.
    Set up the light sensor.
    Go forward until the light sensor reads within about 5 of the saved minimum.
    Stop.
    
  8. The next mission will be to follow the line for a while. We will asssume that the robot starts on or to the right of the line and will follow along the right hand edge of the line, instead of the center of the line.
    Mission 8:
    Get the maximum and minimum from the light values file.
    Take value for the light sensor exaclty on the edge to be the average.
    Assume near the edge if the light sensor reads average +/- 25%.
    Reset the rotation counters.
    While the right rotation counter is < 400 
      If the light sensor is in the edge range: go straight.
      If the light sensor is nearer the minimum: turn toward the right.
      If the light sensor is nearer the maximum: turn toward the left.
    Then stop.
    
  9. For the next mission we will figure out how to detect the end of the line we were following. We could use similar pseudo-code and see if we could find an angle of rotation for the right axle, such that: if it seems we are turning toward the line for too long, we must have run off the end.
    Mission 9:
    ...
    Declare a flag on_or_near_line.
    While the right rotation counter is < 100
      If the light sensor in the edge range: go straight, set flag, reset counter.
      If the light sensor is nearer the minimum: turn right, set flag.
      If the light sensor is nearer the maximum: 
        If flag is set: clear flag and reset the rotation counter.
        turn toward the left.
    Then stop.
    
  10. For the last mission, it is time to look around for tennis balls.
    Mission 10:
    Until close enough to grab it:
      Sweep until finds the nearest object.
      Turn toward that nearest object and go forward one half the distance.
    

Appendix A. Writing, compiling, downloading and running NXC programs. If all your teams' laptops are running Windows, the Bricx Command Center IDE is probably all you need to get started. If you use mixed operating systems, then Emacs may be a help. In any operating system using Bluetooth wireless is a whole lot easier than keeping track of USB cables, and is worth the extra effort to set it up. See the FAQ section for Bricxcc or consider a .emacs function like this to automate things.

(defun nxt-download ()
  "Saves, Compiles and downloads the current buffer to the
NXT Gray Brick.  Uses a Bluetooth wireless connection."
  (interactive)
  (let ((program-name (buffer-name))
        (bytecode-name)
        (include-path "../nbc-1.0.1.b34.src/nxt/")
        (bin-path "../bin/")
        (Bluetooth-serial-port "/dev/tty.NXT-DevB-1"))
    ; Do only NXC files. 
    (cond ((string-match "\\(.*\\.\\)nxc\\'" program-name nil)
             (setq bytecode-name (concat (match-string 1 program-name) "rxe"))
             (save-buffer)
             (shell)
             (goto-char (point-max))
             (insert (format "%snbc -I=%s %s -O=%s"
                             bin-path include-path program-name bytecode-name))
             (comint-send-input)
             (insert (format "%snxtcom -S=%s %s"
                             bin-path Bluetooth-serial-port bytecode-name))
             (comint-send-input))
          (1
             (message "%s is not a .nxc (Not eXactly C source file.)"
                      program-name)))))

Appendix B. What if your school board's IT department is brain dead and does not give teachers administrative access to their team's laptops? One option would be a "Live Distribution" of Linux.

Revised January 25, 2008