0

I am trying to read data from an ECU using the ISO 9141-2 protocol. The cable I'm using is OBD2 to USB, using a FT232R chip. The programs that I am running are in C.

When I write commands to the serial port (ttyUSB0) it writes to it fine but when it reads back the received data it just returns the same data. Instead of returning the data from the ECU, I'm not sure on why it is doing this.

Any help is great, thanks.

Example code - Tried with setting the baud rate etc but no luck either.

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    char *port = "/dev/ttyUSB0";
    char receivedData[100];

    int n, fd; 

    fd = open(port, O_RDWR | O_NOCTTY);

    if(fd > 0){
        n = write(fd, "AT I\r\n", 10);
        read(fd, receivedData, 10);
        printf("%s\n", receivedData);

        close(fd);
    }
    else{
        printf("failed to open device\n");
    }

    return 0;
}
Pablo
  • 13,271
  • 4
  • 39
  • 59
Joe
  • 37
  • 8
  • Perhaps because you use the same buffer to read and write and read fails and that's why it appears to be the same. Please share the code, otherwise we can only guess. – Pablo Mar 11 '18 at 15:27
  • Added code to original question. As I'm trying to read data from an ECU I wasn't sure if authentication would be needed also? – Joe Mar 11 '18 at 15:46
  • `write(fd, "AT I\r\n", 10)` is undefined behaviour, you are overflowing the string literal and you may be sending garbage to the device. `write(fd, "AT I\r\n", strlen("AT I\r\n"))` is correct – Pablo Mar 11 '18 at 15:48
  • After changing that the I just receive the same data back. So I send "AT I\r\n" and receive "AT I" instead of actual data from the ECU. I'm struggling to figure out why it just returns the same thing. – Joe Mar 11 '18 at 16:03
  • I'm not familiar with the ISO 9141-2 protocol, check the manual, perhaps you need to send another AT command before `AT I` in order to enable access to it. – Pablo Mar 11 '18 at 16:06
  • Although when I send the command "AT 0105\r\n" to get the coolant temp (may be wrong) I receive ��� from the ECU for some reason – Joe Mar 11 '18 at 16:07
  • Thank you, I will try to see what I can find. I wasn't aware that there could be some commands before to enable it – Joe Mar 11 '18 at 16:07
  • A quick google search of this protocol showed me many pages but they all use CAN, not AT command. Are you sure that this chip of yours understands AT command? – Pablo Mar 11 '18 at 16:08
  • The ECU that I am connecting to use the pins for the ISO-9141-2 protocol and not CAN – Joe Mar 11 '18 at 16:10
  • Do you have a link to the documentation? All I found are reference of the protocol using CAN. – Pablo Mar 11 '18 at 16:13
  • I'm not 100% sure but I believe it is AT commands that are sent to the device. Nothing I have read says anything about using CAN, as that is a different protocol – Joe Mar 11 '18 at 16:23
  • I know that CAN is a different protocol, I've worked with it for many years. Like I said is that I did a quick google search on ISO 9141-2 and all I got was pages that said it uses CAN, no mention of AT commands. That's why I asked if you have a link to the manual to take a look into it. – Pablo Mar 11 '18 at 16:27
  • You need to initialize the tty. At least turn off echo and set a correct baud rate. – user58697 Mar 11 '18 at 16:32
  • Ah sorry misinterpreted, I'm new to this sort of thing with these protocols. All I know is the ECU that is connected is using ISO-9141-2 protocol, and I have a USB to OBDii cable that i believe has a FT232R chip – Joe Mar 11 '18 at 16:33
  • How would I initialize the tty? and I have tried setting the baud rate but I think i've got the wrong speed – Joe Mar 11 '18 at 16:34

3 Answers3

1

Even though this is not a direct answer to the question, this amount of code does not fit in the comment section.

I use these functions to initialize the serial lines:

#include <stdio.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>


struct baud_map {
    int baud;
    speed_t speed;
};

struct baud_map baudmap[] = {
    { 50        , B50 },
    { 75        , B75 },
    { 110       , B110 },
    { 134       , B134 },
    { 150       , B150 },
    { 200       , B200 },
    { 300       , B300 },
    { 600       , B600 },
    { 1200      , B1200 },
    { 1800      , B1800 },
    { 2400      , B2400 },
    { 4800      , B4800 },
    { 9600      , B9600 },
    { 19200     , B19200 },
    { 38400     , B38400 },
    { 57600     , B57600 },
    { 115200    , B115200 },
    { 230400    , B230400 },
    { 0,      0 }
};

int dbits_map[] = { 0, 0, 0, 0, 0, CS5, CS6, CS7, CS8 };

enum parity_t {
    PARITY_NO_PARITY,
    PARITY_ODD,
    PARITY_EVEN
};

int baud_to_speed(int baud, speed_t *speed)
{
    if(speed == NULL)
        return 0;

    struct baud_map *map = baudmap;
    while(map->baud)
    {
        if(map->baud == baud)
        {
            *speed = map->speed;
            return 1;
        }

        map++;
    }

    return 0;
}

/*
 * tty: "/dev/ttyUSB0"
 * baud: baudrate, for example 9600
 * parity: see enum parity_t
 * stop_bits: 1 or 2
 * data_bits: [5-8]
 *
 * return the fd on success, -1 on failure
 */
int openSerial_long(const char *tty, int baud, enum parity_t parity, int stop_bits, int data_bits)
{
    int fd;

    speed_t speed;

    if(baud_to_speed(baud, &speed) == 0)
    {
        fprintf(stderr, "Invalid baudrate %d\n", baud);
        return 0;
    }

    fd = open(tty, O_RDWR | O_NOCTTY  | O_NONBLOCK);

    if(fd == -1)
    {
        fprintf(stderr, "Could not open %s as a tty: %s\n", tty, strerror(errno));
        return -1;
    }

    struct termios termios;

    if(tcgetattr(fd, &termios) == -1)
    {
        fprintf(stderr, "Could not get tty attributes from %s: %s\n", tty, strerror(errno));
        close(fd);
        return -1;
    }

    // setting common values
    termios.c_iflag &= ~ICRNL;           // do not translate \r into \n
    termios.c_oflag &= ~OPOST;           // do not map \n to \r\n
    termios.c_cflag |= (CREAD | CLOCAL); // enable receiver & ignore model ctrl lines
    termios.c_lflag |= (ISIG | ICANON);  // enable signals and noncanonical mode
    termios.c_lflag &= ~ECHO;             // disable echo


    cfsetispeed(&termios, speed);
    cfsetospeed(&termios, speed);

    switch(parity)
    {
        case PARITY_NO_PARITY:
            termios.c_cflag &= ~PARENB;
            break;
        case PARITY_ODD:
            termios.c_cflag |= PARENB;
            termios.c_cflag |= PARODD;
            break;
        case PARITY_EVEN:
            termios.c_cflag |= PARENB;
            termios.c_cflag &= ~PARODD;
            break;
        default:
            fprintf(stderr, "invalid parity\n");
            break;
    }

    if(stop_bits == 1)
        termios.c_cflag &= ~CSTOPB;
    else if(stop_bits == 2)
        termios.c_cflag |= CSTOPB;
    else
        fprintf(stderr, "Invalid stop bit\n");

    int bits;

    switch(data_bits)
    {
        case 5:
        case 6:
        case 7:
        case 8:
            bits = dbits_map[data_bits];
            break;

        default:
            bits = -1;

    }

    if(bits != -1)
    {
        termios.c_cflag &= ~CSIZE;
        termios.c_cflag |= bits;
    } else
        fprintf(stderr, "Invalid data size\n");


    if(tcsetattr(fd, TCSANOW, &termios) == -1)
    {
        fprintf(stderr, "Could not get tty attributes from %s: %s\n", tty, strerror(errno));
        close(fd);
        return -1;
    }


    return fd;
}

/**
 * tty: "/dev/ttyUSB0"
 * baud: baudrate, for example 9600
 * mode: a string like 8N1 where 
 *       the first character is the number of data bits (range from 5-8)
 *       the second character is N (no parity), O (odd), E (even)
 *       the third character is the number of stop bits (1 or 2)
 */
