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

Handouts

A Not eXactly C Workshop for Robofest 2009

This workshop was in the morning of January 24th. However, this Web page will be updated all the way to World Robofest 2009. Please send me your comments and contributions at jmmiller@ltu.edu. Even if you were not able to come to the workshop, and you have questions or suggestions for further "missions" that we might do together on-line, I would be happy to hear from you. Check back here to see updates like a correction, or a better mission 6 suggested by some team members who were keeping me honest today.

The programming language used 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 the proprietary RobotC in this morning session. (Check the afternoon session for a way to test drive the RobotC product for free.)

The sample programs during the workshop use the following connections:
Motor / Sensor Port
Left Motor C
Right Motor B
Left Light Sensor 2
Right Light Sensor    3
Ultrasonic Sensor 4
Touch Sensor 1
3rd Motor A

The drawing challenge is new to Robofest. In a way it is a return to the foundations of Robofest, forty years ago in the work of Seymour Papert. The text based language Papert wrote for young children is still easily available as Berkeley Logo. Monitors were very rare in those times, and so the original output device was a plastic turtle moving on a sheet of paper with 2 wheels and drawing with a marker stuck through its shell. The unit of drawing measurement was 1 turtle-step. There are lots of Logo commands, but in order to understand turtle drawing, we just need to consider these:
Command Abbr. Function
forward x fd x Go forward x turtle-steps.
back x bk x Go backward x turtle steps.
left x lt x Turn left x degrees.
right x rt x Turn right x degrees.
pendown pd Put pen down on the paper.
penup pu Pick pen up off the paper.

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.
    
    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.
    
    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.
    
    */
    
    #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 the edge of the table, need to be in separate tasks, not separate subroutines.
  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 lighter tabletop and
       go forward until either light sensor detects the edge of the
       tabletop and then backup to the starting point.
    
    */
    
    #define LEFT OUT_B
    #define RIGHT OUT_C
    #define BOTH OUT_BC
    #define R_LIGHT IN_2
    #define L_LIGHT IN_3
    int minimum = 100;
    int maximum = 0;
    int degrees = 0;
     // Semaphore flags for signaling between tasks.
    int l_found_edge = 0;
    int r_found_edge = 0;
    unsigned int result;
    byte handle;  // The "handle" by which we refer to an opened file.
    int file_size = 20;
    
    task l_find_edge()
    {
      int light;
      while (!l_found_edge) {
        light = Sensor(L_LIGHT);
        if (light > maximum) maximum = light;
        if (light < minimum) minimum = light;
        // See if robot has come to a darker edge yet.
        if (maximum - minimum > 7) l_found_edge = 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);
      }
    }
    
    task r_find_edge()
    {
      int light;
      while (!r_found_edge) {
        light = Sensor(R_LIGHT);
        if (light > maximum) maximum = light;
        if (light < minimum) minimum = light;
        // See if robot has come to a darker edge yet.
        if (maximum - minimum > 7) r_found_edge = 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(L_LIGHT);
      SetSensorLight(R_LIGHT);
      start l_find_edge;
      start r_find_edge;
      OnFwdEx(BOTH,40,RESET_ALL);  // Zero the rotation counters and GO.
      // Just wait for the semaphore signal from l_find_edge() or r_find_edge().
      until (l_found_edge || r_found_edge) ;
      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 go up to the 1-liter bottle and stop.
    // mission6.nxc
    // Practice using the ultrasonic sensor.
    
    #define LEFT OUT_B
    #define RIGHT OUT_C
    #define BOTH OUT_BC
    #define ULTRASONIC IN_4
    int distance = 100;
    
    int find_bottle(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_bottle(15);
      Off(BOTH);
      NumOut(0,LCD_LINE2,actual);
      Wait(10000);
    }
    
  7. A variation on mission6.
    // mission7.nxc
    /*
           Practice using the ultrasonic sensor, similar to mission6,
        with prettier display of the numbers using the string data type
        and the subroutine find_bottle() changed to the more reusable
        approach_bottle().
    
    */
    #define LEFT OUT_B
    #define RIGHT OUT_C
    #define BOTH OUT_BC
    #define ULTRASONIC IN_4 
    
    string msg;
    const int far_out = 100;
    
    int approach_bottle(int range)
    {
      string msg;
      int distance = SensorUS(ULTRASONIC);
      if (distance > far_out) {
        TextOut(0,LCD_LINE1,"No bottle near!",true);
      } else {
        while (distance > range) {
          distance = SensorUS(ULTRASONIC);
          msg = NumToStr(distance);
          msg = StrCat(msg," cm away.  ");
          TextOut(0,LCD_LINE1,msg);
        }
      }
      return distance;
    }
    
    task main()
    {
      int actual;
      string msg;
      SetSensorLowspeed(ULTRASONIC);
      OnFwd(BOTH,40);
      actual = approach_bottle(15);
      Off(BOTH);
      msg = NumToStr(actual);
      msg = StrCat("Bottle at ",msg," cm.");
      TextOut(0,LCD_LINE2,msg);
      Wait(10000);
    }
    

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)
        (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 %s -O=%s"
                             bin-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, 2009