EcoModding Lurker
Join Date: May 2008
Location: Central New Mexico
Posts: 18
Bun - '02 Ford Focus ZTS 90 day: 31.3 mpg (US)
Thanks: 0
Thanked 0 Times in 0 Posts
|
Enjoy your break, dcb. I'm really impatient though. I couldn't wait for you to finish your break so I wrote number 2 myself. It seems to provide more stable reading over some conditions like coasting in neutral, othertimes I'm not sure if the reading are indeed better. Maybe I need to build 2 guinos so I can compare head to head.
Strangely, Yoshi's code suggestion seems to shrink the program from 7764 bytes to 7548 bytes. ( 6914 bytes to 6788 bytes in the version on page one )
I'm now testing in a 2002 Ford Focus. I use 15500 pulses/mile (this the doubled value) and 351562500 uS per gallon. (calculated from 4 injectors of 16lb) The readings seem quite reasonable.
EDIT:
Quote:
Originally Posted by diesel_john
The very conservative approach Ford uses to calculate the injector size for the factory engine uses the O.E. typically safe 0.80 duty cycle limit.
So a 19 lb/hr Ford rating is at 80% duty cycle.
|
This would mean that my 16lb injectors are actually 20lb injectors for calculation. The uS per gallon I used was inflating the displayed milage by 25%
Update 5-24-08
I calculated that I should actually use 281250000 uS per gallon. I tested on the interstate where tests are more repeatable. I know from my fuel log that the car gets 37mpg @ 70mph and 30 mpg @ 80mph approximately. This revised calibration is very close. Mileage does vary, so I assume this is accurate data. My previous calculation was suspiciously high.
On the Caravan, I connected the wrong side of the injector due to an incorrect wiring diagram. I did actually observe a pulse when the injector turned on, but the guino would not measure the duty cycle correctly. I calibrated the fuel consumption to be reasonable, but the uS per gallon is incorrect.
Injector data : http://forums.focaljet.com/zetec-tun...low-rates.html
Ecu pinout : http://www.focusfanatics.com/forum/s...d.php?t=159134
Code:
//CHANGE THESE VALUES FOR YOUR CAR/TASTE
#define vssPulsesPerMile 15500ul
#define microSecondsPerGallon 351562500ul //injector flow rate
//#define microSecondsPerGallon 372000000ul //injector flow rate
//#define microSecondsPerGallon 2070744000ul //injector flow rate
//258843000
#define numberOfCylinders 4
#define numberOfInjectors 4
#define rotationPerInjection 720 // degrees
#define tankSize 13300 //in 1000ths of a gallon, this is 13.3 gallons
byte brightness[]={0,85,170,255}; //middle button cycles through these brightness settings
#define brightnessLength (sizeof(brightness)/sizeof(byte)) //array size
byte brightnessIdx=2;
#define contrast 15
#define currentTripResetTimeout 15 //minutes
#define injectorSettleTime 15 //microseconds
//DONT CHANGE ANYTHING BELOW THIS LINE
//(well, change at your own risk anyway)
//Vehicle Interface Pins
#define InjectorOpenPin 2
#define InjectorClosedPin 3
#define VSSPin 14 //analog 0
//LCD Pins
#define DIPin 4 // register select RS
#define DB4Pin 7
#define DB5Pin 8
#define DB6Pin 12
#define DB7Pin 13
#define ContrastPin 6
#define EnablePin 5
#define BrightnessPin 9
#define lbuttonPin 17 // Left Button, on analog 3,
#define mbuttonPin 18 // Middle Button, on analog 4
#define rbuttonPin 19 // Right Button, on analog 5
#define vssBit 1 // pin14 is a bitmask 1 on port C
#define lbuttonBit 8 // pin17 is a bitmask 8 on port C
#define mbuttonBit 16 // pin18 is a bitmask 16 on port C
#define rbuttonBit 32 // pin19 is a bitmask 32 on port C
#define injTimeOutus 1500000ul // if injector is "open" this long then the car is off
#define backLightTimeOutus 20000000ul //time out before turning off backlight
#define loopsPerSecond 2 // how many times will we try and loop in a second
unsigned long maxLoopLength = 0; //see if we are overutilizing the CPU
#define buttonsUp lbuttonBit + mbuttonBit + rbuttonBit // start with the buttons in the right state
byte buttonState = buttonsUp;
//overflow counter used by millis()
extern volatile unsigned long timer0_overflow_count;
unsigned long microSeconds (void){
unsigned long tmp_timer0_overflow_count;
byte tmp_tcnt0;
cli(); //disable interrupts
tmp_timer0_overflow_count = timer0_overflow_count;
tmp_tcnt0 = TCNT0;
sei(); // enable interrupts
return ((tmp_timer0_overflow_count << 8) + tmp_tcnt0) * 4;
}
unsigned long elapsedMicroseconds(unsigned long startMicroSeconds ){
unsigned long msec = microSeconds();
if(msec >= startMicroSeconds)
return msec-startMicroSeconds;
return 4294967295 - (startMicroSeconds-msec);
}
//Trip prototype
class Trip{
public:
unsigned long loopCount; //how long has this trip been running
unsigned long injPulses; //rpm
unsigned long injHiSec;// seconds the injector has been open
unsigned long injHius;// microseconds, fractional part of the injectors open
unsigned long vssPulses;//from the speedo
//these functions actually return in thousandths,
unsigned long miles();
unsigned long gallons();
unsigned long mpg();
unsigned long mph();
void loadNextDummyTrip();
unsigned long time(); //mmm.ss
void update(Trip t);
void reset();
Trip();
};
//LCD prototype
class LCD{
public:
LCD( ) ;
void gotoXY(byte x, byte y);
void print(char * string);
void init();
void tickleEnable();
void cmdWriteSet();
void LcdCommandWrite(byte value);
void LcdDataWrite(byte value);
byte pushNibble(byte value);
};
//main objects we will be working with:
volatile boolean running=true; //keep track if we are running or not
volatile boolean injectorOpen=true; //keep track if we are running or not
volatile unsigned long injHiStart; //for timing injector pulses
volatile unsigned long injLowStart; //for measuring injector duty
volatile unsigned long injOnTime; //on time for instant consumption
volatile unsigned long injCycleTime; //total time for instant consumption, rpm
volatile unsigned long vssHiStart; //for instant mph
volatile unsigned long vssCycleTime; //for instant mph
LCD lcd;
Trip tmpTrip;
Trip instant;
Trip current;
Trip tank;
void processInjOpen(void){
injHiStart = microSeconds();
injectorOpen=true;
}
void processInjClosed(void){
injOnTime = elapsedMicroseconds(injHiStart);
injCycleTime = elapsedMicroseconds(injLowStart);
injLowStart = microSeconds();
if(running==true){
tmpTrip.injHius += injOnTime;
tmpTrip.injPulses++;
}else
running=true;
injectorOpen=false;
}
//attach the vss/buttons interrupt
ISR( PCINT1_vect ){
static byte vsspinstate=0;
byte p = PINC;//bypassing digitalRead for interrupt performance
if ((p & vssBit) != (vsspinstate & vssBit)){
tmpTrip.vssPulses++;
if( p&vssBit )// compare only rising edges
{ // I wouldn't expect the vss to give exactly 50% duty cycle
vssCycleTime = elapsedMicroseconds(vssHiStart);
vssHiStart = microSeconds();
}
}
vsspinstate = p;
buttonState &= p;
}
void setup (void){
pinMode(BrightnessPin,OUTPUT);
analogWrite(BrightnessPin,255-brightness[brightnessIdx]);
lcd.init();
pinMode(ContrastPin,OUTPUT);
analogWrite(ContrastPin,contrast);
lcd.print("OpenGauge ");
lcd.gotoXY(0,1);
lcd.print(" MPGuino");
pinMode(InjectorOpenPin, INPUT);
pinMode(InjectorClosedPin, INPUT);
pinMode(VSSPin, INPUT);
attachInterrupt(0, processInjOpen, FALLING);
attachInterrupt(1, processInjClosed, RISING);
pinMode( lbuttonPin, INPUT );
pinMode( mbuttonPin, INPUT );
pinMode( rbuttonPin, INPUT );
//"turn on" the internal pullup resistors
digitalWrite( lbuttonPin, HIGH);
digitalWrite( mbuttonPin, HIGH);
digitalWrite( rbuttonPin, HIGH);
// digitalWrite( VSSPin, HIGH);
//low level interrupt enable stuff
PCICR |= (1 << PCIE1);
PCMSK1 |= (1 << PCINT8);
PCMSK1 |= (1 << PCINT11);
PCMSK1 |= (1 << PCINT12);
PCMSK1 |= (1 << PCINT13);
delay(1500);
}
typedef void (* DisplayFx)(void);//type for display function pointers
byte screen=0;
#define looptime 1000000ul/loopsPerSecond //1/2 second
//void (*displayFunc)(void)=updateDisplay0;
DisplayFx displayFuncs[] ={updateDisplay0,updateDisplay1,updateDisplay2,updateDisplay3,updateDisplay4,updateDisplay5,updateDisplay6,updateDisplay7,updateDisplay8};
char * displayFuncNames[] ={"Main ","Instant ","Current ","Tank ","Instant raw Data ","Current raw Data ","Tank raw Data ","CPU Monitor ", "Pulse Timer "};
#define displayFuncSize (sizeof(displayFuncs)/sizeof(DisplayFx)) //array size
void loop (void){
while(true){
unsigned long loopStart=microSeconds();
instant.reset(); //clear instant
if(running!=false){
instant.update(tmpTrip); //"copy" of tmpTrip in instant now
tmpTrip.reset(); //reset tmpTrip first so we don't lose too many interrupts
current.update(instant); //use instant to update current
tank.update(instant); //use instant to update tank
}
displayFuncs[screen](); //call the appropriate display routine
lcd.gotoXY(0,0);
//see if any buttons were pressed, display a brief message if so
if(!(buttonState&lbuttonBit) && !(buttonState&mbuttonBit)){// left and middle = tank reset
tank.reset();
lcd.print("Tank Reset ");
}else if(!(buttonState&mbuttonBit) && !(buttonState&rbuttonBit)){// right and middle = current reset
current.reset();
lcd.print("Current Reset ");
}else if(!(buttonState&lbuttonBit)){ //left is rotate through screeens to the left
if(screen!=0)
screen=(screen-1);
else
screen=displayFuncSize-1;
lcd.print(displayFuncNames[screen]);
}else if(!(buttonState&mbuttonBit)){ //middle is cycle through brightness settings
brightnessIdx = (brightnessIdx + 1) % brightnessLength;
analogWrite(BrightnessPin,255-brightness[brightnessIdx]);
lcd.print("Brightness ");
lcd.LcdDataWrite('0' + brightnessIdx);
lcd.print(" ");
}else if(!(buttonState&rbuttonBit)){//right is rotate through screeens to the left
screen=(screen+1)%displayFuncSize;
lcd.print(displayFuncNames[screen]);
}
buttonState=buttonsUp;//reset the buttons
//see if the engine is running or not, cars seem to hold the injector low (open) with the key off.
unsigned long loopX=elapsedMicroseconds(injHiStart);
if(injectorOpen==true){
if(loopX > injTimeOutus){
running=false;
tmpTrip.reset();
}
if(loopX > backLightTimeOutus)
analogWrite(BrightnessPin,255-brightness[0]);
}
if(running==true)
analogWrite(BrightnessPin,255-brightness[brightnessIdx]);
//keep track of how long the loops take before we go int waiting.
loopX=elapsedMicroseconds(loopStart);
if(loopX>maxLoopLength) maxLoopLength = loopX;
while (elapsedMicroseconds(loopStart) < (looptime));//wait for the end of a second to arrive
}
}
char fBuff[7];//used by format
//format a number into NNN.NN the number should already be representing thousandths
char* format(unsigned long num){
unsigned long d = 10000;
long t;
byte dp=3;
byte l=6;
//123456 = 123.46
if(num>9999999){
d=100000;
dp=99;
num/=100;
}else if(num>999999){
dp=4;
num/=10;
}
unsigned long val = num/10;
if ((num - (val * 10)) >= 5) //will the first unprinted digit be greater than 4?
val += 1; //round up val
for(byte x = 0; x < l; x++){
if(x==dp) //time to poke in the decimal point?
fBuff[x]='.';
else{
t = val/d;
fBuff[x]= '0' + t%10;//poke the ascii character for the digit.
val-= t*d;
d/=10;
}
}
fBuff[6]= 0; //good old zero terminated strings
return fBuff;
}
void updateDisplay0() {//draw the main screen
lcd.gotoXY(0,0);
lcd.LcdDataWrite(7); //special IN character
lcd.print(format(instant.mpg()));
lcd.LcdDataWrite(0); //half of the M/G symbol
lcd.LcdDataWrite(1); //other half of the M/G symbol
lcd.print(format(current.mpg()));
// lcd.print(format(instant.mph()));
lcd.LcdDataWrite(6); //special CU character
lcd.gotoXY(0,1);
lcd.LcdDataWrite(5); //special MI character
lcd.print(format(tank.miles()));
lcd.LcdDataWrite(2); //half of the TK symbol
lcd.LcdDataWrite(3); //other half of the TK symbol
//lcd.print(format(memoryTest()));
lcd.print(format(tank.gallons()));
lcd.LcdDataWrite(4); //special GA character
}
void updateDisplay1(void){tDisplay(&instant);} //display instant trip formatted data.
void updateDisplay2(void){tDisplay(¤t);} //display current trip formatted data.
void updateDisplay3(void){tDisplay(&tank);} //display tank trip formatted data.
void updateDisplay4(void){rawDisplay(&instant);} //display instant trip "raw" injector and vss data.
void updateDisplay5(void){rawDisplay(¤t);} //display current trip "raw" injector and vss data.
void updateDisplay6(void){rawDisplay(&tank);} //display tank trip "raw" injector and vss data.
void updateDisplay7(void){
lcd.gotoXY(0,0);lcd.print("C%");lcd.print(format(maxLoopLength*1000/(looptime/100)));lcd.print(" T"); lcd.print(format(tank.time()));
unsigned long mem = memoryTest();
mem*=1000;
lcd.gotoXY(0,1);lcd.print("FREE MEM: ");lcd.print(format(mem));
} //display max cpu utilization and ram.
void updateDisplay8(void){
lcd.gotoXY(0,0);
lcd.print("ss");
lcd.print(format(vssCycleTime));
lcd.print("rm"); lcd.print(format(rpm()*1000));
lcd.gotoXY(0,1);
lcd.print("on"); lcd.print(format(injOnTime));
lcd.print("ij"); lcd.print(format(injCycleTime));
} //display max cpu utilization and ram.
//arduino doesn't do well with types defined in a script as parameters, so have to pass as void * and use -> notation.
void tDisplay( void * r){ //display trip functions.
Trip *t = (Trip *)r;
lcd.gotoXY(0,0);lcd.print("MH");lcd.print(format(t->mph()));lcd.print("MG");lcd.print(format(t->mpg()));
lcd.gotoXY(0,1);lcd.print("MI");lcd.print(format(t->miles()));lcd.print("GA");lcd.print(format(t->gallons()));
}
void rawDisplay(void * r){
Trip *t = (Trip *)r;
lcd.gotoXY(0,0);lcd.print("IJ");lcd.print(format(t->injHiSec*1000));lcd.print("uS");lcd.print(format(t->injHius*1000));
lcd.gotoXY(0,1);lcd.print("IC");lcd.print(format(t->injPulses*1000));lcd.print("VC");lcd.print(format(t->vssPulses*1000));
}
Trip::Trip(){
}
unsigned long Trip::miles(){
return (vssPulses*1000)/vssPulsesPerMile;
}
unsigned long Trip::mph(){
if(loopCount == 0)
return 0;
if( vssPulses == 0 )
return 0;
if( vssPulses < 20 )// slow speed instant
{ // the leading 2 because vssCycleTime is rising to rising,
return 2*360000000/ ((vssCycleTime/100) * (vssPulsesPerMile/100));
}
if(vssPulses<1000)
return loopsPerSecond*((vssPulses*3600000)/(vssPulsesPerMile*loopCount));
return loopsPerSecond*((vssPulses*3600)/(vssPulsesPerMile*loopCount/1000));
}
unsigned long Trip::gallons(){
if(injHiSec<4000) //will overflow here
return ((injHiSec*1000000) + injHius)/(microSecondsPerGallon/1000);
else
return ((injHiSec*1000))/(microSecondsPerGallon/1000000);
}
unsigned long Trip::mpg(){
if(vssPulses==0) return 0;
if(injPulses==0) return 999999000; //who doesn't like to see 999999? :)
unsigned long mi=miles();
unsigned long gal=gallons();
if( injPulses< 50 ) // instant
{
gal = (( (injOnTime*10000)/ (injCycleTime/100) )*1000) / (microSecondsPerGallon/1000);
if( gal==0 ) gal=1;
return (mph()*10000/(36*gal));
}
if( injHiSec<4 ) // prevent overflow
{
gal = ((injHiSec*1000000000) + (injHius*1000))/(microSecondsPerGallon/1000); // 1e-6 gals
if (gal==0) gal=1;//default to a millionth of a gallon so not division by zero
return mi*1000000/gal; // 1e3 mi * 1e6 / 1e-6 gal = 1e3 mpg
}
if (gal==0) gal=1;//default to a thousandth of a gallon so not division by zero
return mi*1000/gal; // 1e3 mi * 1e3 / 1e3 gal = 1e3 mpg
}
//return the seconds as a time mmm.ss, eventually hhh:mm too
unsigned long Trip::time(){
// return seconds*1000;
byte d = 60;
unsigned long seconds = loopCount/loopsPerSecond;
// if(seconds/60 > 999) d = 3600; //scale up to hours.minutes if we get past 999 minutes
return ((seconds/d)*1000) + ((seconds%d) * 10);
}
unsigned long rpm(){
return rotationPerInjection*1000000 / (6*injCycleTime) ;
}
void Trip::update(Trip t){
loopCount++; //we call update once per loop
injPulses+=t.injPulses;
vssPulses+=t.vssPulses;
if(t.injPulses != 0){//chasing ghosts
injHius+=t.injHius;
if (injHius>=1000000){ //rollover into the injHiSec counter
injHiSec++;
injHius-=1000000;
}
}
}
void Trip::reset(){
loopCount=0;
injPulses=0;
injHius=0;
injHiSec=0;
vssPulses=0;
}
//LCD functions
LCD::LCD(){
}
//x=0..16, y= 0..1
void LCD::gotoXY(byte x, byte y){
byte dr=x+0x80;
if (y==1)
dr += 0x40;
if (y==2)
dr += 0x14;
if (y==3)
dr += 0x54;
lcd.LcdCommandWrite(dr);
}
void LCD::print(char * string){
byte x = 0;
char c = string[x];
while(c != 0){
lcd.LcdDataWrite(c);
x++;
c = string[x];
}
}
//do the lcd initialization voodoo
void LCD::init(){
pinMode(EnablePin,OUTPUT);
pinMode(DIPin,OUTPUT);
pinMode(DB4Pin,OUTPUT);
pinMode(DB5Pin,OUTPUT);
pinMode(DB6Pin,OUTPUT);
pinMode(DB7Pin,OUTPUT);
delay(500);
LcdCommandWrite(B00000010); // 4 bit operation
LcdCommandWrite(B00101000);// 4-bit interface, 2 display lines, 5x8 font
LcdCommandWrite(B00001100); // display control:
LcdCommandWrite(B00000110); // entry mode set: increment automatically, no display shift
LcdCommandWrite(B01000000); // set cgram
//create some custom characters
byte chars[]={ //if you squint, you can kind of see the characters below. see the 'N' in the lower right?
// M/,G T,K GA MI CU IN
B00101,B00000,B00000,B00000,B01100,B10100,B01100,B11100,
B00111,B00100,B00000,B00000,B10000,B11100,B10000,B01000,
B00101,B01000,B00111,B10100,B10100,B10100,B10000,B01000,
B00101,B10000,B00010,B11000,B01100,B10100,B01100,B11100,
B00000,B01100,B00010,B10100,B00010,B00111,B00101,B01001,
B00001,B10000,B00010,B10100,B00101,B00010,B00101,B01101,
B00010,B10100,B00000,B00000,B00111,B00010,B00101,B01011,
B00100,B01100,B00000,B00000,B00101,B00111,B00111,B01001};
for(byte x=0;x<8;x++)
for(byte y=0;y<8;y++)
LcdDataWrite(chars[y*8+x]); //write the character data to the character generator ram
LcdCommandWrite(B00000001); // clear display, set cursor position to zero
LcdCommandWrite(B10000000); // set dram to zero
}
void LCD::tickleEnable(){
// send a pulse to enable
digitalWrite(EnablePin,HIGH);
delayMicroseconds(1); // pause 1 ms according to datasheet
digitalWrite(EnablePin,LOW);
delayMicroseconds(1); // pause 1 ms according to datasheet
}
void LCD::cmdWriteSet(){
digitalWrite(EnablePin,LOW);
delayMicroseconds(1); // pause 1 ms according to datasheet
digitalWrite(DIPin,0);
}
byte LCD::pushNibble(byte value){
digitalWrite(DB7Pin, value & 128);
value <<= 1;
digitalWrite(DB6Pin, value & 128);
value <<= 1;
digitalWrite(DB5Pin, value & 128);
value <<= 1;
digitalWrite(DB4Pin, value & 128);
value <<= 1;
return value;
}
void LCD::LcdCommandWrite(byte value){
value=pushNibble(value);
cmdWriteSet();
tickleEnable();
value=pushNibble(value);
cmdWriteSet();
tickleEnable();
delay(5);
}
void LCD::LcdDataWrite(byte value){
digitalWrite(DIPin, HIGH);
value=pushNibble(value);
tickleEnable();
value=pushNibble(value);
tickleEnable();
delay(5);
}
// this function will return the number of bytes currently free in RAM
int memoryTest() {
int byteCounter = 0; // initialize a counter
byte *byteArray; // create a pointer to a byte array
while ( (byteArray = (byte*) malloc (byteCounter * sizeof(byte))) != NULL ) {
byteCounter++; // if allocation was successful, then up the count for the next try
free(byteArray); // free memory after allocating it
}
free(byteArray); // also free memory after the function finishes
return byteCounter; // send back the highest number of bytes successfully allocated
}
Last edited by Mr. Cheap; 05-24-2008 at 03:38 PM..
|