#include #include #include /* OBDuino32K (Requires Atmega328 for your Arduino) Copyright (C) 2008-2010 Main coding/ISO/ELM: Fredric (aka Magister on ecomodder.com) ISO Communication Protocol: Russ, Antony, Mike Features: Mike, Antony, Eimantas Bugs & Fixes: Antony, Fredric, Mike, Eimantas LCD bignum: Fredric based on mpguino code by Dave, Eimantas Latest Changes Nov 7th, 2010 Added Hybrid Big Num Screen for display AVG in Bigfont, and instant in small font in the corner Oct 28th, 2010 Changes to make BigNum fuctions work more fluently September 14th, 2010: Different currency strings for different currencies. PIDs caching during same calculation cycle. Adjusting tank used fuel and distance. September 6th, 2010: If ISO_Reinit is used added modification to skip startup init. ISO_Reinit is used for first and all other initalizations ISO delay constants (10ms and 55ms) moved to define section. Lower values then allowed in specification works on some cars. LCD bignum August 31st, 2010: ISO 9141 VW MK4 compatible Gasoline/LPG/Diesel support - constant in define section DTC read & clear improvement DTC read enable/disable on start DTC read & clear rebuild tested and working External temperature sensor like KTY81-210 support Saving TRIP data in ISO reinit mode after engine is turned off Turning off backlight in ISO reinit mode if RPM = 0 August 30th, 2010: Some LCD optimizations, formula for MAP, fix check_mil (untested) June 9th, 2009: ISO 9141 re-init, ECU polling, Car alarm and other tweaks by Antony June 24, 1009: Added three parameters to the mix, removed unrequired RPM call, added off and full to backlight levels, added waste PIDs: Antony June 25, 2009: Use the metric parameter for fuel price and tank size: Antony June 27, 2009: Minor corrections and tweaks: Antony July 23, 2009: New menuing system for parameters, and got rid of display flicker: Antony Sept 01, 2009: Better handling of 14230 protocol. Tweak in clear button routine: Antony Sept 27, 2009: Correct four line LCD positioning: Nickdigger (via ecomodder.com) To-Do: Bugs: 1. Fix code to retrieve stored trouble codes. (2010.08.22 fixed in ISO, ELM not tested): 2. Make the Menu response less clunky Features Requested: 1. Aero-Drag calculations? 2. SD Card logging 3. Add another Fake PID to track max values ( Speed, RPM, Tank KM's, etc...) 4. Add a "Score" PID, to rate you for a trip (Quickness, IdleTime, Fuel Used etc as factors) Other: 1. Trim the Code as we are aproaching the 30.7K limit. 2. Move variable caching strings to PROGMEM 3. Add a "dirty" flag to tank data when the obduino detects that it has been disconnected from the car to indicate that the data may no longer be complete This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA */ // Some source about fuel/air ratio mixtures: http://www.apvgn.pt/documentacao/iangv_rep_part2.pdf /**************************/ /* GASOLINE ENGINE CONFIG */ /**************************/ // [CONFIRMED] For gas car use 3355 (1/14.7/730*3600)*10000 #define GasConst 3355 #define GasMafConst 107310 // 14.7*730*10 /************************/ /* LPG ENGINE CONFIG */ /************************/ //LPG mass/volume is 520-580gr/ltr depending on propane/butane mix // LPG/air ratio: // 15.8:1 if 50/50 propane/butate is used // 15:1 if 100 propane is used // 15.4 if 60/40 propane/butane is used // experiments shows that something in middle should be used eg. 15.4:1 :) // [TEST PROGRESS] For lpg(summer >20C) car use 4412 (1/15.4/540*3600)*10000 //#define GasConst 4329 //#define GasMafConst 83160 // 15.4*540*10 = 83160 /************************/ /* DIESEL ENGINE CONFIG */ /************************/ // [NOT TESTED] For diesel car use ??? (1/??/830*3600)*10000 //#define GasConst ???? //#define GasMafConst ??? // ??*830*10 // Compilation modifiers: // The following will cause the compiler to add or remove features from the OBDuino build this keeps the // build size down, will not allow 'on the fly' changes. Some features are dependant on other features. // Comment out to disable big numebers (4th and 5th sceeens) #define use_BIG_font // Uncomment only one, that will be used. //#define BIG_font_type_3x2 // CAN'T BE USED WITH BIG_font_hybrid //#define BIG_font_type_2x2_alpha #define BIG_font_type_2x2_beta //Uncomment to enable hybrid display on Average fuel Big num screen //This will display Big Num Average, and in the corner, The instant #define BIG_font_hybrid // Comment for normal build // Uncomment for a debug build //#define DEBUG //#define SERIAL_DEBUG // Comment for normal output build // Uncomment for a debug output build //#define DEBUGOutput // Comment out to just try the PIDs without need to find ECU // Uncomment to use ECU polling to see if car is On or Off #define useECUState // Comment out to use the PID screen when the car is off (This will interfere with ISO reinit process) // Uncomment to use the Car Alarm Screen when the car is off //#define carAlarmScreen // Comment out to disable trip data saving after engine is off and RPM = 0 // Uncomment to save trip data after engine is off and RPM = 0 #define SaveTripDataAfterEngineTurnOff // Comment out to read DTC on OBDuino start. // Uncomment to disable DTC read. #define DisableDTCReadOnStart // Comment out to do not use temperature sensor // Uncomment to use temperature sensor //#define UseInsideTemperatureSensor //#define UseOutsideTemperatureSensor //#define TemperatureSensorTypeKTY81_210 // Define currency symbols #define CurrencyPrintString "$%s" //#define CurrencyPrintString "%s Lt" #define CurrencyAdjustString "- $%s + " //#define CurrencyAdjustString "- %s Lt +" // Uncomment to use PIDs cache, // it makes faster refresh rate in ISO mode, but uses ~200bytes of memory #define UsePIDCache #undef int #include #include #include #include #include // LCD Pins same as mpguino // rs=8, enable=14 (analog Pin 0), data=4,5,6,7 LiquidCrystal lcd(8, 14, 3, 4, 5, 7); #define ContrastPin 6 #define BrightnessPin 9 // LCD prototypes void lcd_print_P(char *string); // to work with string in flash and PSTR() void lcd_cls_print_P(char *string); // clear screen and display string void lcd_char_init(); void lcd_char_bignum(); void bigNum(unsigned long t, char *txt1, char *txt2); // Memory prototypes void params_load(void); void params_save(void); // Others prototypes byte menu_selection(char ** menu, byte arraySize); byte menu_select_yes_no(byte p); void long_to_dec_str(long value, char *decs, byte prec); int memoryTest(void); void test_buttons(void); void get_cost(char *retbuf, byte ctrip); #define KEY_WAIT 300 // Wait for potential other key press //Was 1000, but 300 works better #define ACCU_WAIT 500 // Only accumulate data so often. #define BUTTON_DELAY 100 //Was 125, but 100 works better #ifdef UseOutsideTemperatureSensor #define OutsideTemperaturePin 15 // Inside temperature sensor, on analog 1 #endif #ifdef UseInsideTemperatureSensor #define InsideTemperaturePin 16 // Inside temperature sensor, on analog 2 #endif #define MAX_CONTRAST 100 #define MIN_CONTRAST 0 #define CONTRAST_INCR 10 /* our internal fake PIDs */ #define FIRST_FAKE_PID 0xE7 // same as the first one defined below #define OUTSIDE_TEMP 0xE7 // temperature outside the car #define INSIDE_TEMP 0xE8 // temperature inside the car #define OUTING_WASTE 0xE9 // fuel wasted since car started #define TRIP_WASTE 0xEA // fuel wasted during trip #define TANK_WASTE 0xEB // fuel wasted for this tank #define OUTING_COST 0xEC // the money spent since car started #define TRIP_COST 0xED // money spent since on trip #define TANK_COST 0xEE // money spent of current tank #define ENGINE_ON 0xEF // The length of time car has been running. #define NO_DISPLAY 0xF0 #define FUEL_CONS 0xF1 // instant cons #define TANK_CONS 0xF2 // average cons of tank #define TANK_FUEL 0xF3 // fuel used in tank #define TANK_DIST 0xF4 // distance for tank #define REMAIN_DIST 0xF5 // remaining distance of tank #define TRIP_CONS 0xF6 // average cons of trip #define TRIP_FUEL 0xF7 // fuel used in trip #define TRIP_DIST 0xF8 // distance of trip #define BATT_VOLTAGE 0xF9 #define OUTING_CONS 0xFA // cons since the engine turned on #define OUTING_FUEL 0xFB // fuel used since engine turned on #define OUTING_DIST 0xFC // distance since engine turned on #define CAN_STATUS 0xFD #define PID_SEC 0xFE #ifdef DEBUG #define FREE_MEM 0xFF #else #define ECO_VISUAL 0xFF // Visually dispay relative economy with text (at end of program) #endif #define UP 1 #define DOWN 0 #define LBUTTON 0 #define MBUTTON 1 #define RBUTTON 2 #define PRESSED 1 #define NUMBUTTONS 3 #define DEBOUNCE 40 // ms for debounce // use analog pins as digital pins for buttons #define lbuttonPin 17 // Left Button, on analog 5 #define mbuttonPin 18 // Middle Button, on analog 4 #define rbuttonPin 19 // Right Button, on analog 3 #define lbuttonBit 1 // Bit position #define mbuttonBit 2 // Bit position #define rbuttonBit 4 // Bit position // Easy to read macros #define LEFT_BUTTON_PRESSED(x) (x & lbuttonBit) #define MIDDLE_BUTTON_PRESSED(x) (x & mbuttonBit) #define RIGHT_BUTTON_PRESSED(x) (x & rbuttonBit) int currentButtons[NUMBUTTONS] = {UP, UP, UP}; int previousButtons[NUMBUTTONS] = {UP, UP, UP}; unsigned long lastButtonTimeDown[NUMBUTTONS] = {0, 0, 0}; #define brightnessLength 7 //array size const byte brightness[brightnessLength]={ 0xFF, 0xFF/(brightnessLength+10)*(brightnessLength+10-1), // in night needs more darker 0xFF/brightnessLength*(brightnessLength-1), 0xFF/brightnessLength*(brightnessLength-2), 0xFF/brightnessLength*(brightnessLength-4), 0xFF/brightnessLength*(brightnessLength-5), 0x00}; // right button cycles through these brightness settings (off to on full) byte brightnessIdx=2; /* LCD Display parameters */ /* Adjust LCD_COLS or LCD_ROWS if LCD is different than 16 characters by 2 rows*/ // Note: Not currently tested on display larger than 16x2 // How many rows of characters for the LCD (must be at least two) #define LCD_ROWS 2 // How many characters across for the LCD (must be at least sixteen) #define LCD_COLS 16 // Calculate the middle point of the LCD display width #define LCD_SPLIT (LCD_COLS / 2) //Calculate how many PIDs fit on a data screen (two per line) #define LCD_PID_COUNT (LCD_ROWS * 2) unsigned long pid01to20_support; // this one always initialized at setup() unsigned long pid21to40_support=0; unsigned long pid41to60_support=0; //The Textual Description of each PID prog_char PID_Desc[256][9] PROGMEM= { "PID00-21", // 0x00 PIDs supported "Stat DTC", // 0x01 Monitor status since DTCs cleared. "Frz DTC", // 0x02 Freeze DTC "Fuel SS", // 0x03 Fuel system status "Eng Load", // 0x04 Calculated engine load value "CoolantT", // 0x05 Engine coolant temperature "ST F%T 1", // 0x06 Short term fuel % trim Bank 1 "LT F%T 1", // 0x07 Long term fuel % trim Bank 1 "ST F%T 2", // 0x08 Short term fuel % trim Bank 2 "LT F%T 2", // 0x09 Long term fuel % trim Bank 2 "Fuel Prs", // 0x0A Fuel pressure " MAP ", // 0x0B Intake manifold absolute pressure " RPM ", // 0x0C Engine RPM " Speed ", // 0x0D Vehicle speed "Timing A", // 0x0E Timing advance "Intake T", // 0x0F Intake air temperature "MAF rate", // 0x10 MAF air flow rate "Throttle", // 0x11 Throttle position "Cmd SAS", // 0x12 Commanded secondary air status "Oxy Sens", // 0x13 Oxygen sensors present "Oxy B1S1", // 0x14 Oxygen Sensor Bank 1, Sensor 1 "Oxy B1S2", // 0x15 Oxygen Sensor Bank 1, Sensor 2 "Oxy B1S3", // 0x16 Oxygen Sensor Bank 1, Sensor 3 "Oxy B1S4", // 0x17 Oxygen Sensor Bank 1, Sensor 4 "Oxy B2S1", // 0x18 Oxygen Sensor Bank 2, Sensor 1 "Oxy B2S2", // 0x19 Oxygen Sensor Bank 2, Sensor 2 "Oxy B2S3", // 0x1A Oxygen Sensor Bank 2, Sensor 3 "Oxy B2S4", // 0x1B Oxygen Sensor Bank 2, Sensor 4 "OBD Std", // 0x1C OBD standards this vehicle conforms to "Oxy Sens", // 0x1D Oxygen sensors present "AuxInpt", // 0x1E Auxiliary input status "Run Time", // 0x1F Run time since engine start "PID21-40", // 0x20 PIDs supported 21-40 "Dist MIL", // 0x21 Distance traveled with malfunction indicator lamp (MIL) on "FRP RMF", // 0x22 Fuel Rail Pressure (relative to manifold vacuum) "FRP Dies", // 0x23 Fuel Rail Pressure (diesel) "OxyS1 V", // 0x24 O2S1_WR_lambda(1): ER Voltage "OxyS2 V", // 0x25 O2S2_WR_lambda(1): ER Voltage "OxyS3 V", // 0x26 O2S3_WR_lambda(1): ER Voltage "OxyS4 V", // 0x27 O2S4_WR_lambda(1): ER Voltage "OxyS5 V", // 0x28 O2S5_WR_lambda(1): ER Voltage "OxyS6 V", // 0x29 O2S6_WR_lambda(1): ER Voltage "OxyS7 V", // 0x2A O2S7_WR_lambda(1): ER Voltage "OxyS8 V", // 0x2B O2S8_WR_lambda(1): ER Voltage "Cmd EGR", // 0x2C Commanded EGR "EGR Err", // 0x2D EGR Error "Cmd EP", // 0x2E Commanded evaporative purge "Fuel LI", // 0x2F Fuel Level Input "WarmupCC", // 0x30 # of warm-ups since codes cleared "Dist CC", // 0x31 Distance traveled since codes cleared "Evap SVP", // 0x32 Evap. System Vapor Pressure "Barometr", // 0x33 Barometric pressure "OxyS1 C", // 0x34 O2S1_WR_lambda(1): ER Current "OxyS2 C", // 0x35 O2S2_WR_lambda(1): ER Current "OxyS3 C", // 0x36 O2S3_WR_lambda(1): ER Current "OxyS4 C", // 0x37 O2S4_WR_lambda(1): ER Current "OxyS5 C", // 0x38 O2S5_WR_lambda(1): ER Current "OxyS6 C", // 0x39 O2S6_WR_lambda(1): ER Current "OxyS7 C", // 0x3A O2S7_WR_lambda(1): ER Current "OxyS8 C", // 0x3B O2S8_WR_lambda(1): ER Current "C T B1S1", // 0x3C Catalyst Temperature Bank 1 Sensor 1 "C T B1S2", // 0x3D Catalyst Temperature Bank 1 Sensor 2 "C T B2S1", // 0x3E Catalyst Temperature Bank 2 Sensor 1 "C T B2S2", // 0x3F Catalyst Temperature Bank 2 Sensor 2 "PID41-60", // 0x40 PIDs supported 41-60 " MStDC", // 0x41 Monitor status this drive cycle "Ctrl M V", // 0x42 Control module voltage "Abs L V", // 0x43 Absolute load value "Cmd E R", // 0x44 Command equivalence ratio "R ThrotP", // 0x45 Relative throttle position "Amb Temp", // 0x46 Ambient air temperature "Acc PP B", // 0x47 Absolute throttle position B "Acc PP C", // 0x48 Absolute throttle position C "Acc PP D", // 0x49 Accelerator pedal position D "Acc PP E", // 0x4A Accelerator pedal position E "Acc PP F", // 0x4B Accelerator pedal position F "Cmd T A", // 0x4C Commanded throttle actuator "T MIL On", // 0x4D Time run with MIL on "T TC Crl", // 0x4E Time since trouble codes cleared " 0x4F", // 0x4F Unknown " 0x50", // 0x50 Unknown "Fuel Typ", // 0x51 Fuel Type "Ethyl F%", // 0x52 Ethanol fuel % "", // 0x53 "", // 0x54 "", // 0x55 "", // 0x56 "", // 0x57 "", // 0x58 "", // 0x59 "", // 0x5A "", // 0x5B "", // 0x5C "", // 0x5D "", // 0x5E "", // 0x5F "", // 0x60 "", // 0x61 "", // 0x62 "", // 0x63 "", // 0x64 "", // 0x65 "", // 0x66 "", // 0x67 "", // 0x68 "", // 0x69 "", // 0x6A "", // 0x6B "", // 0x6C "", // 0x6D "", // 0x6E "", // 0x6F "", // 0x70 "", // 0x71 "", // 0x72 "", // 0x73 "", // 0x74 "", // 0x75 "", // 0x76 "", // 0x77 "", // 0x78 "", // 0x79 "", // 0x7A "", // 0x7B "", // 0x7C "", // 0x7D "", // 0x7E "", // 0x7F "", // 0x80 "", // 0x81 "", // 0x82 "", // 0x83 "", // 0x84 "", // 0x85 "", // 0x86 "", // 0x87 "", // 0x88 "", // 0x89 "", // 0x8A "", // 0x8B "", // 0x8C "", // 0x8D "", // 0x8E "", // 0x8F "", // 0x90 "", // 0x91 "", // 0x92 "", // 0x93 "", // 0x94 "", // 0x95 "", // 0x96 "", // 0x97 "", // 0x98 "", // 0x99 "", // 0x9A "", // 0x9B "", // 0x9C "", // 0x9D "", // 0x9E "", // 0x9F "", // 0xA0 "", // 0xA1 "", // 0xA2 "", // 0xA3 "", // 0xA4 "", // 0xA5 "", // 0xA6 "", // 0xA7 "", // 0xA8 "", // 0xA9 "", // 0xAA "", // 0xAB "", // 0xAC "", // 0xAD "", // 0xAE "", // 0xAF "", // 0xB0 "", // 0xB1 "", // 0xB2 "", // 0xB3 "", // 0xB4 "", // 0xB5 "", // 0xB6 "", // 0xB7 "", // 0xB8 "", // 0xB9 "", // 0xBA "", // 0xBB "", // 0xBC "", // 0xBD "", // 0xBE "", // 0xBF "", // 0xC0 "", // 0xC1 "", // 0xC2 "", // 0xC3 Unknown "", // 0xC4 Unknown "", // 0xC5 "", // 0xC6 "", // 0xC7 "", // 0xC8 "", // 0xC9 "", // 0xCA "", // 0xCB "", // 0xCC "", // 0xCD "", // 0xCE "", // 0xCF "", // 0xD0 "", // 0xD1 "", // 0xD2 "", // 0xD3 "", // 0xD4 "", // 0xD5 "", // 0xD6 "", // 0xD7 "", // 0xD8 "", // 0xD9 "", // 0xDA "", // 0xDB "", // 0xDC "", // 0xDD "", // 0xDE "", // 0xDF "", // 0xE0 "", // 0xE1 "", // 0xE2 "", // 0xE3 "", // 0xE4 "", // 0xE5 "", // 0xE6 "OutsideT", // 0xE7 temperature outside car "Inside T", // 0xE8 temperature inside car "OutWaste", // 0xE9 outing waste "TrpWaste", // 0xEA trip waste "TnkWaste", // 0xEB tank waste "Out Cost", // 0xEC outing cost "Trp Cost", // 0xED trip cost "Tnk Cost", // 0xEE tank cost "Out Time", // 0xEF The length of time car has been running "No Disp", // 0xF0 No display "InstCons", // 0xF1 instant cons "Tnk Cons", // 0xF2 average cons of tank "Tnk Fuel", // 0xF3 fuel used in tank "Tnk Dist", // 0xF4 distance for tank "Dist2MT", // 0xF5 remaining distance of tank "Trp Cons", // 0xF6 average cons of trip "Trp Fuel", // 0xF7 fuel used in trip "Trp Dist", // 0xF8 distance of trip "Batt Vlt", // 0xF9 Battery Voltage "Out Cons", // 0xFA cons since the engine turned on "Out Fuel", // 0xFB fuel used since engine turned on "Out Dist", // 0xFC distance since engine turned on "Can Stat", // 0xFD Can Status "PID_SEC", // 0xFE "Eco Vis", // 0xFF Visually dispay relative economy with text }; const prog_char obd_std_strings[17][9] PROGMEM = { /*00*/ /*{ "" },*/ { "OBD2CARB" }, { "OBDEPA" }, { "OBDEPA&2" }, /*04*/ { "OBD1" }, { "NO OBD" }, { "EOBD" }, { "EOBD&2" }, /*08*/ { "EOBD&EPA" }, { "E&EPA&2" }, { "JOBD" }, { "JOBD&2" }, /*0C*/ { "J&EOBD" }, { "J&EOBD&2" }, { "EURO4B1" }, { "EURO5B2" }, /*10*/ { "EURO C" }, { "EMD" } }; // returned length of the PID response. // constants so put in flash prog_uchar pid_reslen[] PROGMEM= { // pid 0x00 to 0x1F 4,4,2,2,1,1,1,1,1,1,1,1,2,1,1,1, 2,1,1,1,2,2,2,2,2,2,2,2,1,1,1,4, // pid 0x20 to 0x3F 4,2,2,2,4,4,4,4,4,4,4,4,1,1,1,1, 1,2,2,1,4,4,4,4,4,4,4,4,2,2,2,2, // pid 0x40 to 0x4E 4,8,2,2,2,1,1,1,1,1,1,1,1,2,2 }; // Number of screens of PIDs #define NBSCREEN 3 // 12 PIDs should be enough for everyone byte active_screen=0; // 0,1,2,... selected by left button prog_char pctd[] PROGMEM="- %d + "; // used in a couple of place prog_char pctdpctpct[] PROGMEM="- %d%% + "; // used in a couple of place prog_char pctspcts[] PROGMEM="%s %s"; // used in a couple of place prog_char pctldpcts[] PROGMEM="%ld %s"; // used in a couple of place prog_char select_no[] PROGMEM="(NO) YES "; // for config menu prog_char select_yes[] PROGMEM=" NO (YES)"; // for config menu prog_char gasPrice[][10] PROGMEM={"- %s\354 + ", CurrencyAdjustString}; // dual string for fuel price // menu items used by menu_selection. prog_char *topMenu[] PROGMEM = {"Configure menu", "Exit", "Display", "Adjust", "PIDs", "Clear DTC"}; prog_char *displayMenu[] PROGMEM = {"Display menu", "Exit", "Contrast", "Metric", "Fuel/Hour"}; prog_char *adjustMenu[] PROGMEM = {"Adjust menu", "Exit", "Tank Size", "Fuel Cost", "Fuel %", "Speed %", "Out Wait", "Trip Wait", "Tank Used", "Tank Dist", "Eng Disp", }; prog_char *PIDMenu[] PROGMEM = {"PID Screen menu", "Exit", "Scr 1", "Scr 2", "Scr 3"}; #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) // Time information #define MILLIS_PER_HOUR 3600000L #define MILLIS_PER_MINUTE 60000L #define MILLIS_PER_SECOND 1000L // to differenciate trips #define TANK 0 #define TRIP 1 #define OUTING 2 //Tracks your current outing #define NBTRIP 3 prog_char * tripNames[NBTRIP] PROGMEM = { "Tank", "Trip", "Outing" }; // parameters // each trip contains fuel used and distance done typedef struct { unsigned long dist; // in cm unsigned long fuel; // in uL unsigned long waste; // in uL } trip_t; // each screen contains n PIDs (two per line) typedef struct { byte PID[LCD_PID_COUNT]; } screen_t; #define MINUTES_GRANULARITY 10 typedef struct { byte contrast; // we only use 0-100 value in step 20 byte use_metric; // 0=rods and hogshead, 1=SI boolean use_comma; // When using metric, also use the comma decimal separator byte per_hour_speed; // speed from which we toggle to fuel/hour (km/h or mph) byte fuel_adjust; // because of variation from car to car, temperature, etc byte speed_adjust; // because of variation from car to car, tire size, etc byte eng_dis; // engine displacement in dL unsigned int gas_price; // price per unit of fuel in 10th of cents. 905 = $0.905 unsigned int tank_size; // tank size in dL or dgal depending of unit byte OutingStopOver; // Allowable stop over time (in tens of minutes). Exceeding time starts a new outing. byte TripStopOver; // Allowable stop over time (in hours). Exceeding time starts a new outing. trip_t trip[NBTRIP]; // trip0=tank, trip1=a trip screen_t screen[NBSCREEN]; // screen } params_t; // parameters default values params_t params= { 40, // Contrast - 40 does not work with some LCD, or some mysterious problem so try 0 if it does not work 1, // use metric true, // use comma 20, // per hour speed 100, // fuel adjust 100, // speed adjust 15, // engine displacement 1090, // gas price in 10th of cents 450, // tank size 6, // outing stopver 60 minutes (6 X 10) stop or less will not cause outing reset 12, // trip stopover - 12 hour stop or less will not cause trip reset { { 0,0,0 }, // tank: dist, fuel, waste { 0,0,0 }, // trip: dist, fuel, waste { 0,0,0 } // outing:dist, fuel, waste }, { { {FUEL_CONS,LOAD_VALUE,TANK_CONS,OUTING_FUEL #if LCD_ROWS == 4 ,OUTING_WASTE,OUTING_COST,ENGINE_ON,LOAD_VALUE #endif } }, { {TRIP_CONS,TRIP_DIST,TRIP_FUEL,COOLANT_TEMP #if LCD_ROWS == 4 ,TRIP_WASTE,TRIP_COST,INT_AIR_TEMP,THROTTLE_POS #endif } }, { {TANK_CONS,TANK_DIST,TANK_FUEL,REMAIN_DIST #if LCD_ROWS == 4 ,TANK_WASTE,TANK_COST,ENGINE_RPM,VEHICLE_SPEED #endif } } } }; prog_char * econ_Visual[] PROGMEM= { "Yuck!!8{", "Aweful:(", "Poor :[", "OK :|", "Good :]", "Great :)", "Adroit:D", "HyprM 8D" }; #define STRLEN 40 long tempLong; // Useful for transitory values while getting PID information. // some globals, for trip calculation and others unsigned long old_time; byte has_rpm=0; long vss=0; // speed long maf=0; // MAF unsigned long engine_on, engine_off; //used to track time of trip. #ifdef UsePIDCache // Cache PID's value #define MaxPIDCacheCount 5 typedef struct { byte PID; long Value; unsigned long Time; char String[STRLEN]; } TPIDCache; TPIDCache PIDCache[MaxPIDCacheCount]; //prog_char PIDCacheString[MaxPIDCacheCount][STRLEN] PROGMEM={"","","","",""}; // Must be initialized byte PIDCacheCount=0; #endif unsigned long getpid_time; byte nbpid_per_second=0; // flag used to save distance/average consumption in eeprom only if required byte engine_started=0; byte param_saved=0; #ifdef useECUState boolean oldECUconnection; // Used to test for change in ECU connection state #endif #ifdef carAlarmScreen boolean refreshAlarmScreen; // Used to cause non-repeating screen data to display #endif boolean ECUconnection; // Have we connected to the ECU or not // the buttons interrupt // this is the interrupt handler for button presses ISR(PCINT1_vect) { unsigned long m = millis(); currentButtons[LBUTTON] = digitalRead(lbuttonPin); currentButtons[MBUTTON] = digitalRead(mbuttonPin); currentButtons[RBUTTON] = digitalRead(rbuttonPin); for (int loop = 0; loop < NUMBUTTONS; loop++) { // Button down press if ((previousButtons[loop] == UP) && (currentButtons[loop] == DOWN)) { lastButtonTimeDown[loop] = m; } // Button release press else if ((previousButtons[loop] == DOWN) && (currentButtons[loop] == UP)) { lastButtonTimeDown[loop] = 0; } previousButtons[loop] = currentButtons[loop]; } } #ifdef DEBUG #define READ_ATTEMPTS 2 #else #define READ_ATTEMPTS 125 #endif // return false if pid is not supported, true if it is. // mode is 0 for get_pid() and 1 for menu config to allow pid > 0xF0 boolean is_pid_supported(byte pid, byte mode) { if(pid==0) return true; else if(pid<=0x20) { if(1L<<(uint8_t)(0x20-pid) & pid01to20_support) return true; } else if(pid<=0x40) { if(1L<<(uint8_t)(0x40-pid) & pid21to40_support) return true; } else if(pid<=0x60) { if(1L<<(uint8_t)(0x60-pid) & pid41to60_support) return true; } else if( mode && pid>=FIRST_FAKE_PID) return true; return false; } // Get value of a PID, and place in long pointer // and also formatted for string output in the return buffer // Return value denotes successful retrieval of PID. // User must pass in a long pointer to get the PID value. boolean get_pid(byte pid, char *retbuf, long *ret) { byte i; byte tempBuf[10]; // to receive the result byte *buf=&tempBuf[3]; // to receive the result byte reslen; char decs[16]; unsigned long time_now, delta_time; static byte nbpid=0; nbpid++; // time elapsed time_now = millis(); delta_time = time_now - getpid_time; if(delta_time>1000) { nbpid_per_second=nbpid; nbpid=0; getpid_time=time_now; } // check if PID is supported (should not happen except for some 0xFn) if(!is_pid_supported(pid, 0)) { #ifdef DEBUGOutput char myBuf[30]; lcd.setCursor(0,1); sprintf_P(myBuf, PSTR("not supported ")); lcd.print(myBuf); delay(1000); #endif // nope sprintf_P(retbuf, PSTR("%02X N/A"), pid); return false; } #ifdef UsePIDCache // Check if PID is available in PID cache byte j=0; while (j < PIDCacheCount && PIDCache[j].PID != pid) { j++; } if (j < PIDCacheCount) { *ret=PIDCache[j].Value; strcpy(retbuf, PIDCache[j].String); // strcpy_P(retbuf, PIDCacheString[j]); return true; } #endif // receive length depends on pid reslen=pgm_read_byte_near(pid_reslen+pid); #ifndef DEBUG if(OBDIICanbus.request_byId(FUNC_PID_REQUEST, pid, tempBuf) != STATUS_OK) { #ifndef DEBUG strcpy_P(retbuf, PSTR("ERROR")); #ifdef DEBUGOutput lcd.setCursor(0,1); lcd_print_P(PSTR("request failed")); delay(2000); #endif return false; #endif } #endif // a lot of formulas are the same so calculate a default return value here // even if it's scrapped after, we still saved 40 bytes! *ret=buf[0]*256U+buf[1]; #ifdef DEBUGOutput sprintf_P(retbuf, PSTR("pid=0x%X"), pid); lcd.setCursor(0,1); lcd.print(retbuf); delay(1000); #endif // formula and unit for each PID switch(pid) { case ENGINE_RPM: #ifdef DEBUG *ret=1726; #else *ret=*ret/4U; #endif sprintf_P(retbuf, PSTR("%ld RPM"), *ret); break; case MAF_AIR_FLOW: #ifdef DEBUG *ret=2048; #endif // ret is not divided by 100 for return value!! long_to_dec_str(*ret, decs, 2); sprintf_P(retbuf, PSTR("%s g/s"), decs); break; case VEHICLE_SPEED: #ifdef DEBUG *ret=100; #else *ret=(buf[0] * params.speed_adjust) / 100U; #endif if(!params.use_metric) *ret=(*ret*1000U)/1609U; sprintf_P(retbuf, pctldpcts, *ret, params.use_metric?"\003\004":"\006\004"); // do not touch vss, it is used by fuel calculation after, so reset it #ifdef DEBUG *ret=100; #else *ret=(buf[0] * params.speed_adjust) / 100U; #endif break; case FUEL_STATUS: #ifdef DEBUG *ret=0x0200; #endif if(buf[0]==0x01) strcpy_P(retbuf, PSTR("OPENLOWT")); // open due to insufficient engine temperature else if(buf[0]==0x02) strcpy_P(retbuf, PSTR("CLSEOXYS")); // Closed loop, using oxygen sensor feedback to determine fuel mix. should be almost always this else if(buf[0]==0x04) strcpy_P(retbuf, PSTR("OPENLOAD")); // Open loop due to engine load, can trigger DFCO else if(buf[0]==0x08) strcpy_P(retbuf, PSTR("OPENFAIL")); // Open loop due to system failure else if(buf[0]==0x10) strcpy_P(retbuf, PSTR("CLSEBADF")); // Closed loop, using at least one oxygen sensor but there is a fault in the feedback system else sprintf_P(retbuf, PSTR("%04lX"), *ret); break; case LOAD_VALUE: case THROTTLE_POS: case REL_THR_POS: case EGR: case EGR_ERROR: case FUEL_LEVEL: case ABS_THR_POS_B: case ABS_THR_POS_C: case ACCEL_PEDAL_D: case ACCEL_PEDAL_E: case ACCEL_PEDAL_F: case CMD_THR_ACTU: #ifdef DEBUG *ret=17; #else *ret=(buf[0]*100U)/255U; #endif sprintf_P(retbuf, PSTR("%ld %%"), *ret); break; case ABS_LOAD_VAL: *ret=(*ret*100)/255; sprintf_P(retbuf, PSTR("%ld %%"), *ret); break; case B1S1_O2_V: case B1S2_O2_V: case B1S3_O2_V: case B1S4_O2_V: case B2S1_O2_V: case B2S2_O2_V: case B2S3_O2_V: case B2S4_O2_V: *ret=buf[0]*5U; // not divided by 1000 for return!! if(buf[1]==0xFF) // not used in trim calculation sprintf_P(retbuf, PSTR("%ld mV"), *ret); else sprintf_P(retbuf, PSTR("%ldmV/%d%%"), *ret, ((buf[1]-128)*100)/128); break; case O2S1_WR_V: case O2S2_WR_V: case O2S3_WR_V: case O2S4_WR_V: case O2S5_WR_V: case O2S6_WR_V: case O2S7_WR_V: case O2S8_WR_V: case O2S1_WR_C: case O2S2_WR_C: case O2S3_WR_C: case O2S4_WR_C: case O2S5_WR_C: case O2S6_WR_C: case O2S7_WR_C: case O2S8_WR_C: case CMD_EQUIV_R: *ret=(*ret*100)/32768; // not divided by 1000 for return!! long_to_dec_str(*ret, decs, 2); sprintf_P(retbuf, PSTR("l:%s"), decs); break; case DIST_MIL_ON: case DIST_MIL_CLR: if(!params.use_metric) *ret=(*ret*1000U)/1609U; sprintf_P(retbuf, pctldpcts, *ret, params.use_metric?"\003":"\006"); break; case TIME_MIL_ON: case TIME_MIL_CLR: sprintf_P(retbuf, PSTR("%ld min"), *ret); break; case COOLANT_TEMP: case INT_AIR_TEMP: case AMBIENT_TEMP: case CAT_TEMP_B1S1: case CAT_TEMP_B2S1: case CAT_TEMP_B1S2: case CAT_TEMP_B2S2: if(pid>=CAT_TEMP_B1S1 && pid<=CAT_TEMP_B2S2) #ifdef DEBUG *ret=600; #else *ret=*ret/10U - 40; #endif else #ifdef DEBUG *ret=40; #else *ret=buf[0]-40; #endif if(!params.use_metric) *ret=(*ret*9)/5+32; sprintf_P(retbuf, PSTR("%ld\005%c"), *ret, params.use_metric?'C':'F'); break; case STFT_BANK1: case LTFT_BANK1: case STFT_BANK2: case LTFT_BANK2: *ret=(buf[0]-128)*7812; // not divided by 10000 for return value long_to_dec_str(*ret/100, decs, 2); sprintf_P(retbuf, PSTR("%s %%"), decs); break; case FUEL_PRESSURE: case MAN_PRESSURE: case BARO_PRESSURE: *ret=buf[0]; if(pid==FUEL_PRESSURE) *ret*=3U; sprintf_P(retbuf, PSTR("%ld kPa"), *ret); break; case EVAP_PRESSURE: *ret=((int)buf[0]*256+buf[1])/4; sprintf_P(retbuf, PSTR("%d kPa"), (int)*ret); break; case TIMING_ADV: *ret=(buf[0]/2)-64; sprintf_P(retbuf, PSTR("%ld\005"), *ret); break; case CTRL_MOD_V: long_to_dec_str(*ret/10, decs, 2); sprintf_P(retbuf, PSTR("%s V"), decs); break; case RUNTIME_START: sprintf_P(retbuf, PSTR("%u:%02u:%02u"), (unsigned int)*ret/3600, (unsigned int)(*ret/60)%60, (unsigned int)*ret%60); break; case OBD_STD: *ret=buf[0]; if(buf[0]<=0x11) strcpy_P(retbuf, obd_std_strings[buf[0]-1]); else sprintf_P(retbuf, PSTR("OBD:%02X"), buf[0]); break; // for the moment, everything else, display the raw answer default: // transform buffer to an hex value *ret=0; for(i=0; i LowestResistance) { byte TemperatureIndex = 0; while (TemperatureIndex < TemperatureListSize - 1 && Resistance > pgm_read_word(&(TemperatureList[TemperatureIndex + 1][0]))) TemperatureIndex++; if (TemperatureIndex < TemperatureListSize - 1) { int UpperResistance = pgm_read_word(&(TemperatureList[TemperatureIndex + 1][0])); int LowerResistance = pgm_read_word(&(TemperatureList[TemperatureIndex][0])); int ResistanceTemperature = pgm_read_word(&(TemperatureList[TemperatureIndex][1])); Temperature = ResistanceTemperature + (Resistance - LowerResistance) * 100 / (UpperResistance - LowerResistance); } else Temperature = 1250; } Temperature = Temperature - 15; // Sensor is showing 1.5°C more then realy it is // convert °C in F if requested if(!params.use_metric) Temperature = convertToFarenheit(Temperature); long_to_dec_str(Temperature, decs, 1); sprintf_P(retbuf, PSTR("%s\005%c"), decs, params.use_metric?'C':'F'); } #endif // instant fuel consumption unsigned int get_instantFuelConsumption(char *retbuf) { long cons; char decs[16]; long toggle_speed = params.use_metric ? params.per_hour_speed : (params.per_hour_speed*1609)/1000; // divide MAF by 100 because our function return MAF*100 // but multiply by 100 for double digits precision // divide MAF by 14.7 air/fuel ratio to have g of fuel/s // divide by 730 (g/L at 15°C) according to Canadian Gov to have L/s // multiply by 3600 to get litre per hour // formula: (3600 * MAF) / (14.7 * 730 * VSS) // = maf*0.3355/vss L/km // mul by 100 to have L/100km // if maf is 0 it will just output 0 if(vss9999) // SI { trip_cons=9999; // display 99.99 L/100 maximum } } else { // it's imperial, convert. // from m/mL to MPG so * by 3.78541178 to have gallon and * by 0.621371 for mile // multiply by 10 to have a digit precision // new comment: convert L/100 to MPG trip_cons=235214/trip_cons; if(trip_cons<10) { trip_cons=10; // display 1.0 MPG min } } } #if 1 long_to_dec_str(trip_cons, decs, (1+params.use_metric)); // hack #else if(params.use_metric) { long_to_dec_str(trip_cons, decs, 2); } else { long_to_dec_str(trip_cons, decs, 1); } #endif sprintf_P(retbuf, pctspcts, decs, (params.use_metric?"\001\002":"\006\007" )); return (unsigned int)trip_cons; } // trip 0 is tank // trip 1 is trip // trip 2 is outing void get_fuel(char *retbuf, byte ctrip) { unsigned long cfuel; char decs[16]; // convert from µL to cL cfuel=params.trip[ctrip].fuel/10000; // convert in gallon if requested if(!params.use_metric) { cfuel = convertToGallons(cfuel); } long_to_dec_str(cfuel, decs, 2); sprintf_P(retbuf, pctspcts, decs, params.use_metric?"L":"G" ); } // trip 0 is tank // trip 1 is trip // trip 2 is outing void get_waste(char *retbuf, byte ctrip) { unsigned long cfuel; char decs[16]; // convert from µL to cL cfuel=params.trip[ctrip].waste/10000; // convert in gallon if requested if(!params.use_metric) { cfuel = convertToGallons(cfuel); } long_to_dec_str(cfuel, decs, 2); sprintf_P(retbuf, pctspcts, decs, params.use_metric?"L":"G" ); } // trip 0 is tank // trip 1 is trip // trip 2 is outing void get_distance(char *retbuf, byte ctrip) { unsigned long cdist; char decs[16]; // convert from cm to hundreds of meter cdist=params.trip[ctrip].dist/10000; // convert in miles if requested if(!params.use_metric) { cdist=(cdist*1000)/1609; } long_to_dec_str(cdist, decs, 1); sprintf_P(retbuf, pctspcts, decs, params.use_metric?"\003":"\006" ); } // distance you can do with the remaining fuel in your tank void get_remain_dist(char *retbuf) { long tank_tmp; long remain_dist; long remain_fuel; long tank_cons; // tank size is in litres (converted at input time) tank_tmp=params.tank_size; // convert from µL to dL remain_fuel=tank_tmp - params.trip[TANK].fuel/100000; // calculate remaining distance using tank cons and remaining fuel if(params.trip[TANK].dist<1000) { remain_dist=9999; } else { tank_cons=params.trip[TANK].fuel/(params.trip[TANK].dist/1000); remain_dist=remain_fuel*1000/tank_cons; if(!params.use_metric) // convert to miles { remain_dist=(remain_dist*1000)/1609; } } sprintf_P(retbuf, pctldpcts, remain_dist, params.use_metric?"\003":"\006" ); } /* * accumulate data for trip, called every loop() */ void accumulate_trip(void) { static byte min_throttle_pos=255; // idle throttle position, start high byte throttle_pos; // current throttle position byte open_load; // to detect open loop char str[STRLEN]; unsigned long delta_dist, delta_fuel; unsigned long time_now, delta_time; #ifdef UsePIDCache // read values from ECU, not from cache PIDCacheCount=0; #endif // if we return early set MAF to 0 maf=0; // time elapsed time_now = millis(); delta_time = time_now - old_time; old_time = time_now; // distance in cm // 3km/h = 83cm/s and we can sample n times per second or so with CAN // so having the value in cm is not too large, not too weak. // ulong so max value is 4'294'967'295 cm or 42'949 km or 26'671 miles if (!get_pid(VEHICLE_SPEED, str, &vss)) { return; // not valid, exit } if(vss>0) { delta_dist=(vss*delta_time)/36; // accumulate for all trips for(byte i=0; i DFCO // detect idle pos if (get_pid(THROTTLE_POS, str, &tempLong)) { throttle_pos = (byte)tempLong; if(throttle_pos>24) & 0x7F; lcd_cls_print_P(PSTR("CHECK ENGINE ON")); lcd.setCursor(0,1); sprintf_P(str, PSTR("%d CODE(S) IN ECU"), nb); lcd.print(str); delay(2000); lcd.clear(); // we display only the first 6 codes // if you have more than 6 in your ECU // your car is obviously wrong :-/ // retrieve code cmd[0]=0x03; // iso_write_data(cmd, 1); // Reading ECU in raw method (normal method is wrong because of different size header 5 vs 4) byte DTCBuf[32]; int DTCBufSize = 0; // Wait until first byte available byte i = 0; byte b; // while(i < 3 && !iso_read_byte(&b)) { i++; } if (i == 3) { lcd_cls_print_P(PSTR("Error reading DTC")); delay(2000); lcd.clear(); return; } DTCBuf[0] = b; DTCBufSize++; // Read until last byte, or until buffer is full // while (DTCBufSize < 31 && iso_read_byte(&b)) { DTCBuf[DTCBufSize] = b; DTCBufSize++; } // VW Jetta 2001 example read: 48 6B 10 43 04 20 00 00 00 00 2A (11 bytes, 1 DTC) // 48 6B 10 - header // 43 - responce to 03 // 04 20 - first code // 00 00 - second code // 00 00 - third code // 2A - checsum // Next 3 DTC would be same order, all 11 bytes. lcd.clear(); for (j = 0; j < (nb-1)/3 + 1; j++) { k = 0; byte DataShift = (j==0 ? 4 : 15); for (i = 0; i < 3; i++) { if (DTCBuf[DataShift + i*2] > 0 || DTCBuf[DataShift + i*2 + 1] > 0) { switch (DTCBuf[DataShift + i*2] & 0xC0) { case 0x00: str[k]='P'; // powertrain break; case 0x40: str[k]='C'; // chassis break; case 0x80: str[k]='B'; // body break; case 0xC0: str[k]='U'; // network break; } k++; str[k++] = '0' + ((DTCBuf[DataShift + i*2] & 0x30) >> 4); // first digit is 0-3 only str[k++] = '0' + (DTCBuf[DataShift + i*2] & 0x0F); str[k++] = '0' + ((DTCBuf[DataShift + i*2 + 1] & 0xF0) >> 4); str[k++] = '0' + (DTCBuf[DataShift + i*2 + 1] & 0x0F); } } str[k]='\0'; // make asciiz lcd.print(str); lcd.setCursor(0, 1); // go to next line to display the 3 next delay(1000); } delay(2000); } else if (!Silent) { lcd_cls_print_P(PSTR("No DTC codes")); delay(1500); lcd.clear(); } } // might be incomplete void clear_mil_code(void) { unsigned long n; char str[STRLEN]; byte nb; byte cmd[2]; byte buf[6]; byte i, j, k; if (!get_pid(MIL_CODE, str, &tempLong)) { return; // Invalid return so abort } n = (unsigned long) tempLong; /* A request for this PID returns 4 bytes of data. The first byte contains two pieces of information. Bit A7 (the seventh bit of byte A, the first byte) indicates whether or not the MIL (check engine light) is illuminated. Bits A0 through A6 represent the number of diagnostic trouble codes currently flagged in the ECU. The second, third, and fourth bytes give information about the availability and completeness of certain on-board tests. Note that test availability signified by set (1) bit; completeness signified by reset (0) bit. (from Wikipedia) */ if(1L<<31 & n) // test bit A7 { // we have MIL on nb=(n>>24) & 0x7F; lcd_cls_print_P(PSTR("CHECK ENGINE ON")); lcd.setCursor(0,1); sprintf_P(str, PSTR("%d CODE(S) IN ECU"), nb); lcd.print(str); delay(1000); lcd_cls_print_P(PSTR("Clearing codes...")); // clear code cmd[0]=0x04; // iso_write_data(cmd, 1); lcd_cls_print_P(PSTR("Codes cleared")); delay(1000); lcd.clear(); } else { lcd_cls_print_P(PSTR("No DTC codes")); delay(1000); lcd.clear(); } } /* * Configuration menu */ int test_button() { unsigned long thisCurrentTime = millis(); int retVal = 0; for (int loop = 0; loop < NUMBUTTONS; loop++) { // Check if button being held down and check if longer then bounce time if ((previousButtons[loop] == DOWN) && (currentButtons[loop] == DOWN) && (lastButtonTimeDown[loop] > 0)) { if ((thisCurrentTime - lastButtonTimeDown[loop]) > DEBOUNCE) { lastButtonTimeDown[loop] = 0; retVal += (1 << loop); } } } if (retVal == 0) // Buttons are up { if (calcTimeDiff(old_time, millis()) > ACCU_WAIT) { accumulate_trip(); } } return retVal; } // common code used in a couple of menu section byte menu_select_yes_no(byte p) { boolean exitMenu = false; do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed)) { p=0; } else if(RIGHT_BUTTON_PRESSED(btnsPressed)) { p=1; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } lcd.setCursor(4,1); if(p==0) { lcd_print_P(select_no); } else { lcd_print_P(select_yes); } } while(!exitMenu); return p; } // Menu selection // // This function is passed in a array of strings which comprise of the menu // The first string is the MENU TITLE, // The second string is the EXIT option (always first option) // The following strings are the other options in the menu // // The returned value denotes the selection of the user: // A return of zero represents the exit // A return of a real number represents the selection from the menu past exit (ie 2 would be the second item past EXIT) byte menu_selection(char ** menu, byte arraySize) { byte selection = 1; // Menu title takes up the first string in the list so skip it byte screenChars = 0; // Characters currently sent to screen byte menuItem = 0; // Menu items past current selection boolean exitMenu = false; // Note: values are changed with left/right and set with middle // Default selection is always the first selection, which should be 'Exit' lcd.clear(); lcd.print((char *)pgm_read_word(&(menu[0]))); do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed) && selection > 1) { selection--; } else if(RIGHT_BUTTON_PRESSED(btnsPressed) && selection < arraySize - 1) { selection++; } else if (MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; //return from function, menu does not need repaiting return selection - 1; } // Potential improvements: // Currently the selection is ALWAYS the first presented menu item. // Current selection could be in the middle if possible. // If few selections and screen size permits, selections could be centered? lcd.setCursor(0,1); screenChars = 1; lcd.write('('); // Wrap the current selection with brackets menuItem = 0; do { lcd.print((char*)pgm_read_word(&(menu[selection+menuItem]))); if (menuItem == 0) { // include closing bracket lcd.write(')'); screenChars++; } lcd.write(' '); screenChars += (strlen((char*)pgm_read_word(&(menu[selection+menuItem]))) + 1); menuItem++; } while (screenChars < LCD_COLS && selection + menuItem < arraySize); // Do any cover up of old data while (screenChars < LCD_COLS) { lcd.write(' '); screenChars++; } } while(!exitMenu); return selection - 1; } void config_menu(void) { char str[STRLEN]; char decs[16]; int lastButton = 0; //we'll use this to speed up button pushes unsigned int fuelUnits = 0; unsigned long tankUnits = 0; boolean changed = false; boolean saveParams = false; // Currently a button press will cause a save, smarter would be to verify a change in value... byte selection = 0; byte oldByteValue; // used to determine if new value is different and we need to save the change unsigned int oldUIntValue; // ditto unsigned long oldULongValue; // tank used and tank distance adjust do { selection = menu_selection(topMenu, ARRAY_SIZE(topMenu)); if (selection == 1) // display { byte displaySelection = 0; do { displaySelection = menu_selection(displayMenu, ARRAY_SIZE(displayMenu)); if (displaySelection == 1) // Contrast { lcd_cls_print_P(PSTR("LCD contrast")); oldByteValue = params.contrast; boolean exitMenu = false; do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed) && params.contrast != MIN_CONTRAST) { params.contrast-=CONTRAST_INCR; } else if(RIGHT_BUTTON_PRESSED(btnsPressed) && params.contrast != MAX_CONTRAST) { params.contrast+=CONTRAST_INCR; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } analogWrite(ContrastPin, params.contrast); // change dynamicaly sprintf_P(str, pctd, params.contrast); displaySecondLine(5, str); } while(!exitMenu); if (oldByteValue != params.contrast) { saveParams = true; } } else if (displaySelection == 2) // Metric { lcd_cls_print_P(PSTR("Use metric unit")); oldByteValue = params.use_metric; params.use_metric=menu_select_yes_no(params.use_metric); if (oldByteValue != params.use_metric) { saveParams = true; } // Only if metric do we have the option of using the comma as a decimal if(params.use_metric) { lcd_cls_print_P(PSTR("Use comma format")); oldByteValue = (byte) params.use_comma; params.use_comma = menu_select_yes_no(params.use_comma); if (oldByteValue != (byte) params.use_comma) { saveParams = true; } } } else if (displaySelection == 3) // Display speed { oldByteValue = params.per_hour_speed; // speed from which we toggle to fuel/hour lcd_cls_print_P(PSTR("Fuel/hour speed")); // set value with left/right and set with middle boolean exitMenu = false; do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed) && params.per_hour_speed!=0) { params.per_hour_speed--; } else if(RIGHT_BUTTON_PRESSED(btnsPressed) && params.per_hour_speed!=255) { params.per_hour_speed++; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } sprintf_P(str, pctd, params.per_hour_speed); displaySecondLine(5, str); } while(!exitMenu); if (oldByteValue != params.per_hour_speed) { saveParams = true; } } } while (displaySelection != 0); // exit from this menu } else if (selection == 2) // Adjust { byte adjustSelection = 0; byte count = ARRAY_SIZE(adjustMenu); if (is_pid_supported(MAF_AIR_FLOW, 0)) { // Use the "Eng Displ" parameter (the last one) only when MAF_AIR_FLOW is not supported count--; } do { adjustSelection = menu_selection(adjustMenu, count); if (adjustSelection == 1) { lcd_cls_print_P(PSTR("Tank size (")); oldUIntValue = params.tank_size; // convert in gallon if requested if(!params.use_metric) { lcd_print_P(PSTR("G)")); fuelUnits = convertToGallons(params.tank_size); } else { lcd_print_P(PSTR("L)")); fuelUnits = params.tank_size; } boolean exitMenu = false; // set value with left/right and set with middle do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed)) { changed = true; fuelUnits--; } else if(RIGHT_BUTTON_PRESSED(btnsPressed)) { changed = true; fuelUnits++; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } long_to_dec_str(fuelUnits, decs, 1); sprintf_P(str, PSTR("- %s + "), decs); displaySecondLine(4, str); } while(!exitMenu); if (changed) { if(!params.use_metric) { params.tank_size = convertToLitres(fuelUnits); } else { params.tank_size = fuelUnits; } changed = false; } if (oldUIntValue != params.tank_size) { saveParams = true; } } else if (adjustSelection == 2) // cost { int lastButton = 0; lcd_cls_print_P(PSTR("Fuel Price (")); oldUIntValue = params.gas_price; // convert in gallons if requested if(!params.use_metric) { lcd_print_P(PSTR("G)")); // Convert unit price to litres for the cost per gallon. (ie $1 a litre = $3.785 per gallon) fuelUnits = convertToLitres(params.gas_price); } else { lcd_print_P(PSTR("L)")); fuelUnits = params.gas_price; } boolean exitMenu = false; // set value with left/right and set with middle do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed)) { changed = true; lastButton--; if(lastButton >= 0) { lastButton = 0; fuelUnits--; } else if (lastButton < -3 && lastButton > -7) { fuelUnits-=2; } else if (lastButton <= -7) { fuelUnits-=10; } else { fuelUnits--; } } else if(RIGHT_BUTTON_PRESSED(btnsPressed)) { changed = true; lastButton++; if(lastButton <= 0) { lastButton = 0; fuelUnits++; } else if (lastButton > 3 && lastButton < 7) { fuelUnits+=2; } else if (lastButton >= 7) { fuelUnits+=10; } else { fuelUnits++; } } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } long_to_dec_str(fuelUnits, decs, fuelUnits > 999 ? 3 : 1); sprintf_P(str, gasPrice[fuelUnits > 999], decs); displaySecondLine(3, str); } while(!exitMenu); if (changed) { if(!params.use_metric) { params.gas_price = convertToGallons(fuelUnits); } else { params.gas_price = fuelUnits; } changed = false; } if (oldUIntValue != params.gas_price) { saveParams = true; } } else if (adjustSelection == 3) { lcd_cls_print_P(PSTR("Fuel adjust")); oldByteValue = params.fuel_adjust; boolean exitMenu = false; do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed)) { params.fuel_adjust--; } else if(RIGHT_BUTTON_PRESSED(btnsPressed)) { params.fuel_adjust++; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } sprintf_P(str, pctdpctpct, params.fuel_adjust); displaySecondLine(4, str); } while(!exitMenu); if (oldByteValue != params.fuel_adjust) { saveParams = true; } } else if (adjustSelection == 4) { lcd_cls_print_P(PSTR("Speed adjust")); oldByteValue = params.speed_adjust; boolean exitMenu = false; do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed)) { params.speed_adjust--; } else if(RIGHT_BUTTON_PRESSED(btnsPressed)) { params.speed_adjust++; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } sprintf_P(str, pctdpctpct, params.speed_adjust); displaySecondLine(4, str); } while(!exitMenu); if (oldByteValue != params.speed_adjust) { saveParams = true; } } else if (adjustSelection == 5) { lcd_cls_print_P(PSTR("Outing stop over")); oldByteValue = params.OutingStopOver; boolean exitMenu = false; do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed) && params.OutingStopOver > 0) { params.OutingStopOver--; } else if(RIGHT_BUTTON_PRESSED(btnsPressed) && params.OutingStopOver < UCHAR_MAX) { params.OutingStopOver++; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } sprintf_P(str, PSTR("- %2d Min + "), params.OutingStopOver * MINUTES_GRANULARITY); displaySecondLine(3, str); } while(!exitMenu); if (oldByteValue != params.OutingStopOver) { saveParams = true; } } else if (adjustSelection == 6) { lcd_cls_print_P(PSTR("Trip stop over")); oldByteValue = params.TripStopOver; boolean exitMenu = false; do { unsigned long TripStopOver; // Allowable stop over time (in milliseconds). Exceeding time starts a new outing. int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed) && params.TripStopOver > 1) { params.TripStopOver--; } else if(RIGHT_BUTTON_PRESSED(btnsPressed) && params.TripStopOver < UCHAR_MAX) { params.TripStopOver++; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } sprintf_P(str, PSTR("- %2d Hrs + "), params.TripStopOver); displaySecondLine(3, str); } while(!exitMenu); if (oldByteValue != params.TripStopOver) { saveParams = true; } } else if (adjustSelection == 7) // tank used params.trip[0].fuel { lcd_cls_print_P(PSTR("Tank used (")); oldULongValue = params.trip[0].fuel; // convert in gallon if requested if(!params.use_metric) { lcd_print_P(PSTR("G)")); tankUnits = convertToGallons(params.trip[0].fuel / 1000 / 100); // converted from uL to dL } else { lcd_print_P(PSTR("L)")); tankUnits = params.trip[0].fuel / 1000 / 100; // converted from uL to dL } boolean exitMenu = false; // set value with left/right and set with middle do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed)) { changed = true; tankUnits-=1; // decrement by 0.1L or G } else if(RIGHT_BUTTON_PRESSED(btnsPressed)) { changed = true; tankUnits+=1; // increment by 0.1L or G } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } long_to_dec_str(tankUnits, decs, 1); sprintf_P(str, PSTR("- %s + "), decs); displaySecondLine(4, str); } while(!exitMenu); if (changed) { if(!params.use_metric) { params.trip[0].fuel = convertToLitres(tankUnits) * 1000 * 100; } else { params.trip[0].fuel = tankUnits * 1000 * 100; } changed = false; } if (oldULongValue != params.trip[0].fuel) { saveParams = true; } } else if (adjustSelection == 8) // tank distance params.trip[0].dist { lcd_cls_print_P(PSTR("Tank dist (")); oldULongValue = params.trip[0].dist; // convert in miles if requested if(!params.use_metric) { lcd_print_P(PSTR("M)")); tankUnits = (params.trip[0].dist / 100 / 1000) * 1000 / 1609; // converted from cm to km } else { lcd_print_P(PSTR("KM)")); tankUnits = params.trip[0].dist / 100 / 1000; // converted from cm to km } boolean exitMenu = false; // set value with left/right and set with middle do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed)) { changed = true; tankUnits-=1; // decrement by 0.1km or mile } else if(RIGHT_BUTTON_PRESSED(btnsPressed)) { changed = true; tankUnits+=1; // increment by 0.1km or mile } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } long_to_dec_str(tankUnits, decs, 0); sprintf_P(str, PSTR("- %s + "), decs); displaySecondLine(4, str); } while(!exitMenu); if (changed) { if(!params.use_metric) { params.trip[0].dist = (tankUnits * 1609 / 1000) * 100 * 1000; } else { params.trip[0].dist = tankUnits * 100 * 1000; } changed = false; } if (oldULongValue != params.trip[0].dist) { saveParams = true; } } else if (adjustSelection == 9) { lcd_cls_print_P(PSTR("Eng dplcmt (MAP)")); oldByteValue = params.eng_dis; // the following setting is for MAP only // engine displacement boolean exitMenu = false; do { int btnsPressed = test_button(); if(LEFT_BUTTON_PRESSED(btnsPressed) && params.eng_dis!=0) { params.eng_dis--; } else if(RIGHT_BUTTON_PRESSED(btnsPressed) && params.eng_dis!=100) { params.eng_dis++; } else if(MIDDLE_BUTTON_PRESSED(btnsPressed)) { exitMenu = true; } long_to_dec_str(params.eng_dis, decs, 1); sprintf_P(str, PSTR("- %sL + "), decs); displaySecondLine(4, str); } while(!exitMenu); if (oldByteValue != params.eng_dis) { saveParams = true; } } } while (adjustSelection != 0); } else if (selection == 3) // PIDs { // go through all the configurable items byte PIDSelection = 0; byte cur_screen; byte pid = 0; // Set PIDs required for the selected screen do { PIDSelection = menu_selection(PIDMenu, ARRAY_SIZE(PIDMenu)); if (PIDSelection != 0 && PIDSelection <= NBSCREEN) { cur_screen = PIDSelection - 1; for(byte current_PID=0; current_PID= 0; speedIndex--) { // Initialize the CANBUS // Try 3 speeds until success perhaps if (!OBDIICanbus.init(speedArray[speedIndex])) { #ifdef DEBUGOutput sprintf_P(str, PSTR(" Init Failed! ")); #endif } else { #ifdef DEBUGOutput sprintf_P(str, PSTR("Init Successful!")); #endif long tempLong; // See if we can get something from the bus to // make sure we are initialized if (get_pid(PID_SUPPORT00, str, &tempLong)) { success=true; break; } } } char str[STRLEN] = {" "}; lcd.setCursor(0,1); lcd.print(str); lcd.setCursor(dotPos,1); lcd.print("."); delay(500); dotPos++; dotPos = dotPos % 16; }while(!success); #ifdef DEBUGOutput lcd.setCursor(0,1); lcd.print(str); delay(1000); lcd.setCursor(0, 1); sprintf_P(str, PSTR(" ")); lcd.print(str); #endif } /* * Initialization */ void setup() // run once, when the sketch starts { #ifdef SERIAL_DEBUG Serial.begin(9600); Serial.println("OBDIIReader"); #endif // Enable Interrupts sei(); // buttons init pinMode(lbuttonPin, INPUT); pinMode(mbuttonPin, INPUT); pinMode(rbuttonPin, INPUT); // "turn on" the internal pullup resistors digitalWrite(lbuttonPin, HIGH); digitalWrite(mbuttonPin, HIGH); digitalWrite(rbuttonPin, HIGH); // low level interrupt enable stuff // interrupt 1 for the 3 buttons PCMSK1 |= (1 << PCINT11) | (1 << PCINT12) | (1 << PCINT13); PCICR |= (1 << PCIE1); // load parameters params_load(); // if something is wrong, default parms are used // LCD pin init analogWrite(BrightnessPin,brightness[brightnessIdx]); analogWrite(ContrastPin, params.contrast); lcd.begin(LCD_COLS, LCD_ROWS); lcd_char_init(); // Temperature sensors init #ifdef UseInsideTemperatureSensor pinMode(InsideTemperaturePin, INPUT); #endif #ifdef UseOutsideTemperatureSensor pinMode(OutsideTemperaturePin, INPUT); #endif engine_off = engine_on = millis(); lcd_cls_print_P(PSTR(" OBDuino CanBus ")); delay(2000); lcd.setCursor(0,1); lcd_print_P(PSTR("OBDIICanbus Init")); delay(1000); #ifdef DEBUGOutput char str[STRLEN] = {0}; #endif #ifndef DEBUG // In debug mode we need to skip init. initializeComms(); #endif #ifdef carAlarmScreen refreshAlarmScreen = true; #endif // check supported PIDs check_supported_pids(); #ifndef DisableDTCReadOnStart // check if we have MIL code check_mil_code(true); #endif lcd.clear(); old_time=millis(); // epoch getpid_time=old_time; } /* * Main loop */ void loop() // run over and over again { char str[STRLEN]; #ifdef BIG_font_hybrid char str2[STRLEN]; #endif #ifdef DEBUG ECUconnection = true; has_rpm = true; #else ECUconnection = verifyECUAlive(); #endif if (oldECUconnection != ECUconnection) { if (ECUconnection) { unsigned long nowOn = millis(); unsigned long engineOffPeriod = calcTimeDiff(engine_off, nowOn); // Engine is now running so switch on LCD if (has_rpm > 0) { // Turn the Backlight on analogWrite(BrightnessPin, brightness[brightnessIdx]); analogWrite(ContrastPin, params.contrast); } if (engineOffPeriod > (params.OutingStopOver * MINUTES_GRANULARITY * MILLIS_PER_MINUTE)) { trip_reset(OUTING, false); engine_on = nowOn; } else { // combine last trip time to this one! Not including the stop over time engine_on = nowOn - calcTimeDiff(engine_on, engine_off); } if (engineOffPeriod > (params.TripStopOver * MILLIS_PER_HOUR)) { trip_reset(TRIP, false); } } else // Car is off { //Save data and turn the Backlight off save_params_and_display(); //clear screen after turn off lcd.clear(); #ifdef carAlarmScreen refreshAlarmScreen = true; #endif } oldECUconnection = ECUconnection; } // If engine was on, and RPM is 0 - save trip data and turn engine off if (engine_started == 1 && has_rpm == 0) { engine_started = 0; #ifdef SaveTripDataAfterEngineTurnOff save_params_and_display(); //Turn the Backlight off analogWrite(BrightnessPin, brightness[0]); analogWrite(ContrastPin, MAX_CONTRAST); #endif } if (ECUconnection) { // If engine was off, but is now running, backlight was turned off, we need to turn it back on if (engine_started == 0 && has_rpm != 0) { engine_started = 1; // Turn the Backlight on analogWrite(BrightnessPin, brightness[brightnessIdx]); analogWrite(ContrastPin, params.contrast); #ifdef carAlarmScreen lcd.clear(); // Clear away any debris from Car Alarm Screen #endif } // this read and assign vss and maf and accumulate trip data accumulate_trip(); // display on LCD if (active_screen params.per_hour_speed) { bigNum(get_instantFuelConsumption(str), "INST", "L/KM"); } else { bigNum(get_instantFuelConsumption(str), "INST", "L/Hr"); } } else { if (vss > params.per_hour_speed) { bigNum(get_instantFuelConsumption(str), "INST", "MPG "); } else { bigNum(get_instantFuelConsumption(str), "INST", "G/Hr"); } } } else { #ifdef BIG_font_hybrid get_instantFuelConsumption(str2); str2[5] ='\0'; if (params.use_metric) { bigNum(get_consumption(str, OUTING), str2, "AVG L/K"); } else { bigNum(get_consumption(str, OUTING), str2, "AVG MPG "); } #else if (params.use_metric) { bigNum(get_consumption(str, OUTING), "AVG", "L/KM"); } else { bigNum(get_consumption(str, OUTING), "AVG", "MPG "); } #endif } } } else // ECU is off { #ifdef carAlarmScreen // ECU is off so print ready screen instead of PIDS while we wait for ECU action displayAlarmScreen(); #else // for some reason the display on LCD // for(byte current_PID=0; current_PID 0) ? 1 : 0; #ifdef DEBUGOutput char retbuf[30]; lcd.setCursor(0,1); sprintf_P(retbuf, PSTR("connected=%d "), connected); lcd.print(retbuf); delay(1000); lcd.setCursor(0,1); sprintf_P(retbuf, PSTR("has_rpm=%d "), has_rpm); lcd.print(retbuf); delay(1000); #endif return connected; } #endif #ifdef carAlarmScreen // This screen will display a fake security heading, // then emulate an array of LED's blinking in Knight Rider style. // This could be modified to blink a real LED (or maybe a short array depending on available pins) void displayAlarmScreen(void) { static byte pingPosition; static boolean pingDirection; static long nextMoveTime; const long pingTimeOut = 1000; const byte lastLCDChar = 15; if (refreshAlarmScreen) { pingPosition = 0; pingDirection = 0; lcd_cls_print_P(PSTR("OBDuino Security" )); lcd.setCursor(pingPosition,1); lcd.write('*'); refreshAlarmScreen = false; nextMoveTime = millis() + pingTimeOut; } else if (millis() > nextMoveTime) { lcd.setCursor(pingPosition,1); lcd.write(' '); if(pingPosition == 0 || pingPosition == lastLCDChar) { // Change direction pingDirection = !pingDirection; } // Move the character if(pingDirection) { pingPosition+= 3; } else { pingPosition-=3; } lcd.setCursor(pingPosition,1); lcd.write('*'); nextMoveTime = millis() + pingTimeOut; } } #endif /* * Memory related functions */ // we have 512 bytes of EEPROM on the 168P, more than enough void params_save(void) { uint16_t crc; byte *p; // CRC will go at the end crc=0; p=(byte*)¶ms; for(byte i=0; i 999999) { num /= 10; dp++; if (dp == 5) break; // We'll lose the top numbers like an odometer } if (dp == 5) dp = 99; // We don't need a decimal point here. // Round off the non-printed value. if ((num % 10) > 4) num += 10; num /= 10; byte x = 6; while (x > 0) { x--; if (x == dp) { //time to poke in the decimal point?{ fBuff[x] = '.'; } else { fBuff[x] = '0' + (num % 10);//poke the ascii character for the digit. num /= 10; } } fBuff[6] = 0; return fBuff; } void bigNum(unsigned long t, char *txt1, char *txt2) { #ifdef BIG_font_type_3x2 static prog_char bignumchars1[40] PROGMEM = { 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 }; static prog_char bignumchars2[40] PROGMEM = { 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 }; #define FontWidth 4 #define DecimalPointSymbol 5 #endif #ifdef BIG_font_type_2x2_alpha static prog_char bignumchars1[30] PROGMEM = { 5, 2, 0, 2, 32, 0, 8, 6, 0, 7, 6, 0, 3, 4, 0, 5, 8, 0, 5, 8, 0, 7, 2, 0, 5, 6, 0, 5, 6, 0 }; static prog_char bignumchars2[30] PROGMEM = { 3, 4, 0, 4, 1, 0, 3, 1, 0, 1, 4, 0, 32, 2, 0, 1, 4, 0, 3, 4, 0, 32, 2, 0, 3, 4, 0, 1, 4, 0 }; #define FontWidth 3 #define DecimalPointSymbol '.' #endif #ifdef BIG_font_type_2x2_beta static prog_char bignumchars1[30] PROGMEM = { 1, 2, 0, 2, 32, 0, 7, 6, 0, 7, 6, 0, 3, 4, 0, 5, 7, 0, 1, 7, 0, 7, 2, 0, 5, 6, 0, 5, 6, 0 }; static prog_char bignumchars2[30] PROGMEM = { 3, 4, 0, 4, 8, 0, 5, 8, 0, 8, 4, 0, 32, 2, 0, 8, 6, 0, 5, 6, 0, 32, 2, 0, 3, 4, 0, 8, 4, 0 }; #define FontWidth 3 #define DecimalPointSymbol '.' #endif t *= 100; // decimal point, start as a "space", can be change by after char dp1 = 32; char dp2 = 32; char * r = "009.99"; //default to 999 if (t <= 9950) { r = format(t ); //009.86 dp1 = DecimalPointSymbol; } else if (t <= 99500) { r = format(t / 10); //0098.6 dp2 = DecimalPointSymbol; } else if (t <= 999500) { r = format(t / 100); //00986 } lcd.setCursor(0, 0); lcd_print_P(&bignumchars1[(r[2] - '0') * FontWidth]); lcd.write(' '); lcd_print_P(&bignumchars1[(r[4] - '0') * FontWidth]); lcd.write(' '); lcd_print_P(&bignumchars1[(r[5] - '0') * FontWidth]); lcd.write(' '); lcd.print(txt1); lcd.setCursor(0, 1); lcd_print_P(&bignumchars2[(r[2] - '0') * FontWidth]); lcd.write(dp1); lcd_print_P(&bignumchars2[(r[4] - '0') * FontWidth]); lcd.write(dp2); lcd_print_P(&bignumchars2[(r[5] - '0') * FontWidth]); lcd.write(' '); lcd.print(txt2); } /* Adj % 0 1 2 3 4 4 5 6 7 8 <==star count 1% 91% 92% 93% 94% 95% 105% 106% 107% 108% 109% 2% 88% 89% 91% 93% 95% 105% 107% 109% 111% 114% 3% 84% 87% 89% 92% 95% 105% 108% 111% 115% 118% 4% 81% 84% 88% 91% 95% 105% 109% 114% 118% 123% 5% 77% 81% 86% 90% 95% 105% 110% 116% 122% 128% 6% 74% 79% 84% 89% 95% 105% 111% 118% 125% 133% 7% 71% 76% 82% 88% 95% 105% 112% 120% 129% 138% 8% 68% 74% 80% 87% 95% 105% 113% 122% 132% 143% 9% 65% 72% 79% 86% 95% 105% 114% 125% 136% 148% 10% 62% 69% 77% 86% 95% 105% 116% 127% 140% 154% 11% 60% 67% 75% 85% 95% 105% 117% 129% 144% 159% 12% 57% 65% 74% 84% 95% 105% 118% 132% 148% 165% 13% 54% 63% 72% 83% 95% 105% 119% 134% 152% 171% */ #define PERCENTAGE_RANGE 108 //108 = 8% void eco_visual(char *retbuf) { //enable our varriables unsigned long tank_cons, outing_cons; unsigned long tfuel, tdist; int stars; tfuel = params.trip[OUTING].fuel; tdist = params.trip[OUTING].dist; if(tdist > 100 && tfuel != 0) //Make sure no devisions by Zero. { outing_cons = tfuel / (tdist / 1000); //our current trip since engine start tfuel = params.trip[TANK].fuel; tdist = params.trip[TANK].dist; tank_cons = tfuel / (tdist / 1000); //our results for the current tank of gas } else { //give some dummy numbers to avoid devide by zero numbers tank_cons = 100; outing_cons = 101; } //lets start off in the middle stars = 3; // 3 = Average. if ( outing_cons < tank_cons ) //doing good :) { outing_cons = (outing_cons*105) / 100; //Check if within 5% of TANK for Average result //Loop to check how much better we are doing //Each time the smaller number will be increased by a set percentage //in order to add or subtract from our star count. while(outing_cons < tank_cons && stars < 7) { outing_cons = (outing_cons*PERCENTAGE_RANGE) / 100; stars++; } outing_cons=0; } else if (outing_cons > tank_cons) //doing bad... so far... { tank_cons = (tank_cons*105) / 100; //Check if within 5% of TANK for Average result while(outing_cons > tank_cons && stars > 0) //Loop to check how much worse we are doing { tank_cons = (tank_cons*PERCENTAGE_RANGE) / 100; stars--; } } //else they are equal, do nothing. //Now we have our star count, use it as an index to access the text sprintf_P(retbuf, PSTR("%s"), (char*)pgm_read_word(&(econ_Visual[stars]))); } //get_engine_on_time will return the time since the engine has started void get_engine_on_time(char *retbuf) { unsigned long run_time; int hours, minutes, seconds; //to store the time #ifdef useECUState if (ECUconnection) {//update with current time, if the car is running #else if(has_rpm) {//update with current time, if the car is running #endif run_time = calcTimeDiff(engine_on, millis()); //We now have the number of ms } else { //car is not running. Display final time when stopped. run_time = calcTimeDiff(engine_on, engine_off); } //Lets display the running time //hh:mm:ss hours = run_time / MILLIS_PER_HOUR; minutes = (run_time % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE; seconds = (run_time % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND; //Now we have our varriables parsed, lets display them sprintf_P(retbuf, PSTR("%d:%02d:%02d"), hours, minutes, seconds); } void get_cost(char *retbuf, byte ctrip) { unsigned long cents; unsigned long fuel; char decs[16]; params.gas_price; // x/1000 = dollars fuel = params.trip[ctrip].fuel / 10000; //cL cents = fuel * params.gas_price / 1000; //now have $$$$cc long_to_dec_str(cents, decs, 2); sprintf_P(retbuf, PSTR(CurrencyPrintString), decs); } void save_params_and_display(void) { engine_off = millis(); //record the time the engine was shut off params_save(); param_saved = 1; engine_started = 0; lcd_cls_print_P(PSTR("TRIPS SAVED!")); //Lets Display how much fuel for the tank we wasted. char str[STRLEN] = {0}; lcd.setCursor(0,1); lcd_print_P(PSTR("Wasted:")); lcd.setCursor(LCD_SPLIT,1); get_waste(str,TANK); lcd.print(str); delay(2000); //Turn the Backlight off needBacklight(false); }