Air/Fuel controller for LPG engines

Control the air/fuel mixture for better fuel economy using an Arduino Nano.


Components and Supplies

  1. Bosch Stationary Valve
  2. Lambda Sensor
  3. Arduino Nano
  4. Air/Fuel Ratio Gauge

Project Description

This project focuses on creating a device to regulate the air/fuel mixture for mechanical LPG mixers (OHG mixers) in cars, improving fuel economy by maintaining an optimal air/fuel ratio.

Why Build This Project?

In the past, commercial solutions like the IMPCO fuel controller were available, but these products are no longer on the market. Furthermore, they often lacked precision and responsiveness. This project bridges that gap by introducing a custom air/fuel controller designed for improved efficiency.

How It Works:

  1. Air Bypass Mechanism:
    • The device introduces air after the LPG mixer but before the throttle valve to lean out the mixture.
    • A Bosch stationary valve regulates the bypass air, controlled precisely using an Arduino Nano.
  2. Sensor Integration:
    • A narrowband lambda sensor in the exhaust monitors air/fuel ratios.
    • The sensor’s output (0–1000 mV) is fed into the Arduino Nano via an analog input pin with a 1M-ohm resistor to reduce noise.
  3. Control Mechanism:
    • Two FETs control the Bosch valve’s coils, with opposite PWM signals from the Arduino.
    • A custom library (GYVER) is used to set the PWM frequency at 8000 Hz, eliminating audible noise.
  4. Signal Processing:
    • Short-term (500ms) and long-term (10s) averages stabilize the fluctuating lambda sensor readings.
    • A selector switch allows the user to view parameters like valve movement and average air/fuel ratios during operation.

RPM Dependency:

The response time adjusts based on engine RPM, ranging from 400ms at low RPM to a minimum of 100ms at higher RPMs (above 2400). While the system works effectively up to 3000 RPM, it is not designed for wide-open throttle (WOT) conditions.


Applications:

Although this device is built for a 512cu Dodge engine running on LPG, it has potential applications for other engines, including gasoline carburetors, provided there is an appropriate air inlet placement.

Limitations:

  • The effectiveness decreases with increasing engine RPM beyond the valve’s capacity.
  • Works best when the base air/fuel mixture is already close to optimal.

<pre><code>
// o2 valve experiment by R.Bos 30-6-2024
//
#include <GyverPWM.h>


const int pwm1 = 9;//5;//pwm output to open valve
const int pwm2 = 10;//6;//pwm output to close valve
const int pot1 = A2;//potmeter 0V---1V
const int sensor = A0;//O2 sensor
const int hall_1 = 2;//hall sensor for rpm
const int ledPin = A5;//LED_BUILTIN;
const int dpswitch = 7;
const int offswitch = 8;//A4;
const int meter = 11;
const int rly = 12;
int valpot = 0;int valpotold = 0;int valpot1 = 0;
int prcold = 0; int diff = 0;
int prc = 0;
int valve1 = 0 ;int valve2 = 64;
int i; int sft = 0;//shift O2 to lower side
int wait = 0;
unsigned long timeoff;
unsigned long timeoffold;
unsigned long timeloop;
unsigned long timeloopold;
unsigned long print1time;
////////// for rpmcounter /////////////////////
unsigned long rpm = 0;
unsigned long revtime = 0;
unsigned long revtimeold = 0;
volatile unsigned long pulses = 0;
volatile unsigned long rpm1 = 0;
unsigned int pulses1 = 0;
unsigned int d1 = 16;// number of pulses before time taken (timerpm)
float revtime1 = 0;//if d1 = 16 , time is for 4 revolutions
///////////// for o2 ////////////////////////////////
unsigned long O2;
int mV10[11];// 10*50mS = 500mS
int mV200[21];//20*500mS = 10 sec
unsigned long mV1;
unsigned long mV2;
unsigned long mV1ava;
unsigned long mV2ava;
unsigned long mV1old;
unsigned long mV2old;
int bb = 0; int bbb = 0;
int Vin = 5000;//4800mV supply voltage , needed for reference , normal 5000mV , laptop only 4740mV
unsigned long waittime;
//////////////// for switch and led ///////////////////////////////////////////////
int ledState = LOW;
unsigned long blinktime;
int interval = 1500;
int ll; int dp = 1;int dpx;
int dpoff = 1;
unsigned long valout;
int valin;
//////////////////////////////////////////////////////////////////////////////////

