View Single Post
Old 09-21-2008, 11:44 AM   #217 (permalink)
HULK
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)