Code hacks

From EcoModder Forum Wiki
Revision as of 11:11, 7 July 2018 by MetroMPG (talk | contribs) (Created page with "__TOC__ == Overview == This is intended as a collaborative place for publishing code patches for various projects, or possibly a complete project if it is small (though sepa...")

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Overview

This is intended as a collaborative place for publishing code patches for various projects, or possibly a complete project if it is small (though separate wiki page might do too). Try to say which version of the code the patch is based on if available.

Why is this needed? Well a main project might maintain a certain level of testing and want to be able to say with confidence that a specific release works and has not broken anything since the previous release, and may not have the manpower to incorporate a lot of options and test all permutations. But those concerns are counter to the "hybrid vigor" that occurs when people can work on one aspect of a program. So there needs to be some place where anyone can put an example concept together for perhaps a customized piece of hardware or situation, or an interesting feature the project keepers cannot incorporate into the main repository in a timely manner.

Intended Audience, this is really for hackers, and folks who already posses similar skill sets. It is not meant as a learning tool though it may lead to unintended learning. This is not a place for discussions though, use the discussion tab or the forum.

Red & Green leds to determine if you are doing better or worse than your average

This is an idea that was posted by ac7ss in the workspace thread and then a thread was created for it, but no further action was taken it seems.

Connect a Green LED to pin 10 and a Red one to pin 11 with proper resistors.


The logic of the code could be improved some though ie...

  • Green on / Red off - Current MPG greater then Tank Average
  • Green on / Red on - Current MPG equals Tank Average
  • Green off / Red on - Current MPG less then Tank Average


// in header section
#define GreenLed 10       
#define RedLed 11
...
// in setup()
pinMode(GreenLed,OUTPUT);       
pinMode(RedLed,OUTPUT);       
...
// After sei() call in loop()
if(lastActivity != nil){
  if(instantmpg()>tank.mpg()){
    //turn on GreenLed
    digitalWrite( GreenLed, HIGH); 
  }else{
    //turn off GreenLed
    digitalWrite( GreenLed, LOW);
  }
  if(instantmpg()>current.mpg()){
    //turn off RedLed
    digitalWrite( RedLed, LOW);
  }else{
    //turn on RedLed
    digitalWrite( RedLed, HIGH);
  }
}else{
  //turn off both lights if we are in sleep mode.
  digitalWrite( RedLed, LOW);
  digitalWrite( GreenLed, LOW);
}

VFD Display for mpguino

Mpguinovfd.jpg

Occasional extreme low temperatures and general interest in heads up display have led to some discussion about using a Vacuum Fluorescent Display (vfd) [1]. Newhaven Display part number M0216SD-162SDAR2-1 (~$35) is a close analogue of the current LCD display, so much so that it displays without doing anything but wiring it up to the mpguino. However a slight code tweak is required to reclaim control over brightness and display on/off as with this unit those functions are commands as the LED backlight pins are absent.

To regain brightness control and have the mpguino turn off/on the display automatically: based on version 0.75 of the mpguino code in google repository. In order of appearance:

change:      analogWrite(BrightnessPin,brightness[brightnessIdx]);   
to: LCD::LcdCommandWrite(B00101000+brightnessIdx);

change:      analogWrite(BrightnessPin,brightness[0]);    //nitey night
to: LCD::LcdCommandWrite(B00001000);

change:    analogWrite(BrightnessPin,brightness[brightnessIdx]);  //wake up!!!
to: LCD::LcdCommandWrite(B00001100);

change (2nd instance):      analogWrite(BrightnessPin,brightness[brightnessIdx]);     
to: LCD::LcdCommandWrite(B00101000+brightnessIdx);

Follow up note: Testing has implied that the VFD appears to be impractical, as it draws a lot of current (overheats existing guino power supply) and is not visible in direct sunlight as one might encounter frequently in a moving vehicle, which also means it is not useable for a HUD. Add in the additional costs and it is hard to justify going this route for a couple days out of the year of waiting for the LCD to thaw.

--Dcb 07:39, 24 April 2009 (EDT)

Enhanced Large Font

New font sm.png

I've made a lot of changes and cleanups to 0.75, but in general this is a simple hack. Had I not made so many changes I could just post a patch here but my patch would be pretty large.