void setup()//////////////////////////////////////////////////////////////////////
{
Serial.begin(9600);
pinMode(pwm1,OUTPUT);
pinMode(pwm2,OUTPUT);
pinMode(hall_1,INPUT);
pinMode(ledPin,OUTPUT);
pinMode(dpswitch,INPUT_PULLUP);
pinMode(offswitch,INPUT_PULLUP);
pinMode(meter,OUTPUT);
pinMode(rly,OUTPUT);
pinMode(pot1,INPUT_PULLUP);
attachInterrupt(0, rpmcounter, FALLING);
timeloop = millis();
PWM_frequency(9, 8000, FAST_PWM);
PWM_frequency(10, 8000, FAST_PWM);
PWM_set(pwm1,0);
PWM_set(pwm2,64);
digitalWrite(rly,1);
//analogWrite(pwm1,0); analogWrite(pwm2,64);

}

void print1()///////////////////////////////////////////////////////////////////
////////for coding and debugging only///////////////////////////////////////////
{
//mV =(( O2 * 473) / 1024) * 10;
//mV =( O2 * 4730) / 1024);
if(millis() - print1time >= 500){
//Serial.print("mV1 =");Serial.print(mV1);
//Serial.print(" mV2 =");Serial.println(mV2);
Serial.print("rpm = ");Serial.println(rpm);
//Serial.print(" revtime =");Serial.println(revtime);
//Serial.print(" waittime =");Serial.println(waittime);
//Serial.print("valin =");Serial.print(valin);
//Serial.print(" valout =");Serial.println(valout);
//Serial.print("dp =");Serial.println(dp);
//Serial.print("prc =");Serial.print(prc);
//Serial.print("millivolt =");Serial.println(mV);
//Serial.print("valve1 = ");Serial.print(valve1);
//Serial.print(" valve2 = ");Serial.println(valve2);
//Serial.print("timeloopold =");Serial.println(timeloopold);
//Serial.print("revtime =");Serial.println(revtime);
print1time = millis();
}
}

