Aircraft Fuel Injection ECU

Experimental Aircraft Fuel Injection and Engine Control Unit

Specifically for experimental homebuilt aircraft, I created a custom ECU, wrote custom firmware, modified a fuel injection system, and set up the ECU to control the ignition. Ultimately, this will also provide the features that a typical pilot would want from a much more expensive Engine Information System (EIS). This page details those efforts.

If you would like to see how I got to this point, that is detailed in a separate page found here:

Experimental Aircraft Fuel Injection and Engine Control Unit.

Current Project Status

This is an experimental project. If you looking for a product to buy and install, all ready to go, this is not for you. Actually, what you need more than the hardware and software is a company that is dedicated to providing you support. You are building an airplane; that is already complicated enough. Get some help with the systems that make the engine run. Those companies are out there.

Organization first. There are three main areas of development on this project:

  • The HARDWARE - This primarily constitutes the ECU, but the sensors, the fuel system, and the ignition system are part of it.

  • The FIRMWARE - This is the software that runs on the ECU.

  • The DISPLAY - This area did not get a lot of attention on my original pages, but it does deserve to be mentioned. The ECU is capturing and transmitting all kinds of current engine information, and being able to display that on a panel is very valuable.

In addition to those areas, I am also working with two releases:

  • The CURRENT release - This is the Hardware, Firmware, and Display currently being used in my Thatcher CX4.

  • The TESTING release - This is where the project is going.

A Word of Caution

This is an experiment, and one that helps an engine have little internal explosions. It might cause one very big one, or make engine pieces, or just quit when you need the engine the most. Use any and all of this information AT YOUR OWN RISK. Explain this to you spouse, partner, kids (or whoever might seek action because you got hurt). Please also read the Terms and Conditions of this website.

This information is being provided to assist those with the skills, expertise, and experience to be able to fully understand and execute this very complicated project. Odds are that is not you. If you are the kind of person that is already experienced in circuit design, microcontroller programming, and engine systems, then this is only a guide for your own experimentation.

Cool? OK, let us begin.

Goals

The ultimate goals of this project are to:

  • Provide a focused ECU specifically for an aircraft engine.

  • Provide the features found in aircraft Engine Information Systems (EIS).

  • Keep this whole thing open-source.

  • Minimize the potential for failure.

Philosophy on Redundancy

OK, that last one is important. Minimize the potential for failure. In the airplane world this becomes a religious argument! This is where my philosophy may differ from the regular crowd.

In an airplane engine the spark is created traditionally by two separate magnetos. The regular airplane crowd is used to this redundancy. In the event that a magneto fails, the engine will still run on the remaining magneto. This creates an expectation that the thing driving the spark plugs must have redundancy.

I believe that redundancy on a digital modern ECU is a bad idea.

A magneto is based on technology only slightly newer than the Civil War. It is a mechanical beast that has a distributor, points, a condenser... all things that will normally fail under normal use. So you better have two of them (it is the only thing I can think of in an airplane that is commonly redundant).

Then there is the more is better idea. Wrong.

  • When building an airplane, if you have a thing to bolt on, and if four bolts is good, then six bolts only makes it stronger. This is absolutely not the case in electronics!

  • In electronics, you need exactly the components to make a circuit. No more, no less.

  • All components have a Mean Time Between Failures (MTBF). Looking at one component, for example, it might have one failure in 1,000,000 hours. If you add two of those, then you get one failure in 500,000 hours. If you add 1,000 of those, you get one failure on average every 1,000 hours. The more you add, the more frequently you get a failure, the lower the MTBF (a bad thing).

  • How about separate redundant ECUs then? Well, they can never be electrically separate. They have to both be wired to the same fuel and spark hardware to run the engine. They need to be in communication with each other, which is just more wires. And, the software gets exponentially more complicated. The software fills with what if the other ECU dies?, what if I loose communication with the other?, what if it comes back... who is in charge?, how can I tell if I am the one failing?. The additional complications are simply many more ways for the system to fail. I would expect that if one ECU electrical burns out, it would most likely drag the second one down with it. Additionally, you now have more than doubled the quantity of components substantially decreasing the MTBF of the system!

My belief: Less is Better.

Testing Release

Happy thoughts. Happy thoughts.

The testing release is stuff I am working on for the future. Maybe my next airplane. It is not quite finished, but it will incorporate:

  • The basic ECU functions to run and tune the engine for both fuel and spark.

  • Additional sensor inputs for the type of expected information found on typical Engine Information Systems.

Really, the basic ECU already has almost everything a pilot would need to know about the operation of the engine. The additional sensors are just a bunch of cylinder head temperature sensors, a couple more fuel level inputs, and the ability to add some external addressable Two-Wire-Interface components (such as a digital barometer and velocity pressure sensor for altitude and airspeed).

The schematics and board design are finished. The software needs to be ported over. This all waits for a test engine to work with (probably the Corvair sitting in the corner of the shop).

The Hardware

This part is finished! It is a complete ECU and EIS system. Nice.

Inputs:

  • Crankshaft Trigger
  • Manifold Pressure
  • Throttle Position
  • Oil Pressure
  • Fuel Pressure
  • (2) O2 Sensors
  • Intake Air Temperature
  • Oil Temperature
  • (3) Fuel Level Gauges
  • (6) Cylinder Head Temperature Thermocouples
  • (4) Dry Contacts (programmable)

Outputs:

  • (2) Injectors Drivers (MOSFET)
  • (6) Coil Signals (5 volt)

The inclusion of only two injector drivers limits this to throttle body or bank type systems.

A digital image of the board is shown below. This incorporates the Microchip/Atmel AVR-DA microcontroller. Basically a newer version of the chip family normally found in the Arduino devices.

Aircraft Fuel Injection ECU

The schematic is available. Click on the following image to download a PDF version of it:

Aircraft Fuel Injection ECU

Boards have been fabricated. When this design is tested, I will eventually upload the PCB files here.

The Firmware

The firmware will be similar to the Current Release. It will have to be ported over to the newer AVR chip and board. This means that the microcontroller setup for the pins and timers will change, and few functions to calculate the new inputs will be added. But the software which controls the operation of the engine will be almost unchanged (it works just fine, no need to change).

The Display

The ECU (both the Current and Testing release) has a Bluetooth output which broadcasts a serial stream of the current engine information. This is available for a separate program running on a separate device to receive and then be presented on a screen and/or saved to a file.

The Current Release display shows a simple text screen, written in Python, and runs on a Raspberry Pi computer (or any Linux laptop for that matter). However this is a bit obtuse. It should be an app on a phone with cool graphics. We like phones. We like cool. So the next display will be an app!

Current Release

The Hardware

As mentioned on the development page, the Current Release uses a Arduino Mega with a custom shield. It looks like this:

Aircraft Fuel Injection ECU

Aircraft Fuel Injection ECU

This is where I would post the PCB files, but I would rather not. There was one pin-out mistake (of which was easy to fix with a soldered jumper) and I would rather we all focus on the newer version anyway. Here is the older schematic, but use it only for historical reference.

Click on the following image to download a PDF version of it:

Aircraft Fuel Injection ECU

The Firmware

Really! The ACTUAL firmware for an ECU!

It is pretty simple in theory, just a lot of very important details. And, it has to be exactly right. Pretty close is not good enough.

This is a snapshot of the almost-finished firmware. There are a couple of sensor details commented out in the Read Inputs ( void readInputs(void) ) section, the whole O2 automated tuning section is not shown here, and the tuning tables are at the default full-rich values. Regardless, if you are a microcontroller programmer this should be obvious and helpful.

And the following is pretty long. There is more webpage after it... so scroll big!

//  HainesECU - Firmware for a dedicated ECU developed for the VW engine.
//  Copyright (C) 2025, Robert Haines
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.


