Erratic behavior of the Serial Port for an OBD project

Hello,

i'm building my first Arduino project which combines and OBD UART interface and and OLED display to get some live data from my car and print them on the display.

My setup is:

  • Arduino UNO Rev 3 board
  • Sparkfun OBD UART interface 9555 (wiring and code instructions here)
  • An unbranded OLED 0.96" display compatible with the Adafruit SSD1306 library

The OBD UART is ELM327 compatible and basically is a serial interface which accepts AT commands and pass them to the car throuth the OBDII port. Then, the car sends back some replies, which are sent back to the serial port by the ELM327.

I've not yet connected the interface to the car, bu I'm already testing the setup by sending a simpler serial command ("ATRV"). After receiving this command, the ELM327 does not pass the request to the car but simply reply with the measurement of DC voltage that is provided to the OBD connector. This test allows me to setup the initial code in the comfort of my house.

It turns out that I've already spotted a problem that I'd like to solve before going to the car. The code kinda works, but results I receive are formatted in the wrong way, with missing characters here and there.

On one hand, if I plug the OBD interface to my pc and manually send the ATRV command a few times, I get consistent results like this.

atrv
12.2V

>atrv
12.2V

>atrv
12.2V

>

On the other hand, when I try to automate this inb my void loop {}, I get variable results. Sometimes I receive the correct output, but other times I receive only partial strings, missing one or more characters. Something similar to this:

>ATRV
12.2V


>ATRV
12.2V

>ATRV
12.2V

12.2VATRV
12ATRV

I have tested two alternative setups: first one with the OBD device wired to the 0 - 1 pins of my arduino (as per manufacturer instructions), and the result printed on the LCD display. The second one with the OBD device wired to pins 8-9 using AltSoftSerial, and the result printed to the hardware serial. I get similar output.

This is my code, mostly based on the example of the OBD manufacturer, related to the first setup.

#include <SPI.h>
#include <Wire.h>
// #include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);




char rxData[20];
char rxIndex=0;


// the setup function runs once when you press reset or power the board
void setup() {
  Serial.begin(9600);
//  mySerial.begin(9600);
  
  pinMode(LED_BUILTIN, OUTPUT);

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    // Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  display.display();


  //Wait for a little while before sending the reset command to the OBD-II-UART
  delay(1500);
  //Reset the OBD-II-UART
  Serial.println("ATZ");
  //Wait for a bit before starting to send commands after the reset.
  delay(2000);
  
  //Delete any data that may be in the serial port before we begin.
  Serial.flush();
}


void loop() {
  //Delete any data that may be in the serial port before we begin.  
  Serial.flush();
  delay(100);
  
  //Query the OBD-II-UART for Battery Voltage
  Serial.println("ATRV");
  delay(100);
  
  //Get the response from the OBD-II-UART board. We get two responses
  //because the OBD-II-UART echoes the command that is sent.
  //We want the data in the second response.
  getResponse();
  delay(100);
  getResponse();
  delay(100);
  // Serial.println(rxData);
  
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.clearDisplay();

  display.println(rxData);
  display.display();
  



  
  delay(1000);

  
}


//The getResponse function collects incoming data from the UART into the rxData buffer
// and only exits when a carriage return character is seen. Once the carriage return
// string is detected, the rxData buffer is null terminated (so we can treat it as a string)
// and the rxData index is reset to 0 so that the next string can be copied.
void getResponse(void){
  char inChar=0;
  //Keep reading characters until we get a carriage return
  while(inChar != '\r'){
    //If a character comes in on the serial port, we need to act on it.
    if(Serial.available() > 0){
      //Start by checking if we've received the end of message character ('\r').
      if(Serial.peek() == '\r'){
        //Clear the Serial buffer
        inChar=Serial.read();
        //Put the end of string character on our data string
        rxData[rxIndex]='\0';
        //Reset the buffer index so that the next character goes back at the beginning of the string.
        rxIndex=0;
      }
      //If we didn't get the end of message character, just add the new character to the string.
      else{
        //Get the new character from the Serial port.
        inChar = Serial.read();
        //Add the new character to the string, and increment the index variable.
        rxData[rxIndex++]=inChar;
      }
    }
  }
}

As I mentioned, I results are meaningful, but not presented in a consistent way. And this will be a problem when I'll move to the car.

I don't know if this erratic behaviour depends on some error I made in the code... Is the serial comunication not properly handled?!? I've read that it is slow and asyncrhonous, but I thought the "Serial.available()>0 in the getResponse() function was taking care of this. I copied such function from the website of OBD interface manufactures, but it looks similar to the examples posted here in this forum.

Additinally, so I've added some delay(100); here and there in the main loop, but they don't help.

You should read Serial Basics for a better way to read in your responses rather than spinning in a function.

Also, Serial.flush() doesn't do what you think it does. It simply waits until any chars in the TX buffer have been transmitted. It does not empty the RX buffer. You have to do that yourself if you need to.

Thanks, I did look to SerialBasic and to me the example 2 was looking kinda similar to the function I received from manufacturer. I'll have a better look now. I guess I'll forget about manufacture's code and restart from scratch following the forum's examples.

Serial.flush() was again just copied from the manufacturer's example. I'll review it... Possibly it's not needed.

Try this variant of your code (compiles, not tested):

//ref: sketch_oct25b

#include <SPI.h>
#include <Wire.h>
// #include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define REQ_INTERVAL    1000ul
#define BUFF_SIZE       20

#define SCREEN_WIDTH    128 // OLED display width, in pixels
#define SCREEN_HEIGHT   64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET      4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

char rxData[BUFF_SIZE];
char rxIndex = 0;