Start by adding this line near the top of the mpguino.pde file:

#define CFG_BIGFONT_TYPE                       2  // 1=Default 2=Modified

By setting this to '1' before you compile, you'll get the current font. Set it to '2' and then recompile, and you get the new, 'rounder' big font. The big font adds about 20 bytes to the compiled code size.

Find the declaration for bignumchars1 and bignumchars2. Delete these lines. Now add the following code near the top of the file, probably right underneath the CFG_BIGFONT_TYPE line you see above.

#if (CFG_BIGFONT_TYPE == 1)
   //32 = 0x20 = space
   const unsigned char LcdNewChars = 5;
   const unsigned char LcdCharHeightPix = 8;
   char bignumchars1[]={4,1,4,0, 1,4,32,0, 3,3,4,0, 1,3,4,0, 4,2,4,0,   4,3,3,0, 4,3,3,0, 1,1,4,0,   4,3,4,0, 4,3,4,0};
   char bignumchars2[]={4,2,4,0, 2,4,2,0,  4,2,2,0, 2,2,4,0, 32,32,4,0, 2,2,4,0, 4,2,4,0, 32,4,32,0, 4,2,4,0, 2,2,4,0};
#elif (CFG_BIGFONT_TYPE == 2)
   //255 = 0xFF = all black character
   const unsigned char LcdNewChars = 8;
   const unsigned char LcdCharHeightPix = 8;
   char bignumchars1[]={7,1,8,0, 1,255,32,0,  3,3,8,0,   1,3,8,0, 255,2,255,0, 255,3,3,0, 7,3,3,0, 1,1,6,0,   7,3,8,0, 7,3,8,0};
   char bignumchars2[]={4,2,6,0, 32,255,32,0, 255,2,2,0, 2,2,6,0, 32,32,255,0, 2,2,6,0,   4,2,6,0, 32,7,32,0, 4,2,6,0, 2,2,6,0};
#endif

The above will eventually tell the LCD how to represent the new characters. bignumchars1 is line 1 of the LCD, and bignumchars2 is line 2. Each grouping of four numbers ("null-terminated" with a zero) points to the new characters we will define next. So for example, the large font number zero consists of:

Special character 7, 1, and 8 on line 1, and special characters 4, 2, and 6 on line 2.

Entries above like 255 and 32 point to already-existing characters in the LCD. 255 (or 0xFF in hex) points to a character that is a solid block. 32 (0x20) is a space (empty character).

Now find the chars[] declaration. Delete it, and replace it with this:

#if (CFG_BIGFONT_TYPE == 1)
  static byte chars[] PROGMEM = {
    B11111,B00000,B11111,B11111,B00000,
    B11111,B00000,B11111,B11111,B00000,
    B11111,B00000,B11111,B11111,B00000,
    B00000,B00000,B00000,B11111,B00000,
    B00000,B00000,B00000,B11111,B00000,
    B00000,B11111,B11111,B11111,B01110,
    B00000,B11111,B11111,B11111,B01110,
    B00000,B11111,B11111,B11111,B01110};
#elif (CFG_BIGFONT_TYPE == 2)
  /* XXX: For whatever reason I can not figure out how 
   * to store more than 8 chars in the LCD CGRAM */
  static byte chars[] PROGMEM = {
    B11111, B00000, B11111, B11111, B00000, B11111, B00111, B11100, 
    B11111, B00000, B11111, B11111, B00000, B11111, B01111, B11110, 
    B00000, B00000, B00000, B11111, B00000, B11111, B11111, B11111, 
    B00000, B00000, B00000, B11111, B00000, B11111, B11111, B11111, 
    B00000, B00000, B00000, B11111, B00000, B11111, B11111, B11111, 
    B00000, B00000, B00000, B11111, B01110, B11111, B11111, B11111,
    B00000, B11111, B11111, B01111, B01110, B11110, B11111, B11111,
    B00000, B11111, B11111, B00111, B01110, B11100, B11111, B11111};
#endif

The above are binary representations of the pixels for the custom characters that we will upload to a special scratch area in the LCD. Each 'block' above is a number in the character generator RAM starting with 1 and going up to 8.

