There is also a document describing how to compile and run programs with the compiler and ground simulator.
R includes a subset of the C language - but you don't need to learn all of it to get something usefull working - check out the samples below, try and understand each one - try some out - then try some of your own.
Initially the rocket is at rest on the launch pad in its start state, the LCO pushes the button and launches it - the motor lights and it transitions to the boost state, the motor burns out and it transitions to the coast state, finally at or near appogee a timer or altimeter pops the chute and it transitions to the recovery state.
As a rule rocketry related software has a lot to do with tracking or causing these sorts of transitions between states. The R launguage provides direct primitives for modeling states and making transitions based on events that are detected by the computer.
state start: on launch: arm(0); next timer; state timer: on timeout 10000: fire(1); next recovery; state recovery: on idle: ;Some things to notice:
state start: pstr("hello world\n"); halt;When you unpacked the R software kit one of the things you got was a program called 'rl' - this is the R Language compiler - when you run it it makes a '.x' file - a file containing portable executable code - one way or another this .x file can be made to run on all computers that support R. Try running it be typing at a command prompt (Windows people pop up a DOS box, Mac people pop into MPW):
rl test.rCheck to make sure it made a test.x file.
Before you try and run it on your flight computer there's an off-line simulator available that lets you ground test your flight computer programs - try typing:
rsim test.x(also check out for more on this topic)
Now load it into your flight computer and run it (how you do this is different for each flight computer - check for the computer specific documentation on how to do this on your flight computer).
R supports a number of events, the most usefull ones are the timeout and launch ones shown above - a full list are:
state start: on launch: arm(0); next timer; state timer: on timeout 10000: fire(1); next recovery; on apogee(): fire(1); next recovery; state recovery: on idle: ;This is basicly the same as the previous program except that the recovery system can be triggered by either a delay of 10 seconds or an apogee detection, whichever comes first.
Note: a timer goes off exactly once after you enter a state - if you want something to happen periodically you must re-enter the state, for example the following program prints out a period every 7 seconds:
state start: on timeout 7000: pchar('.'); next start;
int a, b; char c; long l, m, n;Each declaration names one or more variables seperated by commas, each declaration is terminated with a semicolon.
Variables can be assigned values using an assignment statement:
a = 1; // puts the value 1 in a a = b+1; // puts the value of b plus one more in a a = a+1; // puts the value of a plus one more in a (increments it) a = get(1); // get(N) returns the value from sensor N, by convention // sensor 1 is an accelerometer, sensor 2 is an air pressure sensor
int apogee_value, tmp; state start: on launch: arm(0); next timer; on idle: apogee_value = get(2); state timer: on timeout 10000: fire(1); next recovery; on (get(2) > (apogee_value+10)): fire(1); next recovery; on idle: tmp = get(2); if (tmp < apogee_value) apogee_value = tmp; state recovery: on idle: ;Note that initially when the program is in the start state (the rocket is sitting on the pad) the variable apogee_value is set with the current value from the air pressure sensor - this value will decrease untill the rocket reaches apogee when it will start to climb again - in the timer state on idle we look to see if we have found a value that's smaller than the smallest value we've found before, if so we remember that new smaller value. Finally if we detect that the sensor value is 10 greater than the smallest value we've found so far then we decide that the rocket has passed apogee and is now descending. Note: this value of 10 is arbitrary - all sensors suffer from noise both electrical and because of vibration - actual apogee detection may be much more involved - esp. in the presence of mach shock waves.
Consider the following program, esentially the same as the previous one except that it locks out apogee decection for the first 6 of the 10 seconds delay presumably the period when the rocket is past mach:
int apogee_value, tmp, ground value; state start: ground_value = 0; on launch: arm(0);next timer1; on idle: apogee_value = get(2); if (ground_value < apogee_value) ground_value = apogee_value; state timer1: on timeout 6000: next timer2; state timer2: on timeout 4000: fire(1); next recovery; on (get(2) > (apogee_value+10)): fire(1); next recovery; on idle: tmp = get(2); if (tmp < apogee_value) apogee_value = tmp; state recovery: on idle: ;Want to build an altimeter? Just figure out how apogee_value corresponds to altitude (this depends on the ranges of values returned by your pressure sensor and the relationship between those values and actual altitude) and do the same for the ground_value value - then subtract the difference.
Note: in the above example ground_value is assigned a value of 0 at the start of state 'start' and statements after a state definition, and before its event definitions, are executed once each time you enter a state via a next statement.
if (<exp>) <statement> if equation <exp> is true then execute <statement> otherwise skip to the following statement. if (<exp>) <statement1> if equation <exp> is true else <statement2> then execute <statement1> otherwise execute <statement2> while (<exp>) <statement> repeatedly execute <statement> while <exp> is true for (<asignment1>; execute <asignment1>, then <exp>; while <exp> is true repeatedly <asignment2>) execute <statement> followed <statement> by <asignment2> { <statement> groups together a list of statements <statement> that are be executed in order .... }For example:
if (a > 10) // if the value in variable a is greater than fire(2); // 10 fire pyro channel #1 for (i = 1; i <= 20; i=i+1) { // loops through the two lines below with i taking pval(i); // the values from 1 to 20 in order. These two pstr("\n"); // lines print out the values 1-20 }Note: in the above example - you can include comments (inline documentation) in your program by preceding it with two slashes (//) everything to the right of and including those two slashes to the end of the same line is ignored.
pchr(<exp>); - prints the character <exp> on the console pstr(<str>); - prints the string <str> on the console pval(<str>); - prints the decimal value of lt;exp> on the console phex(<str>); - prints the hexadecimal value of lt;exp> on the consoleFor example:
pchr('.'); // results in a single period appearing on the console pstr("hello world\n"); // results in 'hello world' followed by a new line // appearing on the console pval(4+13); // results in '17' appearin on the console phex(0x1234); // results in '1234' appearing on the consoleYou can generate some characters with special meanings in strings (1 or more characters always surrounded by double quotes "") or single characters (1 character surrounded by single quotes '') by preceding some well known characters with a back slash (\), '\n' below is the most usefull:
'\n' means a new line - moves the screen cursor to the start of the next line '\b' back space - moves the cursor to the left one space '\t' send a tab character '\f' emit a form feed (doesn't mean much these days unless you're a printer) '\r' return - go back to the start of the current line '\\' emit a backslashR does not has a C-style printf() function built in - if you find the need for one you can include the following subroutine declaration in your code before the first state declaration:
printf(char *s, int p) { int *pp; char *cp; pp = &p; cp = s; while (*cp) { if (*cp == '%') { cp++; if (*cp == '%') { pchr('%'); } else if (*cp == 's') { pstr(*pp++); } else if (*cp == 'l') { cp++; if (*cp == 'd') { pval(*(long *)pp); pp += 2; } else if (*cp == 'x') { phex(*(long *)pp); pp += 2; } else { cp--; } } else if (*cp == 'd') { pval(*pp++); } else if (*cp == 'x') { phex(*pp++); } else if (*cp == 'c') { pchr(*pp++); } } else { pchr(*cp); } cp++; } }Explaining this piece of code is outside the scope of this tutorial - but suffice to say that R supports C-style subroutines and you should check out a book on C to explain what this means.
The above printf() supports the %c, %d, %x, %% and %s formats (but no sizes) and the %ld and %lx formats for printing longs.
To start the log you must first tell the logger what you want to log and how often you want to log it. The intrinsic statement log_ctl(a, b) is used to control the data logger, it takes two parameters, the first tells what to control about the log, the second tells a value to give it. So to tell the logger how often to take log entries:
log_ctl(6, <time>); tells the logger how often in milliseconds to put data in the logNext you need to tell it which channels you wish to log:
log_ctl(7, <channel>); tells the loger to start log channel #<channel>Finally the following call starts the log:
log_ctl(0, 1);Your log is of a fixed size and it's size depends on the amount of memory available in your flight computer, each log entry consists of a 6 byte header and 2 bytes for each channel you log - suppose you have 1k bytes for logging (many loggers will actually be in the 16-32k range) and you are logging 2 channels 10 times a second - this means that each log entry will be 10 bytes in size and you will be using 100 bytes/second of log - your 1k log will last about 10 seconds - enough for the boost phase of your rocket's flight - but not enough to waste space sitting on the ground.
The log, when it fills, wraps around ie. it starts to overwrite the oldest information in the log this means that the log has a starting point and an ending point with both of them continualy moving. If the following function:
log_ctl(1, <time>);freezes the beginning of the log at the entry that is closest after the time that is <time> milliseconds prior to the time at which the call is made. The log will continue to be filled untill the entry at the frozen start of the log is about to be overwritten then any further data to be written into the log will be discarded. This way you can leave the log running the entire time your rocket is sitting on the pad, then when your computer detects launch you can freeze the log at some point shortly before (launch detection takes some time - by definition by the time you've detected launch some interesting things you'd like to log have probably already happened). The log data will continue to be accumulated untill the log is full and then will it will stop.
Finally you can print out the contents of the log to the console using:
log_ctl(5, 0);The R language reference has information about the format of the printed log data.
Some other usefull logging functions:
log_ctl(0, 2); // pause the logger log_ctl(0, 3); // continue the logger log_ctl(0, 12); // include launch events in the logger log_ctl(0, 10); // include fire/arm/safe events in the logger log_ctl(0, 5); // unfreeze the log log(X, Y); // puts the values X and Y as user data into the logHere's an example of a simple data logger:
char c; state start: log_ctl(6, 100); // log ever 100 milliseconds (10 times a sec) log_ctl(7, 1); // log channel 1 log_ctl(7, 2); // log channel 2 log_ctl(0, 1); // turn on the logger on launch: log_ctl(1, 2000); // freeze the log 2 secs prior to now next timer; state recovery: on input c: if (c == 'l') next O; state O: on input c: if (c == 'o') next G; next recovery; state G: on input c: if (c == 'g') // when someone has typed 'log' log_ctl(5, 0); // output the log data next recovery;This can of course be combined with any of the previous sample programs to produce something that does deployment etc. The above program sets up the logger and waits on the pad, when it detects a launch it freezes the logger at the point 2 seconds prior to launch detection the log continues to collect data while the rocket flys and when it fills stops. After recover you walk up to the rocket, hook up your computer and type 'log' - that triggers a download of the data back to the host computer.
arm(<channel>); arm a pyro channel, 0 means arm everything safe(<channel>); safe a pyro channel, 0 means safe everything (safe is the opposite of arm) fire(<channel>); fire a channel (this is a mask) values passed mean: 1 - channel #1 2 - channel #2 4 - channel #3 8 - channel #4 16 - channel #5 32 - channel #6 .... you can add values together to fire multiple channels, fire(0) stops firing a channel halt; stops the program that's currently executing and puts the flight computer into a idle state beep(<code>); if the computer has a beeper then beep(1) starts it beeping, beep(0) stops it ltime in an expression ltime returns the time since the last launch event in milliseconds rtime returns the current time in millisconds get(<object>) returns the value of a system specific device by convention get(1) returns the value of the accelerometer if one is present and get(2) returns the value of the pressure sensor (if present).An example of the beep function in use (code to generate altimeter type beeps:
int count, beep_count; state beep1: count = beep_count; // remember the value beep(1); // turn on beep if (count == 0) // 0 case is special long beep next long_beep; count = count - 1; next beep2; state beep2: on timeout 500: beep(0); // after 1/2 sec turn off if (count == 0) // if we've made enough beeps quit next beep_done; next beep3; state beep3: // start a new beep on timeout 500: beep(1); count = count - 1; // decrement the beep counter next beep2; state long_beep: on timeout 1000: beep(0); // finished a long beep? next beep_done; // quit
Consider the above flight profile - if the rocket flies correctly it boosts, when the main motor burns out at #1 a seperation charge seperates the stages and the upper stage coasts for 3 seconds before lighting the upper stage at #2, finally the second stage motor burns out, the rocket coasts to apogee where at #3 it pops a drouge, when the rocket gets close to ground at #4 it pops the main.
At least that's what's supposed to happen - of course lots of things can go wrong - below is a program that's a synthesis of all the previous program samples you've seen above that purports to fly the above flight profile (caution! - this is intended as an example it hasn't actually been flown - you should ALWAYS ground test your code before flight, and the first time fly it with adequate backups).
int accel_1g, apogee_value, tmp, ground_value; int offset, count, beep_count, altitude; char c; int convert(int alt, int gnd) { // something to calculate altitude from pressure readings } state start: log_ctl(6, 100); // log ever 100 milliseconds (10 times a sec) log_ctl(7, 1); // log channel 1 log_ctl(7, 2); // log channel 2 log_ctl(0, 1); // turn on the logger ground_value = 0; accel_1g = get(1); on launch: arm(0);log(2, 1000);next boost1; on idle: apogee_value = get(2); if (ground_value < apogee_value) ground_value = apogee_value; state boost1: on timeout 20000: fire(1); next loop; // failure! on (get(1) < (accel_1g-100)): fire(4); next coast1; // burnout? // pop the active // staging state coast1: on timeout 3000: fire(3); next boost2; // wait 3 secs then // fire next stage motor state boost2: on timeout 4000: next timer2; on (get(1) > (accel_1g+100)): next timer1; // burn out? state timer1: on timeout 6000: next timer2; state timer2: on timeout 4000: fire(1); next loop; // failure? on (get(2) > (apogee_value+10)): fire(2); next recovery; // pop the drouge on idle: tmp = get(2); if (tmp < apogee_value) apogee_value = tmp; state recovery: altitude = convert(apogee_value, ground_value); on (get(2) > (ground_value-500)): fire(1); next loop; // near the ground? // pop the main state loop: on input c: if (c == 'l') next O; on timeout 5000: offset = 10000;next beepx; state beepx: if (altitude == 0) { beep_count = 0; offset = 0; } else { while (altitude < offset) offset = offset/10; beep_count = (altitude/offset)%10; offset = offset/10; } next beep1; state beep_loop: if (offset < 1) next loop; beep_count = (altitude/offset)%10; offset = offset/10; on timeout 2000: next beep1; state beep1: count = beep_count; // remember the value beep(1); // turn on beep if (count == 0) // 0 case is special long beep next long_beep; count = count - 1; next beep2; state beep2: on timeout 500: beep(0); // after 1/2 sec turn off if (count == 0) // if we've made enough beeps quit next beep_loop; next beep3; state beep3: // start a new beep on timeout 500: beep(1); count = count - 1; // decrement the beep counter next beep2; state long_beep: on timeout 1000: beep(0); // finished a long beep? next beep_loop; // quit state O: on input c: if (c == 'o') next G; next loop; state G: on input c: if (c == 'g') // when someone has typed 'log' log_ctl(5, 0); // output the log data next loop;