/* Welcome to Not-Quite-C, a version of C for writing software for the RCX, or LEGO Programmable Brick. This file, Hello.nqc is a template NQC program. It includes all the basic functions, variables, and tasks you need to write an NQC program. Also included are descriptions of each line of code (or comments) so that you can modify Hello.nqc to write many different types of programs. */ // This is a comment, or a note to yourself and others about what your program is doing. // The symbol // indicates that all text until the end of line (return) is a comment. // If your comment is longer than a line, you must start the next line with the // symbol /* Alternatively, you can indicate the start of a multi-line comment (like the first paragraph at the start of this file) with the symbol /*. All multi-line comments end with the symbol */ /* An NQC program is composed of a series of TASKS, or series of actions that the RCX should perform. NQC lets you define several tasks in one program because your RCX is capable of Multi-tasking (or doing more than one action at the same time). All NQC programs must have at least one task -- the MAIN task. */ task main () { // the primary task performed by the RCX // we use pairs of braces {} to link together code belong to the same code block // the area between the braces in a task is called the task BODY // the first thing we do in the body is define any variables we might need // the RCX can only deal with integers so the only variable types you can use are // those of type "int" // NOTE: These lines are indented further than the line containing task main() { // this is because these lines are the body of the code block. Every time // we create a new code block, we create a new level, or SCOPE, within our // code. To make clear which code is in which scope, we use indentation. // Everytime we create a new scope, we indent further, every time // we exit a scope, we return to the previous scope's level of indentation! int time = 500; // an integer variable named "wait" -- to store a length of time // NOTE: time in NQC is calculated in hundreths of a second // so if I set time = 500, it is the same as saying 5 secs int speed = 3; // an integer variable used to store a power level for the motor int SPEED = 7; // an integer variable used to stor a power level for the motor // NOTE: NQC, like most other programming lanuages is TYPE // SENSITIVE. Therefore, the variable "speed" is different // from the variable "SPEED". // Pay attention to your case when writing code: if a function // is named SetSensorType, typing setsensortype will not work! // NOTE: Each line of code ends with a semi-colon (;). This indicates an // end-of-line or an end of the command. You must use a semi-colon at // the e end of any line that does not start or end a code block (code block // beginning and ends are indicated by {}) // second, initialize the robot mode: what types of sensors are you using? // An RCX can have up to three sensors: SENSOR_1, SENSOR_2, and SENSOR_3 // Let's set the sensor type using the FUNCTION SetSensorType: SetSensorType (SENSOR_1, SENSOR_TYPE_TOUCH); // make sensor 1 a touch sensor SetSensorType (SENSOR_2, SENSOR_TYPE_LIGHT); // make sensor 2 a light sensor // Set SensorType takes two parameters (or variables input into a function): // the sensor you wish to initialize and the type of sensor it is. It then outputs // that information to the RCX. We call this a FUNCTION because it MAPS one type // of information into another (just like a mathematical function). // you can also set the sensor mode SetSensorMode (SENSOR_1, SENSOR_MODE_BOOL); // set the sensor mode to BOOLEAN // if SENSOR_1 is activated, it will // send a TRUE, otherwise, // it will send a FALSE SetSensorMode (SENSOR_2, SENSOR_MODE_PERCENT); // this sets the sensor mode to percent // it will scale the sensor reading // from 1-100 // There are many other types of sensor modes and types. Play with them to see // how they work. // NOTE: By now you have may noticed that the BRICX text editor uses several different // types of colors to distinguish different types of text. // COMMENTS are in gray and are italicized // KEYWORDS, or special words pre-defined by the programming language are in black // FUNCTIONS are in blue // INTEGER VALUES are in red // CONSTANTS, or special variables whose values cannot be changed are in green // third write your program! // An RCX can have up to three outputs: OUT_A, OUT_B, and OUT_C. Let's plug motors // into OUT_A and OUT_B and try a few motor commands OnFwd (OUT_A); // turns OUT_A on, in the forward direction OnFwd (OUT_B); // turns OUT_B on, in the forward direction // We can change the speed at which the motor is running by using the // function SetPower: SetPower (OUT_A, OUT_FULL); // sets the speed of OUT_A to full speed // Speeds can be set to OUT_LOW, OUT_HALF, and OUT_FULL, // or to an integer between 0 and 7. For example: SetPower(OUT_B, 3); // sets the speed of OUT_B to 3 // We could also use the speed variable we created earlier: SetPower(OUT_A, speed); // sets the speed of OUT_A to // the value of the variable "speed" /* Why would we prefer to use a variable instead of a number? Well so that we can re-use the same number over and over again and know why we are using that number or so that we can change the value depending on the situation without having to write a lot of extra code. For example, if I had written the following SetPower(OUT_A, speed); SetPower(OUT_B, speed); and decided I wanted to move them at a power of 5 instead, I could just go to the definition of speed, and change it to 5: int speed = 5; If I defined them as SetPower(OUT_A, 3) and SetPower(OUT_B, 3), I would have to change two lines of code instead of 1. What if we had 100 places where we needed to change the speed? Which way would you prefer? */ // We can run a motor in reverse as well: OnRev (OUT_A); // turns OUT_A on, in reverse direction // We can turn an output on for a set amount of time OnFor (OUT_A, time); // run OUT_A for the amount of time stored in "time" OnFor(OUT_B, 500); // run OUT_B for 500 ms // Let's Turn the motors back on: OnFwd(OUT_A); // Turn on OUT_A OnFwd(OUT_B); // turn on OUT_B // Now you probably haven't been able to see much of these changes in speed because // We haven't provided them sufficient pause between commands. To do this we can use // the function Wait. For example: Wait(time); // Wait for the value stored in the variable "time" // Let's just make sure there is a difference between our two speed variables: SetPower(OUT_A, speed); // set OUT_A to the power specified by "speed" SetPower(OUT_B, SPEED); // set OUT_B to the power specified by "SPEED" Wait(time); // wait for the value of time // Finally, we can turn the motors off: Off (OUT_B); // turn off OUT_B // Or, Float to a stop: Float(OUT_A); // gradually slow down (float) OUT_A until it stops // Let's change value of the variable time. We could just go back up to the // defintion of time (at the beginning of the task) and change it from // int time = 500; // to // int time = 100; // That would change the value from 500 to 100, but it would me that everywhere // we used the variable "time" in the program, its value would be 100 ms. What // if I wanted the value to be 500 for the first half of the program and 100 for // the second half? Well, I could create two variables, like we did for the speed. // That, however, could get confusing if I wanted 5 different values for time. Our // other option is to reassign a value to the variable, as follows: time = 100; // set the value of time to 100 // Now let's use the PlaySound function to create some noise. There are six parameters // you can pass to the function to change the type of sound the RCX makes: PlaySound (SOUND_CLICK); // play a click Wait(time); // wait for the value of time (so that we can see // the difference between the types of sounds) PlaySound (SOUND_DOUBLE_BEEP); // play a double beep Wait(time); // wait for the value of time // Notice how I put a comment after almost every line // of code -- this is good programming practice. // this way there is no confusion about why a // line is there. // If you have large code blocks, it is also a good // idea to write a small // paragraph describing the purpose of the block // GOOD COMMENTING IS AN ESSENTIAL PART OF // GOOD PROGRAMMING! PlaySound (SOUND_DOWN); // play a scale decreasing notes as you go Wait(time); // wait for the value of time PlaySound (SOUND_UP); // play an upward scale Wait(time); // wait for the value of time PlaySound (SOUND_LOW_BEEP); // play a low beep Wait(time); // wait for the value of time PlaySound (SOUND_FAST_UP); // play fast upward scale Wait(time); // wait for the value of time // There are other functions you can use to play with sound. // The BRICX also provides a piano player that you can use. // Check it out on the toolbar or in the Tools Menu // Let's move on to adding STRUCTURE to our programs. NQC allows you to add several // types of structures, including both conditionals (IF THEN ELSE statements) and LOOPS // Let's start with if-then-elses. /* In C, and therefore NQC, If-then statements are written as follows: if ("condition") { "actions" } // end if You can read this as follows: if the "condition" is true, then do the following "actions". Notice that we do NOT type the word then, it is implied. Also notice that the if-then statemnet is another type of CODE BLOCK -- you need to use braces {} to group together the statements that will be performed if the condition is true. NQC also allows you to write if-then-else statements so that you can specify actions to be performed when a condition is false. For example: if ("condition") { "actions-1" } // end if else { "actions-2" } // end else This set of statements can be read as follows: if the "condition" is true, then do the following "actions-1", otherwise (if the "condition" is false) do "actions-2". We can also string together a series of if statements as follows: if ("condition-1") { "actions-1" } // end if else if ("condition-2") { "actions-2" } // end else if else { "actions-3" } // end else This can be read as: if the "condition-1" is true, then do "actions-1", otherwise, test "condition-2", if it is true, do "actions-2", otherise (if both conditions are false) do, "actions-3". Remember: A NQC PROGRAM IS SEQUENTIAL, so the ORDER in which you test conditions can be very important. */ // Let's try it out! // What if we wanted to turn on the motors when a our touch sensor was pushed? // We can also use the constant identifying a sensor to get the value // currently being read by the sensor. For example, if we push the touch // sensor assigned to SENSOR_1, the value of SENSOR_1 becomes true! // Because we initialized the touch sensor mode to be a boolean (a true or false // value) there are two ways we can test to see if it was pushed. I've commented // out the second. Do you see the difference? // let's up the value of time again time = 500; // set time to 500 ms if (SENSOR_1 == true) { // test to see if SENSOR_1 was pushed // NOTE: the symbol == tests whether one value is equal to the other. Do not // use the = sign, that symbol is used to assign a value to a variable OnFwd(OUT_A); // turn on OUT_A in forward direction OnFwd(OUT_B); // turn on OUT_B in forward direction Wait(time); // wait for value of time } // end if SENSOR_1 == true -- notice how I comment the end of the // code block so that I won't confuse it with another closing brace /* if (SENSOR_1) { // test to see if SENSOR_1 was pushed OnFwd(OUT_A); // turn on OUT_A in forward direction OnFwd(OUT_B); // turn on OUT_B in forward direction Wait(time); // wait for value of time } // end if SENSOR_1 Why would this work as well? Because the function SensorValue takes on the value of the sensor after we call it. Since we set the mode of SENSOR_1 to a boolean, the value of the function becomes either TRUE or FALSE and we don't have to explicitly test using the == symbol! Cool, isn't it?! */ // Let's try an if-then-else-if-then-else :) Statement. // This series of statements tests to see if the light sensor is reading a // dark value. If so, it reverses direction, if not, it tests to see if // the touch sensor has NOT been pushed. If not, it goes forward at full speed, // if it is not dark and the button is pushed, the rcx will stop if (SENSOR_2 <= 50){ // if the value of SENSOR_2 is less than or // equal to 50 OnRev(OUT_A); // reverse the direction of OUT_A and OUT_B OnRev(OUT_B); Wait(time); // wait for value of time } // end if SENSOR_2 <= 50 else if (!SENSOR_1) { // otherwise if SENSOR_1 is not pushed // the exclamation point symbol(!) means a logical NOT. When we use // this symbol it is the same as saying "the opposite of SENSOR_1 // This condition tests to see if the opposite of SENSOR_1 is true, // Therefore, the condition is true when the value of SENSOR_1 is false. // To clarify, writing if(!SENSOR_1) is the same as writing // if (SENSOR_1 == false) SetPower(OUT_A, OUT_FULL); // turn OUT_A and OUT_B to full power SetPower(OUT_B, OUT_FULL); OnFwd(OUT_A); // turn on OUT_A and OUT_B in forward direction OnFwd(OUT_B); } // end else if !SENSOR_1 else { // if it is brighter than 50 and the sensor value is pushed // turn off OUT_A and OUT_B Off(OUT_A); Off(OUT_B); } // end else // Whew! Now let's try a different type of structure -- loops! There are a few // Different types of loops you can use, we are just going to look at WHILE // and REPEAT loops // Loops are also Code blocks. A while loop allows for the actions in the // code block to be repeated until a condition becomes true. // There are two types of while loops: WHILE loops and DO-WHILE loops. // A while-loop tests the condition before performing the actions. // A do-while loop tests the condition after performing the action. // Therefore, a do-while loop will always happen AT LEAST ONCE; // a while-loop MAY NEVER HAPPEN! // For example: Let's turn the motor off and then write two loops to // run the motor while the touch sensor is pushed Off(OUT_A); // turn off OUT_A and OUT_B Off(OUT_B); Wait(time); // let's give you time to push the button while (SENSOR_1) { // while SENSOR_1 is true OnFwd(OUT_A); // turn on OUT_A and OUT_B in the forward direction OnFwd(OUT_B); } // end while SENSOR_1 // If you never press the touch sensor, this loop would never happen // alternatively, do { // do the following OnFwd(OUT_A); // turn on OUT_A and OUT_B in the forward direction OnFwd(OUT_B); } while (SENSOR_1); // while SENSOR_1 is true -- NOTE: that this line // must be ended with a semi-colon (;)! // this loop will happen at least once! // A REPEAT loop is the same as a FOR loop. It repeats the actions in the // code block for a value. The value may be an integer or a variable or // even a value returned from the sensor. For example, we could have // the robot play a sound the same number of times as the value of the light // intensity recorded by the light sensor. time = SENSOR_2; // get value of light sensor and assign it to // the variable time // We do this because the value of the light // sensor is constantly changing. // The repeat loop needs a constant value // to count down from and so you can't // just use the function as the condition. // Instead, save the current value of the // function to a variable, and use that variable repeat (time) { // repeat time times PlaySound (SOUND_CLICK); // play a sound } // end repeat time // There are a lot of other things you can do with NQC -- but this should // be enough to get you started. Play around with it -- see what you // can do! Figure out for yourself how to use other control structures, such // as a SWITCH or functions such as SetClickTime()! You can multi-task, // write your own functions, or SUB-ROUTINES, and even print information // to the RCX's LCD! // Just one more quick thing: What if you wanted your program to repeat // forever? Use a while loop! If you build you main task as follows: // task main () { // while (true) [ // program body // } // end while TRUE // } // end task main() // your program will only end if you turn the RCX off!! } // end task main() -- label end braces; this way you know what goes with what