View Single Post
Old 05-23-2008, 09:30 PM   #131 (permalink)
Mr. Cheap
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 View Post
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(&current);}   //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(&current);} //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 02:38 PM..
  Reply With Quote