Page 12 of 18

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 9:40 pm
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.

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 9:44 pm
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.

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 9:56 pm
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.

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 10:02 pm
by hjtrbo
Mount it to an angle grinder :)

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 10:07 pm
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.

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 10:09 pm
by AngelMarc
I look forward to showing how unneeded a lot of that is.

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 10:11 pm
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.

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 10:32 pm
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.

Re: Coding my own aftermarket ECU

Posted: Thu Jun 12, 2025 11:41 pm
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.

Re: Coding my own aftermarket ECU

Posted: Sat Jun 14, 2025 3:04 pm
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...