Arduino as ECU: Board reliability

Hello there, i'm retrofitted a gasoline engine with arduino uno instead of mechanical timing belts.

As you guessed, board determining the mill state from a rotary encoder, triggering the spark plug based on measured position.

Currently i'm writing this position data into eeprom on each 50ms, arduino reads the last state at bootup and instantly continues to the operation.

in here i got two issues:

  1. eeprom write limitation: board will cease to function very soon with this behaviour
  2. eeprom access time: it's around 3ms, engine shaking a little bit on each reset

i imagined two solutions:

  1. capacitor based simple memory, build from scratch or a ready to use module if exists
  2. a more reliable board

which way i should follow? i'm open to suggestions

Go with custom board with an FRAM for storing the data.
SRAM read/write speeds (so pretty much instantaneous compared to EEPROM), and EEPROM nonvolatility.
Here are 5V parts with SPI interface that would work great.

1 Like

Would 4Mb of Flash Ram work? Use an ESP8266 board like the Wemos D1 Mini. It comes with 4mB of Flash that you can read and write to like a micro SD card. (It also operates at 80mHz as opposed to the 16mHz of the Uno).

Curious, is your rotary encoder an absolute encoder? or at least does it have a 1/revolution feature?
If not are you saving the position to eeprom to keep track of the absolute position?

Thank you so much, i wasn't aware about this kind of external modules. seems they're approx 0.5 usd at aliexpress which is pretty affordable.

luckily i found the wemos d1 mini in a local electronics shop, i'll try that.

it's not an absolute encoder, the exact part number is: LPD3806-400BM-G5-24C, one complete turn provides 400 pulses. so yes, i'm picking up the last position of mill from eeprom.

that's my new plan for next version, based on your feedback:

arduino ---> fram module ---> xor gate module ----> 5v ssr relay or mosfet ---> 12v/80a relay

fram module keeps the pin high even if arduino is down, also xor module will filter out accidental pin state changes.

in case of reset or watchdog trigger arduino will communicate with fram module first, and pick up the last state, continues to the operation.

seems xor modules like SN74AHCT1G86-Q1 already have a wide usage in automobile industry

If your encoder is not absolute position type, how about a "crankshaft position sensor" (That's what the $500 part in my Jeep is called. I asked for the old part and it turns out to simply be a hall-effect sensor and a magnet imbedded in the flywheel on the motor). This way you don't have to remember anything. One revolution calibrates your encoder position.

yes that part was already exists on stock engine before modding, i removed it. currently quite achieved what i'm trying to do but as stated on title i can't trust to the arduino board in a real world application. one accidental relay hiccup possibly damage to the engine, even might lead to an explosion.

Hi, @psychip
Welcome to the forum.

Have you googled;

arduino ecu

What about the valves?

Most engine ECU use simple inductive pickups to detect TDC and then calculate timing from that, rather then concentrate on an encoder.

How many PPR is the encoder?
What engine speed?

As the motor only turns in one direction, TDC sensor and camshaft position sensor would be all you need.
2 stroke or 4 stroke?

Tom.. :smiley: :+1: :coffee: :australia:

I don't mean to be negative and I may still misunderstand but are you concerned you system might get out of sync if the encoder count pickups a voltage spike and slowly gets out of sync. Automotive electrical systems are terribly noisy. I can't see running a motor for 100's of hours and keeping things in absolute sync by counting the encoder only.

I think you will need a more absolute reference, perhaps a 1/rev signal?

Is your motor particularly slow? The specs I see on this super-cheap encoder say it only encodes up to 2000 RPM and can only physically handle 6000 RPM.

Like, a crankshaft position sensor?

Not familiar with the details of the crankshaft position sensor but it sounds good.

See #7

Typically, the encoder looks like a gear with one tooth missing. There is a sensor that pulses as each tooth goes by. The electronics count the teeth going by to measure RPM and looks for the gap in the sequence for its zero mark. The number of teeth is usually in the 30 to 60 range.

Makes perfect sense, cheap and reliable.

Here is a sketch I posted in the forum back in 2018.
It reads a crank position encoder with "12" teeth (actually 11).

volatile unsigned long PulseInterval; // Microseconds between the last two pulses
volatile unsigned long PulseTime; // Time (micros()) of the rising edge of the latest pulse
volatile byte PulseNumber = 0; // Which pulse was the latest pulse
volatile boolean PulseIsNew = false; // Flag set by the ISR

void myPulseIsr()
  static unsigned long prevPulseTime; // Needed for detecting the new pulse interval
  static unsigned long prevPulseInterval; // Needed for detecting the long pulse

  PulseTime = micros();
  prevPulseInterval = PulseInterval; // save the preceding value
  PulseInterval = PulseTime - prevPulseTime;
  prevPulseTime = PulseTime;

  // A pulse longer than 1.5 times the previous pulse indicates the index pulse (#0).
  if (PulseInterval > (prevPulseInterval + (prevPulseInterval >> 1))) // compare to prev * 1.5
    PulseNumber = 0;
  PulseIsNew = true; // Let loop() know that new data is available

void setup()
  attachInterrupt(digitalPinToInterrupt(2), myPulseIsr, RISING);

void loop()
  static unsigned long localPulseTime = 0;
  static byte localPulseNumber = 0;
  static unsigned long microsecondsPerRevolution = 999;

  if (PulseIsNew)
    // Grab the volatile variables.
    unsigned long localPulseInterval = PulseInterval; // Should work down to 152 RPM
    localPulseTime = PulseTime;
    byte localPulseNumber = PulseNumber;
    PulseIsNew = false;

    microsecondsPerRevolution = localPulseInterval * 12;
    if (localPulseNumber == 0)
      microsecondsPerRevolution = localPulseInterval * 6; // Long Pulse

  unsigned long microsecondsSincePulse = micros() - localPulseTime;
  long int degreesSinceIndex = 30 * localPulseNumber; // First 11 pulses are at 30° intervals

  // Equivalent to (microsecondsPerRevolution / microsecondsSincePulse) * 360 but less subject to truncation
  degreesSinceIndex += (microsecondsPerRevolution * 360UL) / microsecondsSincePulse;


  // This is where you use the new crank position