//  INTRODUCTION
//
//    This code was developed for a custom engine control unit (ECU) to
//    provide fuel injection and spark control of my experimental aircraft.
//    This firmware is targeted for the Arduino Mega2560 board.
//    The shield version is "Haines ECU 2015-02-10".
//
//    There is a document titled "Haines ECU Notebook" that details the
//    operation of this code.


#include <avr/io.h>
#include <avr/iom2560.h>
#include <avr/interrupt.h>

#define setInterrupts   sei()
#define clearInterrupts cli()

void mainConfig(void);
void readInputs(void);
void calcOutputs(void);
void sendSerial(void);
void setDelay(unsigned int);
void setSpark(unsigned int);
void setFuel(void);
void correctVE(void);
void totalize(void);
unsigned int readAD(unsigned int);
unsigned int timerticks(unsigned int, unsigned int);
int main(void);

// Engine
const unsigned int  CYLINDER    = 479;
const unsigned int  TOOTHQTY    = 8;
const unsigned int  TOLERANCE   = 10;
const int           WHEELOFFSET = 0;
const unsigned int  STARTRPM    = 75;
// Fuel
const unsigned int  INJRATE = 7820;
const unsigned int  INJDELAY    = 1000;
const unsigned int  RUNRICH     = 1050;
// Spark
const unsigned int  SPARKQTY    = 2;
const unsigned int  SPARKARC    = 1800;
const unsigned int  MAXADVANCE  = 350;
const unsigned int  DWELLTIME   = 4000;
const unsigned int  SPARKTIME   = 1500;
// Correction
const unsigned int  O2GOAL  = 450;
const unsigned int  VECRATE     = 10;
const unsigned int  VECLIMITL   = 800;
const unsigned int  VECLIMITU   = 1200;
const unsigned int  SAPROBLEM   = 0;    // need some indicator (head temp?)
const unsigned int  SACRATE     = 10;
const unsigned int  SACLIMITL   = 800;
const unsigned int  SACLIMITU   = 1200;


typedef struct {

    unsigned long int ENDOFLINE;
    unsigned int  seconds;
    unsigned int  calculations;
    unsigned int  rpm;
    unsigned int  oiltemp;
    unsigned int  mat;
    unsigned int  map;
    unsigned int  o2;
    unsigned int  throttle;
    unsigned int  battery;
    unsigned int  injopen;
    unsigned int  dutycycle;
    unsigned int  advance;
    unsigned int  dwell;
    unsigned long int totalfuel;
    unsigned int  runrich;
    unsigned int  switch1;
    unsigned int  switch2;

} Variables;

Variables variables;


typedef struct {

    unsigned long int ENDOFLINE;
    unsigned int  VERPM[21];
    unsigned int  VEMAP[21];
    unsigned int  VE00[20];
    unsigned int  VE01[20];
    unsigned int  VE02[20];
    unsigned int  VE03[20];
    unsigned int  VE04[20];
    unsigned int  VE05[20];
    unsigned int  VE06[20];
    unsigned int  VE07[20];
    unsigned int  VE08[20];
    unsigned int  VE09[20];
    unsigned int  VE10[20];
    unsigned int  VE11[20];
    unsigned int  VE12[20];
    unsigned int  VE13[20];
    unsigned int  VE14[20];
    unsigned int  VE15[20];
    unsigned int  VE16[20];
    unsigned int  VE17[20];
    unsigned int  VE18[20];
    unsigned int  VE19[20];
    unsigned int  SA00[20];
    unsigned int  SA01[20];
    unsigned int  SA02[20];
    unsigned int  SA03[20];
    unsigned int  SA04[20];
    unsigned int  SA05[20];
    unsigned int  SA06[20];
    unsigned int  SA07[20];
    unsigned int  SA08[20];
    unsigned int  SA09[20];
    unsigned int  SA10[20];
    unsigned int  SA11[20];
    unsigned int  SA12[20];
    unsigned int  SA13[20];
    unsigned int  SA14[20];
    unsigned int  SA15[20];
    unsigned int  SA16[20];
    unsigned int  SA17[20];
    unsigned int  SA18[20];
    unsigned int  SA19[20];

} Tuning;

Tuning defaultTuning = {

    0xfffffff2,

    {0,625,750,875,1000,1125,1250,1375,1500,1625,1750,1875,2000,2125,2250,2375,2500,2625,2750,2875,3000},
    {0,3400,3800,4200,4600,5000,5400,5800,6200,6600,7000,7400,7800,8200,8600,9000,9400,9800,10200,10600,11000},

    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},

    {0,350,386,422,458,494,531,567,603,639,675,711,747,783,819,856,892,928,964,1000},
    {0,345,381,416,436,488,524,560,595,631,667,703,739,775,810,846,882,918,954,989},
    {0,339,375,411,431,482,517,553,588,624,659,695,730,766,801,837,872,908,943,979},
    {0,334,369,405,426,475,510,546,581,616,651,687,722,757,792,827,863,898,933,968},
    {0,329,364,399,421,469,504,539,574,608,643,678,713,748,783,818,853,888,923,958},
    {0,324,358,393,415,462,497,532,566,601,636,670,705,739,774,809,843,878,913,947},
    {0,318,353,387,410,456,490,525,559,593,628,662,696,731,765,799,834,868,902,937},
    {0,313,347,381,405,449,483,518,552,586,620,654,688,722,756,790,824,858,892,926},
    {0,308,342,375,400,443,477,511,544,578,612,646,679,713,747,781,814,848,882,916},
    {0,303,336,370,394,437,470,504,537,570,604,637,671,704,738,771,805,838,872,905},
    {0,297,331,364,389,430,463,496,530,563,596,629,662,696,729,762,795,828,862,895},
    {0,292,325,358,384,424,457,489,522,555,588,621,654,687,720,753,786,818,851,884},
    {0,287,319,352,379,417,450,482,515,548,580,613,645,678,711,743,776,808,841,874},
    {0,282,314,346,373,411,443,475,508,540,572,605,637,669,702,734,766,799,831,863},
    {0,276,308,340,368,404,436,468,500,532,564,596,629,661,693,725,757,789,821,853},
    {0,271,303,335,363,398,430,461,493,525,557,588,620,652,683,715,747,779,810,842},
    {0,266,297,329,357,392,423,454,486,517,549,580,612,643,674,706,737,769,800,832},
    {0,261,292,323,352,385,416,447,479,510,541,572,603,634,665,696,728,759,790,821},
    {0,255,286,317,347,379,410,440,471,502,533,564,595,625,656,687,718,749,780,811},
    {0,250,281,311,342,372,403,433,464,494,525,556,586,617,647,678,708,739,769,800},

};


Tuning measuredTuning = {

    0xfffffff3,

    {0,625,750,875,1000,1125,1250,1375,1500,1625,1750,1875,2000,2125,2250,2375,2500,2625,2750,2875,3000},
    {0,3400,3800,4200,4600,5000,5400,5800,6200,6600,7000,7400,7800,8200,8600,9000,9400,9800,10200,10600,11000},

    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},

    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},
    {1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000,1000},

};


typedef struct {

    unsigned long int ENDOFLINE;
    unsigned int      RTItick;
    unsigned int      calcticker;
    unsigned int      triggertick;
    unsigned int      triggerlast;
    unsigned int      triggerdelta;
    unsigned int      triggerlastdelta;
    unsigned int      triggertimeout;
    unsigned int      synchronized;
    unsigned int      tooth;
    unsigned int      tootharc;
    unsigned int      spark;
    unsigned int      sparktooth;
    unsigned int      TCNTbuffer;
    unsigned int      serialpart;
    unsigned int      serialpiece;
    void *            serialmemstart1;
    unsigned int      serialmemend1;
    void *            serialmemstart2;
    unsigned int      serialmemend2;
    void *            serialmemstart3;
    unsigned int      serialmemend3;
    void *            serialmemstart4;
    unsigned int      serialmemend4;
    unsigned int      xrpm;
    unsigned int      ymap;
    unsigned int      rpmlimit;
    unsigned long int VEcalc;
    unsigned long int SAcalc;
    unsigned int      delaytime;
    unsigned int      TCNTinjopen;
    unsigned int      TCNTdelaytime;
    unsigned int      TCNTdwell;
    unsigned long int totalizer;

} Global;

