MegaSquirt-II™/GPIO Embedded Code and the C Programming Language

The embedded code for MegaSquirt-II™ and the GPIO is written in the C language. This is the same C language used for many projects on a wide variety of platforms (from embedded microprocessors to Microsoft Windows). In the case of MegaSquirt-II™, a specialized assembler is used to produce the assembly code in an .s19 file needed for MegaSquirt-II. Programming for MegaSquirt® controllers is known as embedding coding, because it is designed for a processor that is not 'general purpose' like your desktop computer.

This document is intended to be a brief overview of C programming for the MS-II™/GPIO. For a complete textbook on programming the HCS12 family of processors, including MegaSquirt-II™'s MC9S12C64, see:

"The HCS12/9S12 : An Introduction to Hardware and Software Interfacing" by Han-Way Huang

Hardcover: 880 pages
Publisher: Delmar Learning
Language: English
ISBN-10: 1401898122
ISBN-13: 978-1401898120

(This is the standard 3rd year university electrical engineering / computer science textbook on this topic. It also includes a CD with freeware compilers and IDEs, among other things.)

The MS-II™ and GPIO code were originally compiled on the Codewarrior compiler from Freescale (né Motorola). This is a 'cross compiler', because it compiles code on one type of computer (your PC) to run on another type of processor (the MegaSquirt HCS12 microcontroller).

Codewarrior Compiler for MegaShift™ Code for GPIO

This code was developed entirely on Codewarrior 4.7 Special Edition. This compiler (actually a compiler/linker/locator) is available for free from Freescale (which used to be called Motorola):

You have to register to get the software. (Note that the link may eventually become stale. If it does, start at and search for 'codewarrior HCS12(x) special edition')

The 'Special Edition' of Codewarrior has a 32 Kbyte code compile limit, and a maximum of 32 files (the number of files and code size are listed on the bottom left side of the Freescale Codewarrior IDE - integrated development environment).

The files required to create a loadable S19 file for the GPIO board using Codewarrior are:

This project does not need the processor expert ("beans") enabled in the Codewarrior project, the required info is embedded into the above files. If you do decide to use the processor expert, you need to set it to 'MC9S12C64MFA' with the small memory model.

Introduction and Example

If people are familiar with C (and there are lots of programming references on the web and in bookstores) the main additional material is understanding and reading/writing directly from/to the various registers (both to turn things off and on, and to set up PWM, ADCs, etc.). There is a very good book on programming the 9S12 microcontroller, it is Huang's "The HCS12/9S12 : An Introduction to Hardware and Software Interfacing" mentioned above.

and this text has examples of all functions (ADC, PWM, Input Capture, Output Compare, etc.) in both assembly language and C. None of the is 'easy'. However, none of it is impossible for anyone who can manage to successfully tune an EFI system.

For an example of how the programming works, we want to add (as yet unwritten) 'refresh' cycles to the PWM of the outputs. The output PWM is controlled by two 'variables' for each channel: PWMPERx and PWMDTYx (where x is 1 to 4 and the values can range from 0 to 255). Technically these are "registers", and are defined at specific memory addresses where the processor will look for them. In this case we can treat them like variables, though. PWMPERx is the period of channel x in 'clock tics' (we set up the duration of the clock tics separately in the program). PWMDTYx is the number of tics the output is held high (or low if you choose to change from the default value). So some PWM percentages are:

and in general PWMDTYx = (target%) * PWMPERx/100;

In the case of the refresh cycle, we want the PWMDTYx to go to 100 for M milliseconds every N milliseconds. In the code there is a section of the code (ISR_Timer_Clock - an interrupt) that is executed every millisecond (as well as sections that execute at other intervals). So all we have to do is if PWMDTYx does not equal zero, then start a counter (just a variable that adds one each time the millisecond code is executed). While this counter is less than N (let the PWMDTYx be set in the main loop), set a flag to let main loop control PWMDTYx. When the counter reaches N, set the PWMDTYx to PWMPERx, and set a flag to disable PWMDTYx setting in the main loop. Then when the counter reaches M+N, reset the counter to zero (and let the main loop control). So the 'pseudo code' looks like this:

