I believe the only thing I need, is hardware crank sensor filtering, like a schmitt trigger or something.
https://www.youtube.com/watch?v=bqC3ijvPBOU Out of date video only showing 1 cylinder, before updated pulse handler code to prevent unsafe rev limiter (and transient conditions) interaction with pulse handler logic.
7/18/2025 latest update to main loop logic.
10/12/2025 All tune variables are 32 bit now. Thought I made that change a while ago. 8 bit temp scaling doesn't go past 100%, meaning you could only go leaner, not richer, and more spark advance, not less. Arduino's compiler for the Pico is very forgiving, things like that often don't matter; but incase somebody is trying on an STM32, ESP32, teensy 4, or similar. Avoids compiler errors about dissimilar variables. 8 bit fixed decimal math instead of floating point math, for performance.
.ino is ALPHA stage V8 full sequential that I only bench tested.
TuneArrays.h file is the same for different cylinder/spark configs.
But fuel numbers would need doubled because half the pulses of course.
And number of microseconds delay for spark to be the same is.... well, more than double.
Put cam signal edges 45 crankshaft degrees before #1 TDC for simplest sorting. Patterns match at TDC, 90 degree increments, etc.
Don't overlap cam signal edges with pattern matches to avoid hard to figure out misfires.
pins 0-7 are spark
Pins 8-15 are fuel
pin 22 = crank sensor
pin 21 = cam sensor
A0 = MAP sensor
A1 = IAT sensor (can be digital choke potentiometer if desired)
Updated pulse handler should be able to bounce off the rev limiter all day and not see partial pulses.
Between the pulse handler update and careful tuning considerations, should handle transients as well as a pure software solution possibly can, with no predictive algorithms, keeping it simple.
Tuning is currently done by editing the arduino source code tables "TuneArrays.h".
Here are numbers for spark advance per RPM zone of main table.
Specific to full sequential. Currently only targeting Pi Pico. Timer strategy requires it.
If you have a different Arduino compatible 32bit board with a 64 bit timer and 2 analog inputs, feel free to try.
All code is generic for easy porting of the entire project or just snippets, like crank decode logic.
GM 24x crank signal only.
17x17 tables using bilinear interpolation.
4, 6, or 8 cylinder fuel and spark capable. Not enough pins on Pico for 12 cylinder fuel and spark, unless I were to change the pin configuration to be hardwired for waste spark, instead of the current way of digitally combining them; of course this also means a 16 cylinder waste spark could be done the same way.
10 doesn't work with 24x pattern without the more complex code other ECUs use.
Current max RPM for tables is about 9,600.
Whatever BAR MAP sensor you want.
IAT scaling for spark and fuel.
If you want, you can turn off temp scaling pretty easy, or leave it on just for fuel and use a potentiometer as a manual choke.
Added EOIT that get's disregarded when required to hit high injector duty cycle and has it's own interpolation.
There's 50 microseconds of "noise"/inaccuracy on output pulse timing, and there seems to be a minimum on time before it just stays zero, I think that number was 100-150 microseconds.
Spark-cut rev limiter.
Minimum of 20 RPM to shut off fuel and spark.
For reference, this is what the tune file looks like.
TuneArrays.h
Code: Select all
volatile unsigned long IATFuel[129] = {128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,226,228,230,232,234,236,238,240,242,244,246,248,250,252,254,256,258,260,262,264,266,268,270,272,274,276,278,280,282,284,286,288,290,292,294,296,298,300,302,304,306,308,310,312,314,316,318,320,322,324,326,328,330,332,334,336,338,340,342,344,346,348,350,352,354,356,358,360,362,364,366,368,370,372,374,376,378,380,382,384};
//Comment above and uncomment below for no temp scaling, 256 = 100% or no change
//volatile unsigned long IATFuel[129] = {256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256};
//volatile unsigned long IATSpark [129] = {128,130,132,134,136,138,140,142,144,146,148,150,152,154,156,158,160,162,164,166,168,170,172,174,176,178,180,182,184,186,188,190,192,194,196,198,200,202,204,206,208,210,212,214,216,218,220,222,224,226,228,230,232,234,236,238,240,242,244,246,248,250,252,254,256,258,260,262,264,266,268,270,272,274,276,278,280,282,284,286,288,290,292,294,296,298,300,302,304,306,308,310,312,314,316,318,320,322,324,326,328,330,332,334,336,338,340,342,344,346,348,350,352,354,356,358,360,362,364,366,368,370,372,374,376,378,380,382,384};
//Comment above and uncomment below for no temp scaling, 256 = 100% or no change
volatile unsigned long IATSpark [129] = {256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256};
volatile unsigned long Limiter = 278 ; //Spark cut rev limiter, microseconds per 15 degrees. divide 2,500,000 by this number to get RPM. Limiter is set after rolling average and before being constrained for table access, can be set beyond table limits
volatile unsigned long EOIT [17] = {500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500};// these were test values, also in microseconds, change as needed
// 0 RPM ---------------------------------------------------------------------------9600 RPM same 600 spacing
volatile unsigned long Dstart [17] [17] = { //lower numbers = more advance, numbers are in microseconds
{2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333, 2954333},//"0" RPM
{94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611, 94611},// 600
{44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611, 44611},// 1200
{28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407, 28407},// 1800
{19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958, 19958},// 2400
{15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167, 15167},// 3000
{11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741, 11741},// 3600
{9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294, 9294},// 4200
{7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632, 7632},// 4800
{6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185, 6185},// 5400
{5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167, 5167},// 6000
{4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207, 4207},// 6600
{3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523, 3523},// 7200
{2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944, 2944},// 7800
{2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448, 2448},// 8400
{2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019, 2019},// 9000
{1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642, 1642} // 9600
};//low KPA --------------------------------------------------------------------------------- high KPA
volatile unsigned long Iend [17] [17] = { //injector on time, in microseconds
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},//"0" RPM
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 600
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 1200
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 1800
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 2400
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 3000
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 3600
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 4200
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 4800
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 5400
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 6000
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 6600
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 7200
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 7800
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 8400
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266},// 9000
{284, 284, 568, 853, 1137, 1422, 1706, 1991, 2275, 1560, 2844, 3129, 3413, 3697, 3982, 4266, 4266}// 9600
};//low KPA --------------------------------------------------------------------------------- high KPA
Code: Select all
#include "TuneArrays.h"
//LS order
const uint8_t pattern0 = 0b01111101;//0 degrees / TDC #1 and paired cylinder 1 and 6
const uint8_t pattern1 = 0b01110011;//90 degrees 8 and 5
const uint8_t pattern2 = 0b11000101;//180 degrees 7 and 4
const uint8_t pattern3 = 0b01000001;//270 degrees 2 and 3
uint8_t RPMIndex = 0;
uint8_t adcIndex = 0;
unsigned long currentOnDelaySpark = 0;
unsigned long currentOffDelaySpark = 0;
unsigned long currentOnDelayFuel = 0;
unsigned long currentOffDelayFuel = 0;
bool pulseHighSpark0 = false;
bool pulseActiveSpark0 = false;
bool triggerPulseSpark0 = false;
unsigned long pulseStartTimeSpark0 = 0;
unsigned long pulseWidthSpark0 = 0;
bool pulseHighSpark1 = false;
bool pulseActiveSpark1 = false;
bool triggerPulseSpark1 = false;
unsigned long pulseStartTimeSpark1 = 0;
unsigned long pulseWidthSpark1 = 0;
bool pulseHighSpark2 = false;
bool pulseActiveSpark2 = false;
bool triggerPulseSpark2 = false;
unsigned long pulseStartTimeSpark2 = 0;
unsigned long pulseWidthSpark2 = 0;
bool pulseHighSpark3 = false;
bool pulseActiveSpark3 = false;
bool triggerPulseSpark3 = false;
unsigned long pulseStartTimeSpark3 = 0;
unsigned long pulseWidthSpark3 = 0;
bool pulseHighSpark4 = false;
bool pulseActiveSpark4 = false;
bool triggerPulseSpark4 = false;
unsigned long pulseStartTimeSpark4 = 0;
unsigned long pulseWidthSpark4 = 0;
bool pulseHighSpark5 = false;
bool pulseActiveSpark5 = false;
bool triggerPulseSpark5 = false;
unsigned long pulseStartTimeSpark5 = 0;
unsigned long pulseWidthSpark5 = 0;
bool pulseHighSpark6 = false;
bool pulseActiveSpark6 = false;
bool triggerPulseSpark6 = false;
unsigned long pulseStartTimeSpark6 = 0;
unsigned long pulseWidthSpark6 = 0;
bool pulseHighSpark7 = false;
bool pulseActiveSpark7 = false;
bool triggerPulseSpark7 = false;
unsigned long pulseStartTimeSpark7 = 0;
unsigned long pulseWidthSpark7 = 0;
bool pulseHighFuel0 = false;
bool pulseActiveFuel0 = false;
bool triggerPulseFuel0 = false;
unsigned long pulseStartTimeFuel0 = 0;
unsigned long pulseWidthFuel0 = 0;
bool pulseHighFuel1 = false;
bool pulseActiveFuel1 = false;
bool triggerPulseFuel1 = false;
unsigned long pulseStartTimeFuel1 = 0;
unsigned long pulseWidthFuel1 = 0;
bool pulseHighFuel2 = false;
bool pulseActiveFuel2 = false;
bool triggerPulseFuel2 = false;
unsigned long pulseStartTimeFuel2 = 0;
unsigned long pulseWidthFuel2 = 0;
bool pulseHighFuel3 = false;
bool pulseActiveFuel3 = false;
bool triggerPulseFuel3 = false;
unsigned long pulseStartTimeFuel3 = 0;
unsigned long pulseWidthFuel3 = 0;
bool pulseHighFuel4 = false;
bool pulseActiveFuel4 = false;
bool triggerPulseFuel4 = false;
unsigned long pulseStartTimeFuel4 = 0;
unsigned long pulseWidthFuel4 = 0;
bool pulseHighFuel5 = false;
bool pulseActiveFuel5 = false;
bool triggerPulseFuel5 = false;
unsigned long pulseStartTimeFuel5 = 0;
unsigned long pulseWidthFuel5 = 0;
bool pulseHighFuel6 = false;
bool pulseActiveFuel6 = false;
bool triggerPulseFuel6 = false;
unsigned long pulseStartTimeFuel6 = 0;
unsigned long pulseWidthFuel6 = 0;
bool pulseHighFuel7 = false;
bool pulseActiveFuel7 = false;
bool triggerPulseFuel7 = false;
unsigned long pulseStartTimeFuel7 = 0;
unsigned long pulseWidthFuel7 = 0;
int readings[24] {4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095, 4095}; // Circular buffer
int bufIndex = 0; // Buffer index
long total = 4095L * 24; // Set this to the initial sum
int readings2[24]; // Circular buffer
int bufIndex2 = 0; // Buffer index
long total2 = 0L * 24; // Set this to the initial sum
int readings3[24]; // Circular buffer
int bufIndex3 = 0; // Buffer index
unsigned long total3 = 0UL * 24; // Set this to the initial sum
unsigned long lastTime = 0;
unsigned long pulseHighTime = 0;
unsigned long pulseLowTime = 0;
uint8_t signalHistory = 0b00000000;
bool newSample = false;
void handleCrankSignal() {
unsigned long currentTime = micros();
if (digitalRead(22) == HIGH) {
pulseLowTime = currentTime - lastTime;
} else {
pulseHighTime = currentTime - lastTime;
uint8_t bit = (pulseHighTime > pulseLowTime) ? 1 : 0;
signalHistory = ((signalHistory << 1) | bit) & 0xFF;
newSample = true; // flag to loop()
}
lastTime = currentTime;
}
void handleSparkPulse(uint8_t pin, unsigned long startTime, bool &highFlag) {
unsigned long now = micros();
unsigned long elapsed = now - startTime;
if (elapsed < currentOnDelaySpark) {
digitalWrite(pin, LOW);
} else {
highFlag = true;
}
}
void handleSparkPulse2(uint8_t pin, unsigned long startTime, bool &activeFlag, bool &highFlag, unsigned long &OnTime) {
unsigned long now = micros();
unsigned long elapsed = now - startTime;
if (activeFlag){
OnTime = currentOnDelaySpark + currentOffDelaySpark;
activeFlag = false;
digitalWrite(pin, HIGH);
}
if (elapsed > OnTime) {
digitalWrite(pin, LOW);
highFlag = false;
}
}
void handleFuelPulse(uint8_t pin, unsigned long startTime, bool &highFlag) {
unsigned long now = micros();
unsigned long elapsed = now - startTime;
if (elapsed < currentOnDelayFuel) {
digitalWrite(pin, LOW);
} else {
highFlag = true;
}
}
void handleFuelPulse2(uint8_t pin, unsigned long startTime, bool &activeFlag, bool &highFlag, unsigned long &OnTime) {
unsigned long now = micros();
unsigned long elapsed = now - startTime;
if (activeFlag){
OnTime = currentOnDelayFuel + currentOffDelayFuel;
activeFlag = false;
digitalWrite(pin, HIGH);
}
if (elapsed > OnTime) {
digitalWrite(pin, LOW);
highFlag = false;
}
}
void setup() {
analogReadResolution(12);
pinMode(0, OUTPUT);
pinMode(1, OUTPUT);
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
pinMode(14, OUTPUT);
pinMode(15, OUTPUT);
pinMode(A0, INPUT);
pinMode(A1, INPUT);
pinMode(22, INPUT);
pinMode(21, INPUT);
attachInterrupt(digitalPinToInterrupt(22), handleCrankSignal, CHANGE);
}
void loop() {
if (newSample) {
newSample = false;
//if (digitalRead(21) == HIGH){
// Spark patterns
if (signalHistory == pattern0) {triggerPulseSpark0 = true; triggerPulseFuel0 = true;}
if (signalHistory == pattern1) {triggerPulseSpark2 = true; triggerPulseFuel2 = true;}
if (signalHistory == pattern2) {triggerPulseSpark4 = true; triggerPulseFuel4 = true;}
if (signalHistory == pattern3) {triggerPulseSpark6 = true; triggerPulseFuel6 = true;}
//}
//if (digitalRead(21) == LOW) {
if (signalHistory == pattern0) {triggerPulseSpark1 = true; triggerPulseFuel1 = true;}
if (signalHistory == pattern1) {triggerPulseSpark3 = true; triggerPulseFuel3 = true;}
if (signalHistory == pattern2) {triggerPulseSpark5 = true; triggerPulseFuel5 = true;}
if (signalHistory == pattern3) {triggerPulseSpark7 = true; triggerPulseFuel7 = true;}
//}
unsigned long RPMTime = pulseHighTime + pulseLowTime;//Calculate RPMtime (µs per 15 degrees)
total2 -= readings2[bufIndex2];
unsigned long newVal2 = RPMTime;
readings2[bufIndex2] = newVal2;
total2 += newVal2;
bufIndex2 = (bufIndex2 + 1) % 24;
unsigned long RPMTimeavg = total2 / 24UL;
uint Predicted360Time = RPMTimeavg * 24;//48 for full sequential, 720 degrees. 24 for waste spark, 360 degrees.
// FIXED: Rev limiter + low-RPM cutoff
unsigned long Dwell = 4000UL;
if (RPMTimeavg < Limiter) {
Dwell = 0UL; // spark cut when RPM too high
} else if (RPMTimeavg > 125000UL) {
Dwell = 0UL; // below 20 RPM, also cut spark
}
RPMTimeavg = constrain(RPMTimeavg, 278UL, 3000000UL); // 9600 RPM to 0 RPM. Always round the decimal up, even if it's 0.0001. Not overflowing arrays is important
// --- ADC rolling average (12-bit to 7-bit scale) ---
total -= readings[bufIndex];
int newVal = analogRead(A0);
readings[bufIndex] = newVal;
total += newVal;
bufIndex = (bufIndex + 1) % 24;
int avg12bit = total / 24;
adcIndex = avg12bit >> 8; //Discarding LSBs to match array resolution
uint32_t IndexFull = ((uint64_t)2500000 << 8) / ((uint32_t)RPMTimeavg * 600);
uint16_t IndexWhole = IndexFull >> 8;
uint8_t Frac = IndexFull & 0xFF;
int Frac2 = avg12bit & 0x1F;
int TerpA = Dstart[IndexWhole][adcIndex];
int TerpB = Dstart[IndexWhole +1][adcIndex];
int TerpD = TerpA + (((TerpB - TerpA) * Frac) >> 8);
int TerpA2 = Dstart[IndexWhole][adcIndex +1];
int TerpB2 = Dstart[IndexWhole +1][adcIndex +1];
int TerpD2 = TerpA2 + (((TerpB2 - TerpA2) * Frac) >> 8);
int TerpD5 = TerpD + (((TerpD2 - TerpD) * Frac2) >> 8);
int TerpA3 = Iend[IndexWhole][adcIndex+1];
int TerpB3 = Iend[IndexWhole +1][adcIndex +1];
int TerpD3 = TerpA3 + (((TerpB3 - TerpA3) * Frac) >> 8);
int TerpA4 = Iend[IndexWhole][adcIndex];
int TerpB4 = Iend[IndexWhole +1][adcIndex];
int TerpD4 = TerpA4 + (((TerpB4 - TerpA4) * Frac) >> 8);
int TerpD6 = TerpD4 + (((TerpD3 - TerpD4) * Frac2) >> 8);
total3 -= readings3[bufIndex3];
int newVal3 = analogRead(A1);
readings3[bufIndex3] = newVal3;
total3 += newVal3;
bufIndex3 = (bufIndex3 + 1) % 24;
int IATavg = total3 / 24;
int IAT7bit = IATavg >> 5;
int Temp = IATSpark [IAT7bit];
int Scaled = ((TerpD5 * Temp) >>8);
int TrimA = EOIT [IndexWhole];
int TrimB = EOIT [IndexWhole +1];
int TrimTerp = TrimA + (((TrimB - TrimA) * Frac) >> 8);
int Temp2 = IATFuel [IAT7bit];
int Scaled3 = ((TerpD6 * Temp2) >>8);
int ScaledOffset = Predicted360Time - (Scaled3 + TrimTerp);
if (ScaledOffset < 0) ScaledOffset = 0;
if (RPMTimeavg > 125000UL){ //if RPM below minimum = 20RPM
Scaled3 = 0UL; //Fuel off
signalHistory = 0b00000000;//Sync reset
}
// --- Update delay values from tables ---
currentOnDelaySpark = Scaled;
currentOffDelaySpark = Dwell;
currentOnDelayFuel = ScaledOffset;
currentOffDelayFuel = Scaled3;
}
// --- Check for new pulse triggers from crank handler ---
// --- Spark pulse triggers ---
if (triggerPulseSpark0) { triggerPulseSpark0 = false; pulseStartTimeSpark0 = micros(); pulseActiveSpark0 = true; }
if (triggerPulseSpark1) { triggerPulseSpark1 = false; pulseStartTimeSpark1 = micros(); pulseActiveSpark1 = true; }
if (triggerPulseSpark2) { triggerPulseSpark2 = false; pulseStartTimeSpark2 = micros(); pulseActiveSpark2 = true; }
if (triggerPulseSpark3) { triggerPulseSpark3 = false; pulseStartTimeSpark3 = micros(); pulseActiveSpark3 = true; }
if (triggerPulseSpark4) { triggerPulseSpark4 = false; pulseStartTimeSpark4 = micros(); pulseActiveSpark4 = true; }
if (triggerPulseSpark5) { triggerPulseSpark5 = false; pulseStartTimeSpark5 = micros(); pulseActiveSpark5 = true; }
if (triggerPulseSpark6) { triggerPulseSpark6 = false; pulseStartTimeSpark6 = micros(); pulseActiveSpark6 = true; }
if (triggerPulseSpark7) { triggerPulseSpark7 = false; pulseStartTimeSpark7 = micros(); pulseActiveSpark7 = true; }
// --- Fuel pulse triggers ---
if (triggerPulseFuel0) { triggerPulseFuel0 = false; pulseStartTimeFuel0 = micros(); pulseActiveFuel0 = true; }
if (triggerPulseFuel1) { triggerPulseFuel1 = false; pulseStartTimeFuel1 = micros(); pulseActiveFuel1 = true; }
if (triggerPulseFuel2) { triggerPulseFuel2 = false; pulseStartTimeFuel2 = micros(); pulseActiveFuel2 = true; }
if (triggerPulseFuel3) { triggerPulseFuel3 = false; pulseStartTimeFuel3 = micros(); pulseActiveFuel3 = true; }
if (triggerPulseFuel4) { triggerPulseFuel4 = false; pulseStartTimeFuel4 = micros(); pulseActiveFuel4 = true; }
if (triggerPulseFuel5) { triggerPulseFuel5 = false; pulseStartTimeFuel5 = micros(); pulseActiveFuel5 = true; }
if (triggerPulseFuel6) { triggerPulseFuel6 = false; pulseStartTimeFuel6 = micros(); pulseActiveFuel6 = true; }
if (triggerPulseFuel7) { triggerPulseFuel7 = false; pulseStartTimeFuel7 = micros(); pulseActiveFuel7 = true; }
// --- Spark pulse timing (Pins 0 to 7) ---
if (pulseHighSpark0) handleSparkPulse2(0, pulseStartTimeSpark0, pulseActiveSpark0, pulseHighSpark0, pulseWidthSpark0);
if (pulseHighSpark1) handleSparkPulse2(1, pulseStartTimeSpark1, pulseActiveSpark1, pulseHighSpark1, pulseWidthSpark1);
if (pulseHighSpark2) handleSparkPulse2(2, pulseStartTimeSpark2, pulseActiveSpark2, pulseHighSpark2, pulseWidthSpark2);
if (pulseHighSpark3) handleSparkPulse2(3, pulseStartTimeSpark3, pulseActiveSpark3, pulseHighSpark3, pulseWidthSpark3);
if (pulseHighSpark4) handleSparkPulse2(4, pulseStartTimeSpark4, pulseActiveSpark4, pulseHighSpark4, pulseWidthSpark4);
if (pulseHighSpark5) handleSparkPulse2(5, pulseStartTimeSpark5, pulseActiveSpark5, pulseHighSpark5, pulseWidthSpark5);
if (pulseHighSpark6) handleSparkPulse2(6, pulseStartTimeSpark6, pulseActiveSpark6, pulseHighSpark6, pulseWidthSpark6);
if (pulseHighSpark7) handleSparkPulse2(7, pulseStartTimeSpark7, pulseActiveSpark7, pulseHighSpark7, pulseWidthSpark7);
if (pulseActiveSpark0) handleSparkPulse(0, pulseStartTimeSpark0, pulseHighSpark0);
if (pulseActiveSpark1) handleSparkPulse(1, pulseStartTimeSpark1, pulseHighSpark1);
if (pulseActiveSpark2) handleSparkPulse(2, pulseStartTimeSpark2, pulseHighSpark2);
if (pulseActiveSpark3) handleSparkPulse(3, pulseStartTimeSpark3, pulseHighSpark3);
if (pulseActiveSpark4) handleSparkPulse(4, pulseStartTimeSpark4, pulseHighSpark4);
if (pulseActiveSpark5) handleSparkPulse(5, pulseStartTimeSpark5, pulseHighSpark5);
if (pulseActiveSpark6) handleSparkPulse(6, pulseStartTimeSpark6, pulseHighSpark6);
if (pulseActiveSpark7) handleSparkPulse(7, pulseStartTimeSpark7, pulseHighSpark7);
// --- Fuel pulse timing (Pins 8 to 15) ---
if (pulseHighFuel0) handleFuelPulse2(8, pulseStartTimeFuel0, pulseActiveFuel0, pulseHighFuel0, pulseWidthFuel0);
if (pulseHighFuel1) handleFuelPulse2(9, pulseStartTimeFuel1, pulseActiveFuel1, pulseHighFuel1, pulseWidthFuel1);
if (pulseHighFuel2) handleFuelPulse2(10, pulseStartTimeFuel2, pulseActiveFuel2, pulseHighFuel2, pulseWidthFuel2);
if (pulseHighFuel3) handleFuelPulse2(11, pulseStartTimeFuel3, pulseActiveFuel3, pulseHighFuel3, pulseWidthFuel3);
if (pulseHighFuel4) handleFuelPulse2(12, pulseStartTimeFuel4, pulseActiveFuel4, pulseHighFuel4, pulseWidthFuel4);
if (pulseHighFuel5) handleFuelPulse2(13, pulseStartTimeFuel5, pulseActiveFuel5, pulseHighFuel5, pulseWidthFuel5);
if (pulseHighFuel6) handleFuelPulse2(14, pulseStartTimeFuel6, pulseActiveFuel6, pulseHighFuel6, pulseWidthFuel6);
if (pulseHighFuel7) handleFuelPulse2(15, pulseStartTimeFuel7, pulseActiveFuel7, pulseHighFuel7, pulseWidthFuel7);
if (pulseActiveFuel0) handleFuelPulse(8, pulseStartTimeFuel0, pulseHighFuel0);
if (pulseActiveFuel1) handleFuelPulse(9, pulseStartTimeFuel1, pulseHighFuel1);
if (pulseActiveFuel2) handleFuelPulse(10, pulseStartTimeFuel2, pulseHighFuel2);
if (pulseActiveFuel3) handleFuelPulse(11, pulseStartTimeFuel3, pulseHighFuel3);
if (pulseActiveFuel4) handleFuelPulse(12, pulseStartTimeFuel4, pulseHighFuel4);
if (pulseActiveFuel5) handleFuelPulse(13, pulseStartTimeFuel5, pulseHighFuel5);
if (pulseActiveFuel6) handleFuelPulse(14, pulseStartTimeFuel6, pulseHighFuel6);
if (pulseActiveFuel7) handleFuelPulse(15, pulseStartTimeFuel7, pulseHighFuel7);
}
Negative means you just can't do it. Not enough time for 4 milliseconds of dwell at that RPM.
If you need more, you need full sequential. That gives 720 degrees for dwell to happen. Following shows only difference between waste spark and full sequential. 4 lines commented or uncommented, and one variable change between 24 or 48. Then don't forget your tune has to be different for each.
Left is waste spark, right is full sequential.