Global global;


void mainConfig(void) {

    // Set Public Variables
    variables.ENDOFLINE        = 0xfffffff1;
    global.ENDOFLINE           = 0xfffffff4;
    global.synchronized        = 0;
    global.tooth               = 0;
    global.tootharc            = 3600/TOOTHQTY;
    global.spark               = 0;
    global.sparktooth          = 0;
    global.serialpart          = 0;
    global.serialpiece         = 0;
    global.serialmemstart1     = &variables;
    global.serialmemend1       = 40;
    global.serialmemstart2     = &defaultTuning;
    global.serialmemend2       = 1688;
    global.serialmemstart3     = &measuredTuning;
    global.serialmemend3       = 1688;
    global.serialmemstart4     = &global;
    global.serialmemend4       = 76;
    global.rpmlimit            = defaultTuning.VERPM[20];
    global.totalizer           = 0;
    variables.totalfuel        = 0;

    // I/O Ports
    // All pins initially to input
    DDRA        = 0b00000000;
    DDRB        = 0b00000000;
    DDRC        = 0b00000000;
    DDRD        = 0b00000000;
    DDRE        = 0b00000000;
    DDRF        = 0b00000000;
    DDRG        = 0b00000000;
    DDRH        = 0b00000000;
    DDRJ        = 0b00000000;
    DDRK        = 0b00000000;
    DDRL        = 0b00000000;

    // Enable all on-chip pull-up resistors for unused pins
    PORTA       = 0b11111111;
    PORTB       = 0b01101111;  // LED Arduino #13, Injector1
    PORTC       = 0b11110101;  // LED 3, LED 2
    PORTD       = 0b01111111;  // LED 1
    PORTE       = 0b11000111;  // Coil3, Coil2, Coil1
    PORTF       = 0b00000000;  // Analog in
    PORTG       = 0b11111101;  // Switch1
    PORTH       = 0b10111111;  // Injector2
    PORTJ       = 0b11111111;
    PORTK       = 0b00000000;  // Analog in
    PORTL       = 0b01111100;  // Switch2, Trigger1, Trigger2

    // LEDs
    DDRB       |= 0b10000000;  // Set B7 to output (Arduino #13)
    DDRD       |= 0b10000000;  // Set D7 to output (LED 1) 
    DDRC       |= 0b00000010;  // Set C1 to output (LED 2)
    DDRC       |= 0b00001000;  // Set C3 to output (LED 3)

    // TIMER0     Real Time Interrupt
    TCCR0A      = 0b00000010;  // CTC mode
    TCCR0B     |= 0b00000011;  // 16 MHz / 64 = 250 kHz
    OCR0A       = 249;         // Results in 1000 Hz
    TIMSK0     |= 0b00000010;  // Enable OC0A interrupt

    // TIMER2     Injector1 on PB4/OC2A
    //            Injector2 on PH6/OC2B
    DDRB       |= 0b00010000;  // Injector1: Set B4 to output
    DDRH       |= 0b01000000;  // Injector2: Set H6 to output
    TCCR2A      = 0b00000000;  // Injector1&2: no change (initially disconnected)
    TCCR2B      = 0b00000000;  // Injector1&2: no change; normal counter mode
    TCCR2B     |= 0b00000111;  // Injector1&2: 16 MHz / 1024 = 15625 Hz
    TCCR2A      = 0b11110000;  // Output mode to On (A&B)
    TCCR2B     |= 0b11000000;  // Forces result to happen (A&B)
//  TCCR2A      = 0b10100000;  // Output mode to Off (A&B)
//  TCCR2B     |= 0b11000000;  // Forces result to happen (A&B)

    // TIMER3     Coil1 on PE3/OC3A
    //            Coil2 on PE4/OC3B
    //            Coil3 on PE5/OC3C
    DDRE       |= 0b00001000;  // Coil1: Set E3 to output
    DDRE       |= 0b00010000;  // Coil2: Set E4 to output
    DDRE       |= 0b00100000;  // Coil3: Set E5 to output
    TCCR3A      = 0b00000000;  // Coil1,2,3: no change (initially disconnected)
    TCCR3B      = 0b00000000;  // Coil1,2,3: no change; normal counter mode
    TCCR3B     |= 0b00000011;  // Coil1,2,3: 16 MHz / 64 = 250000 Hz
    TCCR3A      = 0b10101000;  // Output mode to Off (ABC)
    TCCR3C     |= 0b11100000;  // Forces result to happen (ABC)


    // TIMER5     Trigger1 on PL1/ICP5
    // TIMER4     Trigger2 on PL0/ICP4 (currently not used)
    TCCR5A      = 0b00000000;  // Trigger1: no change
    TCCR5B     |= 0b10000000;  // Trigger1: noise cancel on
    TCCR5B     &= 0b10111111;  // Trigger1: falling edge
//  TCCR5B     |= 0b01000000;  // Trigger1: rising edge
    TCCR5B     |= 0b00000011;  // Trigger1: 16 MHz / 64 = 250000 Hz
    TIFR5      |= 0b00100000;  // Clear interrupt flag
    TIMSK5     |= 0b00100000;  // Enable input capture interrupt

    // Analog Inputs
    ADCSRA     |= 0b00000111;  // Set ADC prescaler to 16Mhz/128 = 125kHz
    ADMUX      |= 0b01000000;  // Set reference V to AVCC
    ADCSRA     |= 0b10000000;  // Set ADEN (AD Enable) on

    // Serial Port
    // Arduino USB
    UBRR0 = 8;                 // 115200 baud (value from datasheet Table 22-12)
    UCSR0B |= 0b00001000;      // Enable Transmitter
    UCSR0C |= 0b00000110;      // 8, none, 1
    // ECU Port
    UBRR1 = 16;                // 57600 baud (value from datasheet Table 22-12)
    UCSR1B |= 0b00001000;      // Enable Transmitter
    UCSR1C |= 0b00000110;      // 8, none, 1

}


void readInputs(void) {

    unsigned int tempBattery;
    unsigned int tempOiltemp;
    unsigned int tempMat;
    unsigned int tempMap;
    unsigned int tempO2;
    unsigned int tempThrottle;

    unsigned long int rawinput;

    // Battery Voltage
    rawinput = readAD(0);
    rawinput = ((unsigned long int) rawinput * 21484) / 100000;
    if (rawinput > 0xffff) rawinput = 0xffff;
    tempBattery = (unsigned int) rawinput;
    //tempBattery = 120;

    // Oil Temperature
    rawinput = readAD(2);
    //rawinput = ((unsigned long int) rawinput * 100) / 100;
    //if (rawinput < 2600) rawinput = 2600;
    //if (rawinput > 5300) rawinput = 5300;
    tempOiltemp = (unsigned int) rawinput;
    tempOiltemp = 3930;

    // Manifold Air Temperature
    rawinput = readAD(5);
    rawinput = (unsigned long int) 3440 - (((unsigned long int) rawinput * 9598) / 10000);
    if (rawinput < 2600) rawinput = 2600;
    if (rawinput > 3300) rawinput = 3300;
    tempMat = (unsigned int) rawinput;
    //tempMat = 2920;

    // Manifold Air Pressure
    rawinput = readAD(7);
    rawinput = ((((unsigned long int) rawinput * 100000) + 11204000) / 10844);
    if (rawinput > 0xffff) rawinput = 0xffff;
    rawinput = ((unsigned long int) rawinput + ((unsigned long int) variables.map) * 9) / 10;
    tempMap = (unsigned int) rawinput;
    //tempMap = 10133;

    // O2 Sensor
    rawinput = readAD(9);
    rawinput = ((unsigned long int) rawinput * 5000) / 1024;
    if (rawinput > 0xffff) rawinput = 0xffff;
    tempO2 = (unsigned int) rawinput;
    tempO2 = 450;

    // Throttle Position
    rawinput = readAD(12);
    rawinput = ((((unsigned long int) rawinput) - 118) * 128) / 100;
    if (rawinput > 0xffff) rawinput = 0xffff;
    tempThrottle = (unsigned int) rawinput;
    //tempThrottle = 750;

    clearInterrupts;

    variables.map = tempMap;
    variables.mat = tempMat;
    variables.oiltemp = tempOiltemp;
    variables.throttle = tempThrottle;
    variables.battery = tempBattery;
    variables.o2 = tempO2;

    setInterrupts;

}


