Coding my own aftermarket ECU

User avatar
AngelMarc
Posts: 104
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 »

ChatGPT is like the worst customer support.
"Oh, this looks promising... oh, nevermind."
Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

OK, there's something 100% ChatGPT that functions. Well, it specified pin 32, which I don't see, and caused boot looping when uploaded. Just changed it to pin 15 that I already had plugged in (random choice from earlier nonsense).

Code: Select all

#define CRANK_SENSOR_PIN 15  // Pin connected to the crankshaft sensor

volatile unsigned long lastTime = 0;    // Last pulse time
volatile unsigned long pulseHighTime = 0;  // Time of high phase
volatile unsigned long pulseLowTime = 0;   // Time of low phase

void IRAM_ATTR handleCrankSignal() {
  unsigned long currentTime = micros();  // Use micros() for high precision timing

  // If it's the rising edge (start of a new pulse)
  if (digitalRead(CRANK_SENSOR_PIN) == HIGH) {
    pulseHighTime = currentTime - lastTime;  // Time the high phase
  } else {
    pulseLowTime = currentTime - lastTime;   // Time the low phase

    // Classify the tooth as '1' or '0' based on the duration comparison
    if (pulseHighTime > pulseLowTime) {
      Serial.println(0);  // Tooth is a '1' (high phase is longer)
    } else {
      Serial.println(1);  // Tooth is a '0' (low phase is longer)
    }
  }

  // Update last time for the next pulse
  lastTime = currentTime;
}

void setup() {
  Serial.begin(115200);

  pinMode(CRANK_SENSOR_PIN, INPUT);  // Set crank sensor pin as input
  attachInterrupt(digitalPinToInterrupt(CRANK_SENSOR_PIN), handleCrankSignal, CHANGE);  // Interrupt on change (rising or falling)

  Serial.println("Crankshaft Sensor Decoder Initialized.");
}

void loop() {
  // The loop is empty as we're handling everything in the interrupt function
}

Hooked it up to my open source signal generator. Just directly, since they're both 3.3 volts.
Attachments
Capture.PNG
Capture.PNG (35.27 KiB) Viewed 5627 times
Last edited by AngelMarc on Fri May 02, 2025 12:22 pm, edited 3 times in total.
Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

Reading the previous one just made sense. Familiar enough. Not this one

Code: Select all

#define CRANK_SENSOR_PIN 15  // Pin connected to the crankshaft sensor
#define HISTORY_SIZE 8       // Number of recent results to store

volatile unsigned long lastTime = 0;
volatile unsigned long pulseHighTime = 0;
volatile unsigned long pulseLowTime = 0;

volatile int resultHistory[HISTORY_SIZE] = {0};  // Stores 1s and 0s
volatile int historyIndex = 0;

void IRAM_ATTR handleCrankSignal() {
  unsigned long currentTime = micros();

  if (digitalRead(CRANK_SENSOR_PIN) == HIGH) {
    pulseHighTime = currentTime - lastTime;
  } else {
    pulseLowTime = currentTime - lastTime;

    // Determine 1 or 0 based on high vs. low time
    int result = (pulseHighTime > pulseLowTime) ? 0 : 1;

    // Save result to history buffer
    resultHistory[historyIndex] = result;
    historyIndex = (historyIndex + 1) % HISTORY_SIZE;

    // Print the current state of the buffer
    Serial.print("Pattern: ");
    for (int i = 0; i < HISTORY_SIZE; i++) {
      Serial.print(resultHistory[(historyIndex + i) % HISTORY_SIZE]);
    }
    Serial.println();
  }

  lastTime = currentTime;
}

void setup() {
  Serial.begin(115200);
  pinMode(CRANK_SENSOR_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(CRANK_SENSOR_PIN), handleCrankSignal, CHANGE);
  Serial.println("Crankshaft Decoder with History Initialized.");
}

void loop() {
  // Nothing here – all action is in the interrupt
}


But it works.
Attachments
Capture.PNG
Capture.PNG (47.72 KiB) Viewed 5621 times
Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

https://chatgpt.com/.../68142e61-9fdc-8 ... 1ed13493b2
If anybody cares to pick up where I left off.
EDIT: Apparently I deleted that conversation.
Last edited by AngelMarc on Mon May 05, 2025 10:07 am, edited 2 times in total.
Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

Well, it's not outpacing the P59 before even adding all that's needed. I think the interrupts tried to overlap, for lack of better explanation.
Attachments
Capture.PNG
Capture.PNG (68.62 KiB) Viewed 5601 times
Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

After getting rid of serial. It keeps up much better.
About 60,000 RPM here.

Code: Select all

#define CRANK_SENSOR_PIN 0  // Pin connected to the crankshaft sensor

volatile unsigned long lastTime = 0;      // Last pulse time
volatile unsigned long pulseHighTime = 0; // Duration of the high phase
volatile unsigned long pulseLowTime = 0;  // Duration of the low phase

volatile uint8_t signalHistory = 0;       // Last 8 results (bit pattern of 1s and 0s)
const uint8_t matchPattern = 0b00001111;  // Replace with your desired pattern

