Skip to content

Commit

Permalink
Merge pull request iwanders#10 from iwanders/dtc_support
Browse files Browse the repository at this point in the history
Adds DTC support
  • Loading branch information
iwanders authored Mar 4, 2018
2 parents bb5f4a5 + ec222a0 commit dd4acbc
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 2 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ In the logic folder are some recordings made with a [Saleae logic analyzer][sale
Three examples are given in the example folder. The `reader` and `simulator` examples are to be used with a hardware serial port. The `reader_softserial` example shows how to use it with the [AltSoftSerial][altsoftserial] library. For more information on how to use the library, refer to to the header files.

Timing
-----
------
Several parameters related to timing are given by the specification, some others are beyond our influence. To understand how a request works, lets consider the case the `0x0D` PID is requested, this represents the vehicle's speed.
![Timing diagram of a request](/../master/extras/timing_diagram/timing_diagram.png?raw=true "Timing diagram of a request")

Expand All @@ -37,6 +37,14 @@ After the echo has been read, the waiting game begins as we wait for the ECU to

The number of bytes to be received for each phase is known beforehand so the `readBytes` method can be used; it ensures that we stop reading immediately after the expected amount has been read. The main impact on the performance is given by the time the ECU takes to send a response and the `INTERSYMBOL_WAIT` between the bytes sent on the bus. There is no delay parameter for the minimum duration between two requests, that is up to the user.

Trouble codes
-------------
The library supports reading diagnostic trouble codes from the ECU (the ones associated to the malfunction indicator light). This was made possible with extensive testing by [Produmann](https://github.com/produmann), under [issue #9](https://github.com/iwanders/OBD9141/issues/9).

Contrary to reading the normal OBD PID's, when trouble codes are read from the ECU the length of the answer is not known beforehand. To accomodate this a method is implemented that handles a variable length request to the ECU. In this variable length response, each trouble code is represented by two bytes. These two bytes can then be retrieved from the buffer and converted into a human readable trouble code with letter and 4 digits (for example `P0113`), which can then be printed to the serial port. An example on how to read the diagnostic trouble codes is available, see [`readDTC`](examples/readDTC/readDTC.ino). Increasing the buffer size `OBD9141_BUFFER_SIZE` in the header file may be necessary to accomodate the response from the ECU.

The following trouble-code related modes are supported: Reading stored trouble codes (mode `0x03`), clearing trouble codes (mode `0x04`) and reading pending trouble codes (mode `0x07`).

License
------
MIT License, see LICENSE.md.
Expand Down
95 changes: 95 additions & 0 deletions examples/readDTC/readDTC.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "Arduino.h"
#include "OBD9141.h"

#define RX_PIN 0
#define TX_PIN 1
#define EN_PIN 2

/*
This example shows how to use the library to read the diagnostic trouble codes
from the ECU and print these in a human readable format to the serial port.
Huge thanks goes out to to https://github.com/produmann for helping with
development of this feature and the extensive testing on a real car.
*/

OBD9141 obd;


void setup(){
Serial.begin(9600);
delay(2000);

pinMode(EN_PIN, OUTPUT);
digitalWrite(EN_PIN, HIGH);

obd.begin(Serial1, RX_PIN, TX_PIN);

}

void loop(){
Serial.println("Looping");

bool init_success = obd.init();
Serial.print("init_success:");
Serial.println(init_success);

//init_success = true;
// Uncomment this line if you use the simulator to force the init to be
// interpreted as successful. With an actual ECU; be sure that the init is
// succesful before trying to request PID's.

// Trouble code consists of a letter and then four digits, we write this
// human readable ascii string into the dtc_buf which we then write to the
// serial port.
uint8_t dtc_buf[5];

if (init_success){
uint8_t res;
while(1){
// res will hold the number of trouble codes that were received.
// If no diagnostic trouble codes were retrieved it will be zero.
// The ECU may return trouble codes which decode to P0000, this is
// not a real trouble code but instead used to indicate the end of
// the trouble code list.
res = obd.readTroubleCodes();
if (res){
Serial.print("Read ");
Serial.print(res);
Serial.println(" codes:");
for (uint8_t index = 0; index < res; index++)
{
// retrieve the trouble code in its raw two byte value.
uint16_t trouble_code = obd.getTroubleCode(index);

// If it is equal to zero, it is not a real trouble code
// but the ECU returned it, print an explanation.
if (trouble_code == 0)
{
Serial.println("P0000 (reached end of trouble codes)");
}
else
{
// convert the DTC bytes from the buffer into readable string
OBD9141::decodeDTC(trouble_code, dtc_buf);
// Print the 5 readable ascii strings to the serial port.
Serial.write(dtc_buf, 5);
Serial.println();
}
}
}
else
{
Serial.println("No trouble codes retrieved.");
}
Serial.println();

delay(200);
}
}
delay(3000);
}




119 changes: 119 additions & 0 deletions src/OBD9141.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,75 @@ bool OBD9141::request(void* request, uint8_t request_len, uint8_t ret_len){
}
}

uint8_t OBD9141::request(void* request, uint8_t request_len){
bool success = true;
// wipe the entire buffer to ensure we are in a clean slate.
memset(this->buffer, 0, OBD9141_BUFFER_SIZE);

// create the request with checksum.
uint8_t buf[request_len+1];
memcpy(buf, request, request_len); // copy request
buf[request_len] = this->checksum(&buf, request_len); // add the checksum

// manually write the bytes onto the serial port
// this does NOT read the echoes.
OBD9141print("W: ");
#ifdef OBD9141_DEBUG
for (uint8_t i=0; i < (request_len+1); i++){
OBD9141print(buf[i]);OBD9141print(" ");
};OBD9141println();
#endif
for (uint8_t i=0; i < request_len+1 ; i++){
this->serial->write(reinterpret_cast<uint8_t*>(buf)[i]);
delay(OBD9141_INTERSYMBOL_WAIT);
}

// next step, is to read the echo from the serial port.
this->serial->setTimeout(OBD9141_REQUEST_ECHO_MS_PER_BYTE * 1 + OBD9141_WAIT_FOR_ECHO_TIMEOUT);
uint8_t tmp[request_len+1]; // temporary variable to read into.
this->serial->readBytes(tmp, request_len+1);

OBD9141print("E: ");
for (uint8_t i=0; i < request_len+1; i++)
{
#ifdef OBD9141_DEBUG
OBD9141print(tmp[i]);OBD9141print(" ");
#endif
// check if echo is what we wanted to send
success &= (buf[i] == tmp[i]);
}

// so echo is dealt with now... next is listening to the reply, which is a variable number.
// set the timeout for the first read to include the wait for answer timeout
this->serial->setTimeout(OBD9141_REQUEST_ANSWER_MS_PER_BYTE * 1 + OBD9141_WAIT_FOR_REQUEST_ANSWER_TIMEOUT);

uint8_t answer_length = 0;
// while readBytes returns a byte, keep reading.
while (this->serial->readBytes(&(this->buffer[answer_length]), 1) && (answer_length < OBD9141_BUFFER_SIZE))
{
answer_length++;
this->serial->setTimeout(OBD9141_REQUEST_ANSWER_MS_PER_BYTE * 1);
}

OBD9141println();OBD9141print("A (");OBD9141print(answer_length);OBD9141print("): ");
#ifdef OBD9141_DEBUG
for (uint8_t i=0; i < min(answer_length, OBD9141_BUFFER_SIZE); i++){
OBD9141print(this->buffer[i]);OBD9141print(" ");
};OBD9141println();
#endif

// next, calculate the checksum
bool checksum = (this->checksum(&(this->buffer[0]), answer_length-1) == this->buffer[answer_length - 1]);
OBD9141print("C: ");OBD9141println(checksum);
OBD9141print("S: ");OBD9141println(success);
OBD9141print("R: ");OBD9141println(answer_length - 1);
if (checksum && success)
{
return answer_length - 1;
}
return 0;
}

/*
No header description to be found on the internet?
Expand Down Expand Up @@ -143,6 +212,30 @@ bool OBD9141::clearTroubleCodes(){
return res;
}

uint8_t OBD9141::readTroubleCodes()
{
uint8_t message[4] = {0x68, 0x6A, 0xF1, 0x03};
uint8_t response = this->request(&message, 4);
if (response >= 4)
{
// OBD9141print("T: ");OBD9141println((response - 4) / 2);
return (response - 4) / 2; // every DTC is 2 bytes, header was 4 bytes.
}
return 0;
}

uint8_t OBD9141::readPendingTroubleCodes()
{
uint8_t message[4] = {0x68, 0x6A, 0xF1, 0x07};
uint8_t response = this->request(&message, 4);
if (response >= 4)
{
// OBD9141print("T: ");OBD9141println((response - 4) / 2);
return (response - 4) / 2; // every DTC is 2 bytes, header was 4 bytes.
}
return 0;
}

uint8_t OBD9141::readUint8(){
return this->buffer[5];
}
Expand All @@ -155,6 +248,15 @@ uint8_t OBD9141::readUint8(uint8_t index){
return this->buffer[5 + index];
}

uint8_t OBD9141::readBuffer(uint8_t index){
return this->buffer[index];
}

uint16_t OBD9141::getTroubleCode(uint8_t index)
{
return *reinterpret_cast<uint16_t*>(&(this->buffer[index*2 + 4]));
}

bool OBD9141::init(){
// this function performs the ISO9141 5-baud 'slow' init.
this->set_port(false); // disable the port.
Expand Down Expand Up @@ -249,3 +351,20 @@ bool OBD9141::init(){
}


void OBD9141::decodeDTC(uint16_t input_bytes, uint8_t* output_string){
const uint8_t A = reinterpret_cast<uint8_t*>(&input_bytes)[0];
const uint8_t B = reinterpret_cast<uint8_t*>(&input_bytes)[1];
const static char type_lookup[4] = {'P', 'C', 'B', 'U'};
const static char digit_lookup[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

// A7-A6 is first dtc character, error type:
output_string[0] = type_lookup[A >> 6];
// A5-A4 is second dtc character
output_string[1] = digit_lookup[(A >> 4) & 0b11];
// A3-A0 is third dtc character.
output_string[2] = digit_lookup[A & 0b1111];
// B7-B4 is fourth dtc character
output_string[3] = digit_lookup[B >> 4];
// B3-B0 is fifth dtc character
output_string[4] = digit_lookup[B & 0b1111];
}
44 changes: 43 additions & 1 deletion src/OBD9141.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,35 @@ class OBD9141{
// length was returned and if the checksum matches.
// User needs to ensure that the ret_len never exceeds the buffer size.


/**
* @brief Send a request with a variable number of return bytes.
* @param request The pointer to read the address from.
* @param request_len the length of the request.
* @return the number of bytes read if checksum matches.
* @note If checksum doesn't match return will be zero, but bytes will
* still be written to the internal buffer.
*/
uint8_t request(void* request, uint8_t request_len);

// The following methods only work to read values from PID mode 0x01
uint8_t readUint8(); // returns right part from the buffer as uint8_t
uint16_t readUint16(); // idem...
uint8_t readUint8(uint8_t index); // returns byte on index.

/**
* @brief This method allows raw access to the buffer, the return header
* is 4 bytes, so data starts on index 4.
*/
uint8_t readBuffer(uint8_t index);

/**
* @brief Obtain the two bytes representing the trouble code from the
* buffer.
* @param index The index of the trouble code.
* @return Two byte data to be used by decodeDTC.
*/
uint16_t getTroubleCode(uint8_t index);


void set_port(bool enabled);
// need to disable the port before init.
Expand All @@ -159,7 +183,25 @@ class OBD9141{
// Check engine light.
// Returns whether the request was successful.

/**
* @brief Attempts to read the diagnostic trouble codes using the
* variable read method.
* @return The number of trouble codes read.
*/
uint8_t readTroubleCodes(); // mode 0x03, stored codes
uint8_t readPendingTroubleCodes(); // mode 0x07, pending codes

static uint8_t checksum(void* b, uint8_t len); // public for sim.

/**
* Decodes the two bytes at input_bytes into the diagnostic trouble
* code, written in printable format to output_string.
* @param input_bytes Two input bytes that represent the trouble code.
* @param output_string Writes 5 bytes to this pointer representing the
* human readable DTC string.
*/
static void decodeDTC(uint16_t input_bytes, uint8_t* output_string);

};


Expand Down

0 comments on commit dd4acbc

Please sign in to comment.