void loop()/////////////////////////////////////////////////////////////////////
{
rpm = rpm1; revtime = revtime1;// / (d1 / 16);// reactiontime is 4*4 pulses = 4 cycles
if(rpm == 0 ){revtime = 400;}
if(revtime < 100){revtime = 100;}//without Rpm input could be set at a fixed timing between 100 and 400ms
getO2valu();
//waittimeold = millis() - waittime;
if(millis() - waittime >= revtime){
if(rpm < 750){stationair();} else {
if(mV2 > 520){sft = 30;} else {
if(mV2 < 490){sft = 0;}}
if(mV1 > 500){rich3();}
if(mV1 < 500){lean3();}}
mV1old = mV1; mV2old = mV2;}
//valpot = analogRead(pot1);
//prc = map(valpot,0,202,0,100);
if(digitalRead(offswitch) == 0 || dpoff == 0){digitalWrite(rly,0);if(prcold != 0){prc = prc - 30;} else {prc = 0;}}
if(digitalRead(offswitch) != 0 || dpoff == 1){digitalWrite(rly,1);}
if(rpm < 400){if(prcold != 0){prc = prc - 30;} else {prc = 0;}}
prc = constrain(prc,0,100);
if(prc < prcold){diff = prcold - prc; valveclose();waittime = millis();}
if(prc > prcold){diff = prc - prcold; valveopen();waittime = millis();}
if(valve2 < 195 && valve1 < 195){wait = 0;timeoff = 0;} else{valveoff();}
Select2();
//if(dp == 1){valout = map(valin,0,234,0,285) ;}
if(dp == 1){valout = (valin * 5) / 4 ;} //input O2 sensor = output
if(dp == 2){valout = (((mV1 * 1024) / Vin) * 5 ) / 4;}//shortterm average output
if(dp == 3){valout = (((mV2 * 1024) / Vin) * 5 ) / 4;}//longterm average output
if(dp == 4){valout = map(prc,0,100,13,255);} //valve position output
valout = constrain(valout,0,255);analogWrite(meter,valout);//gauge output only
//print1();//coding and debugging only , it will disturb ISR of hall sensor input
led1();
timeloopold = millis() - timeloop;
if(timeloopold < 50){delay(50 - timeloopold);}//looptime fixed at 50ms
timeloop = millis();

}
void rich3()/////////////////////////////////////////////////////////////////////
{
if(mV1 < mV1old && mV1 >= 600){prc = prc - 5;} else {
if(mV1 < mV1old && mV1 < 600){prc = prc - 1;} else {
if(mV1 > 550 - sft){prcmin();prc = prc + 1;}
if(mV1 > 600 - sft){prc = prc + 1;}
if(mV1 > 650 - sft){prc = prc + 1;}
if(mV1 > 700 - sft){prc = prc + 2;}
if(mV1 > 750 - sft){prc = prc + 3;}}}
if(mV2 < mV2old && mV2 >= 600){prc = prc - 2;} else {
if(mV2 > 550){prc = prc + 1;}
if(mV2 > 600){prc = prc + 1;}
if(mV2 > 650){prc = prc + 1;}
if(mV2 > 700){prc = prc + 2;}
if(mV2 > 750){prc = prc + 3;}}
}
void lean3()/////////////////////////////////////////////////////////////////////
{
//if(mV1 > mV1old && mV1 > 300){prc = prc + 1;} else {
if(mV1 < 450){prcmax();prc = prc - 1;}
if(mV1 < 400){prc = prc - 1;} //-1
if(mV1 < 350){prc = prc - 2;} //-1
if(mV1 < 300){prc = prc - 3;} //-2
if(mV1 < 250){prc = prc - 8;} //-3
if(mV1 < 200){prc = prc = 0;}
//if(mV2 < 450){prc = prc - 1;} //-1
if(mV2 < 400){prc = prc - 2;} //-1
if(mV2 < 350){prc = prc - 2;} //-1
if(mV2 < 300){prc = prc - 2;} //-2
if(mV2 < 250){prc = prc - 3;} //-3
if(mV2 < 200){prc = prc = 0;}
}
void prcmax()////////////////////////////////////////////////////////////////////
{
if(prcold==100){if(rpm >= 2500){prc = 40;} else{
if(rpm >= 1500 && rpm < 2500){prc = 50;}else{
if(rpm < 1500){prc = 60;}}}}
}
void prcmin()//////////////////////////////////////////////////////////////////////
{
if(prcold == 0){if(rpm >= 2500){prc = 40;} else{
if(rpm >= 1500 && rpm < 2500){prc = 30;}else{
if(rpm < 1500){prc = 20;}}}}
}
void stationair()//////////////////////////////////////////////////////////////////
{
if(mV1 > 800){prcmin();prc = prc + 1;}
//if(mV1 > 750){prc = prc + 2;}
//if(mV1 > 800){prc = prc + 3;}
if(mV1 > 850){prc = prc + 2;}
//if(mV2 > 700){prc = prc + 2;}
//if(mV2 > 750){prc = prc + 2;}
if(mV2 > 800){prc = prc + 1;}
if(mV2 > 850){prc = prc + 2;} else{
if(mV1 < 750){prcmax();prc = prc - 2;}
if(mV1 < 700){prc = prc - 2;}
if(mV1 < 650){prc = prc - 10;}
//if(mV1 < 600){prc = prc - 4;}
if(mV2 < 750){prc = prc - 2;}
if(mV2 < 700){prc = prc - 2;}
if(mV2 < 650){prc = 0;}
//if(mV2 < 600){prc = prc - 4;}
}
if(prc > 60){prc = 60;}

}

void getO2valu()////////////////////////////////////////////////////////////////
{
O2 = analogRead(sensor); valpot = O2; valin = O2; O2 = ( O2 * Vin) / 1024;
//O2 = map(O2,0,1024,0,Vin);
bb++;
mV1ava =(mV1ava + O2) - mV10[bb];
mV1 = mV1ava / 10; //
mV10[bb] = O2;
if(bb >= 10){bb = 0;bbb++;
mV2ava =(mV2ava + mV1) - mV200[bbb];
mV2 = mV2ava / 20;
mV200[bbb] = mV1;
if(bbb >= 20){bbb = 0;}
}
}

void valveclose()//////////////////////////////////////////////////////////////
{
for(i = 0; i < (diff + 1);i++)
{valve1 = map(prcold,0,100,60,195);valve1 = constrain(valve1,0,255);
valve2 = map(prcold,0,100,195,60);valve2 = constrain(valve2,0,255);
PWM_set(pwm1,valve1);PWM_set(pwm2,valve2);
//analogWrite(pwm2,valve2);analogWrite(pwm1,valve1);
prcold--;}
//delay(1);}
prcold = prc;
}
void valveopen()///////////////////////////////////////////////////////////////
{
for(i = 0; i < (diff + 1);i++)
{valve1 = map(prcold,0,100,60,195);valve1 = constrain(valve1,0,255);
valve2 = map(prcold,0,100,195,60);valve2 = constrain(valve2,0,255);
PWM_set(pwm1,valve1);PWM_set(pwm2,valve2);
//analogWrite(pwm1,valve1); analogWrite(pwm2,valve2);
prcold++;}
//delay(1);}
prcold = prc;
}
void valveoff()////////////////////////////////////////////////////////
{
if(valve2 == 195 && wait == 0){timeoffold = millis();wait = 1;}
if(valve2 == 195 && timeoff >= 1000){wait = 0;timeoff = 0;
valve1 = 0;valve2 = 64;
PWM_set(pwm1,valve1);PWM_set(pwm2,valve2);}
//analogWrite(pwm1,valve1); delay(3);analogWrite(pwm2,valve2);}//valve close
if(valve1 == 195 && wait == 0){timeoffold = millis();wait = 1;}
if(valve1 == 195 && timeoff >= 1000){wait = 0;timeoff = 0;
valve1 = 64;valve2 = 0;
PWM_set(pwm1,valve1);PWM_set(pwm2,valve2);}
//analogWrite(pwm2,valve2); delay(3);analogWrite(pwm1,valve1);}//valve open
if(wait == 1){timeoff = millis() - timeoffold;}

}
void rpmcounter()//////////////////////////////////////////////////////
{
pulses++;
if(pulses >= d1){
revtime1 = (millis() - revtimeold);
revtimeold = millis();
//pulses1 = pulses;
pulses = 0;
rpm1 = (1000.0/revtime1)*60.0;
rpm1 = rpm1 * (d1/4);
//d1time();
}
}
void d1time()/////////////////////////////////
//not used in this code , was to stabilize rpm output on oled display
{
if(rpm1 < 400){d1 = 16;}
if(rpm1 >= 400){d1 = 16;}
if(rpm1 >= 1000){d1 = 32;}
if(rpm1 >= 2000){d1 = 48;}
if(rpm1 >= 3000){d1 = 64;}
}
void Select()//////////////////////////////////////////////////////////
{
if(digitalRead(dpswitch) == 0 && dpx >= 1){delay(10);dpx = 2;}
if(digitalRead(dpswitch) == 0 && dpx == 0){delay(10);
dp++; dpx++;
if(dp > 4){dp = 1;}
}
if(digitalRead(dpswitch) != 0 && dpx != 0){
dpx = 0;}
}
void Select2()/////////////////////////////////////////////////////////
{
valpot = analogRead(pot1);
if (valpot < 99) { // groud 0 ohm
dp = 1; dpoff = 1;
}
if (valpot > 110 && valpot < 150) { // resistor 4K7 = 130
dp = 2; dpoff = 1;
}
if (valpot > 320 && valpot < 360) { // resistor 18K = 340
dp = 3; dpoff = 1;
}
if (valpot > 545 && valpot < 585) { // resistor 47K = 565
dp = 4; dpoff = 1;
}
if (valpot > 715 && valpot < 755) { // resistor 100K = 735
dp = 4; dpoff = 0;
}
if (valpot > 995) { // open , pullup only = 1015
dp = 1; dpoff = 0;
}
}
void led1()////////////////////////////////////////////////////////////
{
if(dpoff == 0){digitalWrite(ledPin,0);} else {
if(millis() - blinktime >= interval){blinktime = millis();
if(ledState == LOW){ledState = HIGH; interval = 150;} else{
ledState = LOW; interval = 300;ll++;}
digitalWrite(ledPin,ledState);}
if(ll >= dp){ll = 0;interval = 2000;}}

}
</code></pre>

Leave a Reply