int openSerial(const char *tty, int baud, const char *mode)
{
    if(tty == NULL || mode == NULL)
        return -1;

    if(strlen(mode) != 3)
    {
        fprintf(stderr, "invalid mode\n");
        return -1;
    }

    int stop_bits = mode[2];
    if(stop_bits != '1' && stop_bits != '2')
    {
        fprintf(stderr, "Invalid stop bits\n");
        return -1;
    }

    stop_bits -= '0';

    enum parity_t parity;
    switch(mode[1])
    {
        case 'n':
        case 'N':
            parity = PARITY_NO_PARITY;
            break;
        case 'o':
        case 'O':
            parity = PARITY_ODD;
            break;
        case 'e':
        case 'E':
            parity = PARITY_EVEN;
            break;
        default:
            fprintf(stderr, "Invalid parity\n");
            return -1;
    }

    int data_bits = mode[0] - '0';

    if(data_bits < 5 || data_bits > 8)
    {
        fprintf(stderr, "invalid data bits\n");
        return -1;
    }

    return openSerial_long(tty, baud, parity, stop_bits, data_bits);
}

The most common mode is "8N1" (8 bit data, no parity, 1 stop bit) and I open the serial line with

int fd = open("/dev/ttyUSB0", 9600, "8N1");

if(fd == -1)
{
    fprintf(stderr, "Error, could not initialize serial line\n");
    exit(EXIT_FAILURE);
}

I have no idea which serial settings you have to use, look it up in the manual.

Also be aware that stuff like this

n = write(fd, "AT I\r\n", 10);

is wrong, it's undefined behvaiour because write is reading beyon the bound of the string literal. It would be better to do:

const char *cmd = "AT I\r\n";
n = write(fd, cmd, strlen(cmd));

then you would write the correct amount of data.

Like I said in the comments, I've made a google search on ISO 9141-2 and I could not find any reliable information on whether it uses AT commands. So please check the manual of the OBDII chip you are trying to read.

Wikipedia says

ISO 9141-2. This protocol has an asynchronous serial data rate of 10.4 kbit/s. It is somewhat similar to RS-232; however, the signal levels are different, and communications happens on a single, bidirectional line without additional handshake signals. ISO 9141-2 is primarily used in Chrysler, European, and Asian vehicles.

It seems to me that this protocol is only similar to RS232, perhaps you need another converter (other than FT232R) which supports this baudrate. This page also states that the baudrate is 10400 bits/s, which is not supported by default. I've found this post which also suggests that you should use two converters, one for the communication between OBD2 and converter at 10.4 kbit/s and one between the converter and your PC at a standard baudrate like 19.2 kbits/s.

Alternatively you could try to set a custom baudrate as explained here and here. I've never had to use custom baudrates, so I don't know if this works.

Pablo
  • 13,271
  • 4
  • 39
  • 59
0

Okay so first off: I don't know C. However, you did n = (whatever to send the AT command) and for the read data you receive, then print. Why not attach the result of the read into a variable and print that? Or, you could use bash based commands to communicate with the serial port like "minicom" and I know there are others like that.

A couple other notes, as I have been working with OBD2 a lot lately: AT commands go to the reader, not the ECU. You can use minicom to see when you reset the adapter (ATZ) the ECU does nothing. However, send a 03 (or whatever mode is ECU CODE RESET) and it will clear your codes. For more info on headers and things like that for multiple ECUs see https://mechanics.stackexchange.com/questions/22982/received-frames-from-vehicles-with-multiple-ecu-chips

One last note: don't forget you have 2 different baud rates - one for your USB serial port to FT232R chip and one from said chip to the ECU. With ELM327 the latter is done via a AT command to change the proto. At any rate, use the FT232R baud rate coming from the computer, and test in minicom if necessary. Hope this helped!

Mark D
  • 1
0

It's perfectly normal for an OBD2 adapter to send you back the very same command you have sent. This is called echo mode and can be turned off by sending ATE0\r as the first command. If you read more of the answer, you should see the result of your command coming after the echoed request.

Note also that the AT commands are processed by the OBD2 without sending data to any ECUs, only the PIDs (commands composed out of digits) will be sent over the bus.

DrMickeyLauer
  • 4,455
  • 3
  • 31
  • 67