void handleCrankSignal() {
  unsigned long currentTime = micros();

  if (digitalRead(CRANK_SENSOR_PIN) == HIGH) {
    pulseHighTime = currentTime - lastTime;
  } else {
    pulseLowTime = currentTime - lastTime;

    // Determine if this tooth is a '1' or '0'
    uint8_t bit = (pulseHighTime > pulseLowTime) ? 0 : 1;

    // Shift in the new bit (keep only last 8 bits)
    signalHistory = ((signalHistory << 1) | bit) & 0xFF;

    // Check if the pattern matches
    if (signalHistory == matchPattern) {
      digitalWrite(11, HIGH);
    } else {
      digitalWrite(11, LOW);
    }
  }

  lastTime = currentTime;
}

void setup() {
  pinMode(CRANK_SENSOR_PIN, INPUT);
  pinMode(11, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(CRANK_SENSOR_PIN), handleCrankSignal, CHANGE);
}

void loop() {
  // Nothing here – logic handled in interrupt
}

Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

https://www.youtube.com/watch?v=LKwkRyWzyCE
Got 8 channels (4 fuel, 4 spark) with they're own timing working at 63,000. That's before implementing any dwell or injector pulse math. Just evenly spacing everything out right now. That's just a Pi Pico with just the crank sensor signal. No cam.
So far the code is generic. That will change at some point.
Serial commands slow it down too much. Had to test with oscilloscope instead.
EDIT: At about 60,000 it starts to drop output pulses.
Attachments
ECUTimingIntervalLogic.ino
(2.01 KiB) Downloaded 35 times
Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

Also messing with a SAMD21. It has some interesting hardware features. The crank decode software can just compare the output of 2 registers. These registers after proper hardware setup code.

Code: Select all

// Read the captured values from the register
    uint16_t high_duration = TC3->COUNT8.CC[0].reg;  // Capture 1 (high duration)
    uint16_t low_duration = TC3->COUNT8.CC[1].reg;   // Capture 2 (low duration)


Then a custom timer upper limit to use timer overflow to trigger an "engine stopped" timer overflow interrupt.

Code: Select all

TC3->COUNT32.CC[0].reg = YOUR_TOP_VALUE;  // If using Match Compare
// OR
TC3->COUNT32.PER.reg = YOUR_TOP_VALUE;    // If using waveform generation / periodic reset

Untested examples.
Will be developing both in parallel more or less.
EDIT: Not having luck with the SAMD21 hardware implementation.
Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

Added analog input with a rolling average. MAP sensor. Rolling average is initialized with max values, because that makes sense for fast startup.
Had serial enabled until it seemed to step through everything the way I wanted; but that limited it to dropping pulses by just 13,000 RPM.
After commenting serial out, still seems very stable at 60,000 RPM.
Maybe the rolling average isn't the best idea; I can change it later. I think next is the PIO pulse generator. Get them all triggering as they should, and varying by "MAP" input. Then more convoluted math and tables.

Code: Select all

#define CRANK_SENSOR_PIN 2  // Pin connected to crankshaft sensor

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 = 4095 * 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;
const uint8_t pattern2 = 0b11101110;
const uint8_t pattern3 = 0b01110011;
const uint8_t pattern4 = 0b10011000;
const uint8_t pattern5 = 0b11000101;
const uint8_t pattern6 = 0b00101000;
const uint8_t pattern7 = 0b01000001;

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;

    newSample = true; // Signal to loop() to read ADC and update output
        // Output patterns
    digitalWrite(4, (signalHistory == pattern0) ? HIGH : LOW);
    digitalWrite(5, (signalHistory == pattern1) ? HIGH : LOW);
    digitalWrite(6, (signalHistory == pattern2) ? HIGH : LOW);
    digitalWrite(7, (signalHistory == pattern3) ? HIGH : LOW);
    digitalWrite(8, (signalHistory == pattern4) ? HIGH : LOW);
    digitalWrite(9, (signalHistory == pattern5) ? HIGH : LOW);
    digitalWrite(10, (signalHistory == pattern6) ? HIGH : LOW);
    digitalWrite(11, (signalHistory == pattern7) ? HIGH : LOW);
  }

  lastTime = currentTime;
}

void setup() {
  analogReadResolution(12);
//  Serial.begin(115200);
  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(A0, INPUT);
  pinMode(CRANK_SENSOR_PIN, INPUT);

  attachInterrupt(digitalPinToInterrupt(CRANK_SENSOR_PIN), handleCrankSignal, CHANGE);
}

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

    // Rolling average: subtract old value, read new one, add it
    total -= readings[bufIndex];
    int newVal = analogRead(A0);
    readings[bufIndex] = newVal;
    total += newVal;

    bufIndex = (bufIndex + 1) % 24;

    int avg12bit = total / 24;
    int avg8bit = avg12bit >> 4;



   // Serial.println(avg8bit);
  }
}

Don't stress specific units.
User avatar
AngelMarc
Posts: 104
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 »

An example of dealing with AI. I assume previous conversation memories are involved. Arduino does 10 bit by default, I've been doing 12 bit. It says 10 bit to 8 bit, but the code is 12 bit to 8 bit.
Have to understand enough to have any hope of getting good results.
Attachments
Capture.PNG
Capture.PNG (11.19 KiB) Viewed 231 times
Don't stress specific units.
Post Reply