Go Back   EcoModder Forum > EcoModding > Instrumentation > OpenGauge / MPGuino FE computer
Register Now
 Register Now
 

Reply  Post New Thread
 
Submit Tools LinkBack Thread Tools
Old 09-17-2008, 04:24 PM   #211 (permalink)
OBDuino coder
 
Magister's Avatar
 
Join Date: Jun 2008
Location: Montréal, QC
Posts: 212

Titine - '13 Hyundai Sonata Hybrid
Thanks: 3
Thanked 10 Times in 8 Posts
Instead of using SerialWrite() you can use elm_write("01C1\r") or SerialPrint("01C1\r"), you have to end your string by \r for the ELM to "execute" the string. Also you have to read the serial port after each write to check the result
A lot of PIDs return "encoded" values like RPM which is multiplied by 4, I used public knowledge found in wikipedia to know what to do for each PIDs
OBD-II PIDs - Wikipedia, the free encyclopedia

EDIT: I am using a Freeduino, my code takes about 12K for the moment.

__________________
2013 Hyundai Sonata Hybrid
  Reply With Quote
Alt Today
Popular topics

Other popular topics in this forum...

   
Old 09-18-2008, 06:43 AM   #212 (permalink)
EcoModding Lurker
 
Join Date: Sep 2008
Location: Sweden
Posts: 8
Thanks: 0
Thanked 2 Times in 2 Posts
Magister,

Thanks a lot for the help, i will get back to my code soon and try to solve it .
As soon as i have something functional i will share.

The wiki link is great, was looking for that information before but no luck.

I am looking into buying the sanguino instead, 4 times the memory and twice the pins. http://sanguino.cc/start
This will make it possible to go all the way with a graphical lcd and your existing code after some recoding.

Last edited by HULK; 09-18-2008 at 07:26 AM..
  Reply With Quote
Old 09-18-2008, 09:58 AM   #213 (permalink)
OBDuino coder
 
Magister's Avatar
 
Join Date: Jun 2008
Location: Montréal, QC
Posts: 212

Titine - '13 Hyundai Sonata Hybrid
Thanks: 3
Thanked 10 Times in 8 Posts
We can use the ATMega328P instead of the 168, the 328 has 32K of FLASH, 2K of RAM and 1K of EEPROM IIRC. Remove the old chip in the Arduino, insert the new one, you have to make a few changes in the bootloader for the code to recognize the chip, and you are (almost) in business
__________________
2013 Hyundai Sonata Hybrid
  Reply With Quote
Old 09-19-2008, 04:20 AM   #214 (permalink)
EcoModding Lurker
 
Join Date: Sep 2008
Location: Sweden
Posts: 8
Thanks: 0
Thanked 2 Times in 2 Posts
Quote:
Originally Posted by Magister View Post
We can use the ATMega328P instead of the 168, the 328 has 32K of FLASH, 2K of RAM and 1K of EEPROM IIRC. Remove the old chip in the Arduino, insert the new one, you have to make a few changes in the bootloader for the code to recognize the chip, and you are (almost) in business
Problem is that the ATMega328P will not be possible to buy in the near future and i need more memory now.
So i will have to use the ATMega644P or try to get a good contact at Atmel that can provide me with an early ATmega328P example...
I will buy the ATMega644P today and connect it to my existing arduino board after some modding.
Hope it works so i can start using your code after adjustment.
  Reply With Quote
Old 09-19-2008, 08:53 AM   #215 (permalink)
dcb
needs more cowbell
 
dcb's Avatar
 
Join Date: Feb 2008
Location: ÿ
Posts: 5,038

pimp mobile - '81 suzuki gs 250 t
90 day: 96.29 mpg (US)

schnitzel - '01 Volkswagen Golf TDI
90 day: 53.56 mpg (US)
Thanks: 158
Thanked 269 Times in 212 Posts
FYI, arduino 0012 was released yesterday and the guino code doesn't fit anymore (by 2.5k). I did not see 328 support built in, and am reluctant to suggest using a derivative install as well.

The other developers should weigh in here on the hardware/software options. We've been discussing multiple versions, dont know if obduino 1.0 will still fit on arduino 0012 as is, but 1.0 was intended as the "get something working" version. Not necessarily the graphical one that does spreadsheets.


