Car OBD2 - Header

Hello,
I tried searching a lot regarding Car OBD2 reader with Arduino on Google and I found one working sketch.

The problem is that I need to specify OBD Header with value: da10f1 while Arduino starts connecting with OBD2 ELM327 Bluetooth adapter to read the values of specified PIDs from ECU sice the PIDs on my car are not "common" from Wiki.

I can do it with mobile app Torque very simply but I want to display values on LCD display mounted on dashboard instead of using mobile app.

Does somebody have an idea how can I declare the OBD header with implementation into this code?
Without declared OBD Header I'm not able to read the PID values.

#include <LiquidCrystal_I2C.h>  
#include <Wire.h>  
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); //LCD PINout



byte inData;
char inChar;
String BuildINString="";
String DisplayString="";
String WorkingString="";
long DisplayValue;
long A;
long B;



void setup() {
  
  // LCD setup columns, rows: 
  lcd.begin(20, 2); //16, 2
 
  //LCD turn on message
  lcd.setCursor(0, 0);
  lcd.print("My Car");
  lcd.setCursor(0, 1);
  lcd.print("engine");
  delay(3000);
  lcd.clear();



  Retry:
  lcd.setCursor(0, 0);
  lcd.print("                    ");
  lcd.setCursor(0, 1);
  lcd.print("Connecting...       ");
  
  //9600 baud communication
  Serial.begin(9600);   

  Serial.println("ATZ");
  lcd.setCursor(0, 0);
  lcd.print("ELM327 TZ           ");
  delay(2000);
   ReadData();
                                              
  if (BuildINString.substring(1,3)=="TZ")    // ECU on command: substring(1,3)=="TZ" or: substring(1,4)=="ATZ" 
    {
      lcd.clear();
      lcd.setCursor(0, 0);
      //lcd.print("Hello");
      lcd.setCursor(9, 0);
      lcd.setCursor(0, 1);
      lcd.print("Connected           ");
      delay(1500);
      lcd.clear();
    }
    else
    {
      lcd.setCursor(0, 0);
      lcd.print("Error               ");
      lcd.setCursor(0, 1);
      lcd.print("No Connection!      ");
      delay(1500);
      lcd.clear();
      goto Retry;
    }

//*****************************************************************
//Data reading from ECU
//***************************************************************** 

  Serial.println("0100"); 
  lcd.setCursor(0, 0);
  lcd.print("Initializing...   ");
  delay(4000);
   ReadData();
   lcd.setCursor(0, 0);           
   lcd.print("Initialized.  "); 
   delay(1000);
   lcd.clear();
}

Thank you so much for any suggestions.

That is not a complete sketch. You call some function names ReadData() but it is not defined in the code you provided. I would guess that is where the changes are required.

Please post your complete sketch.

Here is complete sketch:

#include <LiquidCrystal_I2C.h> 
#include <Wire.h> 
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); //LCD PINout

//********************************************************************

byte inData;
char inChar;
String BuildINString="";
String DisplayString="";
String WorkingString="";
long DisplayValue;
long A;
long B;

//********************************************************************

void setup() {
 
  // LCD setup columns, rows:
  lcd.begin(20, 2); //16, 2
 
  //LCD turn on message
  lcd.setCursor(0, 0);
  lcd.print("My Car");
  lcd.setCursor(0, 1);
  lcd.print("engine");
  delay(3000);
  lcd.clear();

//********************************************************************
//Added new code to check if ELM327 responded
//********************************************************************

  Retry:
  lcd.setCursor(0, 0);
  lcd.print("                    ");
  lcd.setCursor(0, 1);
  lcd.print("Connecting...       ");
 
  //9600 baud communication
  Serial.begin(9600);   //initialize Serial  

  Serial.println("ATZ");
  lcd.setCursor(0, 0);
  lcd.print("ELM327 TZ           ");
  delay(2000);
   ReadData();
                                             
  if (BuildINString.substring(1,3)=="TZ")    // ECU on command: substring(1,3)=="TZ" or: substring(1,4)=="ATZ"
    {
      lcd.clear();
      lcd.setCursor(0, 0);
      //lcd.print("Hello");
      lcd.setCursor(9, 0);
      lcd.setCursor(0, 1);
      lcd.print("Connected           ");
      delay(1500);
      lcd.clear();
    }
    else
    {
      lcd.setCursor(0, 0);
      lcd.print("Error               ");
      lcd.setCursor(0, 1);
      lcd.print("No Connection!      ");
      delay(1500);
      lcd.clear();
      goto Retry;
    }

//*****************************************************************
//Data reading from ECU
//*****************************************************************

  Serial.println("0100");
  lcd.setCursor(0, 0);
  lcd.print("Initializing...   ");
  delay(4000);
   ReadData();
   lcd.setCursor(0, 0);           
   lcd.print("Initialized.  ");
   delay(1000);
   lcd.clear();
}