void calcOutputs(void) {

    unsigned int rpmBuffer;
    unsigned int mapBuffer;
    unsigned int dwellBuffer;
    int degreeBuffer;
    unsigned int arcBuffer;
    unsigned long long int calcBuffer;
    unsigned int tempVE;
    unsigned int tempSA;
    unsigned int tempInjopen;
    unsigned int tempDutycycle;
    unsigned int tempAdvance;
    unsigned int tempDwell;
    unsigned int tempDwelladvance;
    unsigned int tempSparktooth;
    unsigned int tempDelaytime;
    unsigned int tempTCNTinjopen;
    unsigned int tempTCNTdelaytime;
    unsigned int tempTCNTdwell;

    rpmBuffer = variables.rpm;
    mapBuffer = variables.map;
    global.xrpm = 20;
    while (global.xrpm > 0) {
        if (rpmBuffer > defaultTuning.VERPM[global.xrpm]) break;
        global.xrpm--;
    }
    global.ymap = 20;
    while (global.ymap > 0) {
        if (mapBuffer > defaultTuning.VEMAP[global.ymap]) break;
        global.ymap--;
    }

    tempVE = 1000;
    switch (global.ymap) {
        case  0: { global.VEcalc = defaultTuning.VE00[global.xrpm]; tempVE = measuredTuning.VE00[global.xrpm]; } break;
        case  1: { global.VEcalc = defaultTuning.VE01[global.xrpm]; tempVE = measuredTuning.VE01[global.xrpm]; } break;
        case  2: { global.VEcalc = defaultTuning.VE02[global.xrpm]; tempVE = measuredTuning.VE02[global.xrpm]; } break;
        case  3: { global.VEcalc = defaultTuning.VE03[global.xrpm]; tempVE = measuredTuning.VE03[global.xrpm]; } break;
        case  4: { global.VEcalc = defaultTuning.VE04[global.xrpm]; tempVE = measuredTuning.VE04[global.xrpm]; } break;
        case  5: { global.VEcalc = defaultTuning.VE05[global.xrpm]; tempVE = measuredTuning.VE05[global.xrpm]; } break;
        case  6: { global.VEcalc = defaultTuning.VE06[global.xrpm]; tempVE = measuredTuning.VE06[global.xrpm]; } break;
        case  7: { global.VEcalc = defaultTuning.VE07[global.xrpm]; tempVE = measuredTuning.VE07[global.xrpm]; } break;
        case  8: { global.VEcalc = defaultTuning.VE08[global.xrpm]; tempVE = measuredTuning.VE08[global.xrpm]; } break;
        case  9: { global.VEcalc = defaultTuning.VE09[global.xrpm]; tempVE = measuredTuning.VE09[global.xrpm]; } break;
        case 10: { global.VEcalc = defaultTuning.VE10[global.xrpm]; tempVE = measuredTuning.VE10[global.xrpm]; } break;
        case 11: { global.VEcalc = defaultTuning.VE11[global.xrpm]; tempVE = measuredTuning.VE11[global.xrpm]; } break;
        case 12: { global.VEcalc = defaultTuning.VE12[global.xrpm]; tempVE = measuredTuning.VE12[global.xrpm]; } break;
        case 13: { global.VEcalc = defaultTuning.VE13[global.xrpm]; tempVE = measuredTuning.VE13[global.xrpm]; } break;
        case 14: { global.VEcalc = defaultTuning.VE14[global.xrpm]; tempVE = measuredTuning.VE14[global.xrpm]; } break;
        case 15: { global.VEcalc = defaultTuning.VE15[global.xrpm]; tempVE = measuredTuning.VE15[global.xrpm]; } break;
        case 16: { global.VEcalc = defaultTuning.VE16[global.xrpm]; tempVE = measuredTuning.VE16[global.xrpm]; } break;
        case 17: { global.VEcalc = defaultTuning.VE17[global.xrpm]; tempVE = measuredTuning.VE17[global.xrpm]; } break;
        case 18: { global.VEcalc = defaultTuning.VE18[global.xrpm]; tempVE = measuredTuning.VE18[global.xrpm]; } break;
        case 19: { global.VEcalc = defaultTuning.VE19[global.xrpm]; tempVE = measuredTuning.VE19[global.xrpm]; } break;
        case 20: { global.VEcalc = defaultTuning.VE19[global.xrpm]; tempVE = measuredTuning.VE19[global.xrpm]; } break;
    }

    calcBuffer = ((unsigned long int) global.VEcalc * tempVE) / 1000;
    global.VEcalc = (unsigned int) calcBuffer;

    tempSA = 1000;
    switch(global.ymap) {
        case  0: { global.SAcalc = defaultTuning.SA00[global.xrpm]; tempSA = measuredTuning.SA00[global.xrpm]; } break;
        case  1: { global.SAcalc = defaultTuning.SA01[global.xrpm]; tempSA = measuredTuning.SA01[global.xrpm]; } break;
        case  2: { global.SAcalc = defaultTuning.SA02[global.xrpm]; tempSA = measuredTuning.SA02[global.xrpm]; } break;
        case  3: { global.SAcalc = defaultTuning.SA03[global.xrpm]; tempSA = measuredTuning.SA03[global.xrpm]; } break;
        case  4: { global.SAcalc = defaultTuning.SA04[global.xrpm]; tempSA = measuredTuning.SA04[global.xrpm]; } break;
        case  5: { global.SAcalc = defaultTuning.SA05[global.xrpm]; tempSA = measuredTuning.SA05[global.xrpm]; } break;
        case  6: { global.SAcalc = defaultTuning.SA06[global.xrpm]; tempSA = measuredTuning.SA06[global.xrpm]; } break;
        case  7: { global.SAcalc = defaultTuning.SA07[global.xrpm]; tempSA = measuredTuning.SA07[global.xrpm]; } break;
        case  8: { global.SAcalc = defaultTuning.SA08[global.xrpm]; tempSA = measuredTuning.SA08[global.xrpm]; } break;
        case  9: { global.SAcalc = defaultTuning.SA09[global.xrpm]; tempSA = measuredTuning.SA09[global.xrpm]; } break;
        case 10: { global.SAcalc = defaultTuning.SA10[global.xrpm]; tempSA = measuredTuning.SA10[global.xrpm]; } break;
        case 11: { global.SAcalc = defaultTuning.SA11[global.xrpm]; tempSA = measuredTuning.SA11[global.xrpm]; } break;
        case 12: { global.SAcalc = defaultTuning.SA12[global.xrpm]; tempSA = measuredTuning.SA12[global.xrpm]; } break;
        case 13: { global.SAcalc = defaultTuning.SA13[global.xrpm]; tempSA = measuredTuning.SA13[global.xrpm]; } break;
        case 14: { global.SAcalc = defaultTuning.SA14[global.xrpm]; tempSA = measuredTuning.SA14[global.xrpm]; } break;
        case 15: { global.SAcalc = defaultTuning.SA15[global.xrpm]; tempSA = measuredTuning.SA15[global.xrpm]; } break;
        case 16: { global.SAcalc = defaultTuning.SA16[global.xrpm]; tempSA = measuredTuning.SA16[global.xrpm]; } break;
        case 17: { global.SAcalc = defaultTuning.SA17[global.xrpm]; tempSA = measuredTuning.SA17[global.xrpm]; } break;
        case 18: { global.SAcalc = defaultTuning.SA18[global.xrpm]; tempSA = measuredTuning.SA18[global.xrpm]; } break;
        case 19: { global.SAcalc = defaultTuning.SA19[global.xrpm]; tempSA = measuredTuning.SA19[global.xrpm]; } break;
        case 20: { global.SAcalc = defaultTuning.SA19[global.xrpm]; tempSA = measuredTuning.SA19[global.xrpm]; } break;
    }

    calcBuffer = ((unsigned long int) global.SAcalc * tempSA) / 1000;
    global.SAcalc = (unsigned int) calcBuffer;

    // Injector Open Time
    calcBuffer = (((((((((unsigned long int) 237027 * global.VEcalc) / 1000) * variables.map) / 1000) * CYLINDER) / variables.mat) * 100 ) / INJRATE );
    if (variables.runrich) calcBuffer = ( calcBuffer * RUNRICH ) / 1000; 
    tempInjopen = (unsigned int) calcBuffer;

    // Dutycycle
    calcBuffer = ((((unsigned long int) tempInjopen + INJDELAY) * rpmBuffer) * SPARKQTY) / 60000;
    tempDutycycle = (unsigned int) calcBuffer;

    // Ignition Advance Time
    calcBuffer = ((unsigned long int) global.SAcalc * MAXADVANCE) / 1000;
    tempAdvance = (unsigned int) calcBuffer;

    // Dwell Time
    dwellBuffer = DWELLTIME;
    if (variables.battery < 120) dwellBuffer = dwellBuffer + 1000;  // add a millisecond
    if (variables.battery <  80) dwellBuffer = dwellBuffer + 1000;  // add another
    if (rpmBuffer > 0) {
        calcBuffer = ((unsigned long int) 60000000 / rpmBuffer) / SPARKQTY;
        if (calcBuffer < (dwellBuffer + SPARKTIME)) {
            calcBuffer = calcBuffer - SPARKTIME;
            tempDwell = (unsigned int) calcBuffer;
        } else tempDwell = dwellBuffer;
    } else tempDwell = dwellBuffer;

    // Dwell Advance
    calcBuffer = (unsigned long int) tempDwell * rpmBuffer / 16667;
    tempDwelladvance = (unsigned int) calcBuffer;

    // Spark Tooth
    degreeBuffer = ((global.spark - 1) * (SPARKARC)) - WHEELOFFSET - tempAdvance - tempDwelladvance;
    if (degreeBuffer <= 0) degreeBuffer = degreeBuffer + 3600;

    tempSparktooth = 1 + (degreeBuffer / global.tootharc);

    arcBuffer = (degreeBuffer - (( tempSparktooth - 1 ) * global.tootharc));

    if (tempSparktooth == TOOTHQTY) {
        tempSparktooth--;
        arcBuffer = arcBuffer + global.tootharc;
    }

    // Delay Time
    calcBuffer = (unsigned long int) arcBuffer * 16667 / rpmBuffer;
    tempDelaytime = (unsigned int) calcBuffer;

    clearInterrupts;

    variables.injopen     = tempInjopen;
    variables.dutycycle   = tempDutycycle;
    variables.advance     = tempAdvance;
    variables.dwell       = tempDwell;
    global.delaytime      = tempDelaytime;
    global.sparktooth     = tempSparktooth;

    setInterrupts;

    // Injector timer
    calcBuffer = ((unsigned long int) variables.injopen + INJDELAY) / 64;
    if (calcBuffer > 0xff) calcBuffer = 0xff;
    tempTCNTinjopen = calcBuffer;

    // Delay timer
    calcBuffer = ((unsigned long int) global.delaytime) / 4;
    if (calcBuffer > 0xffff) {  // this will advance spark, need a disable!
        calcBuffer = 0xffff;
        global.synchronized = 0;
    }
    tempTCNTdelaytime = calcBuffer;

    // Dwell timer
    calcBuffer = ((unsigned long int) variables.dwell) / 4;
    if (calcBuffer > 0xffff) calcBuffer = 0xffff;
    tempTCNTdwell = calcBuffer;

    clearInterrupts;

    global.TCNTinjopen = tempTCNTinjopen;
    global.TCNTdelaytime = tempTCNTdelaytime;
    global.TCNTdwell = tempTCNTdwell;

    setInterrupts;

}


void sendSerial(void) {

    char * readbyte;

    if (UDRE1 && TXC1) {

        // Variables structure
        if (global.serialpart == 1) {
            global.serialpiece++;
            readbyte = ((char *) global.serialmemstart1 + global.serialmemend1 - global.serialpiece);
            UDR1 = (*readbyte);
            if (global.serialpiece >= (global.serialmemend1)) {
                global.serialpiece = 0;
            }
        }

        // Default tuning structure
        if (global.serialpart == 2) {
            global.serialpiece++;
            readbyte = ((char *) global.serialmemstart2 + global.serialmemend2 - global.serialpiece);
            UDR1 = (*readbyte);
            if (global.serialpiece >= (global.serialmemend2)) {
                global.serialpiece = 0;
            }
        }

        // Measured tuning structure
        if (global.serialpart == 3) {
            global.serialpiece++;
            readbyte = ((char *) global.serialmemstart3 + global.serialmemend3 - global.serialpiece);
            UDR1 = (*readbyte);
            if (global.serialpiece >= (global.serialmemend3)) {
                global.serialpiece = 0;
            }
        }

        // Global variables structure
        // Only ever needed for program testing
        if (global.serialpart == 4) {
            global.serialpiece++;
            readbyte = ((char *) global.serialmemstart4 + global.serialmemend4 - global.serialpiece);
            UDR1 = (*readbyte);
            if (global.serialpiece >= (global.serialmemend4)) {
                global.serialpiece = 0;
            }
        }

        if (global.serialpiece == 0) {
            global.serialpart++;
            global.serialpart = 1;                              // For testing
//          global.serialpart = 4;                              // For testing
//          if (global.serialpart == 2) global.serialpart = 4;  // For testing
//          if (global.serialpart > 1) global.serialpart = 1;   // Uncomment to omit tuning tables temporarily
            if (global.serialpart > 3) global.serialpart = 1;   // Comment to use any of the above
            if (global.serialpart > 1  && variables.rpm != 0) global.serialpart = 1;
        }

    }

}


void setDelay(unsigned int activecoil) {

    unsigned long int calcBuffer;

    global.TCNTbuffer = TCNT3;

    calcBuffer = (unsigned long int) global.TCNTdelaytime + global.TCNTbuffer;

    if (activecoil == 1) {
        OCR3A = (unsigned int) calcBuffer;
        TIFR3    |= 0b00000010;        // Clear the interrupt flag (A)
        TIMSK3   |= 0b00000010;        // Enable OC3A interrupt
    }
    if (activecoil == 3) {
        OCR3B = (unsigned int) calcBuffer;
        TIFR3    |= 0b00000100;        // Clear the interrupt flag (B)
        TIMSK3   |= 0b00000100;        // Enable OC3B interrupt
    }
    if (activecoil == 2) {
        OCR3C = (unsigned int) calcBuffer;
        TIFR3    |= 0b00001000;        // Clear the interrupt flag (C)
        TIMSK3   |= 0b00001000;        // Enable OC3C interrupt
    }

}