Now find the code that looks like this below, and either alter it so it looks EXACTLY like this, or just delete it and replace it with this:

    /* write the character data to the character generator ram */
    for(byte x=0;x<LcdNewChars;x++) {
      for(byte y=0;y<LcdCharHeightPix;y++) {
          LcdDataWrite(pgm_read_byte(&chars[y*LcdNewChars+x])); 
      }
    }

I could not figure out how to cram more than eight characters into the LCD's CGRAM. If anyone can help with this I would add a few more characters to make this font even nicer. By following the above structure, you can see how to add a CFG_BIGFONT_TYPE 3, and create your own font variant.

--Skelly 07:05, 24 April 2009 (EDT)


more precision

Singletree5990 has modified his kit version to monitor injector pulse counts and system voltage, and is also looking into monitoring other variables such as fuel temperature in search of ultimate precision. http://ecomodder.com/forum/showthread.php/possible-way-measure-injector-characteristics-8382.html#post110622

Save Current Tank Data / Archive Tank Summary / Track Gas-Used per Speed Range

Here are a couple or three mods I've made to my MPGuino:

1a) Save current tank data to the eeprom, at the 7-minute sleep point. If there is a power loss or reset, tank data will be restored at restart. Also, if you are afraid of a thief stealing your 'guino, you can also force a save through the "Hold Tank" menu option (Left+Middle, then Right), then unplug it, without waiting the 7 minutes to auto-save.

1b) Archive summaries of tanks (or current trips, if you like) to the eeprom. You can later browse through each tank's data: Miles, Gallons, MPG, Avg MPH, Idle Gallons, EOC Miles, and a 10-digit numerical note (date, dollars, whatever... finally found a use for the Scratchpad parm :) Most of us are writing this all down anyway, but why not keep a digital copy too? Tanks are stored in a queue, so if you run out of eeprom space for your next tank, it will overwrite the oldest.

2) Track the percentage of gas used at different speeds. Here, we have 8 speed ranges (plus a ninth, "Idle"): Idle, 1-29mph, 30-39, 40-49, 5x, 6x, 7x, 8x, and 90+. There is a new screen to display these in real-time. Also, if used in conjunction with the above Tank Archive mod, the gas/speed percentages will be archived for later viewing. This data, I hope, can possibly help you analyze why/where/how you were wasting gas... all without external data logging.


Both mods can be compiled together or independently of each other, via compile-time options "#define TrackSpeedGas" and "#define SaveTankToEeprom". I've only tested these on a 168 with v0.82, but they should also work with v0.75. A 328 should give you a lot more archive storage.

Anyway, on with the show:


Add these #define's near the top of your source.

#define eepromSize 512          //512 for atmega168, 1024 for 328

#define TrackSpeedGas           // Define this to track gas used at different speeds.
                                // Costs 85ram + 814 bytes/v0016  766 bytes/v0011
#define SaveTankToEeprom        // Define this to enable Tank data save/restore/view
                                // Save/Restore current "live" tank dataset, or
                                // Archive & View tank summaries
                                // Costs 1432 bytes/v0016  1366 bytes/v0011
//#define SaveCurrentToEeprom   // Uncomment this if you also want to protect Current Trip from power-loss
                                // If your Current auto-clears after 7 minutes, you probably won't need this

#define eeParmStart  4  //eeprom location, beginning of parms[]

#define savetanksig   B11101101
#define eeTankSig       eepromSize-1    //last byte of eeprom
#define eeTankQueueIdx  eepromSize-2    //2nd last byte of eeprom, contains queue # of last tank summary saved

#ifdef SaveCurrentToEeprom
#define eeHoldTankSize   (sizeof(Trip)+sizeof(Trip)+sizeof(injSpeedHiSec)+sizeof(injSpeedHius))
//136 = 36+36+32+32  ... or 36+36+1+1, if !TrackSpeedGas
#else
#define eeHoldTankSize   (sizeof(Trip)+sizeof(injSpeedHiSec)+sizeof(injSpeedHius))
//100 = 36+32+32   ... or 36+1+1, if !TrackSpeedGas
#endif

#define eeHoldTankStart  (eeTankQueueIdx - eeHoldTankSize)
        //start byte of where to store live tank data (in event of power loss)
#define eeHoldCurrentStart  (eeHoldTankStart + sizeof(Trip))
        //start byte of where to store live current-trip data (in event of power loss)