Also think about who your target audience is. The push for the obd version is in part for folks who are not comfortable tapping into 4 wires on their car to install a guino and need an obd plug. It is also those folks who want to see more data. Of course price is always a consideration as is ease of development.
__________________
WINDMILLS DO NOT WORK THAT WAY!!!
  Reply With Quote
Old 09-19-2008, 09:28 AM   #216 (permalink)
OBDuino coder
 
Magister's Avatar
 
Join Date: Jun 2008
Location: Montréal, QC
Posts: 212

Titine - '13 Hyundai Sonata Hybrid
Thanks: 3
Thanked 10 Times in 8 Posts
I'll try version 12 of the IDE to see the code size increase.
__________________
2013 Hyundai Sonata Hybrid
  Reply With Quote
Old 09-21-2008, 11:44 AM   #217 (permalink)
EcoModding Lurker
 
Join Date: Sep 2008
Location: Sweden
Posts: 8
Thanks: 0
Thanked 2 Times in 2 Posts
Finally

After a couple of days reading the code Magister has created i figured it out.
Without the people that made the core of this code it would be hard for me to capture the reply that is given from ELM327 and to display it.
So what i did was to cut down big parts from the original code, parts that will read fault codes and other nice functions are removed so the code will fitt the ATmega168.
I am keeping the bootloader but need more memory so will be removing it soon, buying a AVR-ISP is on the list.
I did try to download the sanguino bootloader to a ATMega644P this weekend but never got it working. Maybe just wait for the ATMega328p that will pop right in the arduino or order a sanguino, time will tell.
So using code below i can display values that you pre set in the code.

Code:
#include <Arial14.h>
#include <ks0108.h>

#undef int   // bug from Arduino IDE 0011
#include <stdio.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>

#define obduinosig B11001100

#define STRLEN  40
#define NUL     '\0'
#define CR      '\r'  // carriage return = 0x0d = 13
#define PROMPT  '>'
#define DATA    1  // data with no cr/prompt

#define rxPin 0 //arduino pin for rx
#define txPin 1 //arduino pin for tx

  long maf;
  long temp;
  long sped;
  long rpm;
  int d = 1;
  int p = 0;
  int x = 0; //x graph coordinate
  int y = 0; //y graph coordinate
  int a = 0; //old x graph coordinate
  int b = 0; //old y graph coordinate
  int e = 0;
  int f = 0;

/* PID stuff */
unsigned long  pid01to20_support=0;
unsigned long  pid21to40_support=0;
#define PID_SUPPORT20 0x00
#define MIL_CODE      0x01
#define FREEZE_DTC    0x02
#define FUEL_STATUS   0x03
#define LOAD_VALUE    0x04
#define COOLANT_TEMP  0x05
#define STF_BANK1     0x06
#define LTR_BANK1     0x07
#define STF_BANK2     0x08
#define LTR_BANK2     0x09
#define FUEL_PRESSURE 0x0A
#define MAN_PRESSURE  0x0B
#define ENGINE_RPM    0x0C
#define VEHICLE_SPEED 0x0D
#define TIMING_ADV    0x0E
#define INT_AIR_TEMP  0x0F
#define MAF_AIR_FLOW  0x10
#define THROTTLE_POS  0x11
#define SEC_AIR_STAT  0x12
#define OXY_SENSORS1  0x13
#define B1S1OXY_SENS_SFT 0x14
#define B1S2OXY_SENS_SFT 0x15
#define B1S3OXY_SENS_SFT 0x16
#define B1S4OXY_SENS_SFT 0x17
#define B2S1OXY_SENS_SFT 0x18
#define B2S2OXY_SENS_SFT 0x19
#define B2S3OXY_SENS_SFT 0x1A
#define B2S4OXY_SENS_SFT 0x1B
#define OBD_STD       0x1C
#define OXY_SENSORS2  0x1D
#define AUX_INPUT     0x1E
#define RUNTIME_START 0x1F
#define PID_SUPPORT40 0x20

#define LAST_PID      0x20  // same as the last one defined above

/* our internal fake PIDs */
#define NO_DISPLAY    0xF0
#define FUEL_CONS     0xF1
#define AVG_CONS      0xF2
#define TRIP_DIST     0xF3

// returned length of the PID response.
// constants so put in flash
prog_uchar pid_reslen[] PROGMEM=
{
   //pid 0x00 to 0x1F
  4,4,8,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,2,

  // pid 0x20 to whatever
  4
};

// flag used to save distance/average consumption in eeprom only if required
byte engine_started;
byte param_saved;

