View Single Post
Old 06-14-2015, 06:19 AM   #73 (permalink)
EcoModding Apprentice
Join Date: Jan 2010
Location: Newark, DE
Posts: 143

'91 CRX - '91 Honda CRX DX
90 day: 34.91 mpg (US)
Thanks: 0
Thanked 14 Times in 14 Posts
This was running fine for me last night:
/*This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
To view a copy of this license, visit or send a
letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.*/

/*Original code by Tristan Fariss (aka bobski)
  Modifications by */

//#define SAA1064display

#include <LiquidCrystal.h>                     //support library for character LCDs
#include <Wire.h>                              //support library for I2C
#ifdef SAA1064display
  #include "SAA1064.ino"                         //support library for SA1064-driven LED display

#define primaryButtonArrayAnalogPin 0
#define numPrimaryButtons 5                    //numPrimaryButtons should be 1 more than the number of hardware buttons to account for an idle state

#define numPrimaryPages 3                      //the number of information/menu pages - used to define the size of the function pointer arrays
#define numSecondaryPages 2
#define numLEDpages 3
#define lcdBacklightPin 11

#define vssPulsesPerUnitDistance 4085          //vehicle-specific calibration information: how many VSS pulses to count as a unit of distance
#define vssTimeOut 250000                      //how many microseconds to wait before deciding the car is stopped when receiving no VSS pulses (only affects displayed speed)

#define injectorOnePin 2
#define injectorTimeOut 125000                 //how many microseconds to wait before deciding the injectors aren't firing
#define injectorOneActiveState LOW             //whether the injector input is active high or low (valid values HIGH and LOW)
#define injectorMicrosecsPerUnitVolume 200000000   //microseconds of injector firing time per standard unit of volume (liters, gallons or whatever you like to use)
#define injectorFiringDelay -20
  /* this is how many microseconds of discontinuity between the injector electrical signal and actual fuel flow time. this number gets subtracted from the measured
     length of each injector firing event. if your injector closes faster than it opens, the fuel flow time will be shorter than the electrical pulse, and 
     injectorFiringDelay should be positive. if the injector opens faster than it closes, this value should be negative. */
#define distanceUnits "Mile"
#define distanceUnitsShort "Mi"
#define speedUnits "Miles/Hour"
#define speedUnitsShort "MPH"
#define volumeUnits "Gallons"
#define volumeUnitsShort "Gal"
#define flowUnits "Gal/Hour"
#define flowUnitsShort "GPH"
#define fuelEconomyUnits "Miles/Gal"
#define fuelEconomyUnitsShort "MPG"
#define engineSpeedUnits "Rev/Min"
#define engineSpeedUnitsShort "RPM"

typedef void (*fptype)();                      //support for function pointers

int primaryButtonState = 0;                           //variables used for UI button handling
int primaryButtonPressed = 0;
/* buttonFunctions is an array of function pointers used to make the software functionality of the physical buttons 
   change depending on context. It is a 2D array, the first coordinate being the page number, the second being the 
   button functions (no buttons pressed, button1 pressed, button2 pressed and so on). When a particular page is being
   displayed (a numPages element, currentPage in practice), it effectively selects a row of functions that correspond
   to the page. The value returned by readAnalogButtonArray() (or some other function if something other than a 
   5-state voltage divider is being used to evaluate button input) determines which specific function in that row 
   will be executed. The first action in each row is an idle state that gets executed when no buttons are being 
const fptype primaryButtonFunctions [numPrimaryPages] [numPrimaryButtons] = { {&buttonVoidAction, &buttonVoidAction, &buttonVoidAction, &buttonLEDNextPage, &buttonPrimaryNextPage},
                                                                              {&buttonVoidAction, &buttonResetInjCumulative, &buttonVoidAction, &buttonLEDNextPage, &buttonPrimaryNextPage},
                                                                              {&buttonVoidAction, &buttonResetVssPulseCount, &buttonVoidAction, &buttonLEDNextPage, &buttonPrimaryNextPage} };

/* primaryRenderPage is an array of function pointers used to arbitrate control of the LCD display. The long variable
   name is for the sake of allowing multiple displays on alternative hardware designs Each pointer in the array
   corresponds to a page-print-function that can be called to print text and data to the display.*/