#define eeHoldSpeedGasStart  (eeTankQueueIdx - sizeof(injSpeedHiSec) - sizeof(injSpeedHius))
#define eeTankArchiveStart  (eeHoldTankStart - eeTankArchiveSize)
 #ifdef TrackSpeedGas
#define eeTankArchiveSize   (9+11+4)
 #else
#define eeTankArchiveSize   (11+4)
 #endif
//      15 bytes to save: 99.99 gallons, 99.99 idle, 99.99 mph, 99.99 eoc, 9999.99 miles, 4-byte decimal note (2+2+2+2+3+4)
//      optionally, store additional 9 bytes (1 3-digit percent of gas-used for 8 speed ranges + idle)

#define eeTankQueueMax   ((eeHoldTankStart-eeParmStart-sizeof(parms)-128) / eeTankArchiveSize)
//# of 20-byte tank summaries we will store.  store at end of eeprom, leave lower space for parms & future growth
//start_loc_of_temp_tank_save) - (parms overhead) - 128 (future growth)

#define READ  0  //for eeprom_block()
#define WRITE 1  //for eeprom_block()
#define LeftButton ((buttonState & lbuttonBit)==0)
#define MiddleButton ((buttonState & mbuttonBit)==0)
#define RightButton ((buttonState & rbuttonBit)==0)


Add these to your other function declarations section, if you have one.

void readWriteCurrentTank (byte);
void updateSpeedGas (void);
void wipeBytes (byte *, int);
void eeprom_block (byte, byte *, int, int);
void viewTankHistory (void);
void doDisplaySpeedGas (void);
void displaySpeedGasPct (void);
byte menuButton (prog_char *, prog_char *);
char * format(unsigned long, char, char);
// ^-- Add this line.  Don't replace the original char *format(unsigned long);


Add these to your class Trip { } declaration

  void archiveTank();
  void calcSpeedGasPct ();


Put these somewhere, maybe under the block of "unsigned volatile long ...InstInj..." lines

#ifdef TrackSpeedGas
// 1-30, 30-40, 4x, 5x, 6x, 7x, 8x, 90+ mph
unsigned long injSpeedHiSec[8]={0,0,0,0,0,0,0,0};
unsigned long  injSpeedHius[8]={0,0,0,0,0,0,0,0};

  #ifdef SaveTankToEeprom
  #define pctSpeedGas         archiveTankData
  #else
  byte pctSpeedGas[9]; // pct[0] contains "idle" %
  #endif
#else
  #ifdef SaveTankToEeprom  //if SaveTank, but not TrackSpeed, define these anyway
byte injSpeedHiSec[1], injSpeedHius[1];
  #endif
#endif

#ifdef SaveTankToEeprom
byte archiveTankData[24];  //9 pctSpeedGas + (2+2+2+2+3+4)
#define archTankGallons     (archiveTankData+9)
#define archTankIdleGallons (archiveTankData+11)
#define archTankMph         (archiveTankData+13)
#define archTankEocMiles    (archiveTankData+15)
#define archTankMiles       (archiveTankData+17) //only use 3 low bytes of (long) miles
#define archTankNote        (archiveTankData+20)
#endif


Add this to the pFunc displayFuncs[] ={ section

#ifdef TrackSpeedGas
  doDisplaySpeedGas,
#endif


Find your "char fBuff[7]" and change it to [18]. If you are enterprising, you can delete mBuff[] and use fBuff in getStr(), rformat() & uformat() to save a few bytes.

char fBuff[18];   //used by format() and hhmmss()   18 bytes gives enough room for 2 hh:mm:ss


Add these lines to the top of the load() function:

#ifdef SaveTankToEeprom
  readWriteCurrentTank(READ);
#endif


Add this to the displayFuncNames in setup()

#ifdef TrackSpeedGas
  displayFuncNames[x++]=  PSTR("%Gas @ Speed "); 
#endif


In loop(), add this to the top ...

  byte menuKey;


... then add the lines marked "//add this" near the update() commands...

    current.update(instant);   //use instant to update current      
    tank.update(instant);      //use instant to update tank      
    #ifdef TrackSpeedGas        //add this
     updateSpeedGas();          //add this
    #endif                      //add this
  .
  .
  .
//wake up ...
        current.update(instant);
        tank.update(instant);
        #ifdef TrackSpeedGas    //add this
         updateSpeedGas();      //add this
        #endif                  //add this
      }else{
        lastActivity=loopStart;
        tankHold = tank.loopCount;


... and add the Tank WRITE command to the timeout section...

//currentTripResetTimeoutUS
    if(instant.vssPulses == 0 && instant.injPulses == 0 && holdDisplay==0){
      if(elapsedMicroseconds(lastActivity) > parms[currentTripResetTimeoutUSIdx] && lastActivity != nil){
        analogWrite(BrightnessPin,brightness[0]);    //nitey night
        readWriteCurrentTank(WRITE);   //add this line
        lastActivity = nil;
      }


... and replace the button section with this:

//see if any buttons were pressed, display a brief message if so      

      if (LeftButton && RightButton)
      {// left and right = initialize      
        LCD::print(getStr(PSTR("Setup ")));    
        initGuino();  //initGuino(0); // 0 == don't not edit == editParms
        lastActivity = microSeconds();  //update lastAct to prevent unwanted timeout after long submenu detour
        tmpTrip.reset(); //who knows what went on while we were in here
      }
        
#ifdef SaveTankToEeprom
      else if (LeftButton && MiddleButton)  //nickdigger
      {//Save ...
        menuKey = menuButton(PSTR("Store View  Hold"), PSTR("Tank  Hist  Tank"));
        if (!(menuKey&rbuttonBit)) { readWriteCurrentTank(WRITE); } //hold tank data, in case of reset
        else if (!(menuKey&mbuttonBit))  { viewTankHistory(); }
//        else if (!(menuKey&lbuttonBit)) { tank.archiveTank(); }
// add option to archive current or tank
        else if (!(menuKey&lbuttonBit))  {
          menuKey = menuButton(PSTR("<-Go Store Store"), PSTR("Back  Tank  Trip"));
          if (!(menuKey&mbuttonBit)) tank.archiveTank();
          else if (!(menuKey&rbuttonBit)) current.archiveTank();
        }
 
        lastActivity = microSeconds();  //update lastAct to prevent unwanted timeout
        tmpTrip.reset(); //who knows what went on while we were in here
      }
      else if(RightButton && MiddleButton)  //nickdigger
      {// Clear ...
        menuKey=menuButton(PSTR("<-Go Reset Reset"), PSTR("Back  Tank  Trip"));
        if ((menuKey&rbuttonBit)==0) {
          current.reset();
          #ifdef SaveCurrentToEeprom
           EEPROM.write(eeHoldCurrentStart, savetanksig);
          #endif
        }
        else if (!(menuKey&mbuttonBit)) {
          tank.reset();  EEPROM.write(eeHoldTankStart, savetanksig);
          #ifdef TrackSpeedGas
          wipeBytes((byte *)injSpeedHiSec, sizeof(injSpeedHiSec));
          wipeBytes((byte *)injSpeedHius,  sizeof(injSpeedHius));
          #endif
        }
        lastActivity = microSeconds();  //update lastAct to prevent unwanted timeout
        tmpTrip.reset(); //who knows what went on while we were in here
      }

#else // not SaveTankToEeprom
      else if (LeftButton && MiddleButton)
      {// left and middle = tank reset      
        tank.reset();
        LCD::print(getStr(PSTR("Tank Reset ")));      
      }
      else if(RightButton && MiddleButton)
      {// right and middle = current reset
        current.reset();
        LCD::print(getStr(PSTR("Current Reset ")));      
      }
#endif // SaveTankToEeprom

      else if (MiddleButton)
      { //middle is cycle through brightness settings      
        brightnessIdx = (brightnessIdx + 1) % brightnessLength;      
        analogWrite(BrightnessPin,brightness[brightnessIdx]);      
        LCD::print(getStr(PSTR("Brightness ")));      
        LCD::LcdDataWrite('0' + brightnessIdx);      
        LCD::LcdDataWrite(' ');      //print(" ");
      }
      else if (LeftButton)
      { //left is rotate through screeens to the left      
        if (screen!=0)  screen=(screen-1);       
        else            screen=displayFuncSize-1;      
        LCD::print(getStr(displayFuncNames[screen]));      
      }
      else if (RightButton)
      { //right is rotate through screeens to the left      
        screen=(screen+1)%displayFuncSize;      
        LCD::print(getStr(displayFuncNames[screen]));      
      }
///////////done checking keys


And now, the code:

#ifdef TrackSpeedGas
void Trip::calcSpeedGasPct (void) {
//Calculate percent of gas used within each speed range, and store into 9-byte array
// idle, 1-30mph, 30-39, 40-49, 5x, 6x, 7x, 8x, 90+
// We could do the calcs in displaySpeedGasPct, but we'll need to recycle that function to display saved tanks.
//
//nickdigger - 288 bytes v0016

  char i;
  unsigned long injHi = (injHiSec*1000 + injHius/1000);  // (injHiSec*100000 + injHius/10) / 100;
                //divide total injHi by 100, so when we divide each time set, we get xx%
  
  if (injHi == 0)  injHi = 1;  //avoid div-by-zero
  pctSpeedGas[0] = (byte) ( (injIdleHiSec*100000 + injIdleHius/10)/injHi );

  for (i=0; i<8; i++)
    pctSpeedGas[i+1] = (byte) ( (injSpeedHiSec[i]*100000 + injSpeedHius[i]/10)/injHi );
  
  return;
  
} //Trip::calcSpeedGasPct()  

  
void displaySpeedGasPct (void) {
//Displays percent of gas used within each speed range: (reallllly squeezing the numbers in)
//i## <30:## ## ##    (idle) (1-30mph) (30-39) (40-49)
//5x## ## ## ## ##    (50-59mph) (60-69) (70-79) (80-89) (90-up)
//
//nickdigger - 126 bytes v0016

  char i;
  
  LCD::gotoXY(0,0);
  LCD::LcdDataWrite('i');

  for (i=0; i<9; i++)
  {
    LCD::print( format(pctSpeedGas[i],2,0) );
    if (i==0) LCD::print(" <30:");
    else if (i==3) { LCD::gotoXY(0,1);  LCD::print("5x"); }
    else if (i<8) LCD::LcdDataWrite(' ');
  }
} //displaySpeedGas()


void doDisplaySpeedGas (void)
{
  tank.calcSpeedGasPct();
  displaySpeedGasPct();
}
#endif //TrackSpeedGas


#ifdef TrackSpeedGas
void updateSpeedGas (void) {
// Accumulates injHius into injSpeedHiSec[]/Hius[] array, for each speed range of
// 1-30, 30-39, 40-49, 5x, 6x, 7x, 8x, and 90+ mph
//nickdigger - 256 bytes

  char i;
  byte imph, speedrange=90;

  if (instant.vssPulses == 0  || instant.injPulses <= 2 || instant.injHius >= 500000)
   return;  //chasing ghosts  & ignoring vss==0

  imph = (byte) (instantmph()/1000ul);  //dont need 32bit comparison for 2-digit mph.  saves 10 bytes or so
  
  for (i=7; i>=0; i--)
  {
    if (imph >= speedrange || i==0)
    {
      injSpeedHius[i] += instant.injHius;
      if (injSpeedHius[i] >=1000000ul)
      {
        injSpeedHiSec[i]++;
        injSpeedHius[i] -= 1000000ul;
      }
      break;  //we found & updated our speed range, now quit
    }
    speedrange -= 10;
  } //for
  
} //updateSpeedGas()
#endif  //TrackSpeedGas


void eeprom_block (byte rw, byte *p, int start, int len)
{ //nickdigger
  for (int end=start+len; start < end; p++)
    if (rw==WRITE)  EEPROM.write(start++, *p);
    else *p = EEPROM.read(start++);
}  //eeprom_blockread()


void wipeBytes (byte *p, int len)
{//nickdigger - sets len bytes to 0
  int i=0;
  while (i<len)
    *(p + i++) = 0;
} //wipeBytes()


#ifdef SaveTankToEeprom
byte menuButton (prog_char *s1, prog_char *s2)
{ //nickdigger - print 2-string menu, wait for & return button
  byte x;
  LCD::gotoXY(0,0);  LCD::print(getStr(s1));
  LCD::gotoXY(0,1);  LCD::print(getStr(s2));
  buttonState=buttonsUp;
  while (buttonState==buttonsUp)
    delay2(250);
  x=buttonState;
  buttonState=buttonsUp;
  return x;
} //menubutton()


void viewTankHistory ()
{
//nickdigger - 698 bytes
/*
tank## 12345note
9999.99mi 99.99g

99.9mpg 99.99mph
9.99idle 9.99eoc

pctSpeedpage

*/

  byte page=0, p0=5, q0=254, q=EEPROM.read(eeTankSig);

  if (q != savetanksig)  return;
  
  q=EEPROM.read(eeTankQueueIdx);
  buttonState=buttonsUp;
  
  while (! MiddleButton)
  {
    q %= eeTankQueueMax;
    if (q != q0)
      eeprom_block(READ, archiveTankData, 
                         eeTankArchiveStart - q*eeTankArchiveSize, eeTankArchiveSize);
    if (page != p0)
    {
      if (page==0) {
        LCD::gotoXY(0,0);   LCD::print(getStr(PSTR("Tank")));
        LCD::print(format((long) q,2,0));
        LCD::print(format(*(unsigned long *)archTankNote,10,0));
        LCD::gotoXY(0,1);  
        LCD::print(format( (*(long *)archTankMiles) & 0x00ffffff ,6,2));  LCD::print("mi");
        LCD::print(format(*(int *)archTankGallons,5,2));  LCD::LcdDataWrite('g');
      }else if (page==1) {
        LCD::gotoXY(0,0);
        LCD::print(format( ((*(long *)archTankMiles) & 0x00ffffff)*10 / 
                            (1 + *(int *)archTankGallons ), 3,1));
             //add .01 to gallons to prevent div-zero.  this will skew mpg for < 1.00 gallons
        LCD::print("mpg");  
        LCD::print(format(*(int *)archTankMph,5,2));  LCD::print("mph");
        LCD::gotoXY(0,1);  LCD::print(format(*(int *)archTankIdleGallons, 3,2));
        LCD::print("idle");  LCD::print(format(*(int *)archTankEocMiles,4,2));  LCD::print("eoc");
      }else if (page==2)
        #ifdef TrackSpeedGas
        displaySpeedGasPct();
        #else
        page=0;  q0=q;  q++;
        #endif
    } //print page

    if (LeftButton)
    {
      p0=page;
      if (page==0) {page=2; q0=q; q=(q==0 ? eeTankQueueMax-1 : q-1); }
      else page--;
      buttonState=buttonsUp;
    } else if (RightButton)   
    {
      p0=page;
      if (page==2) {page=0; q0=q; q++; }
      else page++;
      buttonState=buttonsUp;
    }
    delay2(250);
  } //while
  buttonState=buttonsUp;
  
} //viewTankHistory()


void Trip::archiveTank (void)
{
//nickdigger - 408 bytes
//99.99 gallons, 9.99 idle, 99.99 mph, 9.99 eoc, 9999.99 miles, 32-bit decimal Note/Date (15 hex bytes 2+2+2+2+3+4)
//Because of stupid reverse-byte order, we'll store all 4 bytes of each element -- keeping the low 2 bytes, and 
//overwriting the high 2 bytes (always == 00) with the next element.  Good times.

  byte q=EEPROM.read(eeTankSig);

  if (q != savetanksig) { EEPROM.write(eeTankSig, savetanksig);  q=0; }
  else q=(1 + EEPROM.read(eeTankQueueIdx)) % eeTankQueueMax;

#ifdef TrackSpeedGas
  calcSpeedGasPct();
#endif
  *(unsigned long *)archTankGallons = gallons()/10;
  *(unsigned long *)archTankIdleGallons = idleGallons()/10;
  *(unsigned long *)archTankMph = mph()/10;
  *(unsigned long *)archTankEocMiles = eocMiles()/10;
  *(unsigned long *)archTankMiles = miles()/10; //Miles is only 3 bytes. 4th byte (==0) is overwritten by Note
  editParm(scratchpadIdx);
    //eeprom_block(WRITE, (byte *)&parms[scratchpadIdx], eeParmStart+scratchpadIdx*sizeof(long), sizeof(long));
    //save Note parm to eeprom for quick edit next time.
    //  On 2nd thought, don't save it.  Byte-order is not preserved with original 0.75/0.82 load().
  *(unsigned long *)archTankNote = parms[scratchpadIdx];
  
  eeprom_block(WRITE,archiveTankData,
                     eeTankArchiveStart - q*eeTankArchiveSize,
                     eeTankArchiveSize);
  EEPROM.write(eeTankQueueIdx, q);

}  //archiveTank()


void readWriteCurrentTank (byte rw)
{//nickdigger
/* EEPROM structure:
    511 SaveTankSig (B11101101)
    510 eeTankQueueIdx (# of last tank archived)
478-509 injSpeedHius
446-477 injSpeedHiSec
410-445 current trip hold data (optional)
374-409 tank hold data
354-373 archive tank summary #0
334-353 archive tank summary #1
314-333 archive tank summary #2
etc
etc more tanks, as space allows

 52-179 reserved for future use
  4- 51 parms[]
  0-  3 guinosig, parmcount, 2 empty bytes
*/

#ifdef SaveCurrentToEeprom
  if (rw==WRITE || EEPROM.read(eeHoldCurrentStart) != savetanksig)
    eeprom_block(rw, (byte *)&current, eeHoldCurrentStart, sizeof(Trip));
#endif

  if (rw==WRITE || EEPROM.read(eeHoldTankStart) != savetanksig)
  {
    eeprom_block(rw, (byte *)&tank, eeHoldTankStart, sizeof(Trip));
    #ifdef TrackSpeedGas
     eeprom_block(rw, (byte *)injSpeedHiSec, eeHoldSpeedGasStart, sizeof(injSpeedHiSec));
     eeprom_block(rw, (byte *)injSpeedHius, eeHoldSpeedGasStart + sizeof(injSpeedHiSec), sizeof(injSpeedHius));
    #endif
  }

}  //readWriteCurrentTank()
#endif //SaveTankToEeprom




//new flexible format(). 120 bytes larger than old format().
//function overloading in C++ allows this code to compile & work
//without changing or deleting the old format(x) code

char* format(unsigned long num, char digits, char dp)  //nickdigger
{
//      v0011: 758 bytes for this format(x,len,dp) only, 638 for format(x) only, or 1000 for both.  Weird.
//      v0016: adding v2 to v1 costs 388 bytes;  adding v1 to v2 costs 238 bytes
//
//      Flexible length & decimal places -- does not default to xx.xxx
//      Leading spaces instead of zer0s;
//
//      digits = max # of digits.  dp = # of decimal places desired
//      Overall output length = digits + 1, if dp>0;  else length = digits
//      
//      len.dp : E.g. 4.1 = xxx.y, 5.2 = xxx.yy, 4.0 = xxxx
//      If num is too large to fit len.dp, we will displace decimals 
//      to preserve the more significant (large) digits.
//      E.g, (12345,4,2) wants to display 123.45 but we only have 4 digits.  So, we print 123.5
//      (12345,4,1) prints " 1234".  (123456,4,1) prints " 9999"

  char i=1, dp0=dp;
  unsigned long limit=10;

  while (i++ < digits)  // Wow.  110 bytes for this little *=10 loop.  8 bit CPUs FTL.
    limit *= 10;
  i=0;
  while (dp >0 && i++ <= dp)    //reduce decimal places if num is too big.
  {                             //e.g. (12345,4,1) should be "1234.5",
    if (num >= limit)           //but we only have 4 digits, so print "1234".
    {
      if ((num%10) > 4)         //round up dropped digit
        num+=10;  
      num /= 10;                   
      dp--;                        
    }
  }
  if (num >= limit)  num=limit-1;   //if overflow, then 99999

  if (dp0 > 0) i=digits;  //check dp0 to see if our string has 1 extra space for the '.'
  else         i=digits-1;
  fBuff[i+1]=0;
  dp=digits-dp;

  while (i >= 0)
  {
    fBuff[i--] = (num==0 && i<dp-1 ? ' ' : '0'+(num%10) );
    num/=10;
    if (i==dp) fBuff[i--]='.';
  }
  return fBuff;
}  //format(x,dig,dp)
//nickdigger//

That's it. I've since expanded the "Gas Used Per Speed Range" concept to include "Time Spent per Speed Range", but I made so many other changes to the base data structures & code, that it will no longer splice right into vanilla 0.75 or 0.82.

Add your own hack

Hit the edit button to the right and figure it out.