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

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.)

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
{
OnFwd(BOTH,75);
Wait(2000); // Time in NXC is in thousandths and not hundredths of a second.
OnRev(BOTH,75);
Wait(2000);
Off(BOTH);
}
```
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
{
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.
}
```
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
{
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);
}
```
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;
{
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);
}
```
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;
{
until (SENSOR_1 == 1) ;
touched = true;
} // wait_touch stops itself when it gets here.
{
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);
}
```
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.
{
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.
}
{
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.
}
```
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.
{
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;
}
{
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.
}
```
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.
{
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.
}
{
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;
}
{
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...
}
```
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;
{
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;
}
{
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...
}
```
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
{
until (SensorUS(S4) < 10) ;
Off(BOTH);
}
{
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.
}
}
```
• The Bricx Command Center works well for writing and managing NQC programs on a Windows XP laptop. For NXT there are some issues with properly installing the Fantom drivers in order to use Bluetooth connections. Hopefully Bricx CC will be out of testing and there will be some experience with Windows Vista. In the meantime, if you can produce an .rxe file from your .nxc file, using Bricx CC/NBC, then you can always use the memory tab of the NXT Window in the NXT LEGO software, and simply click the "Download" button to select and download the .rxe file.
• There is no MacNXC yet. However, the NBC compiler runs on Windows, Mac and Linux systems.
• For a PPC Mac, you can use the LEGO software to download your .rxe programs as described above. Unfortunately, newer Intel Macs and Linux boxes are still not supported by LEGO. Maybe soon.
• Downloading your .rxe program file to your NXT robot is relatively easy in Ruby. I have tested this on a PPC Mac and hope to have details on this and testing on other platforms available here soon. You are welcome to use this, but please write and tell me if you have any problems. For example, to compile and download mission1.nxc:
```pbook:~/robotics/nxt/nxc-programs \$ nbc mission1.nxc -O=mission1.rxe
Connected via protocol version: 1.124 and firmware version: 1.1
Battery level 8.184 Volts.
......
306/306 bytes written.
pbook:~/robotics/nxt/nxc-programs \$
```
```#!/usr/bin/ruby
# A program to download an executable (.rxe) to a NXT brick.
# Time-stamp: <2007-02-14 20:09:19 jay>
require 'serialport'
# Constants:
Blocksize = 64 - 3 # This is from the "LEGO Mindstorms NXT Communication
#   Protocol, page 6, but it is not clear if this is
#   just a USB limitation or it also applies to Bluetooth.
Default_serial_device = '/dev/tty.NXT-DevB-1' # Not applicable to Windows.
Open_write_op         = 0x81
Write_op              = 0x83
Close_op              = 0x84
Delete_op             = 0x85
Get_versions_op       = 0x88
Get_battery_level_cmd = 0x0b
Error_codes = {
0x00 => 'Success',
# System operation error codes:
0x81 => 'No more handles ',
0x82 => 'No space',
0x83 => 'No more files',
0x84 => 'End of file expected',
0x85 => 'End of file',
0x86 => 'Not a linear file',
0x89 => 'No linear space',
0x8A => 'Undefined error',
0x8B => 'File is busy',
0x8C => 'No write buffers',
0x8D => 'Append not possible ',
0x8E => 'File is full',
0x8F => 'File exists',
0x91 => 'Out of boundary',
0x92 => 'Illegal file name',
0x93 => 'Illegal handle',
# Direct command error codes:
0x20 => 'Pending communication transaction in progress',
0x40 => 'Specified mailbox queue is empty',
0xBE => 'Unknown command opcode',
0xBF => 'Insane packet',
0xC0 => 'Data contains out-of-range values',
0xDD => 'Communication bus error',
0xDE => 'No free memory in communication buffer',
0xDF => 'Specified channel/connection is not valid',
0xE0 => 'Specified channel/connection not configured or busy',
0xEC => 'No active program',
0xED => 'Illegal size specified',
0xEE => 'Illegal mailbox queue ID specified',
0xEF => 'Attempted to access invalid field of a structure',
0xF0 => 'Bad input or output specified',
0xFB => 'Insufficient memory available',
}
# Global Variables:
\$total_written = 0
# Subroutines:
message = [message.size].pack('v') + message
p message if \$DEBUG
message.each_byte {|b| sp.putc b}
requested_op = message[3,1].unpack('C')[0]
flag = "\000"
if is_reply != 2 || op != requested_op || error != 0
if Error_codes[error] == 'File exists' && op == Open_write_op
flag = "\001"
else
msg = ""
message.each_byte {|b| msg += sprintf "%02x",b}
"#{error}(#{Error_codes[error] || 'unknown error'})" +
"\nfor message:#{msg}"
end
end
end
# Get the file name from the command line.
fn = ARGV[0]
if !fn
puts "No filename"
exit 1
end
fn += '.rxe' if fn !~ /\.\w+\$/ # Extension defaults to .rxe
# Prepare the file details.
fsize = contents.size
fnz = fn + "\x00" * (20 - fn.size) # Pad the filename with null bytes.
# Optionally get the serial device from the 2nd command line argument.
begin # Open the serial port
dev = ARGV[1] || Default_serial_device
sp = SerialPort.new(dev,57600,8,1,SerialPort::NONE)
sp.flow_control = SerialPort::HARD
if sp
print "Connected "
else
puts "Some unforeseen problem with the connection."
exit 1
end
rescue Errno::EBUSY
raise "Cannot connect to #{dev}.  The serial port is busy or unavailable."
end
begin # Block where the serial connection is open.
# Get the protocol and firmware versions.
message = [System_operation].pack('C') + [Get_versions_op].pack('C')
protocol_minor,protocol_major,firmware_minor,firmware_major =
puts "via protocol version: #{protocol_major}.#{protocol_minor} " +
"and firmware version: #{firmware_major}.#{firmware_minor}"
# Get the battery level.
message = [Direct_command].pack('C') + [Get_battery_level_cmd].pack('C')
puts "Battery level #{battery_level/1000.0} Volts."
# Open, write and close the file.
# Open the file for writing on the NXT.
message = [System_operation].pack('C') + [Open_write_op].pack('C') +
fnz + [fsize].pack('V')
if handle & 0x0100 != 0 # Test the flag set by error code 143
puts "(Deleting existing file first.)"
message[1,1] = [Delete_op].pack('C')
message[1,1] = [Open_write_op].pack('C')
if handle & 0x0100 != 0 # Test the flag set by error code 143
raise "Could not delete existing copy of this file."
end
end
# Write the file in blocks of Blocksize. Keep a running total
#    of the amount written from each reply.
i = 0
while i < fsize
j = i + Blocksize - 1
j = fsize - 1  if j > fsize - 1
message = [System_operation].pack('C') + [Write_op].pack('C') +
[handle].pack('C') + contents[i..j]
\$total_written += flashed;
print '.'
i = j + 1 # Move the pointer
end
# Close the file.
message = [System_operation].pack('C') + [Close_op].pack('C') +
[handle].pack('C')