void loop() {

//*****************************************************************
//Coolant Temp on LCD
//*****************************************************************

  lcd.setCursor(0, 0);
  lcd.print("Coolant Temp    "); 

  //resets the received string to NULL  Without it it repeated last string.
  BuildINString = "";  

  Serial.println("221003");  //Request data from PID 221003 of ECU
  delay(1000);

  // Receive complete string from the serial buffer
  ReadData();

// Parse the received string, retrieve only the hex value for temperature  Example: 32 is the 11 and 12th character
// 221003[41 05 32   Correct value reading back  10 degrees. (11,13 or even 11,14 works)

WorkingString = BuildINString.substring(11,13);   

    A = strtol(WorkingString.c_str(),NULL,16);  //convert hex to decimnal

   DisplayValue = A;
   DisplayString = String(DisplayValue - 40) + " C            "; // Subtract 40 from decimal to get the right temperature
   lcd.setCursor(0, 1);
   lcd.print(DisplayString); 
   delay(500);

//*****************************************************************
//Check if coolant temp is over 100 C
//*****************************************************************

int B;
B = DisplayString.toInt();  //Convert String to Integer  .toInt()

   if (B >= 100){     
    
// ------- Quick 3 blinks of backlight  -------------
  for(int i = 0; i< 3; i++)
  {
    lcd.display();   //For I2C use lcd.backlight
    delay(250);
    lcd.noDisplay(); //For I2C use lcd.noBacklight
    delay(250);
  }
  lcd.display(); // finish with backlight on   //For I2C use lcd.backlight
   }
  
}

//*****************************************************************
//Read Data Function
//*****************************************************************

void ReadData()
{
BuildINString="";  
  while(Serial.available() > 0)
  {
    inData=0;
    inChar=0;
    inData = Serial.read();
    inChar=char(inData);
    BuildINString = BuildINString + inChar;
  }
}

Maybe you can find/suggest something what can be used in touch with line "Serial.println("221003")".

You can interface with an ELM327 much easier with the library ELMduino.h. It's installable via the Arduino IDE's Libraries Manager and here is an example sketch (one of many included in the library):

#include <SoftwareSerial.h>
#include "ELMduino.h"


SoftwareSerial mySerial(2, 3); // RX, TX
#define ELM_PORT mySerial


ELM327 myELM327;


uint32_t rpm = 0;


void setup()
{
#if LED_BUILTIN
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
#endif

  Serial.begin(115200);
  ELM_PORT.begin(115200);

  Serial.println("Attempting to connect to ELM327...");

  if (!myELM327.begin(ELM_PORT))
  {
    Serial.println("Couldn't connect to OBD scanner");
    while (1);
  }

  Serial.println("Connected to ELM327");
}


void loop()
{
  float tempRPM = myELM327.rpm();

  if (myELM327.status == ELM_SUCCESS)
  {
    rpm = (uint32_t)tempRPM;
    Serial.print("RPM: "); Serial.println(rpm);
  }
  else
    printError();
}


void printError()
{
  Serial.print("Received: ");
  for (byte i = 0; i < PAYLOAD_LEN; i++)
    Serial.write(myELM327.payload[i]);
  Serial.println();
  
  if (myELM327.status == ELM_SUCCESS)
    Serial.println(F("\tELM_SUCCESS"));
  else if (myELM327.status == ELM_NO_RESPONSE)
    Serial.println(F("\tERROR: ELM_NO_RESPONSE"));
  else if (myELM327.status == ELM_BUFFER_OVERFLOW)
    Serial.println(F("\tERROR: ELM_BUFFER_OVERFLOW"));
  else if (myELM327.status == ELM_UNABLE_TO_CONNECT)
    Serial.println(F("\tERROR: ELM_UNABLE_TO_CONNECT"));
  else if (myELM327.status == ELM_NO_DATA)
    Serial.println(F("\tERROR: ELM_NO_DATA"));
  else if (myELM327.status == ELM_STOPPED)
    Serial.println(F("\tERROR: ELM_STOPPED"));
  else if (myELM327.status == ELM_TIMEOUT)
    Serial.println(F("\tERROR: ELM_TIMEOUT"));
  else if (myELM327.status == ELM_TIMEOUT)
    Serial.println(F("\tERROR: ELM_GENERAL_ERROR"));

  delay(100);
}

