jump to navigation

UnoEngineSim and (hacked) Interrupt Priorities on Arduino Mega 2011/04/05

Posted by Michael in 2JZduino.
trackback

During testing I began to experience a misfire that was only occurring above about 5500 RPM. I wondered if the igniter signals were sometimes being delayed due to over-lapping interrupts that were higher priority. I’d recently purchased an Uno as a second Arduino and so I wrote an engine simulator that would run on the Uno to test my theory.

The Uno was configured to produce Injector and Igniter output signals at the intervals typical of a 2JZ-GE engine running at about 6000RPM. These outputs were connected to the inputs of 2JZduino. One-at-a-time the signals *output* by 2JZduino were connected back into the Uno on Pin8; the Input Capture pin, so that the latency of 2JZduino could be measured. The Uno would then report out statistics on the time from when the simulated output signal was generated, to the time that 2JZduino recreated the signal (captured by the ATmega 168’s Timer1 Input Capture register).

Below is a copy of the EngineSim code run on the Uno. Once a second, it reports out the # of events, minimum/maximum/average event delays, and the number of events that fell below the average (providing an estimate of the spread of the data).

// Pin assignments
// Pin2(PD2) = Inj1sim
// Pin3(PD3) = Inj2sim
// Pin4(PD4) = Inj3sim
// Pin5(PD5) = Inj4sim
// Pin6(PD6) = Inj5sim
// Pin7(PD7) = Inj6sim

// Pin8(PB0) = Input Capture

// Pin9(PB1) = IGT1sim
// Pin10(PB2) = IGT2sim
// Pin11(PB3) = IGT3sim

// ********************
// Establish all timing for engine speed of ~100Hz = 6000RPM (28us / deg)
// ********************

volatile unsigned int tEventQueue; // time the event was queued
volatile unsigned int tEventDelayMax; // max elapsed time from Event-queued to Input-Capture
volatile unsigned int tEventDelayMin; // min elapsed time from Event-queued to Input-Capture
volatile unsigned long tEventDelaySum;
volatile unsigned int tEventCount;
volatile unsigned int tEventCountSmall;
volatile unsigned int tEventDelayAverage;

void MonitorInput()
{
  TCCR1B |= B10000000; // Noise Canceler = ON, Capture Falling Edges
  TIMSK1 |= B00100000; // enable Input Capture interrupt
  DDRB &= B11111110; // B0 is an input
  PORTB |= B00000001; // B0 pull-up resistor ON
}

void StartIGTandInjSim()
{
  DDRB |= B00001110; // IGT1..3 sim on PB1..3
  PORTB &= B11110001; // Ensure IGT1..3 signals are OFF
  DDRD |= B11111100; // Inj1..6 sim on PD2..7
  PORTD |= B11111100; // turn on Inj1..6sim -> signal is active low
  TCCR1A = 0; // Normal operating mode
  TCCR1B = B00000010; // clk/8 -> 32.77ms roll-over
  TIMSK1 |= B00000110; // enable OCIE1B and OCIE1A 
}

void StopIGTandInjSim()
{
  PORTB &= B11110001; // Ensure IGT1..3 signals are OFF
  PORTD |= B11111100; // turn on Inj1..6sim -> signal is active low
  TIMSK1 = 0;
}

ISR(TIMER1_COMPA_vect) // IGT sim
{
  // Toggle Sequence: 1,1,2,2,3,3
  const byte IGTbitmaskSeq[6] = { B00000010, B00000010, B00000100, B00000100, B00001000, B00001000 };
  static byte IDX = 0;

  if (IDX == 1) tEventQueue = TCNT1; // Queue the event for IGT1 ON
  PINB = IGTbitmaskSeq[IDX]; // toggle in sequence
  IDX++;
  if (IDX >= 6) IDX = 0;
  
  OCR1A += 5247; // Next event: 3250/65536 * 32.77ms = 1.62ms -> 3 IGT pulses (equally spaced) each revolution
}

ISR(TIMER1_COMPB_vect) // INJ sim
{
  PIND = B11111100; // toggle all the injectors simultaneously
  OCR1B += 10003; // Next event: 10000/65536 * 32.77ms = 5ms -> Sim. all Injectors pulse each revolution
}

