Coding my own open source 24x ECU [beta available]

Disassembly, Reassembly, Tools and devleopment. Going deep with Hardware and Software.
User avatar
AngelMarc
Posts: 612
Joined: Sat Apr 08, 2023 11:23 am
cars: A CB450 running to 8,000RPM with a P59.

Coding my own open source 24x ECU [beta available]

Post by AngelMarc »

EDIT: Most recent progress
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)
UpdatedPulseHandler7_17_2025.ino
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.
TuneArrays.h


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.
BigChart.PNG
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
Waste spark, nearly identical to full sequential code.

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);
}

Numbers for waste spark timing per each RPM zone.
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.
WastSparkChart.PNG
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.
WasteVSFull.png
You do not have the required permissions to view the files attached to this post.
Last edited by AngelMarc on Fri Aug 21, 2026 10:29 am, edited 114 times in total.
Don't stress specific units.
User avatar
AngelMarc
Posts: 612
Joined: Sat Apr 08, 2023 11:23 am
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

Nearly an hour just trying to get some formatting/syntax right so I can even see what space could be taken up by tune/cal.
It also didn't like me specifying "long" instead of "unsigned long"... "narrowing conversion from long long int, to long" ... um, no.
You do not have the required permissions to view the files attached to this post.
Last edited by AngelMarc on Thu May 01, 2025 10:13 pm, edited 1 time in total.
Don't stress specific units.
User avatar
pman92
Posts: 667
Joined: Thu May 03, 2012 12:50 pm
Location: Castlemaine, Vic

Re: Coding my own aftermarket ECU

Post by pman92 »

You're missing a set of { } to cover the whole lot, and your array is backwards.
You've specified an array of 12 arrays with length 20. But then shown 20 arrays with length 12.

As for working out how much space, you dont actually need to define all the data. " = { }" is enough to allocate memory.
But even easier, 12 x 20 items of type unsigned long (4 bytes each) = 12 x 20 x 4 = 960 bytes of memory.
User avatar
AngelMarc
Posts: 612
Joined: Sat Apr 08, 2023 11:23 am
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

Thank you. Need a quick reference manual as helpful as you. I'm not an encyclopedia that can remember all this arbitrary syntax.
You do not have the required permissions to view the files attached to this post.
Don't stress specific units.
User avatar
pman92
Posts: 667
Joined: Thu May 03, 2012 12:50 pm
Location: Castlemaine, Vic

Re: Coding my own aftermarket ECU

Post by pman92 »

Try chatGPT. It's excellent for stuff like that.
Just tell it you want short answers and you dont want it to try and take over.
If you let it start writing code for you, you'll generally get it working in the end, but you'll learn nothing in the process.
Just keep it for when you get stuck on stuff like that. The show it your code, show it the error you're getting, and it will point out the problem for you
User avatar
AngelMarc
Posts: 612
Joined: Sat Apr 08, 2023 11:23 am
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

I forgot. My approach requires this insanity.
And compiler doesn't just tell me what that consumes.
You do not have the required permissions to view the files attached to this post.
Don't stress specific units.
User avatar
AngelMarc
Posts: 612
Joined: Sat Apr 08, 2023 11:23 am
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

EDIT: think I did my math wrong.
65,536 bytes, so before it was 131,072 bytes.
1 good thing about that circuit python nonsense, the units meant for it have excess resources.
Messed up the edit and deleted original. Don't know how I originally got 20kB.
You do not have the required permissions to view the files attached to this post.
Last edited by AngelMarc on Tue May 27, 2025 4:03 pm, edited 4 times in total.
Don't stress specific units.
User avatar
pman92
Posts: 667
Joined: Thu May 03, 2012 12:50 pm
Location: Castlemaine, Vic

Re: Coding my own aftermarket ECU

Post by pman92 »

The [20][256] table is over 20kb by itself

20 x 256 x 4
User avatar
AngelMarc
Posts: 612
Joined: Sat Apr 08, 2023 11:23 am
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

Oh, yes, I forgot to x4 that.
Don't stress specific units.
User avatar
AngelMarc
Posts: 612
Joined: Sat Apr 08, 2023 11:23 am
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

To keep code more generic, I think I'll just compare 2 counters. Each counter will increment with the high and low of crank signal, then those numbers get compared with an if < or if > for the 24X decode.
Don't stress specific units.