Coding my own aftermarket ECU

User avatar
AngelMarc
Posts: 221
Joined: Sat Apr 08, 2023 9:23 pm
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

hjtrbo wrote: Thu Jun 12, 2025 9:00 pm Consider IAC, accel enrichment & cold start enrichment tables too.
I'm not adding any of that to this code. Not from speeduino or anything.
Don't stress specific units.
User avatar
AngelMarc
Posts: 221
Joined: Sat Apr 08, 2023 9:23 pm
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

hjtrbo wrote: Thu Jun 12, 2025 9:35 pm Untested, no lookups and no floats. For a common 36-1 trigger wheel

Code: Select all

#include <stdio.h>
#include <stdint.h>

#define NUM_TEETH 36
#define MISSING_TOOTH_GAP_MULTIPLIER 3
#define TOOTH_DEGREES_FIXED 40          // 10° * 4
#define ANGLE_SCALE 4 // Resolution is 0.25 crank degrees
#define MAX_ANGLE (360 * ANGLE_SCALE)

uint32_t last_tooth_time = 0;
uint32_t delta_time = 0;

uint16_t last_known_angle_qdeg = 0;      // in 0.25° units
uint16_t angle_per_us_qdeg = 0;          // angle per µs in 0.25° units
uint8_t synced = 0;
uint8_t tooth_number = 0;

// High speed timer module ISR
void on_tooth_detected(uint32_t timestamp_us) {
    static uint32_t last_delta = 0;
    uint32_t now = timestamp_us;
    delta_time = now - last_tooth_time;
    last_tooth_time = now;

    if (!synced) {
        if (last_delta > 0 && delta_time > (last_delta * MISSING_TOOTH_GAP_MULTIPLIER)) {
            synced = 1;
            tooth_number = 0;
            last_known_angle_qdeg = 0;
            printf(">> Sync achieved\n");
            return;
        }
        last_delta = delta_time;
        return;
    }

    // Calculate angle per microsecond in fixed-point (0.25° units)
    angle_per_us_qdeg = TOOTH_DEGREES_FIXED / delta_time;

    last_known_angle_qdeg = (tooth_number * TOOTH_DEGREES_FIXED);
    if (last_known_angle_qdeg >= MAX_ANGLE)
        last_known_angle_qdeg -= MAX_ANGLE;

    printf("Tooth %d: base angle = %u (x0.25°)\n", tooth_number, last_known_angle_qdeg);

    tooth_number = (tooth_number + 1) % NUM_TEETH;
}

uint16_t get_current_crank_angle_qdeg(uint32_t current_time_us) {
    uint32_t time_since_last = current_time_us - last_tooth_time;
    uint32_t interpolated_angle = last_known_angle_qdeg + (time_since_last * angle_per_us_qdeg);

    if (interpolated_angle >= MAX_ANGLE)
        interpolated_angle -= MAX_ANGLE;

    return (uint16_t)interpolated_angle;
}

Not doing it. Maybe in a separate project, not this one.
Also, my code doesn't speak angles. It coincidentally gets an angle reference every 15 degrees, but that's it.
Don't stress specific units.
User avatar
AngelMarc
Posts: 221
Joined: Sat Apr 08, 2023 9:23 pm
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

The good news is, the interpolation code isn't keeping it from holding together at even 75,000 RPM. Shaky, but it's doing it. Need to add 3 more of the same for each table.
I think I'll add a rolling average for RPMtime like the MAP sensor has. Would like to try the signal from a running engine, but I doubt I will. Maybe the wheel mounted to an electric motor.
Last edited by AngelMarc on Thu Jun 12, 2025 10:07 pm, edited 1 time in total.
Don't stress specific units.
hjtrbo
Posts: 227
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

Re: Coding my own aftermarket ECU

Post by hjtrbo »

Mount it to an angle grinder :)
hjtrbo
Posts: 227
Joined: Tue Jul 06, 2021 6:57 pm
cars: VF2 R8 LSA
FG XR6T
HJ Ute w/RB25DET