// the setup function runs once when you press reset or power the board
void setup() 
{
    Serial.begin(9600);
    //  mySerial.begin(9600);

    pinMode(LED_BUILTIN, OUTPUT);

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) 
    { // Address 0x3D for 128x64
        // Serial.println(F("SSD1306 allocation failed"));
        for (;;); // Don't proceed, loop forever
    }

    // Show initial display buffer contents on the screen --
    // the library initializes this with an Adafruit splash screen.
    display.display();
    delay(2000); // Pause for 2 seconds

    // Clear the buffer
    display.clearDisplay();
    display.display();

    //Wait for a little while before sending the reset command to the OBD-II-UART
    delay(1500);
    
    //Reset the OBD-II-UART
    Serial.println("ATZ");
    
    //Wait for a bit before starting to send commands after the reset.
    delay(2000);

    //Delete any data that may be in the serial port before we begin.
    Serial.flush();
}

//state names
#define RECV_DATA   0
#define SEND_REQ    1
//
void loop() 
{
    static unsigned long
        timeOBD = 0;
    static byte
        stateOBD = SEND_REQ;

    switch( stateOBD )
    {
        case    SEND_REQ:
            if( (millis() - timeOBD) < REQ_INTERVAL )
                return;
                
            Serial.println("ATRV");
            stateOBD = RECV_DATA;
            
        break;

        case    RECV_DATA:            
            if( getResponse() == false )
                return;
            else
            {
                display.setTextSize(2);
                display.setTextColor(WHITE);
                display.setCursor(0, 0);
                display.clearDisplay();
            
                display.println(rxData);
                display.display();

                timeOBD = millis();
                stateOBD = SEND_REQ;
                
            }//else
                
        break;
        
    }//switch
    
}//loop

bool getResponse( void )
{
    static bool
        bEcho = true;
                
    if( Serial.available() > 0 )
    {
        do
        {
            char ch = Serial.read();
            if( ch == '\r' )
            {
                rxData[rxIndex] = '\0';
                rxIndex = 0;
                
                //bEcho is true for the first (echo) response                
                if( bEcho )
                {
                    //when we get that, we set this flag false and return false
                    bEcho = false;
                    return false;
                    
                }//else
                else
                {
                    //if not the echo, this is the data response so return true                    
                    bEcho = true;   //set bEcho true as a set up for the next message
                    return true;
                    
                }//else
                
            }//if
            else
            {
                //receive data into the buffer
                rxData[rxIndex] = ch;
                if( rxIndex < (BUFF_SIZE-1) )
                    rxIndex++;
                    
            }//else
            
        }while( Serial.available() > 0 );
        
    }//if

    return false;
    
}//getResponse

I would like to thank both Blackfin and blh64.

Unfortunately, Blackfin's code was not working as expected and gave exactly the same results as the code in my first message. It gave correct results for the first few loops, then became unstable and gave mixed character strings. Curiously, it was also printing the first line of the reply (i.e., the echo of the input command) altought it was meant to skip it. I did not understand why, but that's not a problem...

Afterward, I went back to the second example of the Serial Input Basics and got everything working.

To deal with the multi-line reply that I receive from the OBD thing, I repeat the recvWithEndMarker(); function three times, but print only the second one.

As a precaution, I'm also clearing the serial buffer at the beginning of each loop, but this time with a proper function.

This is the code that I'm testing right now, and is working perfectly stable since about 15 minutes.

#include <SPI.h>
#include <Wire.h>
// #include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);


const byte numChars = 32;
char receivedChars[numChars];   // an array to store the received data

boolean newData = false;




void setup() {
  Serial.begin(9600);
//  mySerial.begin(9600);
  
  // pinMode(LED_BUILTIN, OUTPUT);

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64
    // Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000);
  display.clearDisplay();
  display.display();


  //Wait for a little while before sending the reset command to the OBD-II-UART
  delay(1500);
  //Reset the OBD-II-UART
  Serial.println("ATZ");
    
  //Wait for a bit before starting to send commands after the reset.
  delay(2000);
}

void loop() {
  //Delete any data that may be in the serial port before we begin.  
  Serial.flush();
  emptyReceiv();
  delay(100);
  
  //Query the OBD-II-UART for Battery Voltage
  Serial.println("ATRV");
  delay(100);

  recvWithEndMarker(); // echo of request
  trashNewData();    // don't print
  delay(500);

  recvWithEndMarker(); // reply
  showNewData();       // print
  delay(500);
  
  recvWithEndMarker(); // empty line
  trashNewData();    // don't print
  
  delay(2000); 
}


void emptyReceiv() { 
    while (Serial.available() > 0) {
      Serial.read();      
    }
}

void recvWithEndMarker() {
    static byte ndx = 0;
    char endMarker = '\r';
    char rc;
   
    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (rc != endMarker) {
            receivedChars[ndx] = rc;
            ndx++;
            if (ndx >= numChars) {
                ndx = numChars - 1;
            }
        }
        else {
            receivedChars[ndx] = '\0'; // terminate the string
            ndx = 0;
            newData = true;
        }
    }
}

void showNewData() {
    if (newData == true) {
 
        display.setTextSize(2);
        display.setTextColor(WHITE);
        display.setCursor(0, 0);
        display.clearDisplay();
        
        display.println(n); // loop number
        display.println(m); // received line number
        
        display.println(receivedChars);
        display.display();

        newData = false;
    }
}

void trashNewData() {
    if (newData == true) {
      
        newData = false;
    }
}

There are still a few delay(); which can probably be removed, but they don't harm so for the moment I'm not removing them.

Now, it's time to move to the car and begin printing some real data! :wink: