SparkFun Forums 

Where electronics enthusiasts find answers.

Have questions about a SparkFun product or board? This is the place to be.
By sephers
#173606
Good Afternoon,

I'm trying to get my OBD-II-UART working with my Arduino but it doesn't seem to receive any serial data that the OBD-II-UART sends.

I've tested the serial code to send data then print out returned data on an LCD and this works when using my serial monitor in the arduino program so I know it's not my arduino or code. However I can send data fine to the OBD-II-UART (since the lights flash to indicate it's been reset when I send ATZ) but no data is read back.

Using my arduino uno board I removed the ATMega to use the board as an FTDI converter and again using the serial monitor was able to send data to the OBD-II-UART fine but again no data is returned. I've got TX-O soldered correctly and similarly RX-I also GND is connected to the board so I can't figure out why the OBD-II-UART can receive data but not send it (even tho the TX light flashes).

Any help would be greatly appreciated,
George.

EDIT: I've also noticed that when using a serial terminal (a different one), when typing commands in the device will return these characters but if I remove the rx wire it will stop echoing these characters. Weird that it does that but won't display any actual data
User avatar
By gimpo
#175108
Well... :think: I don't know if what happened to me is your case, but you can check.
I had a problem similar to the one occurred to you: with the terminal program on PC the board seems working correctly, but when the same command was issued by arduino... no results.

If you are based on the code published by Sparkfun here https://learn.sparkfun.com/tutorials/ob ... okup-guide please take note that it doesn't work correctly when using the ISO 14230-4 (aka KWP2000) protocol.
In the above case data in the getResponse function are not collected correctly and the LCD could display empty results. That code assumes that every received message is terminated with a single carriage-return. In my case that assumption was not true, I discovered that a lot of answers was formatted with one or even two carriage-return in the middle and/or at the start (!). So, if an answer starts with CR you will see nothing on the LCD.
As collateral effect (since getResponse doesn't empty the buffer) it could happen that when reading response of successive commands you get part of the previous answer instead.

Solution was to rewrite my own (customized) getResponse function

I repeat: maybe is not your case.
By camando64
#181610
Hello,

I am having the same problem.
This is with current model Camry and Hilux.

I don't get any data returned from getresponse()
It just stays in there !!

I put some additional serial prints in but they return blank.
I am using software.serial on 2,3 to talk to the ODB board.

Can you please let me know how you changed the getresponse() function?

Cam
Code: Select all
//removed lcd
//moved uart-OBD to pins 2,3

//Define Serial Ports so I remember which is which
#include <SoftwareSerial.h>

//Create an instance of the new soft serial library to control the serial uart
//Note, digital pin 3 of the Arduino should be connected to Rx of the serial uart.

SoftwareSerial uart(2,3);

//Set up ring buffer
char rxData[20];
char rxIndex=0;

void setup() 
{
   uart.begin(9600);
   Serial.begin(9600);
  
  ODB_init();
}

void loop() 
{

  //Delete any data that may be left over in the serial port.
  
  Serial.print(getTemp()); //Print the int returned from getTemp
  Serial.print(" C");
  
  delay(1000);//wait 1 seconds and grab another reading
    
}

void ODB_init(void)
{
  //Wait for a little while before sending the reset command to the OBD-II-UART
  delay(2000);
  //Reset the OBD-II-UART
  uart.print("ATZ\r");
  Serial.print("ATZ\r");  //debug print
    delay(2000);  //Wait for a bit before starting to send commands after the reset.
  OBD_read();
  uart.print("ATE0\r");
  Serial.print("ATE0\r"); //debug print
  OBD_read();
  uart.flush();  
}


int getTemp(void)
{
  //Query the OBD-II-UART for the Engine Coolant Temp
  uart.flush();
  uart.print("0105\r");
  Serial.print("0105\r"); //debug print
  OBD_read();

  return strtol(&rxData[6],0,16)-40;
}


void OBD_read(void)
{
 Serial.println("obd_read???????"); //debug print
  char c;
  c = 0;
  Serial.println("obd_read pre while"); //debug print
  while(c != '>'); //The ELM327 ends its response with this char so when we get it we exit out.
  {
    Serial.println("obd_read while"); //debug print
        if(uart.available() > 0)
    {
      Serial.println("obd_read if"); //debug print
      c = uart.read();
      Serial.println("uart_read"); //debug print
      Serial.println(c); //debug print
      if((c!= '>') && (c!='\r') && (c!='\n')) //Keep these out of our buffer
      {
        rxData[rxIndex++] = c; //Add whatever we receive to the buffer
      }  
     }
    Serial.println("obd_read while out"); //debug print     
  }
  rxData[rxIndex++] = '\0';//Converts the array into a string
  rxIndex=0; //Set this to 0 so next time we call the read we get a "clean buffer"
  Serial.println("uart_out"); //debug print
}
By camando64
#182121
Hi all,

I'm obviously not getting a reply from gimpo

Does anyone else have an idea ?
I've been doing a lot of searching but have not come up with anything.

What is the best way to log and interpret the information coming back?
I'll try to get a dump from Serial monitor.
User avatar
By gimpo
#183289
camando64 wrote:Hi all,
I'm obviously not getting a reply from gimpo
Sorry sorry sorry, I apologize. I hope it's not too late to (trying) to help you.

Some prologue is necessary. Let's take the case when I'm asking the RPMs to the ECU of my Motorcycle ECUs (a Magneti Marelli).
In this case I issue the PID command "2161" to the serial line (your command can be different).

Contents of the response are not fixed, the text can contain different kind of inormation (look here below the comment from my code). What is more important is that the resulting response-string can be chaotic; it can contain blank added blank spaces, special chars and sometimes the answer arrive with a big delay in time. When this happens, many ECUs have the strange habit to queue answer in a buffer, so when you issue a second request than the answer could contains also a part that is the answer to the first one (!)
Code: Select all
* Structure of the char-message is one of these:
 * 
 * 		2161*6161NNNN**>
 * 		2161*BUSINIT:ERROR**>
 *		2161*NODATA**>
 * 
 * Where 
 * 		'*' is the carriage return char
 * 		'>' is the prompt sent by ELM327
 * 		blank spaces are ignored and not copied to the buffer.
So what you need is to clean/filter the reply string before using it (i.e. analyzing). This means that you should parse the string char by char and pick just the part of the message that is relevant/significant to you purposes.
I do that filtering in my modified version of the getResponse() function. In that function I parse the answer char by char while copying only significant chars to a second array. That second array is what will be used in the rest of the program.

Here below my function:
Code: Select all
/*
 * Reads chars from serial until the end-message spceial char is received or
 * timeout is triggered.
 * Message chars are stored in the RX_BUFFER, but no more than BUFFER_SIZE are
 * reads from serial.
 */
void getResponse(void)
{
	unsigned long startTime = millis();
	char inChar=0;
	RX_BUFFER.size = 0; 
		
	boolean timeout = false;
	boolean bufferFull = false;
	boolean endMessage = false;
		
	while(!timeout && !bufferFull && !endMessage)
	{
			if(Serial.available() > 0)
			{
				inChar = Serial.read(); 
				switch(inChar)
				{
					case BLANK_SPACE:
						// simply skip it
						break;
					case CARRIAGE_RETURN:
						inChar = '*';
						RX_BUFFER.data[RX_BUFFER.size++]=inChar;
						if( RX_BUFFER.size >= (BUFFER_SIZE-1) )
							bufferFull = true;
						break;

					case ELM_MSG_TERMINATOR:
						RX_BUFFER.data[RX_BUFFER.size++]=inChar;
						endMessage = true;
						break;

					default:
						RX_BUFFER.data[RX_BUFFER.size++]=inChar;
						if( RX_BUFFER.size >= (BUFFER_SIZE-1) )
							bufferFull = true;
						break;
				}
			}
			if( (millis() - startTime) > SERIAL_TIMEOUT)
				timeout = true;
	}
	// wipe out remaining chars in the buffer
	while(Serial.available() > 0)
		Serial.read(); 
	
	// add string terminator (doesn't increment string size)
	RX_BUFFER.data[RX_BUFFER.size]=STRING_TERMINATOR;
	
	// check for errors
	if(timeout)
	{
		RESULT.error = TIMEOUT_ERROR;
		RESULT.int_value = 0;
	}
	else if(bufferFull)
	{
		RESULT.error = BUFFER_FULL_ERROR;
		RESULT.int_value = 0;
	}
	else
	{
		RESULT.error = NO_ERROR;
		RESULT.int_value = RX_BUFFER.size;
	}
}
As you can see I manage timeout error (when the ECU takes to much time to respond), error messages and messages that are too long (my RX_BUFFER size is forced to be max 64 chars). Too long messages probably contains residual chars from previous requests (so it's better choice to discard them and make a new request again)

The RESULT variable is a simply struct that I inspect late ti check if all as gone ok (and the answer can be used) or not. TIMEOUT_ERROR, BUFFER_FULL_ERROR, etc. are constants that I've declare globally in the program.
My RX_BUFFER is declared as the struct below:
Code: Select all
typedef struct
{
	int size; //length measured in chars (string terminator is not counted)
	char data[BUFFER_SIZE];
} buffer;
In conclusion, when you know what kind of messages the ECU is responding with (e.g. playing with a text terminal and the OBD board), and you have a cleaned/filtered message, than you can directly jump to a well-know char position and extract only the character that are relevant to you (for example the four chars of the RPM speed).

Hope it helps! :)
By camando64
#183881
Thank you so much for your help.
I have to admit I am a bit of an amateur at coding.
Your definitions are a step too far at the moment for me to integrate with my code.
Could you do me a huge favour and list your whole code?

I am trying, but I haven't got my head around the array stuff yet.(your buffer)

Cam
User avatar
By gimpo
#184373
At the moment my code has become a giant with ten heads. It's too complicated to post here, I will try to write down a simple basic sketch and post it here (but not today or my wife will cut my head).
By camando64
#184376
thanks for the laugh,
my wife just cut mine off, that's why I'm here...
I will do more study, work's been a killer lately so hope to focus this weekend.
By camando64
#184377
actually struggling with your case calls:
BLANK_SPACE:
CARRIAGE_RETURN:
ELM_MSG_TERMINATOR:
etc
also unsure of "typedef struct"
can you shed any light?
User avatar
By DanV
#184387
sorry for jumping in here, but allow me to shed some light
camando64 wrote:actually struggling with your case calls:
BLANK_SPACE:
CARRIAGE_RETURN:
ELM_MSG_TERMINATOR:
etc
These are most likely constants defined in the program:
BLANK_SPACE = 0x20 or ASCII 32 - just a space character
BARRIAGE_RETURN = 0x0D or ASCII 13
ELM_MSG_TERMINATOR (I don't know what this is - it's going to be particular for your device)
also unsure of "typedef struct"
Here, the author is specifying a new type with the typedef definition, and it's a structure with (2) elements:
Code: Select all
typedef struct
{
   int size; //length measured in chars (string terminator is not counted)
   char data[BUFFER_SIZE];
} buffer;
'size' which is of type integer
'data' which is character array of size BUFFER_SIZE

the name of the new item is 'buffer' and you refer to it via: buffer.data[element] or buffer.size
User avatar
By gimpo
#184454
@camando64

just a consideration in the meanwhile. The main problem when dealing with OBD-board is that you cannot use the "Serial monitor" of the Arduino IDE to printout what you receive as result of your OBD request. I mean... when you wire TX and RX lines to the RX and TX pins of the board every Serial.println("blablabla") will go again to the OBD-board, not to the SerialMonitor.

To be more clear, you cannot write in your code something like (this is fantasy code, eh?)

// send my request to the OBD-BOARD
Serial.println("ATRV");

// read result
result = myFunctionToReadFromSerial();

// printout result to the SerialMonitor
Serial.println(result); // this doesn't work!!! Your sending again other chars to the OBD-board!!!

Alternatives:

1) wire a TFT/LCD display to you arduino, write requests to the serial and printout OBD-answers to the display.
2) use a common terminal program to write manually commands to the RS232 port of your computer and observe answers (so you can tailor your program upon what you see);

For example, I made long sessions talking to the OBD-board by using a (very) simple terminal program like CuteCom for linux (see http://cutecom.sourceforge.net/ )
User avatar
By gimpo
#184455
Here my basic sketch that one could use to query the RPM to an ECU using the KWP-2000 protocol (aka ISO 14230-4).
(PLEASE CHANKE THE FILE SUFFIX FROM ".txt" to ".ino" !)

Notes:

1.
I have changed some variable and struct names, I think that they will be more intuitive now.
2.
The code does not deal with errors. What a program has to do when the board is not responding? Or cannot parse an integer value? Maybe there are interferences/noises over the serial communication wires? (When mounted on a vehicle). Error management is entirely upon you and your needs
3.
Obviously the PID I use ("2130") works only for the ECU of my vehicle, you have to discover/crack the codes for your ECU!!!
In my case (using OBD terminology) 21 = the Service ID of the ECU, 30 = the "real" parameter ID.
4. The code compiles without error, but I've not tested with the OBD.
5. The code has not printout commands. You should have another device (TFT/LCD display) where you can print something since Serial line is occupied by the wires to the OBD board.

@camando64
you should try to vrite a function like "getVoltage()" on your own. The command to use is "ATRV", this does not depends on the type of your ECU since it reads just the volt applied to the power line of the OBD board. To parse a decimal value instead of a integer you should use the function "strtod" in place of "strtol"

For example:
int position = bufferEndPositionOf("ATRV");
VOLTAGE.float_value = strtod(&ANSWER.data[position+2],NULL);

Sorry if I cannot be more detailed but I've really no free time in this period :(
You do not have the required permissions to view the files attached to this post.
User avatar
By gimpo
#184456
@camando64

You should read with big attention the ELM 327 manual. There you will find all your answer, you have to eat with it, sleep with it. :D
(I've read it almost all, many times, before starting to understand something)

Start making some experiment issuing just AT commands (ATRV, ATH1, ATH0, etc. etc.); they are not transmitted to the ECU and you can get some practice about the format of answers.
You do not have the required permissions to view the files attached to this post.