Page 1 of 6

Simple Logger

PostPosted: Sat Jan 08, 2005 6:11 pm
by Visceral
Based on the technical information on the Subaru Select Protocol, I wrote up a very basic application to log a few values:
Image

Development was done in C using Cygwin on Windows. This code is really just "test" quality, and may contain errors.

Code: Select all
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <termios.h>
#include <sys/select.h>
#include <time.h>

#define RESPONSE_MSG_LEN      128

int debug = false;

typedef struct ecu_t
{
  int connected;
  int fd;
} ecu;

int connect(ecu_t *e)
{
  struct termios options;

  e->connected = 0; /* reset in case we fail to connect */

  e->fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY);
  if (e->fd == -1)
    return 0;
  else
    fcntl(e->fd, F_SETFL, 0);

  tcgetattr(e->fd, &options);

  /* go to 4800 baud */
  cfsetispeed(&options, B4800);
  cfsetospeed(&options, B4800);

  options.c_lflag = 0;  /* No local flags */
  options.c_oflag &= ~OPOST; /* No output processing */
  options.c_iflag &= ~(IXON | IXOFF | INPCK | PARMRK | BRKINT | INLCR | ICRNL |
                      IUCLC | IXANY);
  options.c_iflag |= IGNBRK;  /* Ignore break conditions */
  options.c_cflag |= (CLOCAL | CREAD); /* enable */
  options.c_cflag &= ~CRTSCTS; /* Turn off RTS/CTS */
  options.c_cflag &= ~PARENB; /* No parity */
  options.c_cflag &= ~CSTOPB; /* 1 stop bit */
  options.c_cflag &= ~CSIZE;
  options.c_cflag |= CS8;     /* 8 bits */

  /* set all of the options */
  tcsetattr(e->fd, TCSANOW, &options);

  tcflush(e->fd, TCIOFLUSH);  /* Added to flush buffer */

  int status;
  ioctl(e->fd, TIOCMGET, &status);
  status |= TIOCM_DTR;       /* Set DTR high */
  status &= ~TIOCM_RTS;      /* Set RTS low  */
  ioctl(e->fd, TIOCMSET, &status);

  e->connected=1;
  return 1;
}

void disconnect(ecu_t *e)
{
  tcflush(e->fd, TCIOFLUSH);
  if (!e->connected)
    return;
  close (e->fd);
}

void printMsg(u_int8_t* msg, int len)
{
  printf("Message: ");
  for (int i = 0; i < len; i++)
    printf("%X ", msg[i]);
  printf("\n");
}

u_int8_t calcChecksum(u_int8_t* msg, int len)
{
  u_int8_t checksum = 0;
  for (int i = 0; i < (len - 1); i++)
    checksum += msg[i];
  return checksum;
}

int sendMsg(ecu_t *e, u_int8_t* msg, int len)
{
  int status;
  int writeLen = 0;

  tcflush(e->fd, TCIOFLUSH);  // Added to flush buffer

  writeLen = write(e->fd, msg, len);
  return writeLen;
}

int recvMsg(ecu_t *e, u_int8_t* msg, int len)
{
  struct termios oldtio, options;
  tcgetattr(e->fd, &oldtio);
   
  tcgetattr(e->fd, &options);
  options.c_lflag = 0;
  options.c_cc[VTIME] = 0;
  options.c_cc[VMIN] = len;
  //tcflush(e->fd, TCIFLUSH);
  tcsetattr(e->fd, TCSANOW, &options);
 
  int readLen = 0;
  readLen = read(e->fd, msg, len);
 
  tcsetattr(e->fd, TCSANOW, &oldtio);
 
  return readLen;
}

int main(int argc, char** argv)
{
  ecu_t e;
  struct timeval hrTime;
  u_int8_t responseMsg[RESPONSE_MSG_LEN];
  int bytesRead;

  if (debug)
    printf("Opening serial port\n");
  if (!connect(&e))
  {
    perror("Could not open port");
    exit(-1);
  }
  else
  {
    if (debug)
      printf("Port opened\n");
  }

  u_int8_t queryMsg[22];
  queryMsg[0] = 0x80;
  queryMsg[1] = 0x10;
  queryMsg[2] = 0xF0;
  queryMsg[3] = 0x11;
  queryMsg[4] = 0xA8;
  queryMsg[5] = 0x00;
  queryMsg[6] = 0x00;
  queryMsg[7] = 0x00;
  queryMsg[8] = 0x0D;  /* MAP */
  queryMsg[9] = 0x00;
  queryMsg[10] = 0x00;
  queryMsg[11] = 0x0E;  /* RPM High */
  queryMsg[12] = 0x00;
  queryMsg[13] = 0x00;
  queryMsg[14] = 0x0F;  /* RPM Low */
  queryMsg[15] = 0x00;
  queryMsg[16] = 0x00;
  queryMsg[17] = 0x11; /* Ignition Timing */
  queryMsg[18] = 0x00;
  queryMsg[19] = 0x00;
  queryMsg[20] = 0x22;
  queryMsg[21] = calcChecksum(queryMsg, 22);

  sleep(1);

  while(1)
  {
    if (debug)
      printMsg(queryMsg, 22);

    if (sendMsg(&e, queryMsg, 22) == -1)
    {
      perror("Could not write");
      exit(-1);
    }

    memset(responseMsg, 0, RESPONSE_MSG_LEN);
    bytesRead = recvMsg(&e, responseMsg, 33);
    if (debug)
      printMsg(responseMsg, 33);
    /* 80 F0 10 05 E8 MAP RPMH RPML IGN KNK CKSUM */
    if (debug)
      printf("MAP: %X, RPMH: %X, RPML: %X, IGN: %X\n", responseMsg[27],
                                                       responseMsg[28],
                                                       responseMsg[29],
                                                       responseMsg[30]);
    float map = (responseMsg[27] * 37)/255;
    unsigned int rpmH = responseMsg[28];
    unsigned int rpmL = responseMsg[29];
    rpmH <<= 8;
    unsigned int rpm = (rpmH + rpmL)/4;
    int ign   = (responseMsg[30] - 128)/2;
    int knk   = (responseMsg[31] - 128)/2;
    gettimeofday(&hrTime, NULL);
    printf("%ld.%ld RPM: %i MAP: %.0f IGN: %i KNK: %i\n", hrTime.tv_sec,
                                                          hrTime.tv_usec/1000,
                                                          rpm,
                                                          map,
                                                          ign,
                                                          knk);
  }

  disconnect(&e);

  return 0;
}

PostPosted: Sun Jan 09, 2005 10:05 am
by cdvma
Interesting, but without looking at the code itself, you get a knock count of 3 and that doesn't seem right.

PostPosted: Sun Jan 09, 2005 10:22 am
by Visceral
cdvma,

I added "KNK" last, and forgot to comment it. That value (0x22) is knock correction, not the reading from the knock sensor.

The RPM and IGN values are dead on. It idles at ~17 degrees advance, which matches the factory service manual. I haven't verified the MAP values.

Column at the left is time. I'm reading ~12 samples per second. Each additional parameter is 7 additional bytes to transfer per sample (3 bytes for the address, 3 bytes for the echo of the address, and 1 byte for the value).

If anyone is interested, I can add some additional parameters. I was also thinking of considering paramaterizing it so that you can select which values are logged.

Thank you to mumbles, and the member which forwarded him the reference info on the SSM protocol.

PostPosted: Sun Jan 09, 2005 10:32 am
by stanton
Great progress. Yeah, I was concerned about the KNKs too.

During the ~0.08 seconds per sample, at 1000 RPM, there should be 2.7 ignition firings. So the count of 3 made sense. But, yeah, it would have meant you're knocking on all cylinders during that little blip of the throttle if that was knock count.

I definitely think a "user configureable" selection of logged values is very useful.

PostPosted: Tue Jan 11, 2005 4:58 pm
by mumbles
Very good news Visceral, please post some more screenshots and code as you start to be able to log more information.

-B

PostPosted: Thu Jan 13, 2005 5:02 am
by stanton
After DaveImpreza's post, I followed a few links. There's open-source OBD scanning software at http://www.scantool.net/software/scanto ... index.shtm
It (or some modules of it) might be useful here. I haven't looked at it...don't even know what language it's in.

PostPosted: Thu Jan 13, 2005 8:28 am
by Nemis
stanton wrote:After DaveImpreza's post, I followed a few links. There's open-source OBD scanning software at http://www.scantool.net/software/scanto ... index.shtm
It (or some modules of it) might be useful here. I haven't looked at it...don't even know what language it's in.


The program was written in C and it use odb protocol not SSM.
so it's more simple write new soft from zero
(but might be useful when we make and prog that run under windows :-) )
thank you

PostPosted: Thu Jan 13, 2005 4:37 pm
by bofh
Not really, because the UI blows... I would say go from scratch.

PostPosted: Wed Feb 02, 2005 7:33 am
by crazymikie
Maybe a stupid question-

I've been playing around with this and I have a [possibly stupid] question.

I've gone through and removed a lot of the magic numbers- I've made it so that when you add a parameter, you just update one variable and all of the message lengths are calculated and everything 'just works.' I've also added the logic to poll the ECU to see which parameters it supports (I don't do anything with the data, I just print it at this point).

In any case, I was trying to read EGTs (0x106), however, 0x106 = 262 = 100000110 > u_int8. What is the proper way to fix this? I tried using u_int16 arrays and then things got garbled.

I apologize if I'm missing something obvious. If I can get a few more things working, we can start polling some of the undocumented parameters the ECU supports (there are a few), and then we can try and figure out what they are.

Thanks!
Mike

PostPosted: Wed Feb 02, 2005 1:52 pm
by WolfPlayer
crazymikie,

I can't answer your question. I'm actually just trying to get to the point that you are at right now. If I can ask, what hardware are you using? I have an OBDII data cable that came with software I ordered from here: http://www.obd-2.com/

Do you think the above program will work using that data cable or do I need a different one. I'm working with a 2005 Subaru STi.

t

PostPosted: Wed Feb 02, 2005 2:26 pm
by crazymikie
I think any OBD -> serial cable should work fine. I've used both the cable from my AccessPort and the cable from my DeltaDash.

You can definitely try the cable- I can't see any bad side effects. Worse case is it just dones't work. I assume you got the ISO version, right?

Please let me know if you need help.

PostPosted: Wed Feb 02, 2005 5:52 pm
by WolfPlayer
crazymikie wrote:I think any OBD -> serial cable should work fine. I've used both the cable from my AccessPort and the cable from my DeltaDash.

You can definitely try the cable- I can't see any bad side effects. Worse case is it just dones't work. I assume you got the ISO version, right?

Please let me know if you need help.


crazymikie,

Thanks. Yes, I have the ISO version. It's been a REALLY long time since I have used C so I'll probably have problems trying to get this app to work. I am seriously tempted to write an application in Visual Basic or .NET. I'm pretty good with user interfaces so if I can wrap my head around the code such that I can write it in Visual Basic then I could put together a user friendly version that others can use that will log to a datafile.

t

PostPosted: Wed Feb 02, 2005 7:56 pm
by crazymikie
Let's talk. You just need cygwin installed to get this to compile. I can walk you through it if you'd like. I'm a UNIX guy so between the two of us, we can probably get something going.

I think the thing that needs to be done is to gather info on the parameters which aren't labelled so we can try and figure out what they represent, but we can talk and throw some ideas around.

Just let me know what you need. I can show you what I did to the program as well. It's a little easier to add/remove parameters that you want to poll.


Mike

PostPosted: Thu Feb 03, 2005 12:20 pm
by mumbles
Yeah i'm also a .net guy. We totally should build upon this, as it seems like the proper direction. You can map all his functions and stuff to C#. It's good though for now because writing in C is still cross-platform capable.

PostPosted: Fri Feb 04, 2005 12:47 pm
by crazymikie
Figured out the answer to my question. Once again, just overlooking the obvious :)

We got EGTs!


Mike