/* each ELM response ends with '\r' followed at the end by the prompt
 so read com port until we find a prompt */
byte elm_read(char *str, byte size)
{
  int b;
  byte i;
  char str2[8];

  // wait for something on com port
  i=0;
  while((b=serialRead())!=PROMPT && i<size)
  if(/*b!=-1 &&*/ b>=' ')
  str[i++]=b;

  if(i!=size)  // we got a prompt
  {
    str[i]=NUL;  // replace CR by NUL
    return PROMPT;
  }
  else
    return DATA;
}

// buf must be ASCIIZ
void elm_write(char *str)
{
  while(*str!=NUL)
    serialWrite(*str++);
}


// check header byte
byte elm_check_response(byte *cmd, char *str)
{
  return 0;
  // cmd is something like "010D"
  // str should be "41 0D blabla"
  if(cmd[0]+4 != str[0]
    || cmd[1]!=str[1]
    || cmd[2]!=str[3]
    || cmd[3]!=str[4])
    return 1;
  return 0;  // no error
}

byte elm_compact_response(byte *buf, char *str)
{
  byte i;

  // start at 6 which is the first hex byte after header
  // ex: "41 0C 1A F8"
  // return buf: 0x1AF8

  i=0;
  str+=6;
  while(*str!=NUL)
    buf[i++]=strtoul(str, &str, 16);

  return i;
}

byte elm_command(char *str, char *cmd)
{
  sprintf_P(str, cmd);
  elm_write(str);
  return elm_read(str, STRLEN);
}

int elm_init()
{
  char str[STRLEN];

  beginSerial(38400);

  serialFlush();
  // reset, wait for something and display it
  elm_command(str, PSTR("ATWS\r"));
  delay(1000);

  elm_command(str, PSTR("ATSPA6\r"));
  delay(1000);
  
  elm_command(str, PSTR("ATDP\r"));
  delay(1000);

  // turn echo off
  sprintf_P(str, PSTR("ATE0\r"));
  elm_write(str);
  elm_read(str, STRLEN);  // read the ok
  delay(500);

  // send 01 00 to see if we are connected or not

  // init connection
  sprintf_P(str, PSTR("0100\r"));
  do
  {
    elm_write(str);
    elm_read(str, STRLEN);  // read the ok
  } 
  while(elm_check_response((byte*)"0100", str)!=0);

  return 0;
}