[Edit]: I'm not sure if the lib can handle such a long query and it's longer response, but it's still worth a try. If it doesn't work, submit a ticket on GitHub to add such functionality.

If you just want to request a different PID, just specify it

//Serial.println("221003");
Serial.println("DA10F1")

You will have to know what to expect as a response and decode it

Yes,
this is correct for PID request, I don't know how to send/declare OBD Header type for PID.

OBD Header is some access key (DA10F1) which is read by ECU and if there's a match, I'm able to see values stored in my requested PID (221003).

I think it should be defined in sequence "Serial.println("ATZ")" instead of "ATZ" or " Serial.println("0100");" instead of "0100".

I know how to decode received data, I have prepared formulas for calculation for each PID of my interest.
I have done it in Torque app on smartphone.

I think you should use the ELMduino.h library that Power_Broker suggested and check out the SET_HEADER command. It looks like it takes 3 bytes. You may also have to turn headers on.
PDF

  char command[20] = { '\0' };

  strcpy(command, SET_HEADER);
  strcat(command "DA10F1");

  if (sendCommand(command) != ELM_SUCCESS) {
    // deal with error
  }

  if (sendCommand(HEADERS_ON) != ELM_SUCCESS) {
    // deal with error
  }

blh64:

  strcpy(command, SET_HEADER);

strcat(command "DA10F1");

Oops, meant to add "%s" to the end of SET_HEADER so you could easily format it with sprintf(). I'll make the quick update and release a new version.

Otherwise, yes, that's how you would use the library. By default, though, the library initializes the ELM327 to not print spaces. This can be undone by calling

<class>.sendCommand(PRINTING_SPACES_ON);

after

<class>.begin(<serialClass>);

In release 2.1.1 you can do this:

  char command[20] = { '\0' };
  sprintf(command, SET_HEADER, "DA10F1");

Thank you so much for your help.

I can use this code or its wrong?

  char command[20] = { '\0' };
  sprintf(command, SET_HEADER, "DA10F1");
  Serial.println("221003");

You guys wrote certain possible codes how can I make it work and to be honest, I'm little bit confused now.

The code we were talking about assumes you're using ELMduino.h to interface with your ELM327. Look at the examples included in the library and the API listed in the header file.

You might have it work something like this:

#include <SoftwareSerial.h>
#include "ELMduino.h"


SoftwareSerial mySerial(2, 3); // RX, TX
#define ELM_PORT mySerial


ELM327 myELM327;


uint32_t rpm = 0;


void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  Serial.begin(115200);
  ELM_PORT.begin(115200);

  Serial.println("Attempting to connect to ELM327...");

  if (!myELM327.begin(ELM_PORT))
  {
    Serial.println("Couldn't connect to OBD scanner");
    while (1);
  }

  Serial.println("Connected to ELM327");

  myELM327.sendCommand(PRINTING_SPACES_ON);
  
  char command[20] = { '\0' };
  sprintf(command, SET_HEADER, "DA10F1");
  
  if (myELM327.sendCommand(command) != ELM_SUCCESS)
  {
    // deal with error
  }
}


void loop()
{
  if (myELM327.sendCommand("221003") == ELM_SUCCESS)
  {
    for (byte i=0; i< sizeof(myELM327.payload); i++)
      if (myELM327.payload[i] != 0)
        Serial.print(myELM327.payload[i]);
    Serial.println();
  }
  else
  {
    Serial.print(F("\tERROR: "));
    Serial.println(myELM327.status);
    delay(100);
  }
}

You have to send the command after building it. look at reply #6. I would start with the code in reply #3 and then add in this header stuff and test it out.

From a program I am working on, the CAN message package looks like:

 rx_frame.FIR.B.FF = CAN_frame_std;
          rx_frame.MsgID = 1;
          rx_frame.FIR.B.DLC = 8;
          rx_frame.data.u8[0] = *item & 0xFF;
          rx_frame.data.u8[1] = (*item >> 8) & 0xFF;
          rx_frame.data.u8[2] = (*item >> 16) & 0xFF;
          rx_frame.data.u8[3] = (*item >> 24) & 0xFF;
          PassTwo = true;
        } else {
          rx_frame.data.u8[4] = *item & 0xFF;;
          rx_frame.data.u8[5] = (*item >> 8) & 0xFF;
          rx_frame.data.u8[6] = (*item >> 16) & 0xFF;
          rx_frame.data.u8[7] = (*item >> 24) & 0xFF;
          ESP32Can.CANWriteFrame(&rx_frame); // send items over CAN buss
          PassTwo = false;
        }

Are you 'talking' of a extended message header or the frame message ID?