const fptype primaryRenderPage [numPrimaryPages] = {&mpgBasicPage162, &injectorBaseInfoPage162, &vssBaseInfoPage162};

unsigned int currentPrimaryPage = 0;       //which page to actively display on the primary char display
unsigned long screenUpdatePeriod = 100;        //the time (in ms) to wait between LCD updates
unsigned long lastScreenUpdate = 0;            //when the screen was last updated
int displayBrightness = 512;                   //sets illumination brightness on displays, 0 (off) to 1023 (full bright)

const fptype LEDrenderPage [numLEDpages] = {&LEDnumBlankDisplay, &LEDnumOdoDistance, &LEDnumVehicleSpeed};
unsigned int currentLEDPage = 0;
unsigned long lastLedUpdate = 0;
unsigned long ledUpdatePeriod = 100;
const byte firstSaa1064Address = 0x70;         //8-bit address with ADR tied to ground
const byte defaultSaa1064ControlByte = 0x37;   //control byte: 9 mA drive current, no segment test, all digits active, multiplexed

unsigned long injOnToggleTime = 0;             //variables used for injector pulsewidth and frequency measurement
unsigned long injCyclePeriod = 0;
unsigned long injLastOnToggleTime = 0;
unsigned long injOnInterval = 0;
unsigned long injCumulativeOnTime = 0;

unsigned long vssCyclePeriod = 0;              //variables used for vehicle speed and distance measurement
unsigned long vssLastOnTime = 0;
unsigned long vssPulseCount = 0;
const float vssCyclePeriodToSpeedNumerator = 3600000000 / vssPulsesPerUnitDistance;

LiquidCrystal lcd(4, 5, 6, 7, 8, 12, 13);

//The setup routine (runs at startup) and main program (loops constantly):

void setup()
  attachInterrupt(0,singleInjectorInterrupt,CHANGE);  //injector PWM measuring interrupt (pin 2)
  attachInterrupt(1,basicVssInterrupt,RISING);        //VSS measuring interrupt (pin 3)
  pinMode(11,OUTPUT);                                 //pin 11 is PWM backlight control for the LCD
  Wire.begin();                                       //analog 4 and 5 are used for SDA and SCL of the I2C port

  lcd.begin(16,2);                                    //initialize the LCD as 16x2 characters

void loop()
  primaryButtonState = readAnalogFourButtonArray(primaryButtonArrayAnalogPin);
  if(primaryButtonState == 0) primaryButtonPressed = 0;                //this section takes action once per button press
  else if(primaryButtonPressed == 0)
   primaryButtonPressed = 1;
  if(millis() >= lastScreenUpdate + screenUpdatePeriod)  //this section updates the LCD screen at regular intervals
   analogWrite(lcdBacklightPin, (displayBrightness << 4));
   lastScreenUpdate = millis();
  if(millis() >= lastLedUpdate + ledUpdatePeriod)
   lastLedUpdate = millis();

//The following routines are interrupts.

void singleInjectorInterrupt()   //this interrupt tracks injector on time (flow rate) and cumulative on time (dispensed fuel volume)
  if(digitalRead(injectorOnePin) == injectorOneActiveState)
   injOnToggleTime = micros();
   injCyclePeriod = injOnToggleTime - injLastOnToggleTime;
   injLastOnToggleTime = injOnToggleTime;
   injOnInterval = micros() - injOnToggleTime;
   if(injOnInterval > 50000) { injOnInterval = 0; }  //reject any erroneously long injector pulses
   injCumulativeOnTime = injCumulativeOnTime + (injOnInterval - injectorFiringDelay);