Re: Coding my own aftermarket ECU

Post by hjtrbo »

AngelMarc wrote: Thu Jun 12, 2025 9:40 pm
hjtrbo wrote: Thu Jun 12, 2025 9:00 pm Consider IAC, accel enrichment & cold start enrichment tables too.
I'm not adding any of that to this code. Not from speeduino or anything.
Anything LS will be a dog of a thing without them. I've tuned enough to know you'll need at least something to compensate.
User avatar
AngelMarc
Posts: 221
Joined: Sat Apr 08, 2023 9:23 pm
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

I look forward to showing how unneeded a lot of that is.
Don't stress specific units.
User avatar
AngelMarc
Posts: 221
Joined: Sat Apr 08, 2023 9:23 pm
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

hjtrbo wrote: Thu Jun 12, 2025 10:02 pm Mount it to an angle grinder :)
Ha. Maybe if it was a weight balanced design. They are not by default.
Don't stress specific units.
User avatar
AngelMarc
Posts: 221
Joined: Sat Apr 08, 2023 9:23 pm
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

There, stable. Rolling average identical to MAP sensor code.
Still allows TerpD = 0 for some reason.

Code: Select all

#define CRANK_SENSOR_PIN 2  // Pin connected to crankshaft sensor
#include "TuneArrays.h" 

//PICO_FLOAT_IN_RAM=1;
//cmake -DPICO_COPY_TO_RAM=1;
//__attribute__((section(".ramfunc"))) void loop()



enum PinState { WAITING_FOR_ON, WAITING_FOR_OFF };
PinState pinState = WAITING_FOR_ON;
unsigned long previousToggleTime = 0;
uint8_t adcIndex = 0;
bool pulseActive = false;
bool triggerPulse = false;
// Pin 11 (pattern1)
bool pulseActive11 = false;
bool triggerPulse11 = false;
unsigned long pulseStartTime11 = 0;
unsigned long currentOnDelay11 = 1000;
unsigned long currentOffDelay11 = 1000;

// Pin 4 (pattern0)
bool pulseActive4 = false;
bool triggerPulse4 = false;
unsigned long pulseStartTime4 = 0;
unsigned long currentOnDelay4 = 1000;
unsigned long currentOffDelay4 = 1000;



volatile 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

volatile int readings2[24];       // Circular buffer
int bufIndex2 = 0;       // Buffer index
long total2 = 0L * 24;  // Set this to the initial sum

volatile unsigned long lastTime = 0;
volatile unsigned long pulseHighTime = 0;
volatile unsigned long pulseLowTime = 0;
volatile uint8_t signalHistory = 0;
volatile bool newSample = false;
const uint8_t pattern0 = 0b00001111;
const uint8_t pattern1 = 0b01111101;
uint8_t avg8bit = 0;

void handleCrankSignal() {
  unsigned long currentTime = micros();
  if (digitalRead(CRANK_SENSOR_PIN) == HIGH) {
    pulseHighTime = currentTime - lastTime;
  } else {
    pulseLowTime = currentTime - lastTime;
    uint8_t bit = (pulseHighTime > pulseLowTime) ? 0 : 1;
    signalHistory = ((signalHistory << 1) | bit) & 0xFF;
    if (signalHistory == pattern1) triggerPulse11 = true;
    if (signalHistory == pattern0) triggerPulse4 = true;
    //digitalWrite(4, (signalHistory == pattern0) ? HIGH : LOW);
    //digitalWrite(11, (signalHistory == pattern1) ? HIGH : LOW);
    newSample = true; // Signal to loop() to read ADC
  }
  lastTime = currentTime;
}

void setup() {
  analogReadResolution(12);
  pinMode(4, OUTPUT);
  pinMode(11, OUTPUT);
  digitalWrite(11, LOW);
  previousToggleTime = micros();
  pinMode(A0, INPUT);
  pinMode(CRANK_SENSOR_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(CRANK_SENSOR_PIN), handleCrankSignal, CHANGE);
  //Serial.begin(115200);
}