// in millisecond section
if (PWMDTYx > 0) // i.e. if the output x is turned on, otherwise leave it off
  pwm_refresh_count++;  //increment the counter by one - is equivalent to: pwm_refresh_count = pwm_refresh_count +1;
  if (pwm_refresh_count < N)
    { pwm_disable_flag = 0; // clear flag }
    { PWMDTYx = PWMPERx; // set to 100% to refresh the solenoid charge
       pwm_disable_flag = 1; // set flag - will use in main loop to disable lookup of PWM in tables, etc.}
  // endif
  if (pwm_refresh_count) > (M+N-1)) 
    { pwm_refresh_count = 0; // reset the counter}
  } // end if PWMDTYx > 0

(C comments start with // (or are enclosed in /* ... */) and are ignored, but are helpful to explain what's going on)

Note that the variable/counter/flag names are essentially arbitrary (within limits that C imposes), we would have to define them somewhere (as char, int or long). The names for the PWM channels are standard (and defined in the HCS12DEF.H file), and are PWMPER1, PWMDTY1, PWMPER2, PWMDTY2, PWMPER3, PWMDTY3, ... Again there's lots in the comments of the code itself.

That's all there is to it really. Most added functions and features are like this. They are simple to implement (but not always to troubleshoot, and you will spend much more time debugging than writing new code). I am sure there are many, many users out there who could write this sort of thing, and the GPIO is intended to be a platform to get them started.

The code itself is divided into four main sections: compiler instructions, processor set-up, the main loop, and interrupts. The compiler instructions tells the compiler how to arrange things in memory, etc., and this is where you add new variables.

Note that both input and output variables have scaling factors, so you can use 3156 in the code but have it come up as 3.156 in MegaTune by using a scaling factor of 0.001 (this is handy since the processor can't handle fractions, so everything has to be a multiple of 1 at all times - so if you did (1/5)*500 you would get zero since 1/5 would go to zero and 500*0 is zero, whereas 1*(500/5) would be 100 - so you have to be careful sometimes with the math).

Below we have lots of details to clarify the example.

C Statements

The vast majority of statements in the MegaSquirt-II code consist of one of four types:

The last three types of statements are often organized into functions.

A statement usually ends in a semi-colon (;) or a 'closing curly brace' (}). For example, with the IF statement (discussed shortly), it may look like:

if (x > 1) y = 20; // set the value of y to twenty if x is greater than one

However, if we have more than one statement we want to execute if the condition (x > 1) is true, then we can use curly braces:

if (x > 1) // set the value of y to twenty and z to forty if x is greater than one
  y = 20; // first statement in 'true' block
  z = 40; // second statement in 'true' block

Note that it is perfectly okay to write:

if (x > 1) // set the value of y to twenty if x is greater than one
  y = 20;

Also, C is generally tolerant of extra spaces, so these are considered the same:


if ( x == y ) z = lookup();

In some cases a few extra spaces can make the code much easier to read.

Comments are 'hints' that the code writer has included in the code to help others (or herself) understand what an particular line or section of code is doing. Comments can be defined in one of two ways:

Note that C does not generally allow embedding comments within comments.

Declarations and Definitions

MegaSquirt-II™ declares variables in a number of ways. A declaration introduces an identifier to the compiler. The declaration tells the compiler that the identifier is a specified type and its definition will come later. An example of a declaration is extern double pw_calc(double); With this declaration, we introduce to the compiler the identifier pw_calc, specifying it as a function that takes a double precision argument and returns a double precision result. No storage is allocated for this declaration, besides the storage allocated within the compiler internal tables.

A definition tells the compiler to allocate storage for the variable. You can declare a variable many times in your code, but there must be only one place where you define it. Note that a definition is also a declaration, because when you define some variable, automatically the compiler knows what it is, of course. For instance if you write:

#define NO_MAPS 12;

even if the compiler has never seen the identifier before, after this definition it knows it is an unsigned character integer (12 in this case).

,p> A variable is defined when the compiler allocates space for it. For instance, at the global level, space will be allocated by the compiler when it sees a line like this: int a; or int a = 67; In the first case the compiler allocates sizeof(int) bytes in the non-initialized variables section of the program. In the second case, it allocates the same amount of space but writes 67 into it, and adds it to the initialized variables section.

Variable names are case sensitive. C compilers consider uppercase and lowercase characters to be different: XXX, xxx, and Xxx are three different names in C. By convention, constants in C are spelled with uppercase, while variables are spelled with lowercase, or an uppercase/lowercase combination. C Keywords are always lowercase. They can contain numbers or the underscore character, but must not start with either of these.

The storage-class-specifier can be one of the following:
typedef The symbol name "variable-name" becomes a type-specifier of type "type-specifier". No variable is actually created, this is merely for convenience.
extern Indicates that the variable is defined outside of the current file. This brings the variables scope into the current scope. No variable is actually created by this.
static Causes a variable that is defined within a function to be preserved in subsequent calls to the function.
auto Causes a local variable to have a local lifetime (default).
register Requests that the variable be accessed as quickly as possible. This request is not guaranteed. Normally, the variable's value is kept within a CPU register for maximum speed.

In C, variables must be 'declared' before they can be used. Their 'variable type' must be given.

The MS-II or GPIO code uses a number of variable types. The type-specifier can be one of the following:

Type Size signed
(must be specified)
void 0 bits
= 0 byte
null null
char 8 bits
= 1 byte
-128 to 127
S08 in MegaTune
0 to 255
U08 in MegaTune
short, int 16 bits
= 2 bytes
-32,768 to 32,767
U16 in MegaTune
0 to 65,535
U16 in MegaTune
long 32 bits
= 2 bytes
-2,147,483,648 to 2,147,483,647
-2³¹ to 2³¹ - 1
S32 in MegaTune
0 to 4,294,967,295
0 to 2³² - 1
U32 in MegaTune
Variables are declared with a statement like: data_type variable_name; or data_type variable_name = 16; (which also initializes the variable)

See the application of this below.

In general, the shortest variable that will do the job (spans the range of values needed in any circumstance by the program) uses the least space, leaving more room in memory for other variables and code.

These variable declarations can be prefaced by:

In addition to the range of values a variable type can hold, there are issues associated with what happens if the range is exceeded. In general, the value 'wraps around'. That is, it goes to the next value in the range as if the highest and lowest values were connected.

For example, for an unsigned int, you would have:


so adding 1 to 255 = 0, add 2 to 255 = 1, etc. This is often referred to as 'overflow'. Similarly, subtracting 1 from 0 is 255, and so on. This is sometimes called 'underflow'.

This is easier to understand if you look at the binary representation that the CPU actually uses:

255 in binary forum is: 11111111, and this uses 8 bits (short int). Add 1 to it, and you get 1+11111111 = 100000000

But when you try to stuff that into the 8 bits of an unsigned int, the most significant bit (MSB - the leftmost) is truncated, so you get 00000000.

Note also that when you are using signed numbers, the leftmost bit is used to hold the sign of the value (positive [0] or negative [1]). But the number is not simply the positive value with the sign bit changed, that would mess up the processor arithmetic.

-128 is represented by 10000000, -127 is 10000001, -126 is 10000010, ... -1 is 11111111. In that case, when you go from 01111111 (127 decimal) to 01111111 + 00000001 = 10000000 by adding one to 127, you get -128!

This applies not just to constants stored in variables, but also to calculations. Suppose you have two variables with values A=10000 and B=20000, and you want to multiply then to get C.

You might expect C = A*B = 10000 * 20000 = 200000000 However, if C is an unsigned short, then C will equal 20000000 % 255 = 95 (% is the modulus, the remainder after dividing the first number by the second number as many times as it will go) However, if C is an unsigned int, then C will equal 20000000 % 65535 = 11825 Only if C is a long does it equal the expected value of 20000000.

Note that this wrap-around can also happen in intermediate calculations, so you must be aware of this in the order you write assignments if you expect large numbers. For example setting:

C = (A/B) * 1000000;

may not give the same result as:

C = (A*1000) / (B*1000);

if the intermediate calculations overflow or underflow (and this can depend on the type of A, B, and C).

(Note that values grouped in parenthesis are calculated first, creating the intermediate results.)

Also be aware that the compiler (and the HSC12 processor) does not recognized 'floating point' numbers (those with non-zero decimal portions), in either constants or intermediate calculations.

In another example, suppose you want to multiply A by PI=3.1415926... to get B. You cannot write:

B = A * 3.14159; (since decimal values are not allowed),

instead you have to write something like

B = (A * 314159)/100000; (and you can see why things *might* overflow!)

Finally, you should not write:

B = A * (314159/100000);

since the intermediate value (314159/100000) will be truncated to 3 (no floating point numbers), and the result will be B = A * 3; (Note: also see 'casts'.)

The Codewarrior compiler will often produce a "possible loss of data" warning if it can determine that a calculation or variable might overflow, but it is not fool-proof by any means.

Arrays create single or multidimensional matrices. They are defined by appending an integer representing the number of items (-1) in the array, encapsulated in square brackets, at the end of a variable name, like this:

int amap_rpm[6];     Rpm values for alpha_map table

Each additional set of brackets defines an additional dimension to the array.

int adv_table[NO_MAPS][NO_RPMS]

When addressing an index in the array, indexing begins at 0 and ends at 1 less than the defined array. If no initial value is given to the array size, then the size is determined by the initializers. When defining a multidimensional array, nested curly braces can be used to specify which dimension of the array to initialize. The outermost nest of curly braces defines the leftmost dimension, and works from left to right.

The #include directive allows external header files to be processed by the compiler. These included files can contain additional code, as well as addition declaration and definitions. The compiler treats the code in the include file as if it were part of the current file.

#define NO_MAPS    12
#define NO_RPMS    12

User inputs - 1 set in flash, 1 in RAM
typedef struct {
unsigned char no_cyl,no_skip_pulses, skip >=3 pulses
     max_coil_dur,max_spk_dur; ms x 10
} inputs;

Operators are used to perform operations on one or more variables or constants. As an example, in the equation 3² = 9, the ² (squared) is the operator. Similarly, 5+6 = 11 has + as the operator.

C has the usual mathematical operators:

and a number of comparison operators: as well as some 'logical' operators: C also has some 'peculiar' operators that are somewhat unique:

C also allows some 'shortcuts' when writing expressions. Instead of:

x = x + y / 8

We can write:

x += y/8


x *= yx = x * y
x -= yx = x - y
x /= yx = x / y
x += yx = x + y
x %= yx = x % y
Note that y can be an expression itself

The #pragma directive allows a directive to be defined. For example:

#pragma CONST_SEG ROM_VAR    this ends up at 0x5000 to += 1024 bytes;
const inputs inpflash = {.....};

Structures (and unions) provide a way to group common variables together. The syntax to define a structure is:

struct structure-name {


} structure-variables,...;

Structure-name is optional and not needed if the structure variables are defined. Inside it can contain any number of variables separated by semicolons. At the end, structure-variables defines the actual names of the individual structures. Multiple structures can be defined by separating the variable names with commas. If no structure-variables are given, no variables are created. Structure-variables can be defined separately by specifying:

struct structure-name new-structure-variable;
new-structure-variable will be created and has a separate instance of all the variables in structure-name.

To access a variable in the structure, you must use a record selector (.).

In MS-II and GPIO code, there are several structures (sometimes called 'tables' or 'blocks'). For example, inpram., in2ram, and outpc. are 'structures.


If a function is accessed before it is defined, then it must be prototyped so the compiler knows about the function. Prototyping normally occurs at the beginning of the source code, and is done in the following manner:

return-type function-name(parameter-type-list);

return-type and function-name must correspond exactly to the actual function definition. parameter-type-list is a comma separated list of the types of variable parameters. The actual names of the parameters do not have to be given here, although they may for the sake of clarity.

Like any other computer language, C code consists of a series of statements. These statements end with a semi-colon ;. Multiple statements can be combined by enclosing them in curly brackets {}.

For example:

 if((-tpsdot) < inpram.TpsThresh)goto TDE_CHK_DONE;

 if(outpc.engine & 0x10) { if tps accel bit set
   outpc.tpsfuelcut = 100;
   outpc.tpsaccel = 0;
   outpc.engine &= ~0x10;    clear tps accel bit
   PORTB &= ~0x40;      clear accel led
   outpc.engine &= ~0x20;    clear tps decel bit
   goto EGOEN;

Note that in the first IF statement, there is a single expression TDE_CHK_DONE;

In the second IF statement, there are a series of statements enclosed in the brackets. A semi-colon is not needed, since the closing bracket signifies the end of the statement.

The IF statement occurs very frequently in the MegaSquirt-II code. The form of an IF statement is:

 IF (expression=true)
    {statement1; statement2; statement3; ... performed if expression=true}
    {statement10; statement11; statement12; ... performed if expression=false}

Note that the ELSE statement is optional, as in the preceding example.

Another common form of statemnt in the MegaSquirt-II code is the assignment. This assigns the value of a calculation to a variable.

Here are two examples from the MegaSquirt-II code:

   lsum = (pw_open + outpc.tpsaccel) * 100;    usec
   pwcalc1 = (unsigned int)(lsum1 + lsum);    usec

Here lsum is assigned a value based on the addition of (pw_open and tpsaccel)*100. The value of this variable lsum is used in the next calculation (along with a variable called lsum1) to assign a value to the pwcalc1 variable.

C has some odd looking short-cuts for assignments. Instead of i=i+1 you can simply put i++. You can also use i- for i=i-1. These is most useful when incrementing variables in conditional loops.

Note that the assignment operator = is NOT used to test for equality. Equality is tested with ==. So i=j assigns the values of j to i, while i==j return a 1 if j is equal in value to i, zero otherwise.

The two basic logical operators, used with conditional statements, are:

Beware & and have a different meaning for bitwise AND and OR.

A C program basically has the following form:

Each of these will be covered in turn, with examples from the MegaSquirt-II code.


Every C program MUST have a main() function. A function has the form:

type function_name (parameters)
     local variables

     C Statements


In MegaSquirt-II, the main function starts like this (at about line 460):

void main(void) {
  int ix;
  int tmp1,tmp2,tmp3;

and it continues for a large number of statements, many of which are calls to other functions.

The 9S12C64 Microcontroller

The Freescale 9S12C64 microcontroller used on the GPIO board (and with this 4L60E code) has a 16 bit, 24 MHz processor. 16 bit means it can address memory locations from:

0000 0000 0000 0000 to 1111 1111 1111 1111 binary locations
= 0 to 65535 decimal
= 0x0000 to 0xFFFF hexadecimal

Note that in Codewarrior, any number starting with 0x (or 0X) is considered a hexadecimal number (example 0xF1B0) - sometimes written in documentation with a preceeeding $ or a following h, and any number stating with a 1 to 9 is considered a decimal number. C has no native syntax for expressing a binary number in source code (though one can be user defined - google it). Hexadecimal is used instead, and is convenient because of the smaller size, and each digit corresponds to a 4 bit binary block.)

An easy way to convert between binary, hexadecimal, and decimal is to open the Windows calculator (Start/All Programs/Accessories/Calculator) in 'scientific' mode (under 'View') then click between the various formats (it has hexadecimal, decimal, octal, and binary). The number in the display is converted as you click different formats)

The 9S12C64 microcontroller has 3 types of memory:

  1. 4 kilobytes of RAM (Ramdom Access Memory) - used for values that need to be changed frequently (all the values that MegaTune normally changes are changed in RAM, except the thermistor and MAF tables). They can then be 'burned to flash with the 'Burn to ECU' button. RAM loses its contents if the power supply to the GPIO main board is interuppted, even very briefly. At start-up these values are copied from the flash memory (below) into the two 1Kbyte inpram. and in2ram. stuctures in RAM.
  2. 64 kilobytes of flash EEPROM memory - flash EEPROM (Electrically Eraseable Programmable Read Only Memory) memory is retained even if the power is removed for long periods. Flash can be read easily, but is much slower and more difficult to write (since it is erasable only in 1024-byte sectors, and must be erased before even a single bit can be written), so it is used for program instructions and values that don't change much (like the thermistor tables). Note that the compiler may refer to flash as ROM (read only memory), because though it is not 'read-only' it serves much the same purpose as ROM in some other microcontrollers. (Note that rumour has it that the 9S12C64 actually has 128 kiloBytes of flash memory, which can be accessed using the paging mechanism described below.)
  3. Registers - used to configure the microcontroller (I/O pins, timer ports, pulse width modulation (PWM), and to store the status of specific operations etc.)

    For example: Port DDR (data direction register) Configuration

    Many of the I/O port pins on the 9S12C64 processor can be configured as either an input or an output. The way you tell the processor which should be what is by filling zeros or ones in the data direction registers.

    0=input, 1=output, x=not assigned (default is input)

    For the GPIO, for example, we want:

    Value Written to Register
    PAxxxxxxx1= 01= 0x01
    PAD00xx0000= 00= 0x00
    PBxxx1xxxx= 16= 0x10
    PExxx1xxx1= 16= 0x11
    PMxx1111xx= 60= 0x3C
    PTx0011110= 30= 0x1E

    Note that the data direction registers are memory addresses, but there are 'convenient' names defined for these addresses in the hcs12def.h file (open it and look for things like DDRA for port A, etc.). In most cases, the HCS12 registers are 8 bits (1 byte).

    BTW, these user configurable input/output ports are called "general purpose I/O" ports which is where the GPIO board gets its name.

The memory is addressed as a number from 0x0000 to 0xFFFF. For example, the memory addresses for the 4L60E code for the GPIO are arranged as:

0x0000 to 0x03FF - Registers
0x03FF to 0x3000 - 16K fixed Flash EEPROM (page 0x003D)
0x3000 to 0x3FFF - RAM (mappable to any 4K boundary)
0x4000 to 0x7FFF - 16K fixed Flash EEPROM (fixed page 0x003E)
0x8000 to 0xBFFF - 16K fixed Flash EEPROM (ppage = 0x003C)
0xC000 to 0xFEFF - 16K fixed Flash EEPROM (fixed page 0x003F)
0xFF00 to 0xFFFF - Interrupt Vectors (BDM if active)

and the memory 'segments' are defined in the monitor_linker.prm file in the PRM folder as:

SEGMENTS RAM = READ_WRITE 0x3000 TO 0x3FFF; 4096 bytes

// unbanked FLASH ROM CLT_ROM = READ_ONLY 0x4000 TO 0x47FF;
EGO_ROM = READ_ONLY 0x5000 TO 0x53FF;
ROM_7000 = READ_ONLY 0x7000 TO 0x7FFF;

// ROM_8000 = READ_ONLY 0x8000 TO 0xBFFF;
// banked FLASH ROM
PAGE_3D = READ_ONLY 0x3D8000 TO 0x3DBFFF; 16384 bytes, OTHER_ROM
ROM_C000 = READ_ONLY 0xC000 TO 0xF77E; 14206 bytes, NON_BANKED, COPY, RUNTIME


In the monitor_linker.prm file is a section called 'PLACEMENT' which contians the user-specified labels (you can a call them whatever you want - except for reserved words) that the pragma code_seg directives use. This allows us to separate the pragma statements from the memory addresses, and makes things like changing to other processor variants, etc. much easier.


'pragma's are directives to the compiler at compile time (not when the code is executed) that direct the actions of the compiler in a particular portion of a program without affecting the program as a whole. Most commonly, we use them to specifiy where the compiler (actually the linker) should put things in memory.

The rpm file, pragma directives, and ppage statements in the code work together. The prm file tells the comiler how the memory is organized. The pragma statments tell the linker where to put sections of the code in memnory. The ppage statements tell the running processor where to look in memory for code and values, etc.

Note that some apparently different memory areas overlap. For example, in the 4L690E code:

Only the FLASH area 0x0000 - 0x3FFF overlaps with Flash area 0x3D8000 - 0x3DBFFF. (Not the SFR registers and internal RAM). This is because the SFR Registers and internal RAM have higher priority from Flash in the bus arbitrator, so when they overlap with some of the Flash memory, they win precedence over the Flash that occupies the same address range, and so the internal registers and RAM are accessed as needed, rather than the underlying Flash locations.

In other words, Flash portions that are not covered with SFR registers and internal RAM are accessible through the 0x0000 - 0x7FFF address range. These Flash locations are also accessible through PPAGE pages 0x3D and 0x3E in the 0x8000 - 0xBFFF program page window - where no Flash locations are usually covered by SFR registers or internal RAM (unless INITRM is set to this address range).

For more information on the prm file, ppaged memory, #pragmas and how to use them in C programming, see:


or search the freescale site for document AN2216.pdf

In general, you won't need to know these unless you are doing a major re-working of the code. For adding features, etc., you just need to know where specific variables are located.

Memory locations for specific variables can be found in the file that the linker creates. These locations will be very helpful (i.e., necessary) to write the INI file that MegaTune uses.

Also, note that all the variables you want to see in MegaTune, that are outpc from MS-II to the laptop rather than inputs to MS-II, need to be in the outpc. stucture to be recognized by MegaTune (they also have to be set up in the INI file)

MegaSquirt® and MicroSquirt® controllers are experimental devices intended for educational purposes.
MegaSquirt® and MicroSquirt® controllers are not for sale or use on pollution controlled vehicles. Check the laws that apply in your locality to determine if using a MegaSquirt® or MicroSquirt® controller is legal for your application.

© 2004, 2009 Al Grippo and Bruce Bowling - All rights reserved. MegaSquirt® and MicroSquirt® are registered trademarks.