void setSpark(unsigned int activecoil) {

    unsigned long int calcBuffer;

    global.TCNTbuffer = TCNT3;
    calcBuffer = (unsigned long int) global.TCNTdwell + global.TCNTbuffer;

    if (activecoil == 1) {
        TCCR3A     |= 0b11000000;      // Output mode to On (A)
        TCCR3C     |= 0b10000000;      // Forces result to happen (A)
        TCCR3A     &= 0b10111111;      // Output mode to Off (A)
        OCR3A = (unsigned int) calcBuffer;
        TIFR3      |= 0b00000010;      // Clear the interrupt flag (A)
    }
    if (activecoil == 3) {
        TCCR3A     |= 0b00110000;      // Output mode to On (B)
        TCCR3C     |= 0b01000000;      // Forces result to happen (B)
        TCCR3A     &= 0b11101111;      // Output mode to Off (B)
        OCR3B = (unsigned int) calcBuffer;
        TIFR3      |= 0b00000100;      // Clear the interrupt flag (B)
    }
    if (activecoil == 2) {
        TCCR3A     |= 0b00001100;      // Output mode to On (C)
        TCCR3C     |= 0b00100000;      // Forces result to happen (C)
        TCCR3A     &= 0b11111011;      // Output mode to Off (C)
        OCR3C = (unsigned int) calcBuffer;
        TIFR3      |= 0b00001000;      // Clear the interrupt flag (C)
    }

}


void setFuel(void) {

    unsigned long int calcBuffer;

    global.TCNTbuffer = TCNT2;
    calcBuffer = (unsigned long int) global.TCNTinjopen + global.TCNTbuffer;

    TCCR2A      = 0b00000000;      // Clear all
//  TCCR2A     |= 0b11110000;      // Output mode to On (A&B)
//  TCCR2B     |= 0b11000000;      // Forces result to happen (A&B)
    TCCR2A      = 0b10100000;      // Output mode to Off (A&B)
    TCCR2B     |= 0b11000000;      // Forces result to happen (A&B)
    TCCR2A      = 0b11110000;      // Output mode to On (A&B)
    OCR2A = (unsigned int) calcBuffer;
    OCR2B = (unsigned int) calcBuffer;
    TIFR2      |= 0b00000110;      // Clear the interrupt flag (A&B)

    calcBuffer = (unsigned long int) variables.injopen + global.totalizer;
    global.totalizer = calcBuffer;
}


void correctVE(void) {

/*
    VE
    New constants
        "lower limit to limit based on RPM" ?
        "upper limit to limit based on MAP" ?
    if O2 voltage is above(rich)/below(lean) target, subtract/add correction rate
    check if correction is within limits otherwise set to limit

    SA
    New constants
        "engine head temp limit" (also would need head temp sensor)
    if head temperature is above target, subtract correction rate
    check if correction is within limits otherwise set to limit
*/

    unsigned int * tempp;

    switch (global.ymap) {
        case  0: { tempp = &measuredTuning.VE00[global.xrpm]; } break;
        case  1: { tempp = &measuredTuning.VE01[global.xrpm]; } break;
        case  2: { tempp = &measuredTuning.VE02[global.xrpm]; } break;
        case  3: { tempp = &measuredTuning.VE03[global.xrpm]; } break;
        case  4: { tempp = &measuredTuning.VE04[global.xrpm]; } break;
        case  5: { tempp = &measuredTuning.VE05[global.xrpm]; } break;
        case  6: { tempp = &measuredTuning.VE06[global.xrpm]; } break;
        case  7: { tempp = &measuredTuning.VE07[global.xrpm]; } break;
        case  8: { tempp = &measuredTuning.VE08[global.xrpm]; } break;
        case  9: { tempp = &measuredTuning.VE09[global.xrpm]; } break;
        case 10: { tempp = &measuredTuning.VE10[global.xrpm]; } break;
        case 11: { tempp = &measuredTuning.VE11[global.xrpm]; } break;
        case 12: { tempp = &measuredTuning.VE12[global.xrpm]; } break;
        case 13: { tempp = &measuredTuning.VE13[global.xrpm]; } break;
        case 14: { tempp = &measuredTuning.VE14[global.xrpm]; } break;
        case 15: { tempp = &measuredTuning.VE15[global.xrpm]; } break;
        case 16: { tempp = &measuredTuning.VE16[global.xrpm]; } break;
        case 17: { tempp = &measuredTuning.VE17[global.xrpm]; } break;
        case 18: { tempp = &measuredTuning.VE18[global.xrpm]; } break;
        case 19: { tempp = &measuredTuning.VE19[global.xrpm]; } break;
        case 20: { tempp = &measuredTuning.VE19[global.xrpm]; } break;
    }

//  if (variables.o2 > O2GOAL) {
    if (variables.switch2) {
        if (*tempp < VECLIMITU) *tempp = *tempp + VECRATE;
    }
//  if (variables.o2 < O2GOAL) {
    if (variables.switch1) {
        if (*tempp > VECLIMITL) *tempp = *tempp - VECRATE;
    }

    switch (global.ymap) {
        case  0: { tempp = &measuredTuning.SA00[global.xrpm]; } break;
        case  1: { tempp = &measuredTuning.SA01[global.xrpm]; } break;
        case  2: { tempp = &measuredTuning.SA02[global.xrpm]; } break;
        case  3: { tempp = &measuredTuning.SA03[global.xrpm]; } break;
        case  4: { tempp = &measuredTuning.SA04[global.xrpm]; } break;
        case  5: { tempp = &measuredTuning.SA05[global.xrpm]; } break;
        case  6: { tempp = &measuredTuning.SA06[global.xrpm]; } break;
        case  7: { tempp = &measuredTuning.SA07[global.xrpm]; } break;
        case  8: { tempp = &measuredTuning.SA08[global.xrpm]; } break;
        case  9: { tempp = &measuredTuning.SA09[global.xrpm]; } break;
        case 10: { tempp = &measuredTuning.SA10[global.xrpm]; } break;
        case 11: { tempp = &measuredTuning.SA11[global.xrpm]; } break;
        case 12: { tempp = &measuredTuning.SA12[global.xrpm]; } break;
        case 13: { tempp = &measuredTuning.SA13[global.xrpm]; } break;
        case 14: { tempp = &measuredTuning.SA14[global.xrpm]; } break;
        case 15: { tempp = &measuredTuning.SA15[global.xrpm]; } break;
        case 16: { tempp = &measuredTuning.SA16[global.xrpm]; } break;
        case 17: { tempp = &measuredTuning.SA17[global.xrpm]; } break;
        case 18: { tempp = &measuredTuning.SA18[global.xrpm]; } break;
        case 19: { tempp = &measuredTuning.SA19[global.xrpm]; } break;
        case 20: { tempp = &measuredTuning.SA19[global.xrpm]; } break;
    }

    if (0 > SAPROBLEM) {           // This is just place-holder silliness
        if (*tempp > SACLIMITL) *tempp = *tempp - SACRATE;
    }

}


void totalize(void) {

    unsigned long int calcBuffer;

    if (global.totalizer > 1000000) {
        calcBuffer = (unsigned long int) global.totalizer - 1000000;
        global.totalizer = calcBuffer;
        calcBuffer = (unsigned long int) variables.totalfuel + INJRATE;
        variables.totalfuel = calcBuffer;
    }

}


