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

Disassembly, Reassembly, Tools and devleopment. Going deep with Hardware and Software.
Denny
Posts: 71
Joined: Sun Nov 10, 2024 8:04 pm
cars: 2007 Chevrolet Silverado 4.8

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

Post by Denny »

AngelMarc wrote: Mon Jul 14, 2025 3:51 pm That why I kept getting
"The forum is currently unavailable. Try again in a few minutes."
I was wondering too.
User avatar
pman92
Posts: 667
Joined: Thu May 03, 2012 12:50 pm
Location: Castlemaine, Vic

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

Post by pman92 »

antus wrote: Sun Jul 13, 2025 6:19 am BTW the site is behind cloudflare now. We got broken hard over the last 2 weeks.
I'm still getting the try again later warning and getting logged out occasionally.

Bloody AI is going to kill what good things are left of the internet :comp:
User avatar
antus
Site Admin
Posts: 10012
Joined: Sat Feb 28, 2009 10:34 am
cars: TX Gemini 2L Twincam 8psi
TX Gemini SR20 18psi
Datsun 1200 Ute
Subaru Blitzen '06 EZ30 4th gen, 3.0R Spec B
Subaru WRX 2007

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

Post by antus »

Lets let this thread be for the 24 times open source ECU. But yes load problems are ongoing. Cloudflare helped, significantly, but we are still too popular and overloading the server. I'm not sure what I can do yet. I think i'm out of low cost options and need to scale up. Any further comments about load, start a new thread (or post to one, once it exists) and we can talk about it there.
Have you read the FAQ? For lots of information and links to significant threads see here: http://pcmhacking.net/forums/viewtopic.php?f=7&t=1396
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 open source 24x ECU [beta available]

Post by AngelMarc »

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 open source 24x ECU [beta available]

Post by AngelMarc »

Not liking ChatGPT's suggestions for pulse gen changes that don't change things outside those functions, so I'm trying to slowly type something out manually. Really need something that doesn't cause chaos when bouncing off the rev limiter.
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 open source 24x ECU [beta available]

Post by AngelMarc »

Didn't test yet. Surprised myself with only 1 typo and no Ai help with this change.
Separated pulse handlers into 2 halves. Basically borrowed a little from how I sorted my crank signal generator. A little difference, 1st function/loop doesn't de-assert itself, so second loop can use that flag to lock in its pulse width, then de-assert the flag. Should be able to bounce off the rev limiter all day and not see partial pulses. Same with transient conditions.

Code: Select all

#include "TuneArrays.h" 

const uint8_t pattern0 = 0b01111101;//0 degrees / TDC #1 and paired cylinder
const uint8_t pattern1 = 0b01110011;//90 degrees
const uint8_t pattern2 = 0b11000101;//180 degrees
const uint8_t pattern3 = 0b01000001;//270 degrees

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;
  } else if (elapsed < OnTime) {
    digitalWrite(pin, HIGH);
  } else {
    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 < currentOnDelaySpark) {
    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 = currentOnDelaySpark + currentOffDelaySpark;
    activeFlag = false;
  } else if (elapsed < OnTime) {
    digitalWrite(pin, HIGH);
  } else {
    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 * 48;//48 for full sequential, 720 degrees. 25 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);


}


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 open source 24x ECU [beta available]

Post by AngelMarc »

Apparently I messed this up.
So much for what was commented here.
Last edited by AngelMarc on Sun Sep 28, 2025 3:57 am, edited 1 time 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 open source 24x ECU [beta available]

Post by AngelMarc »

When I split the pulse handlers, I sorted spark (thought I did anyway, god damn ampersand), then copy pasted that for fuel. Changed the function name but forgot to change some variable in the handlers. And apparently the "&" for passing variables for whatever the fuck reason. Going to replace that in original post.

Code: Select all

#include "TuneArrays.h" 

const uint8_t pattern0 = 0b01111101;//0 degrees / TDC #1 and paired cylinder
const uint8_t pattern1 = 0b01110011;//90 degrees
const uint8_t pattern2 = 0b11000101;//180 degrees
const uint8_t pattern3 = 0b01000001;//270 degrees

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 * 48;//48 for full sequential, 720 degrees. 25 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);
}


Don't stress specific units.
User avatar
antus
Site Admin
Posts: 10012
Joined: Sat Feb 28, 2009 10:34 am
cars: TX Gemini 2L Twincam 8psi
TX Gemini SR20 18psi
Datsun 1200 Ute
Subaru Blitzen '06 EZ30 4th gen, 3.0R Spec B
Subaru WRX 2007

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

Post by antus »

Apparetly the "&" on variables passed to the pulse handler functions is pretty damn important.
Sure is. Now you are in the land of pointers. That is often considered advanced C in tutorial.

When you put the & in front of the variable or array element the compiler will use the address of the data, not the data itself. A very different thing. This is sometimes done as an optimisation, depending on the context. Sometimes when you want to modify data in place for speed instead of copying it, modifying it and writing it back. Absolutely if the compiler treats a pointer (a variable with an * in front of the name of it, which makes it store an address, not data) as data it'll corrupt the memory address, and if you treat data as a pointer you'll probably read or write invalid memory. What happens then depends on the platform. On a PC it'll crash, on an pi pico it might crash mbed, or hardware, or might keep running. But these sorts of bugs are exactly why C has a bad reputation for security, and a good reputation for being able to write fast code when used correctly. Bugs can be hard to find, because the crash may come much later after its been triggered depending on what happened.

In your case its passing the address of those bools so that you can update the original data, instead of a local copy of the variable that you'd then need to return and save back to the original location if you didn't.
Have you read the FAQ? For lots of information and links to significant threads see here: http://pcmhacking.net/forums/viewtopic.php?f=7&t=1396
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 open source 24x ECU [beta available]

Post by AngelMarc »

OK, then that's how it's effectively making multiple copies for overlapping channel pulses without... "cross talk". Function call and not some compile level thing to just make writing repetative code unneeded. Also, seemed no "&" just used the initialized value of zero, without updating, didn't verify that, because whatever it was doing was wrong and just needed changed.
I've left it running overnight before with pointers (before splitting the handlers), woke up to it doing the same thing it's suppose to do.
not going to pretend I understand all of what you said. Just like I'm not going to get use to what programmers consider a "3D array".
Don't stress specific units.