// get value of a PID, return as a long value
// and also formatted for output in the return buffer
long get_pid(byte pid, char *retbuf)
{
  byte i;
  byte cmd[2];    // to send the command
  char str[STRLEN];   // to send/receive
  byte buf[10];   // to receive the result
  long ret;       // return value
  byte reslen;
  char decs[8];

  // check if PID is supported
  if( pid!=PID_SUPPORT20 && (1L<<(32-pid) & pid01to20_support) == 0 )
  {
    // nope
    sprintf_P(retbuf, PSTR("0x%02X N/A"), pid);
    return -1;
  }

  cmd[0]=0x01;    // ISO cmd 1, get PID
  cmd[1]=pid;

  sprintf_P(str, PSTR("%02x%02x\r"), cmd[0], cmd[1]);
  elm_write(str);

  // receive length depends on pid
  if(pid<=LAST_PID)
    reslen=pgm_read_byte_near(pid_reslen+pid);
  else
    reslen=0;

  elm_read(str, STRLEN);
  if(elm_check_response(cmd, str)!=0)
  {
    sprintf_P(retbuf, PSTR("ERROR"));
    return -255;
  }
  // first 2 bytes are 0x41 and command, skip them
  // and remove spaces by calling a function,
  // convert in hex and return in buf
  elm_compact_response(buf, str);

  // formula and unit
  switch(pid)
  {
    
  case ENGINE_RPM:
    ret=(buf[0]*256+buf[1])/4;
    sprintf_P(retbuf, PSTR("%ld RPM"), ret);
    break;
  case MAF_AIR_FLOW:
    ret=buf[0]*256+buf[1];
    // not divided by 100 for return value!!
    int_to_dec_str(ret, decs, 2);
    sprintf_P(retbuf, PSTR("%s g/s"), decs);
    break;
  case LOAD_VALUE:
  case THROTTLE_POS:
    ret=(buf[0]*100)/255;
    sprintf_P(retbuf, PSTR("%ld %%"), ret);
    break;
  case COOLANT_TEMP:
  case INT_AIR_TEMP:
    ret=buf[0]-40;
    sprintf_P(retbuf, PSTR("%ld C"), ret);
    break;
  case STF_BANK1:
  case LTR_BANK1:
  case STF_BANK2:
  case LTR_BANK2:
    ret=(buf[0]-128)*7812;  // not divided by 10000
    int_to_dec_str(ret/100, decs, 2);
    sprintf_P(retbuf, PSTR("%s %%"), decs);
    break;
  case FUEL_PRESSURE:
  case MAN_PRESSURE:
    ret=buf[0];
    if(pid=FUEL_PRESSURE)
      ret*=3;
    sprintf_P(retbuf, PSTR("%ld kPa"), ret);
    break;
  case VEHICLE_SPEED:
    ret=buf[0];
    sprintf_P(retbuf, PSTR("%ld \003\004"), ret);
    break;
  case TIMING_ADV:
    ret=(buf[0]/2)-64;
    sprintf_P(retbuf, PSTR("%ld deg"), ret);
    break;
  case OBD_STD:
    ret=buf[0];
    switch(buf[0])
    {
    case 0x01:
      sprintf_P(retbuf, PSTR("OBD2CARB"));
      break;
    case 0x02:
      sprintf_P(retbuf, PSTR("OBD2EPA"));
      break;
    case 0x03:
      sprintf_P(retbuf, PSTR("OBD1&2"));
      break;
    case 0x04:
      sprintf_P(retbuf, PSTR("OBD1"));
      break;
    case 0x05:
      sprintf_P(retbuf, PSTR("NOT OBD"));
      break;
    case 0x06:
      sprintf_P(retbuf, PSTR("EOBD"));
      break;
    case 0x07:
      sprintf_P(retbuf, PSTR("EOBD&2"));
      break;
    case 0x08:
      sprintf_P(retbuf, PSTR("EOBD&1"));
      break;
    case 0x09:
      sprintf_P(retbuf, PSTR("EOBD&1&2"));
      break;
    case 0x0a:
      sprintf_P(retbuf, PSTR("JOBD"));
      break;
    case 0x0b:
      sprintf_P(retbuf, PSTR("JOBD&2"));
      break;
    case 0x0c:
      sprintf_P(retbuf, PSTR("JOBD&1"));
      break;
    case 0x0d:
      sprintf_P(retbuf, PSTR("JOBD&1&2"));
      break;
    default:
      sprintf_P(retbuf, PSTR("OBD:%02X"), buf[0]);
      break;
    }
    break;
    // for the moment, everything else, display the raw answer  
  case PID_SUPPORT20:
  case MIL_CODE:
  case FREEZE_DTC:
  case PID_SUPPORT40:
  default:
    // transform buffer to an integer value
    ret=0;
    for(i=0; i<reslen; i++)
    {
      ret*=256L;
      ret+=buf[i];
    }
    sprintf_P(retbuf, PSTR("%08X"), ret);
    break;
  }

  return ret;
}


// ex: get a long as 687 with prec 2 and output the string "6.87"
// precision is 1 or 2
void int_to_dec_str(long value, char *decs, byte prec)
{
  byte pos;

  // sprintf_P does not allow * for the width ?!?
  if(prec==1)
    sprintf_P(decs, PSTR("%02ld"), value);
  else
    if(prec==2)
      sprintf_P(decs, PSTR("%03ld"), value);

  pos=strlen(decs)+1;  // move the \0 too
  // a simple loop takes less space than memmove()
  for(byte i=0; i<=prec; i++)
  {
    decs[pos]=decs[pos-1];
    pos--;
  }
  decs[pos]='.';  
}




void display(byte corner, byte pid)
{
  char str[16];

  /* check if it's a real PID or our internal one */
  switch(pid)
  {
  case NO_DISPLAY:
    return;
  default:
    (void)get_pid(pid, str);
    break;
  }
}

