iwanders / OBD9141

A class to read an ISO 9141-2 port found in OBD-II ports.
MIT License
232 stars 72 forks source link

Mode 04 Clear Trouble Codes, CEL, MIL #8

Closed crashmaxx closed 6 years ago

crashmaxx commented 6 years ago

I couldn't find anything in the current code that will put the ECU in Mode 04 to clear trouble codes, the CEL, and/or MIL. If this feature already exists, it needs to be documented.

Assuming it doesn't exist, it should be a simple feature to add as far as I understand it. A function called OBD9141::ClearCodes or similar would be created and it would just send the hex code for Mode 04 to the ECU.

iwanders commented 6 years ago

Hi Crashmaxx,

I couldn't find anything in the current code that will put the ECU in Mode 04 to clear trouble codes, the CEL, and/or MIL.

You are correct, there is no code that does this.

Assuming it doesn't exist, it should be a simple feature to add as far as I understand it. A function called OBD9141::ClearCodes or similar would be created and it would just send the hex code for Mode 04 to the ECU.

Yeah I think so! I considered implementing this when I worked on it. But since I had no way to test this I chose not to do a blind implementation. Having non working code is worse than not having that code, as it creates false expectations.

The implementation should indeed be something along the lines of:

bool OBD9141::clearTroubleCodes(){
    uint8_t message[4] = {0x68, 0x6A, 0xF1, 0x04};
    // 0x04 without PID value should clear the trouble codes / malfunction indicator lamp.

    bool res = this->request(&message, 4, /* return probably same length? */ 4);
    return res;
}

Wikipedia says there are no data bytes returned. The return format is a bit of a mystery unfortunately, I expect the response to be the same length as the request if the data length is zero. That would continue the pattern set for the other PID's (see the getPID method).

If you have a vehicle where you can safely trigger a trouble code, feel free to test the above snippet. If the malfunction light goes off but the method returns false, increase the return length from 4 into a 5 and check the result again, it wouldn't surprise me if the ECU returns something different for this case. But with some experimentation you should be able to get this to work even without a logic analyzer.

Let us know how this works out!

crashmaxx commented 6 years ago

Wow! Thank you for the quick response!

I have a car with an engine swap, so it is missing some sensors. It will get trouble codes as soon as it starts and the CEL will come on in a few minutes.

I will test the code tomorrow and get back to you. I hope I can verify it well enough you can add it to the library. Thanks again!

crashmaxx commented 6 years ago

OBD9141_Mode04.zip

Attached is a zip with my modified OBD9141.h, OBD9141.cpp, the sketch I tested with (Clear_Codes2.ino), and the serial console data I recorded (ClearCodesData.text).

It worked great! The CEL went away when I started the car. The function seems to return 0, but maybe I am collecting that data wrong.

I wanted to have it only clear the codes when the car is on, but not running, and had driven at least 10 miles since the codes were last cleared. I couldn't get PID 31 (Distance since CEL cleared) to read though.

I'd be happy to do more testing if you want to try anything different. Thanks a lot!

iwanders commented 6 years ago

Your zipfile is awesome! Very thoughtful of you to include the serial console data and code, that's great. I didn't see the file Clear_Codes2.ino in the zipfile, but there was a Clear_Codes.ino, so I'm basing my reasoning on that.

It worked great! The CEL went away when I started the car. The function seems to return 0, but maybe I am collecting that data wrong.

Superb! Yes, the printout isn't entirely what you wanted I think, but because you included both the serial log and the code we can deduce exactly what happened, the part of the code where the new method is called:

      if (RPM == 0){
        delay(5000);
        res = obd.clearTroubleCodes();

        if (res){
          SerialUSB.print("Resetting CEL: ");
          SerialUSB.println(obd.readUint8());
          for (int t = 0; t < 10; t++) {
              digitalWrite(DS3, LOW);  // turn the LED on by making the voltage LOW
              delay(100);
              digitalWrite(DS3, HIGH);  // turn the LED off by making the voltage HIGH
              delay(100);
          }
        }        
      }

And the output:

Looping
init_success:1
Result 0x0C (RPM): 0
Result 0x0D (Speed MPH): 0
Result 0x05 (ECT C): 9
Result 0x05 (IAT C): -40
Resetting CEL: 0

The thing is, the line Resetting CEL is present, the print for this line is inside of the if (res) clause, so this means that the obd.clearTroubleCodes(); call returned true, otherwise we wouldn't have seen anything in the serial console! So that means the clearTroubleCodes is working!

That you couldn't receive 0x31, Distance traveled since codes cleared could have various reasons, most likely that the ECU doesn't support it, the implementation should be something along the lines of:

      res = obd.getCurrentPID(0x31, 2);

      if (res){
        SerialUSB.print("Result 0x31 (distance since CEL km): ");
        uint16_t  distance = obd.readUint16();
        SerialUSB.println(distance);
      }

Another plausible reason is that this value will be zero since your test setup isn't really making a lot of distance if you are swapping the engine ;) Take a look at PID 0x4D and 0x4E, they may be more insightful if the ECU supports them.

There are ways to check if your ECU supports a particular request. Wikipedia has a paragraph on how to interpret the bitmask returned by mode 1 PID 0x00. This bitmask tells you which PID's are supported. If you look at the big table, at pid 0x20, you see that that is the bitmask to obtain whether the PID's 21-40 are supported. So, you can use the bitmask from 0x00 to check whether retrieving the bitmask at PID 0x20 is supported, if it is you can retrieve that and check if PID 0x31 is supported.

The code to do this something along the lines of:


      res = obd.getCurrentPID(0x00, 4);  // This is the block 0x01-0x20
      // res = obd.getCurrentPID(0x20, 4);  This is the block 0x21-0x40

      if (res){
        SerialUSB.println("Supported PIDs in range 0x21-0x40");
        SerialUSB.print("a: 0x"); Serial.println(obd.readUint8(0), HEX);
        SerialUSB.print("b: 0x"); Serial.println(obd.readUint8(1), HEX);
        SerialUSB.print("c: 0x"); Serial.println(obd.readUint8(2), HEX);
        SerialUSB.print("d: 0x"); Serial.println(obd.readUint8(3), HEX);
      }

Though, I don't really know if the a byte or the d byte is the highest, I don't remember the endianness of the system. But if 0x20 returns all zero's you know for sure that the ECU doesn't support it.

Also, small tip; if you want to to run with #define OBD9141_DEBUG, or if you want to change a library with any defines, you have to set that before you include the library:

#define OBD9141_DEBUG
#include <OBD9141.h>
#include <Arduino.h>
// Instead of having #define OBD9141_DEBUG here.

This is because the flags / defines need to be set before the header file is included, otherwise setting it will have no effect. Alternatively (this is what I used during development), you can pass it as a flag to your compiler (add -DOBD9141_DEBUG to the options), but if you use the Arduino IDE I'm not sure how or if that is possible.

iwanders commented 6 years ago

I've added the method to the library! Could you download a fresh version and check whether it also works with the one from the library? (Just to be 100% sure).

crashmaxx commented 6 years ago

That's awesome! Thanks for all the help! I'll download a fresh copy of the library and try it out tomorrow. I'll experiment with requesting the list of supported PIDs too.

crashmaxx commented 6 years ago

Everything_Fails.zip

I downloaded a new copy of the library. None of my sketches work today. I've tried checking the OBD2 port, checking my wiring to the M2, and that the M2 still works. Everything seems good, but nothing will initialize. They all output the same as Console_Log.txt.

I feel like I must have a problem with my setup, because the library looks fine. If you wouldn't mind looking at my sketches and letting me know if you see anything that looks wrong.

Or if you could give me a really basic debugging sketch that just initializes and outputs as much as possible. Thanks for all the help so far. I want to help add more features if I can get everything to work again.

iwanders commented 6 years ago

Hey,

I feel like I must have a problem with my setup, because the library looks fine. If you wouldn't mind looking at my sketches and letting me know if you see anything that looks wrong.

Yeah, I'm afraid it must be something with the hardware setup, the sketches look fine. Check wiring / pullup resistor / connectors, check voltage on the K-line (should be around battery voltage when no communication is happening). As you said the library is practically unchanged, the only difference is the commit with the new method, but that certainly won't cause the init to fail.

if you could give me a really basic debugging sketch that just initializes and outputs as much as possible.

This is tricky, because I have no way to check if the debugging sketch I make works for your hardware. You should try to use the most simple example you have that used to work (probably Clean_Ex). You could add the debug define to get some more output, then you would change the following at the top of the sketch

#include "Arduino.h"
#include "OBD9141.h"

into:

#include "Arduino.h"
#define OBD9141_DEBUG
#include "OBD9141.h"

But this is unlikely to solve the initialization problems you are having. I'm almost certain it's a wiring or hardware problem if it doesn't work with a simple sketch that used to work a few days ago.

crashmaxx commented 6 years ago

Thanks for the feedback. I'll do more diagnostics tomorrow and get back to you.

I just wanted to make sure I didn't have a coding error I was copying over and over into all the sketches.

produmann commented 6 years ago

after analyzing the method, you are wrong

// No data is returned to this request, we expect the request itself

you can return a report on the successful erasure of error codes, here is my log:

C7 0 0 0 0 0 0 0 0 0  - return response vehicle ecu in case of unsuccessful reset
C7 48 6B 1 44 F8 0 0 0 0  - return response vehicle ecu in case of a successful reset

so I used the last returned byte 0хF8 and returned the marker for a successfully executed reset. Can be different car manufacturers in different ways will give a response to the 0x04 mode, or maybe I'm mistaken

iwanders commented 6 years ago

With "No data is returned" I don't mean any bytes are returned, I just mean that the data part of the response will be zero, as per wikipedia.

you can return a report on the successful erasure of error codes, here is my log:

C7 0 0 0 0 0 0 0 0 0  - return response vehicle ecu in case of unsuccessful reset
C7 48 6B 1 44 F8 0 0 0 0  - return response vehicle ecu in case of a successful reset

Hmm... this 0xC7 you have at the start is suspicious... that shouldn't be there, I'll explain. The mode 0x04 method is here. The actual request is four bytes:

0x68, 0x6A, 0xF1, 0x04

This 4 byte long request gets passed to the request method where the checksum byte is added. If we calculate the checksum byte manually for the four request bytes (hex((0x68 + 0x6a + 0xf1 + 0x04) % 0x100) = 0xc7, in Python for example, or use the checksum method in the class), so we get 0xc7 as a checksum byte.

So the actual bytes transmitted during the request will be:

0x68, 0x6A, 0xF1, 0x04, 0xC7

Since the tx and rx are both connected to the Kline transceiver chip, we will see anything we transmit as an echo on the rx line. The write method tries to read this 'echo' from the data line such that it isn't read as part of the response from the ECU, but it appears that the value of OBD9141_WAIT_FOR_REQUEST_ANSWER_TIMEOUT is too low for the ECU in your car. Increasing the echo values here may prevent that 0xC7 from appearing in the response.

In your results we see:

C7 48 6B 1 44 F8 0 0 0 0 - return response vehicle ecu in case of a successful reset

If we are to disregard the 0xC7 echo from the request... we actually have:

48 6B 1 44 F8

Which is exactly what the code and I would expect, the response is the standard 4 byte to the request and the checksum to the bytes 0x48, 0x6b, 0x01, 0x44 is the 0xF8 byte, so if you can prevent the 0xc7 from appearing you would probably see that the method would return true.

produmann commented 6 years ago

I understood, but how can I prevent the 0xc7 byte from appearing? increase time OBD9141_REQUEST_ECHO_MS_PER_BYTE? on how much?

iwanders commented 6 years ago

Change it from 3 to 5 or 10... even setting it to 100 should work, since we only wait for echo's if we expect it. Just change it a bit and see what happens... the 3 is only tuned the car I used to write this library.

produmann commented 6 years ago

hello, it works, here's the log:

Looping
Before magic 5 baud.
Before setting port.
After setting port.
First read is: 85
read v1: 8
read v2: 8
v1: 8
v2: 8
w: 247
read 0xCC?: CC
init_success:1
w: 104
w: 106
w: 241
w: 3
w: 0
w: 198
Trying to get x bytes: 12
72 107 1 67 1 19 0 0 0 0 11 0 
res 1
Read 1 codes:P0113

res = obd.clearTroubleCodes();
w: 104
w: 106
w: 241
w: 4
w: 199
Trying to get x bytes: 5
72 107 1 68 248 
 clearTroubleCodesclearTroubleCodes res =1
w: 104
w: 106
w: 241
w: 3
w: 0
w: 198
Trying to get x bytes: 12
72 107 1 67 0 0 0 0 0 0 247 0 
res 1
Read 1 codes:P0000
iwanders commented 6 years ago

Awesome, that's great news, what value did you end up using for OBD9141_REQUEST_ECHO_MS_PER_BYTE? It may be useful for others to know what value you used, and we could even change it in the library since in theory its value doesn't matter as we only wait while we expect bytes. For the variable length request we're working on in the other issue it does make a difference though.

produmann commented 6 years ago

OBD9141_REQUEST_ECHO_MS_PER_BYTE I did not change, set by default, earned so