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)
// 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.
}
// 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.
}
// 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();
}
// 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).
// 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();
}
// 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);
}
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.
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.
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.
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