jump to navigation

Programmable Closed-Loop AFR Control 2010/08/24

Posted by Michael in 2JZduino.

[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.



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 )

Google+ photo

You are commenting using your Google+ 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 )


Connecting to %s

%d bloggers like this: