R Language Tutorial and Primer

This is intended to be a tutorial for people who have never used the R launguage before and a primer for non-programmers who wish to use it. A full launguage description is available in the R language reference manual.

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.

Flight Profiles

Imagine you are flying a rocket - during the flight the rocket is in a series of states with sudden transitions between them:

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.

A simple example

The basic primitive in R is the 'state' - it has a name and a list of 'events' things that can happen in that state that can cause things to happen. Consider a very simple timer - it wants to wait for launch, wait 10 seconds, fire a recovery device, there are 3 states - waiting for launch (start), waiting for the timer timer and when we are done recovery. An R program to implement this looks like:
	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: In this case the program starts in state 'start' and waits untill it detects a launch, at this point it switches to state 'timer' and waits 10000 milliseconds (10 seconds) it then fires the recovery charge and then switches to state 'recovery' where it idles for all time.

Building and running a Program

Let's look at a (very) simple R program. Type the following in to your favorite editor and save it as a text file called "hello.r":

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

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

Events

Each state declaration must have one or more events, R supports a number of built in events - or your program can poll (check periodically) for some event of your own choosing. The events are listed in the statedeclaration - and the order is important - each time the program looks for events to process it checks all the events in order, if processes the statements of the first one that has been triggered then goes back and looks for more from the start.

R supports a number of events, the most usefull ones are the timeout and launch ones shown above - a full list are:

A state declaration can have multiple events - look at this R program:
	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;

Variables

You can declare variables - places to hold numbers - you put these at the beginning of the program before the first state declaration, there are 3 basic types: You declare a variable this way:

	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

Detecting apogee

Here's a slightly more involved example - detecting apogee:

	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.

Statements

Above we've shown how states and events can be involved with the execution of 'statements', these are individual actions that you can tell the program to do - we've already looked at assignments above - where a new value for a variable is calculated - other types of statements supported by R include:

	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.

I/O

Most flight computers have a serial port to connect to a host PC on which you do much of your development - this acts as a console for the flight computer - to help debugging you programs there are a small number of built in statements (called 'intrinsics') that you can use to help debug your program by printing out what is happening inside them. These are:

	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 console

For 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 console
You 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 backslash

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

Logging

Some (but not all) flight computers support a built in data logger - these allow you to periodically log data from the sensors (the same data available from the get() intrinsic) - by enabling this log from an R program you can have it collect data while you program is flying.

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 log

Next 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 log

Here'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.

Other Intrinsics

There are some other intrinsics built in to the R system runtime, much more data about them is available in the R language reference manual:

	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

A More complicated example

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;