Project showcase
IoT4Car (2)

IoT4Car (2) © CC BY

ObdiiUartMkrShield is a socket board for Arduino MKR board to talk with vehicles through ODB-II interface.

  • 4,982 views
  • 15 comments
  • 49 respects

Components and supplies

Apps and online services

About this project

Background

Previously, I published a blog of "IoT4Car" demonstrating how to collect the vehicle data using a SparkFun OBD-II UART board and an Arduino MKR1000 board, and uploading the data into clouds for real-time monitoring. That prototype was made on a breadboard with a lot of wires, which makes it vulnerable to vibrations and bad contact. So I decided to make a customized PCB board, so called ObdiiUartMkrShield, that has similar functionalities but compatible to Arduino MKR board form factor and IO voltage tolerance.

Design Flow

On-board diagnostics (OBD) is an automotive term referring to a vehicle's self-diagnostic and reporting capability. Up till now, OBD has evolved to OBD-II standard with 16 pins interface. 5 signaling protocols are permitted in the OBD-II interface, being SAE J1850 PWM, SAE J1850 VPW, ISO 9141-2, ISO 14230 KWP2000, and ISO 15765 CAN. To simply the design, this PCB only supports Controller Area Network (CAN) protocol.

The communication between an Arduino board and a vehicle is realized by two chips. MCP2551 is used as the interface between a CAN physical layer and a CAN protocol controller. STN1110 is a multiprotocol OBD to UART interpreter that supports all 5 protocols. Here we only use its CAN protocol translation functionality. The schematics of the connection is shown in the attachment.

Though both chip datasheets provide sample circuit schematics, It is worth noting that selection of the capacitors for the crystal is critical. Differential crystal may have different cap loading. Selecting wrong cap values will result in different oscillation frequency, and thus the communication between STN1110 and MCP2551 will fail. Adafruit has a good tutorial of how to calculate the value of the capacitors that attach to the crystal. A general rule of thumb is C1, C2 = 2*CL - 2*Cstray, where CL is the crystal's load capacitance, Cstray is the parasitic capacitance and is approximately 2-5 pF.

PCB Design

The PCB layout is implement in KiCAD, which has no limitation on board size or number of connections. The schematic can be found in the attachment. Below is a screenshot of the layout.

The 3D model is rendered by raytracing engine in KiCAD.

Assembly

The PCB was fabricated by OSHpark. It usually takes 10 days to fab, and a few more days for shipping. After get the boards back, and purchasing the required components, the device is ready to be assembled. For a hobbyist like me, I usually choose through hole components because they are easy to solder. Electrical engineer veterans and professionals should use SMT components because they are cheaper.

Test

To test the ObdiiUartMkrShield PCB board, I wrote a program that can collect the car speed, and uploaded the code in an Arduino MKR1000 board. The program is available in the attachment. The car speed will be collected and plotted on the screen. Place the ObdiiUartMkrShield PCB under the Arduino MKR1000 as a socket, and connect the PCB with a DB9 to OBD-II cable. The OBD-II connector will be plugged into the car.

Here is a simple demo video. After introduction, you will see the comparison of car speed from the car dashboard and the Arduino serial plotter.

Code

ObdiiUartMkrShield_v13C/C++
/*
* OBDII-UART-Serial version 11
* This program will talk to vehicle using the OBDII-UART board, 
* and display the results on the LCD, and show the data in the Serial monitor
* 
* Author: Frank Zhao
* Updated: 2018-11-04
* 
* updates:
*   v3: modified the getResponse() function so that the buffer receives the correct response.
*       add the getRPM() to get the engine RPM from the vehicle.
*   v4: add the getSpeed() function to get the speed of the vehicle
*   v5: add the LCD module and display the speed and RPM on the LCD
*   v6: is the wifi version
*   v7: is the non-wifi, non-serial version. Remove serial initialization,
*       so that the board can work without a computer.
*   v8: is the non-wifi, non-serial version. Add fuel level and coolant temperature.
*       rearrange the display location.
*   v9: is the wifi, non-serial version. Upolad speed, RPM, fuel level and coolant temperture
*   v10: is the non-wifi, serial version. Get speed, RPM, fuel level and coolant temperature
*   v11: is the non-wifi, non-LCD, serial version. Get speed, RPM, fuel level and coolant temperature
*   v13: is the non-wifi, non-LCD, serial plotter version. The speed, RPM, fuel level and collant tempareture can be plotted.
*/

// This is a character buffer that will store the data from the serial port:
char rxData[20];
char rxIndex = 0;
char inChar = 0;
String message;

// Variables to hold the speed and the RPM data:
int vSpeed = 0;
int vRPM = 0;
int vFuel = 0;
int vTemp = 0;

void setup() {
  
 // Initialize the serial communications:
  Serial.begin(9600);
  while(!Serial){
    ; // if not ready, wait  
  }
//  Serial.println("Serial Ready");

  // Serial1 is the acutal port to talk to vehicle
  Serial1.begin(9600);
  while(!Serial1){
    ;// if not ready, wait  
  }
//  Serial.println("Serial1 Ready");

  delay(200);
  resetBuffer();
}

void loop() {
  while(Serial){
    getSpeed();
    resetBuffer();
//    getRPM();
//    resetBuffer();
//    getFuel();
//    resetBuffer();
//    getCoolTemp();
//    resetBuffer();
    Serial.println();
  }
}

// getRPM data sends the "010C" command to the Serial1 port
// and call the getResponse() to collect the data. Then it prints
// the RPM data on the Serial Monitor.

void getRPM(void){
  message = "010C";
  Serial1.println(message);
  delay(200);

  //wait reponse
  getResponse();
  // The RPM response divided by 4 gives the correct value.
  vRPM = ((strtol(&rxData[6],0,16)*256) + strtol(&rxData[9],0,16))/4;

  Serial.print(vRPM); // nomarlized by 100
//  Serial.print("rpm");
  Serial.print(" ");
}


void getSpeed(void){
  message = "010D";
  Serial1.println(message);
  delay(200);

  //wait for the response from the car
  getResponse();
  vSpeed = strtol(&rxData[6], 0, 16); // in the unit of km/h
  vSpeed = vSpeed * 0.621371; // in the unit of mph

  Serial.print(vSpeed);
//  Serial.print("mph");
  Serial.print(" ");
}

void getFuel(void){
  message = "012F";
  Serial1.println(message);
  delay(200);
 
  //wait for the response from the car
  getResponse();
  vFuel = strtol(&rxData[6], 0, 16); // in the scale of 255

  vFuel = 1.0* vFuel / 255 *100; // in the scale of 100

  Serial.print(vFuel);
//  Serial.print("%");
  Serial.print(" ");
}

void getCoolTemp(void){
  message = "0105";
  Serial1.println(message);
//  Serial.println(message);
  delay(200);
 
  //wait for the response from the car
  getResponse();
  vTemp = strtol(&rxData[6], 0, 16); // in the unit of C but offset by 40 degrees
  vTemp = vTemp - 40; // offset by 0

  Serial.print(vTemp);
//  Serial.print("C");
  Serial.print(" ");
}

// The getResponse function collects incoming data from the UART into the rxData buffer
// and exits when the response is transferred. Once the carriage return string
// is detected, the rxData buffer is null terminated (so that 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){
  while(Serial1.available() > 0) {
      // Start by checking if we've received the end of message character ('\r').
      if(Serial1.peek() == '\r'){
        // reach the end of the message, clear the Serial buffer
        inChar = Serial1.read();
        rxData[rxIndex] = '\0';
        // Reset the buffer index so that the next character goes back at the beginning of the string
        rxIndex = 0;  
      }
      // If we didnt get the end of the message character, just add the new character to the string
      else{
        // Get the new character from the Serial port:
        inChar = Serial1.read();
        // add the new character to the string, and increase the index variable:
        rxData[rxIndex++] = inChar;
      }  
  }

}

void resetBuffer(void){
  for (int i = 0; i < 20; i++){
    rxData[i] = 0;  
  }
}

Schematics

ObdiiUartMkrShield

Comments

Similar projects you might like

IoT4Car

Project tutorial by zhaoshentech

  • 13,184 views
  • 1 comment
  • 93 respects

LCD1602MkrUnoShield

by zhaoshentech

  • 1,142 views
  • 0 comments
  • 4 respects

Binary Wristwatch

Project showcase by thallia

  • 937 views
  • 0 comments
  • 9 respects

Smart Blinds

Project tutorial by Froz3nArcher

  • 13,646 views
  • 3 comments
  • 47 respects

CarSmart

Project showcase by Bill Lovegrove

  • 13,406 views
  • 0 comments
  • 66 respects

Two Ultrasonic Sensor Arduino Radar - Continuous Rotation

Project showcase by MicroLab Greece

  • 9,914 views
  • 7 comments
  • 31 respects
Add projectSign up / Login