void basicVssInterrupt()   //this interrupt counts pulses (distance) and measures cycle time (speed)
  unsigned long vssNow = micros();
  vssCyclePeriod = vssNow - vssLastOnTime;
  vssLastOnTime = vssNow;

//The following are button-press functions, called to perform an action when a button is pressed.

void buttonVoidAction() { } // do nothing

void buttonPrimaryNextPage()
  if(currentPrimaryPage >= numPrimaryPages) {currentPrimaryPage = 0;}
  if(currentPrimaryPage < 0) {currentPrimaryPage = 0;}

void buttonLEDNextPage()
  if(currentLEDPage >= numLEDpages) {currentLEDPage = 0;}

void buttonResetInjCumulative()
  injCumulativeOnTime = 0; 

void buttonResetVssPulseCount()
  vssPulseCount = 0;

/* The following are LCD page-render functions. Each function should contain the commands necessary to print a page of
   information to the LCD display.*/

void injectorBaseInfoPage162()  /*for 16x2 character displays. prints "### us" on the top line and "### ###" on the bottom.
                               cumulative microsecs, current pulse on microsecs and cycle microsecs, respectively*/
  if(micros() < injOnToggleTime + injectorTimeOut)

void vssBaseInfoPage162()  /*for 16x2 character displays. prints "### p ### Mi" on the top line and "### ### MPH" on
                          the bottom. pulses, miles, pulselength and speed respectively.*/
  float vssSpeed = 0;
  float distance = ( float(vssPulseCount) / float(vssPulsesPerUnitDistance) );
  if(vssCyclePeriod != 0) {vssSpeed = ( vssCyclePeriodToSpeedNumerator / float(vssCyclePeriod) );}
  if(micros() > vssLastOnTime + vssTimeOut) {vssSpeed = 0;}

void mpgBasicPage162()  //for 16x2 character displays. prints "Trip: ### MPG" on the top line and "Inst: ### MPG" on the bottom
  float tripMpg = ( (float(vssPulseCount) / vssPulsesPerUnitDistance) / (float(injCumulativeOnTime) / injectorMicrosecsPerUnitVolume) );
  float instantMpg = ( (vssCyclePeriodToSpeedNumerator/float(vssCyclePeriod)) / float(3600000000 / (injectorMicrosecsPerUnitVolume)*(float(injOnInterval)/float(injCyclePeriod))));  //that's MPH/GPH fyi
  lcd.print("Trip: ");
  lcd.print("Inst: ");
  if(micros() < injOnToggleTime + injectorTimeOut) {lcd.print(instantMpg);}
  else if(micros() > vssLastOnTime + vssTimeOut) {lcd.print("0.00");}

//The following are functions for and related to displaying data on LED displays (7-segment numerical, bar graph, etc) 

void LEDnumBlankDisplay()
  ledDisplayNumerical(firstSaa1064Address, 0, 0x00);

void LEDnumOdoDistance()
  ledDisplayNumerical(firstSaa1064Address, (10*vssPulseCount)/vssPulsesPerUnitDistance, 0x32);

void LEDnumVehicleSpeed()
  float vssSpeed = 0;
  if(vssCyclePeriod != 0) {vssSpeed = ( vssCyclePeriodToSpeedNumerator / float(vssCyclePeriod) );}   //make sure we aren't dividing by zero
  if(micros() > vssLastOnTime + vssTimeOut) {vssSpeed = 0;}    //zero out speed if the vehicle is stopped
  ledDisplayNumerical(firstSaa1064Address, vssSpeed * 10, 0x32);

void saa1064Update(byte addy, byte ctrl, byte data0, byte data1, byte data2, byte data3)  //This function controls an SAA1064 I2C LED driver
  Wire.beginTransmission(addy >> 1);    //bit-shift the 8-bit address to 7-bit for wire.h
  Wire.write(0x00);                      //byte-address pointer
  Wire.write(ctrl);                      //control register byte
  Wire.write(data0);                     //first display output byte (7 segs + decimal, 8 discreet LEDs or whatever)
  Wire.write(data1);                     //second display output byte
  Wire.write(data2);                     //third display output byte
  Wire.write(data3);                     //fourth display output byte

