jump to navigation

Engine Bay Organization 2011/01/01

Posted by Michael in my IS300.
1 comment so far

Fitting a supercharger into the IS300 engine required a little more work and effort that I originally bargained for. The stock location for the ABS block is right in the path of where the compressor stage should sit, so it needs to move. And unfortunately the most convenient alternate location for the ABS block is where the battery sits. So, I set about moving the battery to the trunk.

It seems as though some Engineers at Toyota at one time thought the battery was going to be in the trunk from the factory; in the right side of the trunk there are two threaded studs welded into the body sheet metal, a tab with a thru-hole suitable for a grounding strap, and a drain hole with rubber plug ideal for a battery vent. I purchased an MT-47 from Interstate Batteries; a liquid lead-acid battery with a sealed top and vent tube. Note that vent tubes are a *must* for trunk-mount batteries to prevent explosive gases from collecting in the enclosed space. The MT-47 fits nicely into the recessed well in the right of the trunk. I fabricated a mounting plate out of aluminum bar stock that attaches to the existing threaded studs and mounts two eye-bolts so that the battery can be secured with a Nylon strap. The battery vent runs down and out through the drain hole (lined with a rubber grommet) in the bottom of the trunk.

For the wiring, I ran #2 AWG from the battery to the engine bay (running it beneath the rear seat, along the passenger-side door sills, and then through an existing rubber grommet in the firewall, about 15′ is needed). A 175 AMP slow-blow fuse is connected within 12″ of the battery +ive terminal. A #4 AWG connects the battery -ive to the sheet metal tab in the trunk (cleaned with a wire brush to ensure good electrical contact), and a second #4 AWG runs up to the stock battery -ive connection point in the engine bay (following the same bath as the #2 wire). Note that the stock starter is a 1400W motor (117A @ 12VDC). I originally had run a #4 AWG for the battery +ive from the trunk but the voltage drop was too significant and resulted in what sounded like a dying battery every time I started the car. (Refer to for further information on current ratings for conductors and a voltage drop calculator).

Next I set about relocating the ABS block. This turned out to be easier than expected as each of the brake lines leading to the ABS block only needed to be bent to the new location (they didn’t need to be shortened and re-flared). Care was taken to ensure that new bends in the brake line were in the virgin material to avoid fatigue cracks. All bends were made with the smallest brake-line bending tool I could find ($10 at Princess Auto). I also took the opportunity to flush the brake fluid, install braided SS brake lines, and refill with ATE Dot4.

Tube Bender used for ABS relocation

The images below show the results of the battery relocation…



and the ABS block relocation…

2JZduino PCBs 2010/12/08

Posted by Michael in 2JZduino.
add a comment

I’m having my first custom PCB made for 2JZduino, using v0.3 of the code (which isn’t available as of this writing). It will support intercepting the IGT igniter signals to control ignition timing (instead of the v0.2 method which controlled ignition by intercepting the crank timing signal). The wiring inside my project box is getting messy enough that I don’t have much trust left in it to be reliable. So these are the first steps toward eventually releasing the Eagle files and a Mouser order BOM for anyone that wants to build one of these for themselves.

The board is being made through http://www.dorkbotpdx.org.

The custom board allows for a much cleaner assembly and packaging job. Below are a couple of solid model images of 2JZduino (semi) pupulated and packaged with an Arduino Mega.

Arduino Buck-boost converter 2010/12/08

Posted by Michael in 2JZduino.
5 comments

[Edit: Previously posted as a “charge pump”, I was corrected by a reader this circuit is actually a buck-boost converter…]

If you’ve ever needed a negative voltage source for components connected to your Arduino, you might find this post helpful.

I built a simple buck-boost converter circuit powered by the Timer2 Compare Match Output Unit available on the ATMega 1280 (Arduino Mega). The effort was part of troubleshooting related to the simulated crank sensor output. I thought the stock ECU might need a voltage below ground to prevent noise from triggering extra zero-crossings, but this turned out to not be the problem. So I discarded this as part of my circuit design but it was a breadboard exercise worth sharing.

First, note that I believe this interferes with the PWM functionality of the Arduino environment. If you use other functions that reference Timer2, this will break them.

On the Arduino Mega the Compare Match Output Unit can be configured to toggle the value of pin 10 (port B4) at a particular frequency through hardware (i.e consuming zero processor cycles). My charge pump design is configured for an input voltage that switches between 0 & 5V every 0.4ms.

DDRB = B00010000;  // B4 (pin 10) is an output
TCCR2A = B01000010; // Toggle OC2A on Compare Match, CTC mode
TCCR2B = B00000011; // Timer2 prescaler = 1/32
OCR2A = 0xC8; // Compare Match @ TCNT2 = 200, occurs every 200*32/16MHz = 0.4ms

Below is a schematic for the converter circuit, drawn in LTspice. V1 is Arduino Mega pin 10 (OC2A output). R2 limits the current out of the Arduino. When the output is ON L1 (47mH & 82 Ohm) is energized, and when the output switches OFF, L1 pulls current from C1 (100 uF) through D1 while it discharges. C1 is what stores the negative voltage. R3 is the load consuming this negative voltage. It’s shown as 1k Ohms, but the circuit will maintain -0.5V for R3 values as low as 220 Ohms. For high impedance loads, the circuit will generate approx. -2.7V.

The voltage at C1 vs. time as simulated in LTspice is shown in the graph below (R3 = 1000 Ohms).

And that’s it. 4 lines of code and 4 components to generate a negative voltage.

0.2 Alpha (2nd public release) 2010/11/12

Posted by Michael in 2JZduino.
add a comment

Thanks to one of my (few) readers for a new idea on how to provide ignition delay. To date, my solution has been to intercept and delay the crank sensor signals. This has posed numerous challenges for reliably detecting the signal at the full RPM range.

The suggestion was to instead simply intercept the signals going out to the igniter. I did not expect the signal to the igniter (IGT) to be a 5V logic-level signal and so before now hadn’t looked into it. I’ve plans to revise the under-hood wiring and the logic to intercept the IGT signals and simply tap into the crank sensor signal so as to get an accurate engine RPM reading. This should provide a more direct approach to delaying ignition while also eliminating some of the burden on the Arduino processor and some of the risk in measuring the crank signal in such a fragile way.

Because of this significant shift I’ve decided to release version 0.2 of the 2JZDuino code…
2JZduino at SourceForge

Most of the changes (along with ramblings) are documented at the top of IS300_MAPadd.cpp. Worth particular mention is that I implemented new logic to detect, filter, and reproduce injector signals; there were significant errors there in the 0.1 code.

I’ve also tested the Wideband/Narrowband conversion and simulation logic; it seems to work for bank 1 but I need to purchase a 2nd Wideband to provide close loop control for bank 2.

Lastly, there’s been some interest at miataturbo.net in developing an Arduino standalone engine controller. See this thread…
http://www.miataturbo.net/showthread.php?t=50695

One of the members found me out and sent me an email (my first post in that thread @ #157).

Programmable Closed-Loop AFR Control 2010/08/24

Posted by Michael in 2JZduino.
add a comment

[Edit: September 20, 2010 – This post originally contained some incorrect information. Through continued testing and development I’ve grown a better understanding of this topic. See edits below. The original (significant) comments made in error have been struck out.]

One of the objectives of this 2JZduino project is to be able to trick the stock ECU into pursuing any Air-Fuel-Ratio in closed loop. From the factory, the vehicle pursues an AFR of 14.7:1 (the stoichiometric ratio for petrol). This provides the cleanest burning exhaust.

The problem is that with a supercharger, under part-throttle cruising conditions the manifold air pressure will be higher than normal and a 14.7:1 AFR may lead to pre-ignition. It is desirable to adjust the target AFR as a function of manifold air pressure. I’ve done just that using a digital output on the 2ZJduino and a simple electrical circuit.

A Typical Narrowband Oxygen Sensor Signal
The stock 2JZ oxygen sensor is what is known as a narrowband sensor. This type of sensor transitions between ~1V when the fuel mixture (for petrol) is richer than 14.7:1, and 0V when the fuel mixture is leaner than 14.7:1. The transition is gradual and changes at a rate of ~10V/s ~5V/s when healthy. An output of 0.45V represents a stoichiometric fuel mixture of 14.7:1 however, the sensor is extremely sensitive to AFR changes. As a result, an ECU using narrowband oxygen sensors continually chases the fuel mixture between rich and lean causing the output of a narrowband sensor to oscillate.

If the fuel mixture is held steady at 14.7:1 the signal reliably oscillates around 0.45V at between 1 and 5 Hz (slower at idle). Note that 0.45V is the rich/lean switching-point but it is not a stable output Voltage for the sensor. A slightly rich mixture will still oscillate around 0.45V but it will spend more time above 0.45V than below (i.e. it will switch back toward 1V sooner). The opposite is true for a slightly lean mixture.

The task then is to engineer a circuit and algorithm that simulates this output depending on the the actual AFR read by a wideband oxygen sensor (in my case, the Innovate Motorsports LC-1).

Circuit Design

The only available outputs on the Arduino are logic level digital outputs. Below is the schematic I arrived at to convert this 5V digital output signal into a 1V signal with gradual attack and decay.

The analog output signal of the LC-1 connects to Arduino Analog Input 1. On the output side, connected to digital output #37, there is a voltage divider (330 & 68 Ohm resistors), and an RC circuit (680 Ohm and 47uF capacitor). The signal connected to the Narrowband sensor input at the ECU is the voltage level carried by the capacitor. These connections are C28 (Bank2, Sensor1, O2 signal) and D28 (Bank1, Sensor1, O2 signal) at the stock ECU. Note that two separate circuits and wideband sensors are required. Initially I tested this in-vehicle using a single LC-1 (reading cylinder #2 exhaust) with the processed output feeding both Banks 1&2. The result however was that Bank 2 long-term fuel trims went extremely lean. Since the LC-1 was being used for closed-loop control for Bank 2 but wasn’t actually reading Bank 2 AFR I expect the control loop was simply unstable. A second LC-1 will be required to properly run Bank 2 cylinders closed-loop.

Ref: 02+IS300_ECU_Pinout

The component values were chosen as a compromise between current draw on the Arduino digital output pin, and signal level for the input side of the ECU (assumed high impedance). The voltage divider resistors step the 5V voltage down to 0.85V. The resistor and capacitor values were selected by simulating the circuit output in Excel using Euler’s method (and the ODE for a capacitor: i(t) = C dV(t)/dt) and adjusting the values until satisfied.

Worst-case current draw occurs when the capacitor charge is 0V and the digital output turns on. At t=0 peak current is 13 mA, perfectly safe for a digital output pin on the Arduino. Below is an Excel of the output voltage of the simulator for a 5V square-wave input @ 4Hz; the signal for a fuel mixture that is at the target AFR.

Logic Design
The code used for generating this controlling the output is shown below…

ISR(ADC_vect) {
  ...
    static byte counter = 0;
    static unsigned int AFRsum = 0;

    AFRsum += ADCH;  // store the Left Adjusted ADC High Byte value
    counter++;
    
    if (counter == 64) { // AFR value is an average of 64 readings
      AirFuelRatioB1_x10 = 83 + (byte(AFRsum >> 6) >> 1);  // program LC1 range for 0V @ 8.3AFR -> 5V @ 21.1AFR (255 ADC counts for 12.8AFR range)
      AFRsum = 0;
      counter = 0;
    }
  ...
}

ISR(TIMER4_OVF_vect, ISR_NOBLOCK) { // Timer4 Overflow
{ // This function is called on Timer4 Overflow (~every 32ms)
  noInterrupts();
  
  static byte counter = 0;
  static boolean richstate = false;
  
  if (counter < 255) counter++;  // used to control minimum dwell on a rich/lean signal output
  
  if (counter >= 2) { // minimum dwell at rich/lean is ~64ms (32ms x 2) to allow a complete signal transition
    if (AirFuelRatioB1_x10 > AFRTargetTable[MAPindex]) { // lean
      PORTC &= ~NarrowbandB1S1BitMask;  // C0 output LOW (lean condition)
      if (richstate) counter = 0;  // reset counter when output changes
      richstate = false;
    }
    else { // rich
      PORTC |= NarrowbandB1S1BitMask;  // C0 output HIGH (rich condition)
      if (!richstate) counter = 0;  // reset counter when output changes
      richstate = true;
    }
  }
  interrupts();
}

The first function shown handles the Analog to Digital Conversion of the LC-1 AFR output signal. Note that the LC-1 is programmed to output 0V for an AFR of 8.3:1 and 5V for an AFR of 21.1:1. This is done strictly to help with the efficiency of the math on the Arduino (avoiding expensive floating point and division operations). The incoming AFR value is taken as an average of 64 consecutive readings (the ADC runs nearly continuously).

The second function is the interrupt handler for Timer4 overflow which runs regularly at about a 32ms interval. This function sets the outputs for narrowband simulation LOW if the AFR is lean, and HIGH if the AFR is rich, using the AFRTargetTable[MAPindex] table to determine what AFR determines the rich/lean switch-point.

AFRTargetTable[MAPindex] is a one-dimensional array that contains a list of target AFRs. The current target is selected based on the current manifold intake pressure. This list of Target AFRs is the part that is programmable, and allows the user (me) to set a target AFR of 13:1 for example for all manifold intake pressures above atmospheric. It could also be used to adjust the AFR to 15.5:1 at pressures below 40 KPa and create a leaner idle.

This function also tracks what the current output state is and prevents it from toggling in less than 64ms.

Note that the logic needs duplicating to handle the Bank 2 Wideband sensor and simulated output.

Emissions and Air-Fuel-Ratio data logs 2010/08/07

Posted by Michael in 2JZduino.
add a comment

I realize it’s been more than a month since my last post, in most part because progress has been slow.

I had some exhaust work done to improve airflow in preparation for the Supercharger. This involved replacing the stock exhaust manifold with an after-market long-runner header. The OE exhaust manifold includes two primary catalytic converters, so in addition to the installation of the new header I had a local shop also install a high-flow catalytic converter: the Vibrant Performance (p/n 7001) ceramic core high-flow. I had the car tested for emissions both before and after the exhaust work. Results below…

Vehicle: 2004 IS300
****************************
“Before” Configuration: Lexus stock setup for all engine and exhaust except SRT intake with piggyback ECU
Test preparation: 20 minute commute to the E-Test shop at highway speeds
Date: July 21 2010 @ ~8am

ASM2525 Test readings (limit) _ RESULT: @ 2650 RPM
– HC ppm 6 (58) _ PASS
– CO% 0.00 (0.32) _ PASS
– NO ppm 0 (435) _ PASS

Curb Idle Test readings (limit) _ RESULT: @ 800 RPM
– HC ppm 5 (150) _ PASS
– CO% 0.00 (0.70) _ PASS

**************************
“After” Configuration: Megan racing exhaust manifold, Vibrant Performance (p/n 7001) high-flow ceramic core cat installed ~6″ downstream of y-pipe/header connection (custom flange and merge collector on the y-pipe, two O2 bungs post-cat, welded to stock mid-pipe).
Test preparation: 20 minute commute to the E-Test shop at highway speeds
Date: July 30 2010 @ ~8am

ASM2525 Test readings (limit) _ RESULT: @ 2700 RPM
– HC ppm 17 (58) _ PASS
– CO% 0.04 (0.32) _ PASS
– NO ppm 64 (435) _ PASS

Curb Idle Test readings (limit) _ RESULT: @ 830 RPM
– HC ppm 17 (150) _ PASS
– CO% 0.01 (0.70) _ PASS
– NO 75 (N/A) _ N/A

**************************

I also had an extra bung welded into the the Megan header and have installed a wideband oxygen sensor: the Innovate Motorsports LC-1. I’ve been monitoring air-fuel-ratios for the past 2 weeks while going about my normal driving. As expected the vehicle operates at 14.7:1 under closed loop. Below I’ve posted data collected through 2JZduino during a full-throttle pull in 2nd gear right up to the throttle limiter to observe the fuel mixture of the stock ECU in open-loop (pardon the slow data rate… code was a bit of a hack).

This log begins at part throttle rolling in 2nd gear at ~700 rpm. The MAP climbing to 100 KPa (1 atmosphere) identifies the throttle going wide open. At ~6400 RPM the stock ECU closes the throttle to limit engine RPM. Note the stock ECU seems to maintain quite a rich mixture throughout all RPMs, never going above 12.5:1 and reaching as low as 10.7:1 at 5000 RPM.

Crank Signal Input Detection Logic 2010/07/02

Posted by Michael in 2JZduino.
6 comments

Accurately reproducing or delaying the crank signal relies on accurate detection of the incoming crank signal. Through testing I’ve determined the signals arriving from the crank VR sensor range from as little as ~0.8V outputs peak (during engine start) up to +30v peak at high RPMs.

Unfortunately at low speeds the signal peak is not sufficiently high to trigger the Arduino digital inputs to TRUE. And so the input signal is connected to both a digital input and analog input. The mode that it operates in is dependent on the engine speed. At slower engine speeds the signal is detected using the ADC that is on-board Arduino. At slow speeds the crank signals arrive slowly enough that the ADC can capture them adequate resolution. At higher engine speeds the logic relies on digital input interrupts to accurately capture the crank signal.

The input circuit for the VR sensor consists of a Schottky diode (1N5817) and Zener diode (1N4733A) both connected between signal (cathode) and ground (anode). The Schottky diode provides protection for the Arduino inputs when the VR sensor goes below -0.5V, and the Zener diode provides protection for the Arduino inputs when the VR signal rises above 5.1 V. A schematic of the input circuit is shown below.

The crank input signal is connected to pin 49: Timer4 Input Capture Pin. For digital input mode this provides a method to capture the precise Timer4 value in the Input Capture Register (ICR) when the event occurred (which may not be the same time that the interrupt gets serviced).

Timer4 input capture events can be either rising or falling edge. In either case, the response is similar. 2JZduino either reproduces the signal immediately, or stuffs it in a to be reproduced later. The decision is based on the Manifold Air Pressure at the time of the event. The amount of delay is stored in EEPROM as a 25-element byte-array. Each byte stores the delay in 10ths of a degree for Manifold Air Pressures ranging from 80-180KPa (in 4KPa increments). At startup this data is converted into a two-dimensional lookup table (30×25) of unsigned integers. This pre-calculated table stores the delay in # of timer counts for a 2-dimensional lookup of Manifold Air Pressures and Engine speed measured in Hz. The conversion is done upfront to prevent expensive math operations during processing of the input signals.

The calculation between 10ths of a degree and timer counts is shown here…

  for (int i=0; i < RPMIntervals_LookupTable; i++) {  // calculate the Timing Retard table
    for (int j=0; j < BoostPressureIntervals_LookupTable; j++) {
      TimingRetardTable[i][j] = (unsigned int)((556/HzInterval) * EEPROM.read(TimingRetard_EEPROMadr + j) / (i +1) );
    }
  }

Constants RPMIntervals_LookupTable = 30, BoostPressureIntervals_LookupTable = 25, and HzInterval = 4. Note that Timer4 is configured to run at 1/8th of the system clock (2MHz). The # of timer ticks for timing delay is calculated as…
Timer_ticks = 2*10^6 [ticks/second] / 360 [deg/rev] * Delay [deg] / EngineHz [seconds/rev]

Since Delay is stored in EEPROM in 10ths of degrees the first 3 terms simplify to the constant 556 (2*10^6 / 360 / 10). Also, EngineHz is stored in the table in buckets of 4Hz so the value of i is multiplied by HzInterval (=4). The result is the calculation shown above which has been arranged to guarantee that there is never an overflow in any of the calculations.

With that explained, here is the interrupt handler for a Crank signal event…

ISR(TIMER4_CAPT_vect)  // use Timer4 InputCapture interrupt with Noise Cancellation for CRANK signal detection
{
  unsigned int InterruptTime = ICR4;
  
  if (TCCR4B & B01000000) { // Rising Edge events caught by Input Capture
    if (CrankFilter_Abort(true, InterruptTime)) return;  // don't process this Rising Edge if the CrankFilter rejects it
    TCCR4B &= B10111111;  // switch to falling edge detection
    if (TCNT4 - InterruptTime + MIN_OCR_OFFSET > TimingRetardTable[EngineHzIndex][MAPindex]) h_ExecuteCRANKON_Event();  // execute immediately if it is already/almost late
    else if (TIMSK4 & B00000010) { // OCR4A is already scheduled
      if (!CrankONQueueFull) { // use the CrankONQueue if it's available
        CrankONQueueFull = true;
        CrankONQueueTime = InterruptTime + TimingRetardTable[EngineHzIndex][MAPindex];
      }
      else {
        GenerateErrorMessage("Crank ON collision!   ");  // check for OCR4A over-run
        CrankONQueueFull = false;  // reset the queue
      }
    }
    else ScheduleOCR4A(InterruptTime + TimingRetardTable[EngineHzIndex][MAPindex]);
  }
  
  else {
    if (CrankFilter_Abort(false, InterruptTime)) return;  // don't process this Falling Edge if the CrankFilter rejects it
    TCCR4B |= B01000000;  // switch to rising edge detection
    if (TCNT4 - InterruptTime + MIN_OCR_OFFSET > TimingRetardTable[EngineHzIndex][MAPindex]) h_ExecuteCRANKOFF_Event();  // execute immediately if it is already/almost late
    else if (TIMSK4 & B00000100) { // OCR4B is already scheduled
      if (!CrankOFFQueueFull) { // use the CrankOFFQueue if it's available
        CrankOFFQueueFull = true;
        CrankOFFQueueTime = InterruptTime + TimingRetardTable[EngineHzIndex][MAPindex];
      }
      else {
        GenerateErrorMessage("Crank OFF collision!   ");  // check for OCR4B over-run
        CrankOFFQueueFull = false;  // reset the queue
      }
    }
    else ScheduleOCR4B(InterruptTime + TimingRetardTable[EngineHzIndex][MAPindex]);
  }
}

First, the input signal must receive a value of TRUE from the CrankFilter_Abort function. This function is a noise filter. It stores the last rising->falling pulse length. If less than 1/4 of this pulse length has elapsed since the previous acceptable pulse, then the current pulse is rejected. The code is shown below…

boolean CrankFilter_Abort(boolean IsRising, unsigned int EventTime)
{
  static unsigned int T_LastPulse = 0;
  static unsigned int T_LastEdge = 0;
  	
  if ((EventTime - T_LastEdge) < (T_LastPulse >> 2)) return true; // abort if this signal is too near the last one
  if (!IsRising) T_LastPulse = EventTime - T_LastEdge;
  T_LastEdge = EventTime;
  return false;  // this signal is ok to process
}

Next, the signal delay is evaluated against MIN_OCR_OFFSET. If the current time (TCNT4) is almost the same as the delayed signal event time then it gets executed immediately. This prevents accidentally scheduling an event for the past (which would manifests itself as a long delay, executing finally after Timer4 rolls-over and wraps around).

The last option is for the event to be scheduled for future execution. This either gets scheduled directly in the Output Compare Register, or queued if the Output Compare Register is already in use (which occurs when the timing is delayed by more than the distance between teeth on the crank reluctor wheel). Event scheduling and signal reproduction logic (h_ExecuteCRANKON_Event) will be covered in a future post. If there is no room to schedule this event a Collision Error is reported to the LCD.

For Analog operating mode the crank sensor signal is read repeatedly by the ADC. Here, the logic looks for the Crank signal level to be either above or below a threshold, and changes the output level if required. The ON threshold varies between 30 (~0.6V) when the Engine is stopped, and 60 (~1.2V) when the engine is at normal idle speed. The actual value depends on an average of recent Peak Crank signal values. No timing delay logic is performed when in the analog operating mode.

    byte OFFthreshold = 10;
    byte ONthreshold = 30;
    if (EngineHz <= ENGINEHZ_MINIDLE) ONthreshold = 30;  // reset threshold to 30 at slow engine speeds (for startup)
    else if (ONthreshold < 60) ONthreshold = min(60, PeakCrankADC);  // ramp threshold up to 60 once it starts

    Crank_ADCvalue = ADCH;  // store the Left Adjusted ADC High Byte value
    if (Crank_ADCvalue > peakCranklocal) peakCranklocal = Crank_ADCvalue;
    
    if (!(*CamCrankOutputPinReg & CrankOutputBitMask) && Crank_ADCvalue >= ONthreshold) {
      if (!CrankFilter_Abort(true, TCNT4)) h_ExecuteCRANKON_Event();  // process this Rising Edge only if the CrankFilter accepts it
    }
    else if ((*CamCrankOutputPinReg & CrankOutputBitMask) && Crank_ADCvalue <= OFFthreshold) {
      if (!CrankFilter_Abort(false, TCNT4)) { // process this Falling Edge only if the CrankFilter accepts it
        h_ExecuteCRANKOFF_Event();

        static byte counter = 0;
        static unsigned int PeakSum = 0;
    
        PeakSum += peakCranklocal;
        peakCranklocal = 0;
        counter++;
        
        if (counter == 64) { // PeakCrankADC value is an average of 64 readings
          PeakCrankADC = byte(PeakSum >> 6);  // divide by 64
          PeakSum = 0;
          counter = 0;
        }
      }
    }

(new) Engine Speed Measurement 2010/06/06

Posted by Michael in 2JZduino.
add a comment

I recently removed the cam signal processing from the design of 2JZduino on the grounds that it isn’t required to effect moderate ignition timing retard, and also because there remained an unsolved problem.

As a result I needed an alternate method to measure engine speed (originally I was using every third cam sensor signal to count 1 revolution).

I’ve taken a slightly different approach. The crank sensor provides a higher resolution measurement of total rotation. Rather than count the time for one revolution the new method calculates the total rotation for a fixed time interval. See relevant code below…

void setup()
{
  TCCR4B = B11000010;  // Input Capture Noise Canceler = ON, Input Capture Edge Select = RISING, Prescaler = clk/8
  TIMSK4 |= B00001000;  // enable Timer4 Output Compare Match C
}

ISR(TIMER4_COMPC_vect) { // Event every ~30ms for RPM calculation
{
  OCR4C += 59000; // next Interrupt in +29.5ms
  static byte iteration = 1;
  
  if (iteration >= 4) { // calculate engine speed every 29.5*4 = 118ms
    EngineHz = CrankSignalCounter >> 2; // Hz ~= RisingCrankSignals/4 (for a 118ms period)
    EngineHzIndex = constrain(EngineHz >> 2, 0, RPMIntervals_LookupTable-1);  // index: 0->30 = Hz: 0-120
    iteration = 1;
    CrankSignalCounter = 0;
  }
  else iteration++;
}

In the above, the Timer4 Output Capture C register fires every 59000 counts or 29.5ms (prescaler = 8), and the engine Hz is calculated on every 4th iteration of the interrupt. Thus EngineHz is calculated every 118ms. A sufficiently long duration is used so that at idle at least one full revolution occurs between calculations to minimize the potential error attributed to the 2 absent signals in the crank wheel (34 teeth spaced 10 degrees apart + 2 missing teeth).

The number of rotations that occurred since the beginning of the 118ms interval is CrankSignalCounter/34. The frequency is then calculated as rotations/period. 34 * 0.118 simplifies to 4.0 and the result is the efficient calculation…
EngineHz = CrankSignalCounter / 4

0.1 Alpha (first public release) 2010/06/03

Posted by Michael in 2JZduino.
11 comments

For a few months now I’ve been struggling with the cam sensor signal when the engine is at idle speeds (300 – 900 RPM). At low speed, the signal strength of the cam VR sensor is too low to be register as a digital input (it’s peaks are sometimes < 2v when the engine is turning at 600 RPM). The same thing happens with the crank sensor.

The solution I've implemented is to switch over to an ADC mode when the engine speed is below ENGINEHZ_ADCMODESWITCH. In this "ADC mode" the ADC samples the cam and crank signals and switches the digital outputs depending on the values.

This never worked reliably despite implementing a number of trigger and filter algorithms for the on/off events. My latest theory is that the cam output signal from the 2JZduino was settling too close to 0v between incoming pulses (i.e. the inductor had completely discharged), and electrical noise was causing false triggers at the input of the stock ECU.

For now, I've stripped out the cam sensor signal from 2JZduino (the sensor now connects directly to the stock ECU), so that it is no longer possible to delay it. More thought and research needs to go into the permanence of this decision, but I suspect this will hold. It will not have an affect on delayed ignition timing. It might have an effect on the operation of VVT-i, however this affect might be favourable for a forced induction application.

Nevertheless, after removing the cam signal DSP I've finally been able to operate my 2JZ-GE engine without issue between 300 and 5000 RPM. And so with one of my few readers requesting a preview of the full code package, I've decided finally to make an Alpha release (v0.1)…

https://sourceforge.net/projects/twojzduino/

The download package contains both the code for the Arduino environment (compiles for the Arduino Mega), and also the Windows-based .exe to write values to the 2JZduino EEPROM (for injector scaling and crank signal delay).

Features in this version include…
LCD display support
Support for Fuel Injector drivers
Support for Crank signal DSP
Engine simulator code for bench-testing

Not included: working code for oxygen sensors.

Obviously, as an Alpha release use this at your own risk. Documentation is incomplete and various sections of the code remain untested.

2JZDuino Pin Assignments 2010/05/31

Posted by Michael in 2JZduino.
add a comment

For anyone interested, I’ve posted below a PDF of the Arduino Mega pins in use for 2JZDuino. Further detailed explanation isn’t warranted just yet, but someone carefully following along may find this useful.
2JZDuino Pin Assignments