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.
The schematic is available. Click on the following image to download a PDF version of it:
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:
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:
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