ISR(TIMER1_CAPT_vect)
{
  unsigned int Delay = ICR1 - tEventQueue;
  if (Delay > tEventDelayMax) tEventDelayMax = Delay; // store the peak delay that occurred
  if (Delay < tEventDelayMin) tEventDelayMin = Delay; // store minimum delay that occurs
  if (Delay < tEventDelayAverage) tEventCountSmall++;
  tEventDelaySum += Delay;
  tEventCount++;
}

void StartSim()
{
    StartIGTandInjSim();
    MonitorInput();
}

void StopSim()
{
  StopIGTandInjSim();
  tEventDelayMax = 0;
}

void setup() {
  Serial.begin(115200);
  Serial.println("IGT1 ON Event Delay in clk/8 counts...");
  MonitorInput();

  StartSim();
  Serial.println("Begin...");
}

void loop() {
  if (Serial.available())
  {
    char c = Serial.read();
    
    if (c == 's') {
      StartSim();
      Serial.println("Begin...");
    }
    else if (c == 'x') {
      StopSim();
      Serial.println("stop.");
    }
  }
  if (tEventDelayMax > 0) 
  {
    Serial.print(tEventDelayMin, DEC);
    Serial.print(" .. ");
    Serial.print(tEventDelayMax, DEC);
    Serial.print(" (");
    Serial.print(tEventDelayAverage, DEC);
    Serial.print(":");
    Serial.print(tEventCount, DEC);
    Serial.print(":");
    Serial.print(tEventCountSmall, DEC);
    Serial.println(")");
  }
  else Serial.print(".");
  tEventDelayAverage = tEventDelaySum/tEventCount;
  tEventDelayMax = 0;
  tEventDelayMin = 65535;
  tEventDelaySum = 0;
  tEventCount = 0;
  tEventCountSmall = 0;
  delay(1000);
}

Note that the Injector and IGT signals are intentionally setup on different frequencies so they drift in and out of phase in over-lap. This was done to ensure a worst-case scenario would eventually surface where the injector events, ADC_complete events, timer overflows, etc. would all interfere with the IGT events. What I found is that on some occasions the igniter signals would be delayed by upwards of 600us. At 5500 RPM this translates to about 20 degrees. The Uno simulator was written as worst-case (with all 6 injectors firing simultaneously), but it was at least seeming plausible that under some circumstances interrupt conflicts could cause the igniter signal to be delayed long enough that a misfire could occur because the engine spark arrived too late.

Looking at the datasheet for the Atmega 1280, the Interrupt vectors show that the IGT interrupt requests (Pin Change Interrupt Requests 0/1/2) are lower in priority than the Injector interrupt requests (External Interrupt Request 0/1/2/3/4/5). What I wanted was for the IGT interrupts to be treated with higher priority than the Injector interrupts. Spark events are critical in timing, but getting slightly more or less fuel would have a negligible effect. The solution I arrived at was to configure the interrupt handlers for the Injector External Interrupts like this…

ISR(INT0_vect, ISR_NOBLOCK) { // Ext Interrupt 0 on D0
  noInterrupts();
  myIS300.h_InjectorChange(0);
  interrupts();
};

In this way with the “ISR_NOBLOCK” argument, if all 6 Injector interrupts and an IGT interrupt occurred simultaneously the external interrupts would all queue up on the stack, each one interrupting the other, until the IGT interrupt gets serviced as the highest priority interrupt. The IGT interrupt, being a blocking interrupt, would be fully-serviced, and then the Injector interrupts would start getting popped off the stock one-by-one, unwinding until normal program flow continued.

With this change the maximum delay on the IGT signal was reduced from 600us down to 40us. 95% of the time the IGT signal delay was < 20us. This translates to better than 1deg of latency in the IGT signal at 6000 RPM under worst-case conditions.

It turned out that the real-problem causing the high-RPM misfire was due to a 220 Ohm resistor being installed where there should have been a 10k Ohm resistor. But the above was a good improvement nonetheless.

Advertisements

Comments»

No comments yet — be the first.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: