jump to navigation

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
    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)
  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;

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.