void check_supported_pid(void)
{
  unsigned long n;
  char str[16];

  n=get_pid(PID_SUPPORT20, str);
  pid01to20_support=n; 

  // do we support pid 21 to 40?
  if( (1L<<(32-PID_SUPPORT40) & pid01to20_support) == 0)
    return;  //nope

  n=get_pid(PID_SUPPORT40, str);
  pid21to40_support=n;
}

 void setup(){
  
  GLCD.ClearScreen(); 
  GLCD.SelectFont(Arial_14);
  GLCD.Init(NON_INVERTED);
  
  byte r;
  char str[16];

  ////////////////////////////////////////////////////////////display bakground and signs
  GLCD.DrawHoriLine(0, 60, 127, BLACK); //bottom line to avoid lcd display problems in the rounded rectangle
  GLCD.DrawRoundRect(95, 0, 32, 43, 3, BLACK);  // rounded rectangle around peak & current value                   
  GLCD.DrawRoundRect(0, 45, 127, 18, 3, BLACK); // rounded rectangle around battery, clock, temprature valeus
    
    GLCD.GotoXY(02,47); //battery sign
    GLCD.Puts("#");
    
    GLCD.GotoXY(68,47); //clock sign
    //GLCD.Puts("&");
    GLCD.Puts("km/h");
    
    GLCD.GotoXY(35,46); //temprature sign
    GLCD.Puts("$");
    
  for (int i=0; i <= 46; i++){ //dotted graph line
  GLCD.SetDot(d, 22, BLACK);
  d = d + 2;
  } 
  ////////////////////////////////////////////////////////////display bakground and signs

  do // init loop
  {
    sprintf_P(str, PSTR("ELM Init"));
    r=elm_init();
 
    if(r==0)
      sprintf_P(str, PSTR("Successful!"));
    else
      sprintf_P(str, PSTR("Failed! "));
  } 
  while(r!=0); // end init loop

  // check supported PIDs
  check_supported_pid();

  // check if we have MIL code
  //check_mil_code();

  engine_started=0;
  param_saved=0;
 }


 void  loop(){
  
  char str[16];
  
  static float p; 
  static float x;
  
  rpm=get_pid(ENGINE_RPM, str);
  temp=get_pid(COOLANT_TEMP, str);
  //maf=get_pid(MAF_AIR_FLOW, str);
  sped=get_pid(LOAD_VALUE, str);
  
  GLCD.FillRect(97, 30, 29, 10, WHITE);
  GLCD.GotoXY(98,30); 
  GLCD.PrintNumber(rpm); 
  
  if (rpm > p){ //print peak graph value
  GLCD.FillRect(97, 05, 29, 10, WHITE);
  GLCD.GotoXY(98,05);
  GLCD.PrintNumber(rpm);
  p= rpm;
  }
  
  //GLCD.FillRect(10, 47, 25, 10, WHITE);
  //GLCD.GotoXY(12,47); 
  //GLCD.PrintNumber(maf);
  
  GLCD.FillRect(41, 47, 19, 10, WHITE); 
  GLCD.GotoXY(43,47); 
  GLCD.PrintNumber(temp); 
  
  GLCD.FillRect(101, 47, 19, 10, WHITE); 
  GLCD.GotoXY(103,47); 
  GLCD.PrintNumber(sped); 
  
  int y = 45 - (rpm /99);  //y coordinate of graph curve
  if (b == 0){
  b = y;
  }
  
  GLCD.DrawLine(a,b,x,y, BLACK);
  x++; // add 1 to old x coordinate for line drawing in right direction
  
  if (x == 96){ //clean screan when graph cycle will start again from left
  GLCD.FillRect(0, 0, 94, 42, WHITE);
  d = 1;
  for (int i=0; i <= 46; i++){ // draw new dotted line after clean screan
  GLCD.SetDot(d, 22, BLACK);
  d = d + 2;
   } 
  x = 0; //x coordinate to start graph from left again
  }
  
  a = x; //old x coordinate to use for drawing line to new x coordinate
  b = y; //old y coordinate to use for drawing line to new y coordinate
 
 }
Remember that this code is far from complete and that i am a rookie but proud of my progress...
I set the baud rate to 38400 and found that my car supports protocol 4 and 6. Protocol 4 contains some intresting valueus that can not be found in protocol 6 but the protocol update rate is really slow. Do not know if that is due to the code or just that can bus protocol 6 has a faster response rate?

Now i need to clean up the code, get more memory, add buttons to controll a menu, arrenge more graphical options, activate the fault code reading and so on.
Any ideas are welcome.

In the attached link below you can find the black box prototype build i have done, ELM327, KS0108 graphical LCD and a arduino. Trust me, there is no free space in that box now.

Graphical OBD MPGuino album | HULK77 | Fotki.com

Picture of the display showing speed, temprature and curve of the rpm. RPM is also shown in a value on the right side and the top right value is the peak value of the RPM.



Next prototype will not contain the complete ELM327 board supporting a computer connection. I will use another LCD and a arduino mini or smaller, all will be mounted in a smaller case.


Had a hard time to get this code working one of the main reason was that i did not activate the automatic protocol search ATSP0. In the code above i am using the ATSPA6 command to point out the protocol 6 as a deafault but if not found it will look for another.
I am using Arduino version 11 for his code and the GLCD Library found below.
Arduino playground - GLCDks0108

Sorry, no video. Phone picture will have to do for now.
  Reply With Quote
The Following User Says Thank You to HULK For This Useful Post:
lamb.chop (01-19-2012)
Old 09-22-2008, 09:44 AM   #218 (permalink)
OBDuino coder
 
Magister's Avatar
 
Join Date: Jun 2008
Location: Montréal, QC
Posts: 212

Titine - '13 Hyundai Sonata Hybrid
Thanks: 3
Thanked 10 Times in 8 Posts
Nice

But be careful at my code, init does not test the response back, and there is no check for error or things like this

Also on my car the "01 00" command return 2 responses from 2 different devices, so I will have to add filter or make sure we talk to the ECU and not something else etc.

It's working but it's fas from finished
__________________
2013 Hyundai Sonata Hybrid
  Reply With Quote
Old 10-09-2008, 12:30 PM   #219 (permalink)
Civic 4 Life
 
KJSatz's Avatar
 
Join Date: Jun 2008
Location: USA
Posts: 229

Civics Lesson - '08 Honda Civic LX
Team Honda
90 day: 40.53 mpg (US)
Thanks: 0
Thanked 2 Times in 1 Post
Hey guys, I'm just a silly bystander. I was wondering how the project is going. I remember reading about how your plan is to have a first iteration using the ELM chip and then a second iteration using a different process. Do you know how far into the process you are? Not trying to put any pressure on anyone--just curious. I'd love to build one of these, because I know it would really help out my FE to have instrumentation.
__________________
  Reply With Quote
Old 10-09-2008, 01:02 PM   #220 (permalink)
OBDuino coder
 
Magister's Avatar
 
Join Date: Jun 2008
Location: Montréal, QC
Posts: 212

Titine - '13 Hyundai Sonata Hybrid
Thanks: 3
Thanked 10 Times in 8 Posts
The version with ELM is pretty stable now, you can have more info there:
OBDuino - opengauge - Google Code - OBDuino Description

I made the interface for CAN only, like described and pictured at the bottom half of this page:
OBDuinoInterface - opengauge - Google Code - OBDuino OBD2 cable and Interface adapter

It's very small and easy to do. If your car use another protocol, you can build the interface for it, see end of http://www.elmelectronics.com/DSheets/ELM327DS.pdf

It displays all kind of PIDs and also real-time fuel consumption and average on a trip. You can set it for imperial (miles, gallon, fahrenheit) or SI (metres, litres, celsius).

It can detect MIL code and display them, I will add the code to clear them too.

My car has a MAF but I put the code to emulate the MAF using the MAP and IAT, however I didn't test it.

There is virtual screen, menu, etc. You can configure the 4 PIDs on each virtual screen.

All of this in 14k of code so you can use standard or clone Arduino board and their IDE to compile/upload.

The connection for LCD and buttons are the same as the MPGuino.

If you know how to solder, to do the little ELM327 interface, do it!

__________________
2013 Hyundai Sonata Hybrid
  Reply With Quote
Reply  Post New Thread


Tags
obd2

Thread Tools


Similar Threads
Thread Thread Starter Forum Replies Last Post
MPGuino release one workspace dcb OpenGauge / MPGuino FE computer 1061 01-17-2020 01:37 AM
Just some quick info on Scangauge vs. MPGuino NoCO2 OpenGauge / MPGuino FE computer 4 06-01-2015 04:58 PM
All New Nissan Models to Feature Fuel Efficiency Gauge MetroMPG General Efficiency Discussion 6 11-18-2008 04:57 PM
Vacuum gauge problems :( DifferentPointofView Instrumentation 3 05-14-2008 11:04 PM
Will Scan Gauge work on mine? bennelson Instrumentation 9 02-19-2008 10:04 PM



Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.
Content Relevant URLs by vBSEO 3.5.2
All content copyright EcoModder.com