Full RL Language Specification

This is the dry - legalistic specification for the RL language - don't come here to learn it - non programmers should check out the primer, programmers should check out the primer and a C manual.

The RL language is a subset of C with additions for state machines and instrinsics for controlling the system environment.

Each RL program is contained complete in a single file and contains 2 sections - a declarations section followed by a main program section.

Lexical Conventions

In general white space (blank and tab characters, and line breaks are ignored except that they are used to seperate lexical units.

This compiler does not have a C-style preprocessor built in, if you want this functionality grab a copy of the GNU cccp C-preprocessor and use it as a first pass over your source prior to compilation.

Comments

Comments are text in a source file that is ignored by the compiler - two sorts of comment styles are supported by the compiler - '//' commets that start with a '//' and go to the end of a line, and '/*' comments that start from a '/*' and go untill the following '*/'.

Variable names

Variable names are strings of alphanumeric characters or '_' starting with a non-numeric character. Case is important - 'A' is a different variable from 'a'.

Constants

Integer constants can be represented by any string of decimal characters. Strings starting with '0x' or '0X' followed by hexadecimal characters are interpreted in hex (this is case insensitive). Single characters surrounded by single quote (ie ') characters are also interpreted as the ascii value corresponding to that character, character pairs starting with a backslash (ie \) are also allowed and mean:

	\\	a single backslash
	\n	a new line
	\t	a tab
	\f	a form feed
	\b	a back space

String constants are surrounded by double quotes - (ie ") they can contain any number of characters (including the backquoted ones mentioned above and represent a null terminated string in memory.

Declarations

The declarations section may be empty, or it may contain any of the following sorts of declarations (note that all variables or subroutines must be strictly declared before use):

Constants

A constant declaration declares a shorthand symbol for a constant value. It simply consists of a line of the form:

	<name> = <constant expression> ;

Declarations

Declarations declare global data structures. Global data structures are initialized to 0 at start time. Unlike C the types of data structures are currently relatively limited, and initializers are not supported. The following types are primitive:

	char			1 byte
	int(short)		2 bytes
	long			4 bytes
	unsigned char		1 byte
	unsigned int(short)	2 bytes
	unsigned long		4 bytes

In addition to these types you can create pointers to them, and arrays of them and pointers to them. Pointers are 2 bytes in size. Here are some examples of declarations:

	int	a,		// a 2 byte integer
		*b,		// a 2 byte pointer to an integer
		c[6],		// and array of 6 2-byte values
		*d[5];		// 5 pointers to 2 byte objects

	char	x, *y;		// a 1 byte char and a pointer to a char

	long	j, k;		// 2 4-byte longs

Subroutines

Subroutines are C-style subroutines - they can return primitive values, or simple pointers or void. Parameters can be primitives or simple pointers and are passed as 2-byte ints by default unless they are long - there is no parameter type checking when subroutines are called. Subroutines can have local variables which are not initialized with known values (and again initializers are not allowed). For example:


void
ex1(int a, char b)
{
	int x, y;

	x = a+b;
	y = a-b;
	if (y == 0)
		return;
	pval(x/y);
}

int
fact(int y)
{
	if (y <= 1)
		return(1);
	return(y*fact(y-1));
}

States

The major difference between RL and C is the explicit creation of states - basicly a program sits in a state waiting for something to happen - when it does you get to execute some code, one of the things you can do is to move to a new state - or stay in the current state waiting for something to happen - the 'things that happen' are called 'events' when an even is recognized it is cleared - that way you never see events more than once unless they happen more than once.

The basic syntax for a state declaration is:

	state <STATE_NAME>:
		<some optional statements to executed once on entry to state STATE_NAME>
		on <event a>: <some statements to be executed on event a>
		on <event b>: <some statements to be executed on event b>
		etc

Once a state has been entered the program executes the optional entry code, it also starts a timer for the timeout event.

It then looks for the events specified in order, if it finds none it goes back and looks for them again untill one of the events has happened. Valid events are:

	on timeout <number>:	- trigger when <number> milliseconds have passed
				  once a timer has triggered it does not get rearmed
				  without passing through the beginning of a state
				  again - if you want a state to periodically trigger
				  its timer you should complete the 'on timeout'
				  event with a 'next STATE' for the same state.
	on output:		- trigger when a character may be output by the
				  pchr() intrinsic without stalling
	on input <name>:	- if a serial character is available on the serial
				  input return it and put it in the variable <name>
	on launch:		- trigger when a launch even is detected
	on log_full:		- trigger when the log is full
	on interrupt <num>:	- trigger when interrupt <num> has occured (this
				  is a hardware specific thing - check with your
				  implementation's specifications)
	on <exp>:		- trigger when expression <exp> is non 0
	on idle:		- this is the default - it always triggers,
				  put this at the end because no other event
				  statements in this state that follow an 'on idle'
				  will be recognized.

You can transition from one state to another using a 'next <STATE NAME>' statement - this enters a new state from the start. Statements attached to an event description are executed in order untill a 'next' statement is executed (statements following a 'next' are ignored). If all the statements are executed then the program will go back to the start of the list of events looking for new events that have triggered.

Below is C-like pseudo code for a state declaration showing how it works:


	state fred:
		<start code>
		on timeout 100: <timeout statements>;	// note no next
		on interrupt 1: <int 1 statements>; next fred;
		on interrupt 2: <int 2 statements>; next joe;
		on launch: <launch statements>; next mike;
		on idle: <idle statements>; 

is equivalent to:

	fred_entry:
		timer = current_time;
		timer_running = 1;
		<start code>
	fred_loop:
		if launch detected)
			launch_flag = 1;
		time = current_time;	// for 'time' variable
		check to see if any logging needs to be done
		if (timer_running && (current_time-timer) > 100) {
			timer_running = 0;
			<timeout statements>;
			goto fred_loop;	// no next
		}
		if (interrupt 1 happened) {
			<int 1 statements>;
			goto fred_entry;
		}
		if (interrupt 2 happened) {
			<int 2 statements>;
			goto joe_entry;
		}
		if (launch_flag) {
			launch_flag = 0;
			<int 2 statements>;
			goto mike_entry;
		}
		<idle statements>;
		goto fred_loop;

Each program must have a state called 'start' which is the first state entered when the program starts.

Statements

Statements are C-style statements that can be included in subroutines or state declarations. Most C statements are supported - currently the following are NOT:

Expressions

Logical operations are the same as for C - an value is 'true' if it's non 0 and 'false' if it's 0.

Expressions follow the normal C expression forms with the expected operators:


	?:			- ( <exp1> ? <exp2> : <exp3> )

				  selection - if <exp1> is true return <exp2> otherwise
				  <exp3>

	||			- <exp1> || <exp2>

				  guarded or -  return true if <exp1> or <exp2> are true,
				  if <exp1> is true <exp2> is not evaluated

	&&			- <exp1> && <exp2>

				  guarded and -  return true if <exp1> and <exp2> are true,
				  if <exp1> is false <exp2> is not evaluated

	<			- <exp1> < <exp2>

				  less than - true if <exp1> is less than <exp2>

	<=			- <exp1> <= <exp2>

				  less than or equal - true if <exp1> is less than or equal to <exp2>

	>			- <exp1> > <exp2>

				  greater than - true if <exp1> is greater than <exp2>

	>=			- <exp1> >= <exp2>

				  greater than or equal - true if <exp1> is greater
				  than or equal to <exp2>

	==			- <exp1> == <exp2>

				  equal - true if <exp1> is equal to <exp2>

	!=			- <exp1> != <exp2>

				  not equal - true if <exp1> not equal to <exp2>

	|			- <exp1> | <exp2>

				  bitwise or - each bit in <exp1> is logically ored to
				  the same bit in <exp2>

	&			- <exp1> & <exp2>

				  bitwise and - each bit in <exp1> is logically anded to
				  the same bit in <exp2>

	^			- <exp1> ^ <exp2>

				  bitwise xor - each bit in <exp1> is logically xored to
				  the same bit in <exp2>

	<<			- <exp1> << <exp2>

				  shift left - <exp1> shifted left by <exp2>

	>>			- <exp1> >> <exp2>

				  shift right - <exp1> shifted right by <exp2>

	+			- <exp1> + <exp2>

				  add - <exp1> is added to <exp2>

	-			- <exp1> - <exp2>

				  subtract - <exp2> is subtracted from <exp1>

	*			- <exp1> * <exp2>

				  multiply - <exp1> is multiplyed by <exp2>

	/			- <exp1> / <exp2>

				  divide - <exp1> is divided by <exp2>
				  (if <exp2> is 0 the result is undefined)

	%			- <exp1> % <exp2>

				  mod - <exp1> is moded by <exp2> (arithmetic remainder)
				  (if <exp2> is 0 the result is undefined)

	call			-  subr( <exp>, ....)

				   call a subroutine and return a value

	()			- (<exp>)

	variable		- name
				  name++
				  name--
				  ++name
				  --name

	array index		- name[<exp>]

	number			- 1232
				  0x1234
				  'a'
	
	string			- "abcd"

	unary -			- -<exp>
				  
				  return 0-<exp>

	unary +			- +<exp>
				  
				  return <exp>

	unary *			- *<exp>
				  
				  return the value <exp> points to

	~			- ~<exp>

				  the bitwise complement of the value <exp>

	!			- !<exp>

				  true if <exp> is false, false if it's true

	unary &			- &name
				  &name[<exp>]

				  return the address of an object 

	cast			- (char)<exp>
				  (int)<exp>
				  (long)<exp>
				  (char *)<exp>
				  (int *)<exp>
				  (long *)<exp>

				  change the type of a value

	size			- sizeof(type)
				  sizeof(<exp>)

				  get the size of an object

	intrinsics		- time			- time in milliseconds
							  we last passed through the state
							  polling 
				  ltime 		- time in milliseconds since last
							  launch event
				  rtime			- current time in milliseconds
				  stime			- time when the last state was entered
							  in milliseconds
				  get(<exp>)		- return value of a system object
							  (an analog channel, a pin state etc)

				  (see the section below on intrinsics)

Operator priority is the same as C - in order from lowest to highest they are:

	?:
	||
	&&
	< > <= >= == !=
	|
	&
	^
	<< >>
	+ -
	* / %
	call
	name unary_ops cast ~ ! 

Intrinsics

'Intrinsics' are features built in to the language intended to support the underlying hardware - as such they are often hardware specific - this section is intended to document the intrinsic functions and try and describe which parts of the them that are standardized and which parts are optional or system specific.

halt;

The halt intricic is supported on all platforms it puts the flight computer into an idle reset state. How the computer leaves the halt state and runs a program is undefined and computer specific.

Executing halt may destroy the contents of the log.

arm(channel)

Support of this call is optional - if not supported nothing will happen when it is invoked, if supported it can be supported in either of two ways: The number of pyro channels supported by a particular computer is implementation specific. By convention channel 1 is for main chute release, channel 2 is for drouge release, channel 3 is for lighting a sustainer or airstarts, channel 4 is for active staging, use these as a guide but feel free to change to suit your situation.

safe(channel)

This is the opposite of the arm() call mentioned above and uses exactly the same parameter conventions as arm().

fire(code)

This command fires one or more pyro channels - its parameter is bit encoded, bit 0 of the parameter drives pyro channel 1, bit 1 channel 2, .... bit N channel N+1. Channels need to be fired, the returned to 0 after an appropriate period of time has passed (say 1 sec - it depends on your ignitors). Some examples:
	arm(0);		// arm everything
	fire(1);	// fire channel 1
	fire(2);	// fire channel 2
	fire(4);	// fire channel 3
	fire(8);	// fire channel 4
	fire(9);	// fire channel 1 and 4
	fire(1<<(N-1));	// fire channel N	
	fire(0);	// stop firing
	safe(0);	// save all channels

beep(code)

Optional - if not supported calling this causes nothing to happen. This call activates the on board beeper, not all codes are supported - if the beeper is supported the first two are manadatory:

	Code		Action

	0		turns beeper silent
	1		turns beeper on
	10+N		sends the beep code for N
	>=256		implementation specific

log_ctl(option, value)

Support of a logging function is optional - a log is a buffer of recorded events which can be played back after flight. The internal details of how the log works are system specific, but the following is true of all logs: The log can contain a number of different types of log records: The log_ctl() intrinsic controls the log, it has 2 parameters, the first specifies a logger option to control, and the second the value to control it with:

	Option		Value		Comment

	0		0		turn the logger off
			1		empty the log and start the logger
			2		pause the logger
			3		continue the logger
			4		empty the log
			5		unfreeze the log
			6		enable automatic logging to EEPROM
					(system specific)
			7		save log in EEPROM
			8		load log from EEPROM
			9		erase EEPROM log
			10		enable logging of fire/arm/safe calls
			11		disable logging of fire/arm/safe calls
			12		enable logging of launch events
			13		disable logging of launch events
			14		enable logging of set() calls
			15		disable logging of set() calls
			16		log data is printed in decimal (default)
			17		log data is printed in hex
			18		enable logging of log_ctl() events
			19		disable logging of log_ctl() calls

	1		N		freeze the log start at N milliseconds
					prior to the current time

	2		addr		set EE address for log save
	3		count		copy first count bytes of log information
					to eeprom address saved by log_ctl(2)
	4		count		empty log then copy first count bytes of
					log information from eeprom address saved by
					log_ctl(2).

	5		count		output (to serial port) first count entries
					from log. '0' means all the log entries.

	6		time		set periodic time for automatic logging
					(time in milliseconds) 
	7		N		enable automatic logging for channel N

	8		N		disable automatic logging of channel N

The logger is started by a log_ctl(0, 1) call - once running it starts to collect log records - when it sees a log_ctl(1, *) call the starting point of the log is marked at the current time less N milliseconds and the log continues to fill untill no more space is available - at this time it stops collecting log data and goes into the 'frozen' state. This ability to freeze at an earlier time is usefull because the launch detection event actually occurs some time after launch (this is because it needs to see thrust for some period of time so as not to be triggered by casual bumping) by freezing at a fixed point prior to the launch detection all of the launch can be logged.

A 'frozen' log can be unfrozen by means of a log_ctl(0, 5) call which means that the start of the log starts to be overwritten again.

If log_ctl(7,*)/log_ctl(8,*) is called then the value specified by log_ctl(6,*) is used to trigger a period logging If EEPROM is supported there are three ways to save data into the log, which one(s) are supported are system specific:

log_ctl(5,*) will copy the log to the standard output in a standard format, each entry in the log appears on 1 line, each line starts with an 8-digit hex number - the timestamp in milliseconds of the entry, 2 digit hex number which describes the entry type, the next number is a 2 digit hex number giving the number of pieces of data to follow, the rest of the entry is a series of N 16-bit (4 hex digits) terminated with a CR/LF. Note - by default the results are not printed in hex - data is printed in decimal with tabs between numbers (suitable for plonking into a spreadsheet).

The entry types are:

NOTE: the logger like much of the rest of the system is driven when the program runs through the idle portion of a state construct - if your program jumps off into a subroutine (or some other loop - like outputting text) and doesn't keep coming back to an event loop every so often then periodic logging will not happen (neither will other operations that require periodical actions like launch detection) also for this reason periodic actions may not get sampled exactly every N milliseconds, instead they will occur at the next convenient time - post processing software of dumped logs must take this into account, esp. when graphing.

log(value1, value2)

Puts user data type entries into the log - 2 16-bit values from the parameters.

eesave(ee_addr, mem_addr, count)

This call is optional - count bytes of data is copied to EEPROM starting at address ee_addr from main memory starting at address mem_addr.

eeload(mem_addr, ee_addr, count)

This call is optional - count bytes of data is copied from EEPROM starting at address ee_addr to main memory starting at address mem_addr.

time

This returns the time (in milliseconds) that the system last started checking events in a state construct. A long (32-bit) value - remember that time is 'circular' it starts when the computer is switched on - a 32-bit value can hold 4000 seconds worth of precision - which is about an hour - if your computer is going to sit on the pad for that long it's going to wrap around - much more usefull is the concept of 'relative' time - the time since something interesting has happened - you can do this by remembering an interesting time and simply subtracting it from the current time. ltime below provides a usefull automatic form of this function.

If your rocket's going to be flying for more than an hour get in touch I'm sure we can work something interesting out :-)

rtime

'real time' - returns the exact real time in milliseconds. A long (32-bit) value.

stime

'state time' - returns the time in milliseconds when the current state was entered. A long (32-bit) value.

ltime

'launch time' - time in milliseconds since the last launch event. A long (32-bit) value.

set(object, value)

Provides a system specific way to set system variables (for example to contoll parallel ports, or other system specific IO devices) - by convention the following objects are reserved - all other codes are available for computer specific functions:

	0x00		undefined
	0x01-0x3f	sensor channels	- writing is undefined
			by convention
			0x01 - accelerometer
			0x02 - pressure sensor

	0x40-0x7f	reserved for R future assignment
	

get(object)

Reads the value of a system object - see the above list for sensor specific numbers.