unsigned int readAD(unsigned int channel) {

    ADMUX      &= 0b11111000;      // Clear the current channel
    ADCSRB     &= 0b11110111;

    if (channel <= 7) {            // Set the channel in MUX5:0
        ADMUX  |= channel;
    }
    if (channel > 7 && channel <= 15) {
        ADMUX  |= (channel-8);
        ADCSRB |= 0b00001000;
    }

    ADCSRA     |= 0b01000000;      // Start the conversion (ADSC)
    while (ADCSRA & 0b01000000);   // Wait for the conversion to finish
    return ADC;

}


unsigned int timerticks(unsigned int thistick, unsigned int lasttick) {

    unsigned long int ticktime;

    if (thistick < lasttick) ticktime = (unsigned long int) thistick + 0xFFFF - lasttick;
    else ticktime = thistick - lasttick;

    return (unsigned int) ticktime;

}


ISR(TIMER0_COMPA_vect) {

    global.RTItick++;

    // Trigger timeout counter
    if (global.triggertimeout > 0) global.triggertimeout--;
    else {
        variables.rpm = 0;
        global.synchronized = 0;
    }

    // When Switch1 is on (grounded) PING1 is off
    if (PING & 0b00000010) {
        variables.switch1 = 0;
    }
    else {
        variables.switch1 = 1;
    }

    // When Switch2 is on (grounded) PINL7 is off
    if (PINL & 0b10000000) {
        variables.switch2 = 0;
    }
    else {
        variables.switch2 = 1;
    }

    // Second counter
    if (global.RTItick >= 999) {

        global.RTItick=0;
        variables.seconds++;
        variables.calculations = global.calcticker;
        global.calcticker = 0;

        // Correct VE tables when switch1 is on
//      if (variables.switch1) {
        if (1) {
            if (variables.rpm > 0) correctVE();
            variables.runrich = 0;
        } else {
            variables.runrich = 1;
        }

        // Prime when switch2 is on
//      if (variables.switch2) {
//          setFuel();
//      }

        // Indicate if synchronized
        if (global.synchronized) {
            PORTD |= 0b10000000; // D7 (LED 1) On
        } else  PORTD &= 0b01111111; // D7 (LED 1) Off

        PORTB ^= 0b10000000; // B7 (Arduino #13 LED) Toggle
//      PORTD ^= 0b10000000; // D7 (LED 1) Toggle (green)
//      PORTC ^= 0b00000010; // C1 (LED 2) Toggle (yellow)
//      PORTC ^= 0b00001000; // C3 (LED 3) Toggle (red)
//      PORTD |= 0b10000000; // D7 (LED 1) On
//      PORTD &= 0b01111111; // D7 (LED 1) Off
//      PORTC |= 0b00000010; // C1 (LED 2) On
//      PORTC &= 0b11111101; // C1 (LED 2) Off
//      PORTC |= 0b00001000; // C3 (LED 3) On
//      PORTC &= 0b11110111; // C3 (LED 3) Off
    }

    return;
}


ISR(TIMER3_COMPA_vect) {

    TIMSK3   &= 0b11111101;    // Disable OC3A interrupt
    setSpark(1);

    return;
}


ISR(TIMER3_COMPB_vect) {

    TIMSK3   &= 0b11111011;    // Disable OC3B interrupt
    setSpark(3);

    return;
}


ISR(TIMER3_COMPC_vect) {

    TIMSK3   &= 0b11110111;    // Disable OC3C interrupt
    setSpark(2);

    return;
}


ISR(TIMER5_CAPT_vect) {

    unsigned long int calcBuffer;

    global.triggertick = ICR5;
    global.triggerdelta = timerticks(global.triggertick,global.triggerlast);
    global.triggerlast = global.triggertick;

    global.tooth++;

    if (global.tooth >= TOOTHQTY) {
        global.synchronized = 0;
        global.tooth = TOOTHQTY;
    }

    // Missing Tooth Check
    if ((global.triggerdelta < ((2 * (global.triggerlastdelta * ((unsigned long int) 100 + TOLERANCE))) / 100) ) &&
    (global.triggerdelta > ((2 * (global.triggerlastdelta * ((unsigned long int) 100 - TOLERANCE))) / 100) )) {
        global.triggerdelta = global.triggerlastdelta;
        global.tooth = 1;
        global.synchronized = 1;
        global.spark = 2;
    } else global.triggerlastdelta = global.triggerdelta;

    // RPM
    if (global.triggerdelta > 0) {
        calcBuffer = ((unsigned long int) 15000000 / TOOTHQTY) / global.triggerdelta;
//      calcBuffer = ((unsigned long int) calcBuffer + (1 * variables.rpm)) / 2;
//      calcBuffer = ((unsigned long int) calcBuffer + (2 * variables.rpm)) / 3;
        calcBuffer = ((unsigned long int) calcBuffer + (3 * variables.rpm)) / 4;
        if (calcBuffer <= 0xffff) {
            variables.rpm = (unsigned int) calcBuffer;
        } else variables.rpm = 0;
    } else variables.rpm = 0;

    if ((variables.rpm < STARTRPM) || (variables.rpm > global.rpmlimit)) {
        global.synchronized = 0;
    }

    // Start an Ignition Event
    if (global.synchronized && (global.tooth == global.sparktooth)) {
        setDelay(global.spark);
        global.spark++;
        if (global.spark >= SPARKQTY) global.spark = 1;
    }

    // This obviously should be setup in more detail, but the current
    // engine with "two injections per revolution", this hack works.
    if (global.synchronized) {
        if (global.tooth == 1) setFuel();
        if (global.tooth == 5) setFuel();
    }

    global.triggertimeout = 250;

    return;
}


int main(void) {

    mainConfig();
    setInterrupts;

    while(1) {
        readInputs();
        calcOutputs();
        sendSerial();
        totalize();
        global.calcticker++;
    }

    return (1);
}

There is a notebook I kept which explain everything. This will be posted here.

More to come.

The Display

As mentioned before, the ECU has a Bluetooth output which broadcasts a serial stream of the current engine information. This is available for a separate program running on a separate device to receive and then be presented on a screen and/or saved to a file.

To get things moving quickly, I created a display which shows a simple text screen. NCURSES style! It was written in Python, and runs on a Raspberry Pi computer (or any Linux laptop for that matter). It is organized for a seven inch PI screen set at 800x480. It has linear bars for gauges, it is really slick. It did require that you RFCOMM connected prior to running. But...

This will be replaced with a cool phone app. For historical reference I am providing the Python file below:

# At 800x480 resolution:
# Pi with  8x16 font = 100x30 window
# Pi with 12x24 font =  66x20 window
# Pi with 16x32 font =  50x15 window

import os
import sys
import struct
import serial
import binascii
import curses
import time

biglist = []
linesd = []
linecd = []
timestr = time.strftime('%Y%m%d-%H%M%S')
outputf = './data/output' + timestr
tuningf = './data/tuning' + timestr

try:
    fo = open('output', 'w', buffering=1)
    fi = open('output', 'r')
    f1 = open(outputf, 'w')
    f2 = open(tuningf, 'w')
except:
    cleanup()
try:
    #ser = serial.Serial('/dev/ttyUSB0', 115200)
    #ser = serial.Serial('/dev/ttyACM0', 115200)
    ser = serial.Serial('/dev/rfcomm0', 57600)
except:
    cleanup()

stdscr = curses.initscr()
curses.start_color()
curses.use_default_colors
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_BLUE, curses.COLOR_BLACK)
curses.curs_set(0)
ssy,ssx = stdscr.getmaxyx()
winy = 15
winx = 50
winby = int((ssy/2) - (winy/2))
winbx = int((ssx/2) - (winx/2))
win = curses.newwin(winy,winx,winby,winbx)
win.border()
win.addstr( 1, 1,'    RPM                                         ')
win.addstr( 2, 1,'    MAP                                    mBar ')
win.addstr( 3, 1,'     TP                                    %    ')
win.addstr( 4, 1,'                                                ')
win.addstr( 5, 1,'  FUsed                                    kg   ')
win.addstr( 6, 1,'  OTemp                                    C    ')
win.addstr( 7, 1,'   Batt                                    V    ')
win.addstr( 8, 1,'                                                ')
win.addstr( 9, 1,'  IOpen             us   Advnc             deg  ')
win.addstr(10, 1,'   Duty             %    Dwell             us   ')
win.addstr(11, 1,'     O2             mV   ATemp             C    ')
win.addstr(12, 1,'                                                ')
win.addstr(13, 1,'         s        c/s    RR:   Sw1:   Sw2:      ')
win.refresh()

