NQC Programmer's Guide
Version 2.1 rev 1, written by Dave Baum
Introduction
NQC stands for Not Quite C, and is a simple language for programming the LEGO RCX. The preprocessor and control structures of NQC are very similar to C. NQC is not a general purpose language - there are many restrictions that stem from limitations of the standard RCX firmware.
Although NQC was created specifically for the RCX, there still is a logical separation between the NQC language and the RCX API used to control the RCX. This division tends to get blurred is a few special cases such as multi-tasking support. In general, the compiler implements the language, and a special NQC file defines the RCX API in terms of the primitives of the language itself.
This document describes the NQC language and the RCX API. In short, it provides the information needed to write NQC programs. Since there are several different interfaces for NQC, this document does not describe how to use an NQC implementation. Refer to the documentation provided with the NQC tool, such as the NQC User Manual for information specific to that implementation.
For up-to-date information and documentation for NQC, visit the NQC Web Site at http://www.enteract.com/~dbaum/nqc
Lexical Considerations
Comments
Two forms of comments are supported in NQC. The first form (traditional C comments) begin with /* and end with */. They may span multiple lines, but do not nest:
/* this is a comment */
/* this is a two
line comment */
/* another comment...
/* trying to nest...
ending the inner comment...*/
this text is no longer a comment! */
The second form of comments begins with // and ends with a newline (sometimes known as C++ style comments).
// a single line comment
Comments are ignored by the compiler. Their only purpose is to allow the programmer to document the source code.
Whitespace
Whitespace (spaces, tabs, and newlines) is used to separate tokens and to make programs more readable. As long as the tokens are distinguishable, adding or subtracting whitespace has no effect on the meaning of a program. For example, the following lines of code both have the same meaning:
x=2;
x = 2 ;
Some of the C++ operators consist of multiple characters. In order to preserve these tokens whitespace must not be inserted within them. In the example below, the first line uses a right shift operator ('>>'), but in the second line the added space causes the '>' symbols to be interpreted as two separate tokens and thus generate an error.
x = 1 >> 4; // set x to 1 right shifted by 4 bits
x = 1 > > 4; // error
Numerical Constants
Numerical constants may be written in either decimal or hexadecimal form. Decimal constants consist of one or more decimal digits. Hexadecimal constants start with 0x or 0X followed by one or more hexadecimal digits.
x = 10; // set x to 10
x = 0x10; // set x to 16 (10 hex)
Identifiers and Keywords
Identifiers are used for variable, task, and function names. The first character of an identifier must be an upper or lower case letter or the underscore ('_'). Remaining characters may be letters, numbers, an underscore.
A number of potential identifiers are reserved for use in the NQC language itself. These reserved words are call keywords and may not be used as identifiers. A complete list of keywords appears below:
__sensor
__type
abs
asm
break
case
const
continue
default
do
else
false
if
inline
int
repeat
return
sign
start
stop
sub
switch
task
true
void
while
Program Structure
An NQC program is composed of code blocks and global variables. There are three distinct types of code blocks: tasks, inline functions, and subroutines. Each type of code block has its own unique features and restrictions, but they all share a common structure.
Tasks
The RCX implicitly supports multi-tasking, thus an NQC task directly corresponds to an RCX task. Tasks are defined using the task keyword using the following syntax:
task name()
{
// the task's code is placed here
}
The name of the task may be any legal identifier. A program must always have at least one task - named "main" - which is started whenever the program is run. A program may also contain up to 9 additional tasks.
The body of a task consists of a list of statements. Tasks may be started and stopped using the start and stop statements (described in the section titled Statements). There is also an RCX API command, StopAllTasks, which stops all currently running tasks.
Inline Functions
It is often helpful to group a set of statements together into a single function, which can then be called as needed. NQC supports functions with arguments, but not return values. Functions are defined using the following syntax:
void name(argument_list)
{
// body of the function
}
The keyword void is an artifact of NQC's heritage - in C functions are specified with the type of data they return. Functions that do not return data are specified to return void. Returning data is not supported in NQC, thus all functions are declared using the void keyword.
The argument list may be empty, or may contain one or more argument definitions. An argument is defined by its type followed by its name. Multiple arguments are separated by commas. All values in the RCX are represented as 16 bit signed integers. However NQC supports four different argument types which correspond to different argument passing semantics and restrictions:
Type |
Meaning |
Restriction |
int |
pass by value |
none |
const int |
pass by value |
only constants may be used |
int& |
pass by reference |
only variables may be used |
const int & |
pass by reference |
function cannot modify argument |
Arguments of type int are passed by value from the calling function to the callee. This usually means that the compiler must allocate a temporary variable to hold the argument. There are no restrictions on the type of value that may be used. However, since the function is working with a copy of the actual argument, any changes it makes to the value will not be seen by the caller. In the example below, the function foo attempts to set the value of its argument to 2. This is perfectly legal, but since foo is working on a copy of the original argument, the variable y from main task remains unchanged.
void foo(int x)
{
x = 2;
}
task main()
{
int y = 1; // y is now equal to 1
foo(y); // y is still equal to 1!
}
The second type of argument, const int, is also passed by value, but with the restriction that only constant values (e.g. numbers) may be used. This is rather important since there are a number of RCX functions that only work with constant arguments.
void foo(const int x)
{
PlaySound(x); // ok
x = 1; // error - cannot modify argument
}
task main()
{
foo(2); // ok
foo(4*5); // ok - expression is still constant
foo(x); // error - x is not a constant
}
The third type, int &, passes arguments by reference rather than by value. This allows the callee to modify the value and have those changes visible in the caller. However, only variables may be used when calling a function using int & arguments:
void foo(int &x)
{
x = 2;
}
task main()
{
int y = 1; // y is equal to 1
foo(y); // y is now equal to 2
foo(2); // error - only variables allowed
}
The last type, const int &, is rather unusual. It is also passed by reference, but with the restriction that the callee is not allowed to modify the value. Because of this restriction, the compiler is able to pass anything (not just variables) to functions using this type of argument. In general this is the most efficient way to pass arguments in NQC.
There is one important difference between int arguments and const int & arguments. An int argument is passed by value, so in the case of a dynamic expression (such as a sensor reading), the value is read once then saved. With const int & arguments, the expression will be re-read each time it is used in the function:
void foo(int x)
{
if (x==x) // this will always be true
PlaySound(SOUND_CLICK);
}
void bar(const int x)
{
if (x==x) // may not be true..value could change
PlaySound(SOUND_CLICK);
}
task main()
{
foo(SENSOR_1); // will play sound
bar(2); // will play sound
bar(SENSOR_1); // may not play sound
}
Functions must be invoked with the correct number (and type) of arguments. The example below shows several different legal and illegal calls to function foo:
void foo(int bar, const int baz)
{
// do something here...
}
task main()
{
int x; // declare variable x
foo(1, 2); // ok
foo(x, 2); // ok
foo(2, x); // error - 2nd argument not constant!
foo(2); // error - wrong number of arguments!
}
NQC functions are always expanded as inline functions. This means that each call to a function results in another copy of the function's code being included in the program. Unless used judiciously, inline functions can lead to excessive code size.
Subroutines
Unlike inline functions, subroutines allow a single copy of some code to be shared between several different callers. This makes subroutines much more space efficient than inline functions, but due to some limitations in the RCX, subroutines have some significant restrictions. First of all, subroutines cannot use any arguments. Second, a subroutine cannot call another subroutine. Last, a maximum of 8 subroutines may be defined in a program. In addition, if the subroutine is called from multiple tasks then it cannot have any local variables (or temporary variables). These significant restrictions make subroutines less desirable than inline functions, therefore their use should be minimized to those situations where the resultant savings in code size is absolutely necessary. The syntax for a subroutine appears below:
sub name()
{
// body of subroutine
}
Variables
All variables in the RCX are of the same type - specifically 16 bit signed integers. The RCX supports up to 32 such variables. This pool of variables is utilized by NQC in several different ways. Variables are declared using the int keyword followed by a comma separated list of variable names and terminated by a semicolon (';'). Optionally, an initial value for each variable may be specified using an equals sign ('=') after the variable name. Several examples appear below:
int x; // declare x
int y,z; // declare y and z
int a=1,b; // declare a and b, initialize a to 1
Global variables are declared at the program scope (outside any code block). Once declared, they may be used within all tasks, functions, and subroutines. Their scope begins at declaration and ends at the end of the program.
Local variables may be declared within tasks, functions, and sometimes within subroutines. Such variables are only accessible within the code block in which they are defined. Specifically, their scope begins with their declaration and ends at the end of their code block. In the case of local variables, a compound statement (a group of statements bracketed by { and }) is considered a block:
int x; // x is global
task main()
{
int y; // y is local to task main
x = y; // ok
{ // begin compound statement
int z; // local z declared
y = z; // ok
}
y = z; // error - z no longer in scope
}
task foo()
{
x = 1; // ok
y = 2; // error - y is not global
}
In many cases NQC must allocate one or more temporary variables for its own use. In some cases a temporary variable is used to hold an intermediate value during a calculation. In other cases it is used to hold a value as it is passed to a function. These temporary variables deplete the pool of available variables in the RCX. NQC attempts to be as efficient as possible with temporary variables (including reusing them when possible).
Statements
The body of a code block (task, function, or subroutine) is composed of statements. Statements are terminated with a semi-colon (';').
Variable Delcaration
Variable declaration, as described in the previous section, is one type of statement. It declares a local variable (with optional initialization) for use within the code block. The syntax for a variable declaration is:
int variables;
where variables is a comma separated list of names with optional initial values:
name
[=expression]Assignment
Once declared, variables may be assigned the value of an expression:
variable
assign_operator expression;There are nine different assignment operators. The most basic operator, '=', simply assigns the value of the expression to the variable. The other operators modify the variable's value in some other way as shown in the table below
Operator |
Action |
= |
Set variable to expression |
+= |
Add expression to variable |
-= |
Subtract expression from variable |
*= |
Multiple variable by expression |
/= |
Divide variable by expression |
&= |
Bitwise AND expression into variable |
|= |
Bitwise OR expression into variable |
||= |
Set variable to absolute value of expression |
+-= |
Set variable to sign (-1,+1,0) of expression |
Some examples:
x = 2; // set x to 2
y = 7; // set y to 7
x += y; // x is 9, y is still 7
Control Structures
The simplest control structure is a compound statement. This is a list of statements enclosed within curly braces ('{' and '}'):
{
x = 1;
y = 2;
}
Although this may not seem very significant, it plays a crucial role in building more complicated control structures. Many control structures expect a single statement as their body. By using a compound statement, the same control structure can be used to control multiple statements.
The if statement evaluates a condition. If the condition is true it executes one statement (the consequence). An optional second statement (the alternative) is executed if the condition is false. The two syntaxes for an if statement is shown below.
if (condition) consequence
if (condition) consequence else alternative
Note that the condition is enclosed in parentheses. Examples are shown below. Note how a compound statement is used in the last example to allow two statements to be executed as the consequence of the condition.
if (x==1) y = 2;
if (x==1) y = 3; else y = 4;
if (x==1) { y = 1; z = 2; }
The while statement is used to construct a conditional loop. The condition is evaluated, and if true the body of the loop is executed, then the condition is tested again. This process continues until the condition becomes false (or a break statement is executed). The syntax for a while loop appears below:
while (condition) body
It is very common to use a compound statement as the body of a loop:
while(x < 10)
{
x = x+1;
y = y*2;
}
A variant of the while loop is the do-while loop. Its syntax is:
do body while (condition)
The difference between a while loop and a do-while loop is that the do-while loop always executes the body at least once, whereas the while loop may not execute it at all.
The repeat statement executes a loop a specified number of times:
repeat (expression) body
The expression determines how many times the body will be executed. Note that it is only evaluated a single time, then the body is repeated that number of times. This is different from both the while and do-while loops which evaluate their condition each time through the loop.
A switch statement can be used to execute one of several different blocks of code depending on the value of an expression. Each block of code is preceded by one or more case labels. Each case must be a constant and unique within the switch statement. The switch statement evaluates the expression then looks for a matching case label. It will then execute any statements following the matching case until either a break statement or the end of the switch is reaches. A single default label may also be used - it will match any value not already appearing in a case label. Technically, a switch statement has the following syntax:
switch (expression) body
The case and default labels are not statements in themselves - they are labels that precede statements. Multiple labels can precede the same statement. These labels have the following syntax
case constant_expression :
default :
A typical switch statement might look like this:
switch(x)
{
case 1:
// do something when X is 1
break;
case 2:
case 3:
// do something else when x is 2 or 3
break;
default:
// do this when x is not 1, 2, or 3
break;
}
NQC also defines the until macro which provides a convenient alternative to the while loop. The actual definition of until is:
#define until(c) while(!(c))
In other words, until will continue looping until the condition becomes true. It is most often used in conjunction with an empty body statement:
until(SENSOR_1 == 1); // wait for sensor to be pressed
Other Statements
A function (or subroutine) call is a statement of the form:
name
(arguments);The arguments list is a comma separated list of expressions. The number and type of arguments supplied must match the definition of the function itself.
Tasks may be started or stopped with the following statements:
start task_name;
stop task_name;
Within loops (such as a while loop) the break statement can be used to exit the loop and the continue statement can be used to skip to the top of the next iteration of the loop.
break;
continue;
It is possible to cause a function to return before it reaches the end of its code using the return statement.
return;
Any expression is also a legal statement when terminated by a semicolon. It is rare to use such a statement since the value of the expression would then be discarded. The one notable exception is expressions involving the increment (++) or decrement (--) operators.
x++;
The empty statement (just a bare semicolon) is also a legal statement.
Expressions and Conditions
In C there is no distinction between expressions and conditions. However, within NQC they are two syntactically different entities. The legal operations for expressions cannot be used on conditions and vice versa.
Expressions can be assigned to variables, used as arguments in a function, or as the count in a repeat statement. Conditions are used in most of the conditional control structures (if, while, etc.).
Expressions
Values are the most primitive type of expressions. More complicated expressions are formed from values using various operators. The NQC language only has two built in kinds of values: numerical constants and variables. The RCX API defines other values corresponding to various RCX features such as sensors and timers.
Numerical constants in the RCX are represented as 16 bit signed integers. NQC internally uses 32 bit signed math for constant expression evaluation, then reduces to 16 bits when generating RCX code. Numeric constants can be written as either decimal (e.g. 123) or hexadecimal (e.g. 0xABC). Presently, there is very little range checking on constants, so using a value larger than expected may have unusual effects.
Values may be combined using operators. Several of the operators may only be used in evaluating constant expressions, which means that their operands must either be constants, or expressions involving nothing but constants. The operators are listed here in order of precedence (highest to lowest).
Operator |
Description |
Associativity |
Restriction |
Example |
abs() sign() |
Absolute value Sign of operand |
n/a n/a |
abs(x) sign(x) |
|
++ -- |
Increment Decrement |
left left |
variables only variables only |
x++ or ++x x-- or --x |
- ~ |
Unary minus Bitwise negation (unary) |
right right |
constant only |
-x ~123 |
* / % |
Multiplication Division Modulo |
left left left |
constant only |
x * y x / y 123 % 4 |
+ - |
Addition Subtraction |
left left |
x + y x - y |
|
<< >> |
Left shift Right shift |
left left |
constant only constant only |
123 << 4 123 >> 4 |
& |
Bitwise AND |
left |
x & y |
|
^ |
Bitwise XOR |
left |
constant only |
123 ^ 4 |
| |
Bitwise OR |
left |
x | y |
|
&& |
Logical AND |
left |
constant only |
123 && 4 |
|| |
Logical OR |
left |
constant only |
123 || 4 |
Where needed, parentheses may be used to change the order of evaluation:
x = 2 + 3 * 4; // set x to 14
y = (2 + 3) * 4; // set y to 20
Conditions
Conditions are generally formed by comparing two expressions. There are also two constant conditions - true and false - which always evaluate to true or false respectively. A condition may be negated with the negation operator, or two conditions combined with the AND and OR operators. The table below summarizes the different types of conditions.
Condition |
Meaning |
true |
always true |
false |
always false |
expr1 == expr2 |
true if expr1 equals expr2 |
expr1 != expr2 |
true if expr1 is not equal to expr2 |
expr1 < expr2 |
true if one expr1 is less than expr2 |
expr1 <= expr2 |
true if expr1 is less than or equal to expr2 |
expr1 > expr2 |
true if expr1 is greater than expr2 |
expr1 >= expr2 |
true if expr1 is greater than or equal to expr2 |
! condition |
logical negation of a condition - true if condition is false |
cond1 && cond2 |
logical AND of two conditions (true if and only if both conditions are true) |
cond1 || cond2 |
logical OR of two conditions (true if and only if at least one of the conditions are true) |
The Preprocessor
The preprocessor implements the following directives: #include, #define, #ifdef, #ifndef, #if, #elif, #else, #endif, #undef. Its implementation is fairly close to a standard C preprocessor, so most things that work in a generic C preprocessor should have the expected effect in NQC. Significant deviations are listed below.
#include
The #include command works as expected, with the caveat that the filename must be enclosed in double quotes. There is no notion of a system include path, so enclosing a filename in angle brackets is forbidden.
#include "foo.nqh" // ok
#include <foo.nqh> // error!
#define
The #define command is used for simple macro substitution. Redefinition of a macro is an error (unlike in C where it is a warning). Macros are normally terminated by the end of the line, but the newline may be escaped with the backslash ('\') to allow multi-line macros:
#define foo(x) do { bar(x); \
baz(x); } while(false)
The #undef directive may be used to remove a macros definition.
Conditional Compilation
Conditional compilation works similar to the C preprocessor. The following preprocessor directives may be used:
#if condition
#ifdef symbol
#ifndef symbol
#else
#elif condition
#endif
Conditions in #if directives use the same operators and precedence as in C. The defined() operator is supported.
Program Initialization
The compiler will insert a call to a special initialization function, _init, at the start of a program. This default function is part of the RCX API and sets all three outputs to full power in the forward direction (but still turned off). The initialization function can be disabled using the #pragma noinit directive:
#pragma noinit // don't do any program initialization
The default initialization function can be replaced with a different function using the #pragma init directive.
#pragma init function // use custom initialization
RCX API
The RCX API defines a set of constants, functions, values, and macros that provide access to features of the RCX such as sensors and outputs. The RCX API is defined by a system include file that is generally included before any source code (unless the -n option is used for NQC). There are two versions of the system include file: rcx1.nqh and rcx2.nqh. The first file contains the old version of the API used by NQC 1.x compilers. The second file describes the current 2.x version of the API. Both system include files are contained within the compiler itself, however they are also included as separate files in the NQC distribution for reference purposes.
Sensors
The names SENSOR_1, SENSOR_2, and SENSOR_3 are used to identify the RCX's sensor ports. Before a sensor's value can be read, it must be configured properly. A sensor has two different settings: its type and its mode. The type determines how the RCX reads the sensor electrically, while the mode determines how the sensors value is interpreted. For some sensors types only one mode makes sense, but for others (such as the temperature sensor) it can be read equally well in multiple modes (e.g. Fahrenheit and Celsius). The type and mode may be set using SetSensorType(sensor, type) and SetSensorMode(sensor, mode).
SetSensorType(SENSOR_1, SENSOR_TYPE_LIGHT);
SetSensorMode(SENSOR_1, SENSOR_MODE_PERCENT);
For convenience both the mode and type may be set using the SetSensor(sensor, configuration) command. This is the easiest and most common way to configure a sensor.
SetSensor(SENSOR_1, SENSOR_LIGHT);
SetSensor(SENSOR_2, SENSOR_TOUCH);
Valid constants for a sensor's type, mode, and configuration are given below.
Sensor Type |
Meaning |
SENSOR_TYPE_TOUCH |
a touch sensor |
SENSOR_TYPE_TEMPERATURE |
a temperature sensor |
SENSOR_TYPE_LIGHT |
a light sensor |
SENSOR_TYPE_ROTATION |
a rotation sensor |
Sensor Mode |
Meaning |
SENSOR_MODE_RAW |
raw value from 0 to 1023 |
SENSOR_MODE_BOOL |
boolean value (0 or 1) |
SENSOR_MODE_EDGE |
counts number of boolean transitions |
SENSOR_MODE_PULSE |
counts number of boolean periods |
SENSOR_MODE_PERCENT |
value from 0 to 100 |
SENSOR_MODE_FAHRENHEIT |
degrees F |
SENSOR_MODE_CELSIUS |
degrees C |
SENSOR_MODE_ROTATION |
rotation (16 ticks per revolution) |
Sensor Configuration |
Type |
Mode |
SENSOR_TOUCH |
SENSOR_TYPE_TOUCH |
SENSOR_MODE_BOOL |
SENSOR_LIGHT |
SENSOR_TYPE_LIGHT |
SENSOR_MODE_PERCENT |
SENSOR_ROTATION |
SENSOR_TYPE_ROTATION |
SENSOR_MODE_ROTATION |
SENSOR_CELSIUS |
SENSOR_TYPE_TEMPERATURE |
SENSOR_MODE_CELSIUS |
SENSOR_FAHRENHEIT |
SENSOR_TYPE_TEMPERATURE |
SENSOR_MODE_FAHRENHEIT |
SENSOR_PULSE |
SENSOR_TYPE_TOUCH |
SENSOR_MODE_PULSE |
SENSOR_EDGE |
SENSOR_TYPE_TOUCH |
SENSEO_MODE_EDGE |
A sensor's value can be read by using its name within an condition. For example, the following code checks to see if the value of sensor 1 is greater than 20:
if (SENSOR_1 > 20)
// do something...
Some sensor types (such as SENSOR_TYPE_ROTATION) allow you to reset the sensor's internal counter with the following command:
ClearSensor(expression sensor);
Outputs
The names OUT_A, OUT_B, and OUT_C are used to identify the RCX's three outputs. All of the commands to control outputs can work on multiple outputs at the same time. In order to specify more than one output for a command, add the names of the outputs together. For example, use "OUT_A + OUT_B" to specify outputs A and B together.
Each output has three different attributes: mode, direction, and power level. The mode can be set with the SetOutput(outputs, mode) command. The mode parameter should be one of the following constants:
Output Mode |
Meaning |
OUT_OFF |
output is off (motor is prevented from turning) |
OUT_ON |
output is on (motor will be powered) |
OUT_FLOAT |
motor can "coast" |
The other two attributes, direction and power level, may be set at any time, but only have an effect when the output is on. The direction is set with the SetDirection(outputs, direction) command. The direction parameter should be one of the following constants:
Direction |
Meaning |
OUT_FWD |
Set to forward direction |
OUT_REV |
Set to reverse direction |
OUT_TOGGLE |
Switch direction to the opposite of what it is presently |
The power level can range 0 (lowest) to 7 (highest). The names OUT_LOW, OUT_HALF, and OUT_FULL are defined for use in setting power level. The level is set using the SetPower(outputs, power) command.
Be default, all three motors are set to full power and the forward direction (but still turned off) when a program starts.
Since control of outputs is such a common feature of programs, a number of convenience functions are provided that make it easier to work with the outputs. It should be noted that these commands do not provide any new functionality above the SetOutput and SetDirection commands. They are merely convenient ways to make programs more concise.
Command |
Action |
On(outputs) |
turns motors on |
Off(outputs) |
turns motors off |
Float(outputs) |
makes outputs "float" |
Fwd(outputs) |
sets outputs to forward direction |
Rev(outputs) |
sets outputs to reverse direction |
Toggle(outputs) |
toggles direction of outputs |
OnFwd(outputs) |
sets direction to forward, then turns on |
OnRev(outputs) |
sets direction to reverse, then turns on |
OnFor(outputs, time) |
turns outputs on for specified amount of time (in 100ths of a second) |
Some examples of using the output commands are shown below:
OnFwd(OUT_A); // turn on A in the forward direction
OnRev(OUT_B); // turn on B in the reverse direction
Toggle(OUT_A + OUT_B); // flip directions of A and B
Off(OUT_A + OUT_B); // turn off A and B
OnFor(OUT_C, 100); // turn on C for 1 second
All of the output functions require constants for their arguments with the following exceptions:
OnPower - an expression may be used for the power level
OnFor - and expression may be used for the time
Miscellaneous
Wait
(time) - Make a task sleep for specified amount of time (in 100ths of a second). The time argument may be an expression or a constant:Wait(100); // wait 1 second
Wait(Random(100)); // wait random time up to 1 second
PlaySound
(sound) - Play one of the 6 preset RCX sounds. The sound argument must be a constant. The following constants are pre-defined for use with PlaySound: SOUND_CLICK, SOUND_DOUBLE_BEEP, SOUND_DOWN, SOUND_UP, SOUND_LOW_BEEP, SOUND_FAST_UP.PlaySound(SOUND_CLICK);
PlayTone
(frequency, duration) - Play a single tone of the specified frequency and duration. Both arguments must be constant. The frequency is in Hz, the duration is in 100ths of a second.PlayTone(440, 50); // Play 'A' for one half second
SelectDisplay
(mode) - Select which mode the LCD display should use. There are seven different display modes as shown below. The RCX defaults to DISPLAY_WATCH.
Mode |
LCD Contents |
DISPLAY_WATCH |
show the system "watch" |
DISPLAY_SENSOR_1 |
show value of sensor 1 |
DISPLAY_SENSOR_2 |
show value of sensor 2 |
DISPLAY_SENSOR_3 |
show value of sensor 3 |
DISPLAY_OUT_A |
show setting for output A |
DISPLAY_OUT_B |
show setting for output B |
DISPLAY_OUT_C |
show setting for output C |
SelectDisplay(DISPLAY_SENSOR_1); // view sensor 1
ClearMessage()
- Clear the message buffer for RCX to RCX communication. This allows detection of the next received IR message. The Message() expression may be used to read the current contents of the receive buffer.ClearMessage(); // clear out the received message
until(Message() > 0); // wait for next message
SendMessage
(message) - Send an IR message to another RCX. 'message' may be any expression, but the RCX can only send messages with a value between 0 and 255, so only the lowest 8 bits of the argument are used.SendMessage(3); // send message 3
SendMessage(259); // another way to send message 3
SetWatch
(hours, minutes) - Set the system watch to the specified number of hours and minutes. Hours must be a constant between 0 and 23 inclusive. Minutes must be a constant between 0 and 59 inclusive.SetWatch(3, 15); // set watch to 3:15
ClearTimer
(timer) - Clear one of the RCX's four internal timers. The timer parameter must be a constant between 0 and 3 inclusive.ClearTimer(0); // clear the first RCX timer
StopAllTasks
() - Stop all currently running tasks. This will halt the program completely, so any code following this command will be ignored.StopAllTasks(); // stop the program
SetTxPower
(power) - Set the power level for the RCX's IR transmitter. The power level should be either TX_POWER_LO or TX_POWER_HI.SetTxPower(TX_POWER_LO); // set IR to lower power
Data Logging
The RCX contains a datalog which can be used to store readings from sensors, timers, variables, and the system watch. Before adding data, the datalog first needs to be created using the CreateDatalog(size) command. The 'size' parameter must be a constant and determines how many data points the datalog can hold.
CreateDatalog(100); // datalog for 100 points
Values can then be added to the datalog using AddToDatalog(value). When the datalog is uploaded to a computer it will show both the value itself and the source of the value (timer, variable, etc). The datalog directly supports the following data sources: timers, sensor values, variables, and the system watch. Other data types (such as a constant or random number) may also be logged, but in this case NQC will first move the value into a variable and then log the variable. The values will still be captured faithfully in the datalog, but the sources of the data may be a bit misleading.
AddToDatalog(Timer(0)); // add timer 0 to datalog
AddToDatalog(x); // add variable 'x'
AddToDatalog(7); // add 7 - will look like a variable
The RCX itself cannot read values back out of the datalog. The datalog must be uploaded to a host computer . The specifics of uploading the datalog depend on the NQC environment being used. For example, in the command line version of NQC, the following commands will upload and print the datalog:
nqc -datalog
nqc -datalog_full
Values
The RCX has several different sources of data: sensors, variables, timers, etc. Within NQC, these data sources can be accessed using special expressions.
The RCX contains four timers which measure time in increments of a tenth of a second (100ms). To read the value of a timer, use the Timer(n) expression where n is a constant between 0 and 3 and specifies which timer to read.
Sensors have a number of different values associated with them. The sensor's raw value, its boolean value, or its normal value can be read. In addition, it is possible for a program to read a sensor's configured type and mode. All of the expressions for sensor values require an argument specifying which sensor to use. This argument should be between 0 and 2. Note that the names SENSOR_1, SENSOR_2, and SENSOR_3 are really just macros for SensorValue(n) expressions:
#define SENSOR_1 SensorValue(0)
#define SENSOR_2 SensorValue(1)
#define SENSOR_3 SensorValue(2)
The RCX API also provides expressions to generate a random number, read the system watch, or read the last received IR message. All of the RCX API expressions are summarized below:
Expression |
Meaning |
Timer(n) |
value of timer n |
Random(n) |
random number between 0 and n |
Watch() |
value of system watch (time in minutes) |
Message() |
value of last IR message received |
SensorValue(n) |
value of sensor n |
SensorType(n) |
type of sensor n |
SensorMode(n) |
mode of sensor n |
SensorValueRaw(n) |
raw value of sensor n |
SensorValueBool(n) |
boolean value of sensor n |
Appendix A - Quick Reference
Statements
Statment |
Description |
while (cond) body |
Execute body zero or more times while condition is true |
do body while (cond) |
Execute body one or more times while condition is true |
until (cond) body |
Execute body zero or more times until condition is true |
break |
Break out from while/do/until body |
continue |
Skip to next iteration of while/do/until body |
repeat (expression) body |
Repeat body a specified number of times |
switch (expression) body |
Execute alternatives within body based on value of expression |
if (cond) stmt1 if (cond) stmt1 else stmt2 |
Execute stmt1 if condition is true. Execute stmt2 (if present) if condition is false. |
start task_name |
Start the specified task |
stop task_name |
Stop the specified task |
function (args) |
Call a function using the supplied arguments |
var = expression |
Evaluate expression and assign to variable |
var += expression |
Evaluate expression and add to variable |
var -= expression |
Evaluate expression and subtract from variable |
var *= expression |
Evaluate expression and multiply into variable |
var /= expression |
Evaluate expression and divide into variable |
var |= expression |
Evaluate expression and perform bitwise OR into variable |
var &= expression |
Evaluate expression and perform bitwise AND into variable |
return |
Return from function to the caller |
expression |
Evaluate expression |
Conditions
Conditions are used within control statements to make decisions. In most cases, the condition will involve a comparison between expressions.
Condition |
Meaning |
true |
always true |
false |
always false |
expr1 == expr2 |
true if expressions are equal |
expr1 != expr2 |
true if expressions are not equal |
expr1 < expr2 |
true if expr1 is less than expr2 |
expr1 <= expr2 |
true if expr1 is less than or equal to expr2 |
expr1 > expr2 |
true if expr1 is greater than expr2 |
expr1 >= expr2 |
true if expr1 is greater than or equal to expr2 |
! condition |
logical negation of a condition |
cond1 && cond2 |
logical AND of two conditions (true if and only if both conditions are true) |
cond1 || cond2 |
logical OR of two conditions (true if and only if at least one of the conditions is true) |
Expressions
There are a number of different values that can be used within expressions including constants, variables, and sensor values. Note that SENSOR_1, SENSOR_2, and SENSOR_3 are macros that expand to SensorValue(0), SensorValue(1), and SensorValue(2) respectively.
Value |
Description |
number |
A constant value (e.g. 123) |
variable |
A named variable (e.g x) |
Timer(n) |
Value of timer n, where n is between 0 and 3 |
Random(n) |
Random number between 0 and n |
SensorValue(n) |
Current value of sensor n, where n is between 0 and 2 |
Watch() |
Value of system watch |
Message() |
Value of last received IR message |
Values may be combined by using operators. Several of the operators may only be used in evaluating constant expressions, which means that their operands must be either constants or expressions involving nothing but constants. The operators are listed here in order of precedence (highest to lowest).
Operator |
Description |
Associativity |
Restriction |
Example |
abs() sign() |
Absolute value Sign of operand |
n/a n/a |
none none |
abs(x) sign(x) |
++ -- |
Increment Decrement |
left left |
variables only variables only |
x++ or ++x x-- or --x |
- ~ |
Unary minus Bitwise negation (unary) |
right right |
none constant only |
-x ~123 |
* / % |
Multiplication Division Modulo |
left left left |
none none constant only |
x * y x / y 123 % 4 |
+ - |
Addition Subtraction |
left left |
none none |
x + y x - y |
<< >> |
Left shift Right shift |
left left |
constant only constant only |
123 << 4 123 >> 4 |
& |
Bitwise AND |
left |
none |
x & y |
^ |
Bitwise XOR |
left |
constant only |
123 ^ 4 |
| |
Bitwise OR |
left |
none |
x | y |
&& |
Logical AND |
left |
constant only |
123 && 4 |
|| |
Logical OR |
left |
constant only |
123 || 4 |
RCX Functions
Most of the functions require all arguments to be constant expressions (numbers or operations involving other constant expressions). The exceptions are functions that use a sensor as an argument and those that can use any expression. In the case of sensors, the argument should be a sensor name: SENSOR_1, SENSOR_2, or SENSOR_3. In some cases there are predefined names (e.g. SENSOR_TOUCH) for appropriate constants.
Function |
Description |
Example |
SetSensor(sensor, config) |
Configure a sensor. |
SetSensor(SENSOR_1, SENSOR_TOUCH) |
SetSensorMode(sensor, mode) |
Set sensor's mode |
SetSensor(SENSOR_2, SENSOR_MODE_PERCENT) |
SetSensorType(sensor, type) |
Set sensor's type |
SetSensor(SENSOR_2, SENSOR_TYPE_LIGHT) |
ClearSensor(sensor) |
Clear a sensor's value |
ClearSensor(SENSOR_3) |
On(outputs) |
Turn on one or more outputs |
On(OUT_A + OUT_B) |
Off(outputs) |
Turn off one or more outputs |
Off(OUT_C) |
Float(outputs) |
Let the outputs float |
Float(OUT_B) |
Fwd(outputs) |
Set outputs to forward direction |
Fwd(OUT_A) |
Rev(outputs) |
Set outputs to reverse direction |
Rev(OUT_B) |
Toggle(outputs) |
Flip the direction of outputs |
Toggle(OUT_C) |
OnFwd(outputs) |
Turn on in forward direction |
OnFwd(OUT_A) |
OnRev(outputs) |
Turn on in reverse direction |
OnRev(OUT_B) |
OnFor(outputs, time) |
Turn on for specified number of 100ths of a second. Time may be an expression. |
OnFor(OUT_A, x) |
SetOutput(outputs, mode) |
Set output mode |
SetOutput(OUT_A, OUT_ON) |
SetDirection(outputs, dir) |
Set output direction |
SetDirection(OUT_A, OUT_FWD) |
SetPower(outputs, power) |
Set output power level (0-7). Power may be an expression. |
SetPower(OUT_A, x) |
Wait(time) |
Wait for the specified amount of time in 100ths of a second. Time may be an expression. |
Wait(x) |
PlaySound(sound) |
Play the specified sound (0-5). |
PlaySound(SOUND_CLICK) |
PlayTone(freq, duration) |
Play a tone of the specified frequency for the specified amount of time (in 10ths of a second) |
PlayTone(440, 5) |
ClearTimer(timer) |
Reset timer (0-3) to value 0 |
ClearTimer(0) |
StopAllTasks() |
Stop all currently running tasks |
StopAllTasks() |
SelectDisplay(mode) |
Select one of 7 display modes: 0: system watch, 1-3: sensor value, 4-6: output setting. Mode may be an expression. |
SelectDisplay(1) |
SendMessage(message) |
Send an IR message (1-255). Message may be an expression. |
SendMessage(x) |
ClearMessage() |
Clear the IR message buffer |
ClearMessage() |
CreateDatalog(size) |
Create a new datalog of the given size |
CreateDatalog(100) |
AddToDatalog(value) |
Add a value to the datalog. The value may be an expression. |
AddToDatalog(Timer(0)) |
SetWatch(hours, minutes) |
Set the system watch value |
SetWatch(1,30) |
SetTxPower(hi_lo) |
Set the infrared transmitter power level to low or high power |
SetTxPower(TX_POWER_LO) |
RCX Constants
Many of the values for RCX functions have named constants that can help make code more readable. Where possible, use a named constant rather than a raw value.
Sensor configurations for SetSensor() |
SENSOR_TOUCH, SENSOR_LIGHT, SENSOR_ROTATION, SENSOR_CELSIUS, SENSOR_FAHRENHEIT, SENSOR_PULSE, SENSOR_EDGE |
Modes for SetSensorMode() |
SENSOR_MODE_RAW, SENSOR_MODE_BOOL, SENSOR_MODE_EDGE, SENSOR_MODE_PULSE, SENSOR_MODE_PERCENT, SENSOR_MODE_CELSIUS, SENSOR_MODE_FAHRENHEIT, SENSOR_MODE_ROTATION |
Types for SetSensorType() |
SENSOR_TYPE_TOUCH, SENSOR_TYPE_TEMPERATURE, SENSOR_TYPE_LIGHT, SENSOR_TYPE_ROTATION |
Outputs for On(), Off(), etc. |
OUT_A, OUT_B, OUT_C |
Modes for SetOutput() |
OUT_ON, OUT_OFF, OUT_FLOAT |
Directions for SetDirection() |
OUT_FWD, OUT_REV, OUT_TOGGLE |
Output power for SetPower() |
OUT_LOW, OUT_HALF, OUT_FULL |
Sounds for PlaySound() |
SOUND_CLICK, SOUND_DOUBLE_BEEP, SOUND_DOWN, SOUND_UP, SOUND_LOW_BEEP, SOUND_FAST_UP |
Modes for SelectDisplay() |
DISPLAY_WATCH, DISPLAY_SENSOR_1, DISPLAY_SENSOR_2, DISPLAY_SENSOR_3, DISPLAY_OUT_A, DISPLAY_OUT_B, DISPLAY_OUT_C |
Tx power level for SetTxPower() |
TX_POWER_LO, TX_POWER_HI |
Keywords
Keywords are those words reserved by the NQC compiler for the language itself. It is an error to use any of these as the names of functions, tasks, or variables.
__sensor
abs
asm
break
const
continue
do
else
false
if
inline
int
repeat
return
sign
start
stop
sub
task
true
void
while