void ledDisplayNumerical(byte addr, int value, byte forceDig_dpSelect)  //This routine formats numerical variables for display on a 4-digit 7-segment display
  byte ctrlByte = (defaultSaa1064ControlByte & 0x0F) | ((displayBrightness >> 3) & 0x70);
  byte digitOne = findSegs(value % 10);      //break value up into its component numerical digits and convert them to 7-segment font codes
  byte digitTwo = findSegs((value % 100) / 10);
  byte digitThree = findSegs((value % 1000) / 100);
  byte digitFour = findSegs((value % 10000) / 1000);
  if((value < 1000) && ((forceDig_dpSelect & 0x80) == 0)) {digitFour = 0x00;}  //the upper four bits of forceDig_dpSelect disable leading zero blanking digit by digit
  if((value < 100) && ((forceDig_dpSelect & 0x40) == 0)) {digitThree = 0x00;}
  if((value < 10) && ((forceDig_dpSelect & 0x20) == 0)) {digitTwo = 0x00;}
  if((value < 1) && ((forceDig_dpSelect & 0x10) == 0)) {digitOne = 0x00;}
  digitOne = digitOne | ((forceDig_dpSelect << 7) & 0x80);  //the lower four bits of forceDig_dpSelect activate the decimal points
  digitTwo = digitTwo | ((forceDig_dpSelect << 6) & 0x80);
  digitThree = digitThree | ((forceDig_dpSelect << 5) & 0x80);
  digitFour = digitFour | ((forceDig_dpSelect << 4) & 0x80);
    //send digits serially to display chip. digit order is a matter of hardware wiring. play around with it if yours isn't coming out right
  saa1064Update(addr, ctrlByte, digitFour, digitTwo, digitThree, digitOne);

byte findSegs(int alpha)  //this routine converts a single digit decimal into a byte to drive a 7-segment display
   case 0:
   return 0x3F;            //These byte values are designed for a SAA1064 wired with P1-8 connected to a, b, c, d, e, f, g, dp of the first
                           //digit (or pair of digits if multiplexing), respectively. P9-16 are similarly connected to the second digit 
   case 1:
   return 0x06;
   case 2:
   return 0x5B;
   case 3:
   return 0x4F;
   case 4:
   return 0x66;
   case 5:
   return 0x6D;
   case 6:
   return 0x7D;
   case 7:
   return 0x07;
   case 8:
   return 0x7F;
   case 9:
   return 0x6F;
   return 0x00;

//The following are miscellaneous functions.

int readAnalogFourButtonArray(int analogPin)  //this function converts the value from the button voltage divider to a useful value
  int analogIn = analogRead(analogPin);
  /* the following values are selected to work with a voltage divider consisting of four buttons lined up in parallel. one contact
     of each button is connected to VCC (5V+). the remaining contact goes to a resistor. the first button has a 15k-ohm resistor, 
     the second has a 4.7k-ohm resistor, the third has a 1.5K-ohm resistor, and the fourth has a 0-ohm resistor (a plain wire). the 
     remaining lead of the resistors (and jumper) are connected together and tied to an analog pin of the user's choice. to complete
     the voltage divider, a 4.7k-ohm resistor is connected between the resistors-analog pin connection and ground. this circuit 
     presents 0V when no buttons are being pressed, about 1.25V when button one is pressed, 2.5V when button two is pressed, 3.75V
     for button three and 5V for button 4. the higher numbered button dominates if more than one button is pressed at the same time. */
  if(analogIn > 120)
    if(analogIn > 380)
      if(analogIn > 640)
        if(analogIn > 900)
          return 4;
        return 3;
      return 2;
    return 1;
  return 0;
  Reply With Quote