def panel():
    while True:
        capture()
        bigline = fi.readline()
        if bigline[:12] == '65521;65535;':
            vsec   = int(bigline[12:17])
            vcalc  = int(bigline[18:23])
            vrpm   = int(bigline[24:29])
            votemp = int(bigline[30:35])
            vatemp = int(bigline[36:41])
            vmap   = int(bigline[42:47])
            vo2    = int(bigline[48:53])
            vtp    = int(bigline[54:59])
            vbatt  = int(bigline[60:65])
            viopen = int(bigline[66:71])
            vduty  = int(bigline[72:77])
            vadvnc = int(bigline[78:83])
            vdwell = int(bigline[84:89])
            vtotal = int(bigline[90:100])
            vrich  = int(bigline[101:106])
            vsw1   = int(bigline[107:112])
            vsw2   = int(bigline[113:118])
            votemp -= 2730
            votemp /= 10
            vatemp -= 2730
            vatemp /= 10
            vmap   /= 10
            vtp    /= 10
            vbatt  /= 10
            vduty  /= 10
            vadvnc /= 10
            vtotal /= 1000000
            hbar(1,10,27,0,5000,0,4000,vrpm)
            win.addstr(' {:5d}'.format(vrpm))
            hbar(2,10,27,0,1200,0,1200,vmap)
            win.addstr(' {:5.0f}'.format(vmap))
            hbar(3,10,27,0,100,0,100,vtp)
            win.addstr(' {:5.1f}'.format(vtp))
            hbar(5,10,27,0,20,0,15,vtotal)
            win.addstr(' {:5.1f}'.format(vtotal))
            hbar(6,10,27,50,150,110,125,votemp)
            win.addstr(' {:5.0f}'.format(votemp))
            hbar(7,10,27,8,16,12,15,vbatt)
            win.addstr(' {:5.1f}'.format(vbatt))
            hbar(9,9,6,0,5000,0,5000,viopen)
            win.addstr(' {:4d}'.format(viopen))
            hbar(9,32,6,0,40,0,30,vadvnc)
            win.addstr(' {:4.0f}'.format(vadvnc))
            hbar(10,9,6,0,100,0,80,vduty)
            win.addstr(' {:4.0f}'.format(vduty))
            hbar(10,32,6,0,5000,0,4500,vdwell)
            win.addstr(' {:4d}'.format(vdwell))
            hbar(11,9,6,400,500,400,500,vo2)
            win.addstr(' {:4d}'.format(vo2))
            hbar(11,32,6,0,50,0,50,vatemp)
            win.addstr(' {:4.0f}'.format(vatemp))
            win.addstr(13, 4,'{:5d}'.format(vsec))
            win.addstr(13,13,'{:5d}'.format(vcalc))
            win.addstr(13,29,'{:1d}'.format(vrich))
            win.addstr(13,36,'{:1d}'.format(vsw1))
            win.addstr(13,43,'{:1d}'.format(vsw2))
            win.refresh()

def hbar(brow,bcol,blength,bmin,bmax,blimitl,blimitu,binput):
    win.move(brow,bcol)
    if (binput < bmin):
        binput = bmin
    if (binput > bmax):
        binput = bmax
    inlimit = 1
    if (binput < blimitl):
        inlimit = 0
    if (binput > blimitu):
        inlimit = 0
    full = int( blength*((binput-bmin)/(bmax-bmin)) )
    empt = int( blength - full )
    if inlimit:
        for x in range(full): win.addstr(u'\u25a0'.encode('utf-8'), curses.color_pair(1))
        for x in range(empt): win.addstr(u'\u2504'.encode('utf-8'), curses.color_pair(1))
    else:
        for x in range(full): win.addstr(u'\u2588'.encode('utf-8'), curses.color_pair(2))
        for x in range(empt): win.addstr(u'\u2591'.encode('utf-8'), curses.color_pair(2))

def capture():
    while True:
        biglist.extend(ser.read())
        if biglist[-4:] == [0xff,0xff,0xff,0xf1]:
            if len(biglist) == 40:
                getline('iiiliiiiiiiiiiiiiii',1)
                break
            else:
                biglist.clear()
        if biglist[-4:] == [0xff,0xff,0xff,0xf2]:
            if len(biglist) == 1688:
                getline('ii',2)
                getline('iiiiiiiiiiiiiiiiiiiii',2)
                getline('iiiiiiiiiiiiiiiiiiiii',2)
                for x in range(20):
                    getline('iiiiiiiiiiiiiiiiiiii',2)
                for x in range(20):
                    getline('iiiiiiiiiiiiiiiiiiii',2)
                break
            else:
                biglist.clear()
        if biglist[-4:] == [0xff,0xff,0xff,0xf3]:
            if len(biglist) == 1688:
                getline('ii')
                getline('iiiiiiiiiiiiiiiiiiiii',2)
                getline('iiiiiiiiiiiiiiiiiiiii',2)
                for x in range(20):
                    getline('iiiiiiiiiiiiiiiiiiii',2)
                for x in range(20):
                    getline('iiiiiiiiiiiiiiiiiiii',2)
                break
            else:
                biglist.clear()
        if biglist[-4:] == [0xff,0xff,0xff,0xf4]:
            if len(biglist) == 76:
                getline('liiiilliiiiiiiiiiiiiiiiiiiiiiiiiiii',1)
                break
            else:
                biglist.clear()

def getline(linestruct, whichfile):
    for x in reversed(range(len(linestruct))):
        if (linestruct[x] == 'i'):
            value = (biglist[-2]<<8 | biglist[-1])
            linesd.append('{:5n}'.format(value))
            linesd.append(' ')
            linecd.append('{:5n}'.format(value))
            linecd.append(';')
            biglist.pop()
            biglist.pop()
        if (linestruct[x] == 'l'):
            value = (biglist[-4]<<24 | biglist[-3]<<16 | biglist[-2]<<8 | biglist[-1])
            linesd.append('{:10n}'.format(value))
            linesd.append(' ')
            linecd.append('{:10n}'.format(value))
            linecd.append(';')
            biglist.pop()
            biglist.pop()
            biglist.pop()
            biglist.pop()
    linecd.append('\n')
    if (whichfile == 1):
        fo.write(''.join(linecd))
        f1.write(''.join(linecd))
    if (whichfile == 2):
        f2.write(''.join(linecd))
    linesd.clear()
    linecd.clear()
    biglist.clear()
    fo.flush()
    f1.flush()
    f2.flush()
#   os.fsync(fo)
#   os.fsync(f1)
#   os.fsync(f2)

def cleanup():
    try:
        fi.close()
    except:
        pass
    try:
        fo.close()
    except:
        pass
    try:
        f1.close()
    except:
        pass
    try:
        f2.close()
    except:
        pass
    try:
        ser.close()
    except:
        pass
    try:
        curses.endwin()
    except:
        pass
    quit()

try:
    panel()
except:
    pass
finally:
    cleanup()

That was a lot! I can not believe you made it to the bottom of this page! Congratulations.

Keep on keeping on




Copyright, provided by, and under the protection of Worktable CNC, LLC.
Details and Terms of Use may be found here: worktablecnc.us/legal