void loop() {
  if (newSample) {
    newSample = false;

    unsigned long RPMTime = pulseHighTime + pulseLowTime;
    total2 -= readings2[bufIndex2];
    int newVal2 = RPMTime;
    readings2[bufIndex2] = newVal2;
    total2 += newVal2;
    bufIndex2 = (bufIndex2 + 1) % 24;
    int RPMTimeavg = total2 / 24;

    // --- 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 >> 5) - 1;  // () for simple order of opperation, minus 1 for avoiding index wrapping


RPMTimeavg = constrain(RPMTimeavg, 134, 125000);

// Calculate IndexFull in fixed-point 30.2 format (<<2 = 2 fractional bits)
uint32_t IndexFull_fixed = (uint32_t)(((uint32_t)2500000 << 4) / (RPMTimeavg * 600UL));
uint8_t Frac_fixed = IndexFull_fixed & 0x0F;      
uint32_t IndexWhole = IndexFull_fixed >> 4;       

IndexWhole = constrain(IndexWhole, 0, 31);
uint8_t IndexPlusOne = IndexWhole + 1;


uint32_t TerpA = Iend[20][adcIndex];
uint32_t TerpB = Iend[21][adcIndex];


int32_t Diff = (int32_t)TerpB - (int32_t)TerpA;
uint32_t TerpD = TerpA + ((Diff * Frac_fixed) >> 4); 




    // --- Update delay values from tables ---
    currentOnDelay11 = Dstart[IndexWhole][adcIndex];
    currentOffDelay11 = Dend[IndexWhole][adcIndex];

    currentOnDelay4 = Istart[IndexWhole][adcIndex];
    //currentOnDelay4 = Iend[IndexWhole][adcIndex];
    currentOffDelay4 = TerpD;
  }

  // --- Check for new pulse triggers from crank handler ---
  if (triggerPulse11) {
    triggerPulse11 = false;
    pulseStartTime11 = micros();
    pulseActive11 = true;
  }

  if (triggerPulse4) {
    triggerPulse4 = false;
    pulseStartTime4 = micros();
    pulseActive4 = true;
  }

  // --- Pulse timing for pin 11 (pattern1) ---
  if (pulseActive11) {
    unsigned long now = micros();
    unsigned long elapsed = now - pulseStartTime11;

    if (elapsed < currentOnDelay11) {
      digitalWrite(11, LOW);
    } else if (elapsed < currentOnDelay11 + currentOffDelay11) {
      digitalWrite(11, HIGH);
    } else {
      digitalWrite(11, LOW);
      pulseActive11 = false;
    }
  }

  // --- Pulse timing for pin 4 (pattern0) ---
  if (pulseActive4) {
    unsigned long now = micros();
    unsigned long elapsed = now - pulseStartTime4;

    if (elapsed < currentOnDelay4) {
      digitalWrite(4, LOW);
    } else if (elapsed < currentOnDelay4 + currentOffDelay4) {
      digitalWrite(4, HIGH);
    } else {
      digitalWrite(4, LOW);
      pulseActive4 = false;
    }
  }
}

Spoke too soon. Forgot to change back some test constants.
Don't stress specific units.
User avatar
AngelMarc
Posts: 221
Joined: Sat Apr 08, 2023 9:23 pm
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

The 0 value output is a separate thing. I probably noticed it before and didn't pay no mind or something. Does need sorted out.
Well, think I've had enough minimum setting simulation failures for today.
Don't stress specific units.
User avatar
AngelMarc
Posts: 221
Joined: Sat Apr 08, 2023 9:23 pm
cars: A CB450 running to 8,000RPM with a P59.

Re: Coding my own aftermarket ECU

Post by AngelMarc »

Was stabbing in the dark with includes.
Gives a drop down of options.
Seen

Code: Select all

#include <mbed/platform/bare_metal/

Looking for those files now.
File explorer has been searching the Arduino15 folder for about 20 minutes now...
Don't stress specific units.
Post Reply