SparkFun Forums 

Where electronics enthusiasts find answers.

Have questions about a SparkFun product or board? This is the place to be.
By tz
#79953
This is what I'm working on. I had a soldering attempt which failed so this is it on the breadboard. From left to right:

Sparkfun Venus GPS (10Hz)
Sparkfun Arduino Pro (328 3.3v/8Mhz)
Parani/Sena BT module http://www.sena.com/products/industrial ... th/esd.php (there is a buysena.com from there)
Sparkfun AD345XL 3 axis Accelerometer
Dimension 1A efficient switcher 3.3v supply http://www.robotshop.us/dimension-engin ... tor-1.html

J1850 goes in on D8.
Valentine V1 Radar Detector data on D9 (the 39k resistors in the background enables this).

This is to go with a miniGPSD project I have, mainly for Maemo (nokia tablets) but runs generally under Linux which decodes the Harley stream into RPM/Fuel/Gear/Turn-Signals, etc. state. I have an unreleased update which works with this AVR based system. Before I was using an OBD-II (OBDPros bluetooth) and direct uart to bluetooth radar detector connection (at 19.2k baud the pulses work out to distinct characters), but one BT connection is better than 3. Right now I have a couple of Python GTK/Hildon displays, one for radar (fronts window when radar present, lights screen), and one for the harley dashboard. Minigpsd itself has a link to google maps and a weather radar display located where I'm at. (I have a PHS-350 and now a Mifi so I have internet, then there's my phone with ttl serial via the headset jack - Motorola C168i, but that would be for SMS - from someone else's gpslogger).

All GPL. The updated stuff will be posted when I get a chance.

http://repository.maemo.org/extras/pool ... /minigpsd/

The AVR merges the GPS stream with Kxxx,yyy,zzz hex from the accelerometer (100Hz), Vhhhhhhhh from the radar detector, and Jaabbddee...cc from the J1850 stream off my Harley.

(Edit- make pic a URL) The pic is 1.2Mb (from a 12 megapixel cam) which shows everything but is huge:

http://www.zdez.org/MCsystem.JPG
Last edited by tz on Mon Aug 31, 2009 9:32 am, edited 1 time in total.
By Liencouer
#79986
can we get a "warning - huge picture"?

seems like a cool project. looks like a hp netbook below everything - is that how you're going to display the data?

looks like you're using the avr for data aggregation, and spitting it all off to the pc to be processed?
By tz
#79989
I made the image a link.

Yes, it is sitting on a Verizon HP 1151NR, but I'm running Ubuntu (tri-boot XP/Hackintosh/Ubuntu).

The netbook has built-in bluetooth so is convenient for testing, but I normally have my n810 (I have 2, the wimax version is out for repair at the moment) in a Ram mount waterproof case on my Dock-n-Rock (handle bar clamp + amp). The n810 also has bluetooth.

Yes, the AVR aggregates, mainly the GPS output. The rest is more acquisition, but all that stuff is merged and send out over the same serial port. I also typically have the Parani BT serial output to the Venus Input, but that might not work with the special commands used to set it (but I could look for the special responses on the AVR and switch if needed).
By tz
#80238
The current code is at http://www.zdez.org/j1850.c

If you ground D8 (j1850 - no pullup) and D9 (V1 - maybe not needed), this works quite well as a vehicle dynamics acquisition system.

100Hz 3d accelerometer plus GPS.

Venus with just Gnd, Tx, and 3.3V, Tx goes to Arduino Rx.

AD345XL with SDA/SCL to Arduino A4/A5 on the Arduino, with 3.3v and Gnd. Ints floating, the rest to 3.3 or to pullups on SDA/SDL.

Parani BT (or your favorite) from Arduino Tx, with 3.3 and Gnd.

All Uarts at 57600 baud, both the Parani and Venus via a FTDI breakout.

Connect Parani Tx to Venus Rx to try AGPS/configuration.

J1850 is not recommended to be left open or to connect directly. Use a 4.7k or similar, with a 50k to ground.

The V1 radar detector needs a pullup on the data and I recommend a series between the data and D9 input. 39k-47k for each (47k between data and 12v, and a second 47k between data and D9)
By tz
#80410
URLs are to the 1-2 Meg 12MP version

The Nokia Tablet with the dashboard program:
Image
http://www.zdez.org/NokTabletHDash.jpg

Bottom of the board.
Image
http://www.zdez.org/BoardBot.jpg

Top of the board.
Image
http://www.zdez.org/BoardTop.jpg

The Ram box on the DockNRock on the handlebars
Image
http://www.zdez.org/Handlebar3q.jpg

The test stack in my bag for the test
Image
http://www.zdez.org/TestStacked.jpg

Closeup of the Ram Aqua Box (medium/wide)
http://www.ram-mount.com/CatalogResults ... fault.aspx
Image
http://www.zdez.org/RamMountNear.jpg

The Ram Aqua Box. A blackberry bluetooth audio gateway is in the back, and I have a nokia recharger behind.
Image
http://www.zdez.org/RamMountFar.jpg
By tz
#80680
I updated the code http://www.zdez.org/j1850.c.

(license for now is GPL v3, but I need to add the copyright),

Zero data segment usage (copy from rom for the TWI state table), and some interrupt cleanup to fix some wrong or missing bits in the J1850 due to other interrupts.

It does 100Hz accelerometer (TWI), V1 on D9, J1850 on IC1, and Venus GPS at 10Hz rate in RxI, aggregated out TX
By beast
#88181
tz, your project is awesome! I would like to do something similar for my HD-VRod. I have a Serial OBDPro (I'll go wireless once I'm sure this will all work) I've looked through your OBD code pretty thoroughly and believe I understand everything that's going on. (It's relatively straightforward) However, I've been unable to get the bike to respond to requests. I can monitor the bus via the obdpros ATMA command and that works fine. (So this tells me that the protocol, baud rates, etc are all cool.) But when I issue any command to request data (e.g. 01 00 - supported pids) I always get NO DATA.
I suspect that HD isn't completely OBDII compliant, or they have something in place to prevent standard requests (maybe you have to address the ecu directly - or some such thing) But in your code I notice that you don't seem to have anything special. Does your obd software work with the obdpro out of the box, or did you have to tweak/fiddle a bit?... or does it only with the hardware you built? Seeing that your code is issuing obdpro commands (ATSP0, ATH1, etc. I'm assuming that it does work with the obdpro.) Am I missing something?
I would greatly appreciate any tips/hints etc you may have. I'm a software engineer by trade and would gladly provide anything new/interesting/useful I come up with in return.

Thanks much
-Bob
By tz
#88187
HD does NOT support OBD2, so requests won't work. Probing the stream will. The code for minigpsd (at maemo.org) has the decoder. I've changed it when I switched from the OBD-pro to the arduino, but the current version should support both.

The hex streams contain the information, you just need to point to the right bytes and decode them (and know things like 1 odometer count = 4 millimeters, and 1 fuel count = 0.05 milliliters - both approximate but fairly close).

I would be interested if there are any more or different messages on your v-rod (and I really need to test one with a cruise control).

The harley decode routine follows: (with bbcode unchecked - Ed).
Code: Select all
#include "minigpsd.h"

static int crc(int *data, int len)
{
    unsigned char crc, dimg;
    unsigned char poly;
    int i;

    crc = 0xff;
    while (len--) {
        dimg = *data++;
        //      printf("%02x ", dimg);
        for (i = 0; i < 8; i++) {
            poly = 0;
            if (0x80 & (crc ^ dimg ))
                    poly = 0x1d;
            crc = (crc << 1) ^ poly;
            dimg <<= 1;
        }
    }
    //printf("= %02x\n", crc);
    return crc;
}

static unsigned short odolast = 0, fuellast = 0;

extern struct harley hstat;

void calcobd(char *outb, int mstime)
{
    int i, j, x;
    unsigned short y;
    int hex[8];
    char inb[512], *c;

    strcpy(inb, outb);
#if 0
    // obdpros
    i = sscanf(inb, "%02x %02x %02x %02x %02x %02x %02x %02x",
               );
    // AVR
    i = sscanf(inb, "%02x%02x%02x%02x%02x%02x%02x%02x",
               &hex[0], &hex[1], &hex[2], &hex[3], &hex[4], &hex[5], &hex[6], &hex[7]);
#endif
    i = 0;
    c = inb;
    while( *c && (*c < '0' || *c > 'F') ) // remove leading J or whatever
        c++;

    for( i = 0 ; i < 8 ; i++ ) {
        j = sscanf(c, "%02x", &hex[i] );
        if( !j )
            break;
        if( !*c++ )
            break;
        if( !*c++ )
            break;
        while( *c && *c <= ' ' )
            c++;
        if( !*c )
            break;
    }
    if( j )
        i++;

    if (i < 5 || 0xc4 != crc(hex, i)) {
        sprintf(outb, "$PDERR,%d,", i);
        strcat(outb, inb); 
        return;
    }

    i--;
    x = hex[0] << 24;
    x |= hex[1] << 16;
    x |= hex[2] << 8;
    x |= hex[3];

    y = hex[4] << 8 | hex[5];

    if (x == 0x281b1002) {
        sprintf(outb, "$PDRPM,%d.%02d,", y / 4, y % 4 * 25);
        hstat.rpm = y * 250;
    } else if (x == 0x48291002) {
        sprintf(outb, "$PDSPD,%d.%03d,", y / 200, y % 200 * 5);
        hstat.vspd = y * 5;
    } else if (x == 0xa8491010) {
        sprintf(outb, "$PDHOT,%d,", hex[4]);
        hstat.engtemp = hex[4];
    } else if (x == 0xa83b1003) {
        y = hex[4];
        j = 0;
        if (y)
            while (y >>= 1)
                j++;
        else
            j = -1;
        sprintf(outb, "$PDGER,%d,", j);
        hstat.gear = j;
    } else if (x == 0x48da4039 && (hex[4] & 0xfc) == 0) {
        char turns[] = "NRLB";
        sprintf(outb, "$PDSGN,%c,", turns[hex[4]]);
        hstat.turnsig = hex[4];
    } else if ((x & 0xffffff7f) == 0xa8691006) {
        if (!(x & 0x80)) {
            hstat.odolastval = y;
            hstat.odolastval -= odolast;
	    if( hstat.odolastval < 0 )
	        hstat.odolastval += 65536;
            hstat.odoaccum += hstat.odolastval;
            if (mstime >= hstat.odolastms)
                hstat.odolastms = mstime - hstat.odolastms;
            else
                hstat.odolastms = 100000 + mstime - hstat.odolastms;
            sprintf(outb, "$PDODO,%d,%d,", hstat.odolastval, hstat.odolastms);
            odolast = y;
            hstat.odolastms = mstime;
        } else {
            odolast = 0;
            hstat.odolastms = mstime;
            hstat.odoaccum = 0;
            sprintf(outb, "$PDODO,-0,-0,");
        }
    } else if ((x & 0xffffff7f) == 0xa883100a) {
        if (!(x & 0x80)) {
            hstat.fuellastval = y;
            hstat.fuellastval -= fuellast;
	    if( hstat.fuellastval < 0 )
	        hstat.fuellastval += 65536;
            hstat.fuelaccum += hstat.fuellastval;
            if (mstime >= hstat.fuellastms)
                hstat.fuellastms = mstime - hstat.fuellastms;
            else
                hstat.fuellastms = 100000 + mstime - hstat.fuellastms;
            sprintf(outb, "$PDFUL,%d,%d,", hstat.fuellastval, hstat.fuellastms);
            fuellast = y;
            hstat.fuellastms = mstime;
        } else {
            fuellast = 0;
            hstat.fuellastms = mstime;
            hstat.fuelaccum = 0;
            sprintf(outb, "$PDFUL,-0,-0,");
        }
    } else if ((x & 0xffffffff) == 0xa8836112 && (hex[4] & 0xd0) == 0xd0) {
        sprintf(outb, "$PDGAS,%d,", hex[4] & 0x0f);
        hstat.full = hex[4] & 0x0f;
    } else if ((x & 0xffffff5d) == 0x483b4000) {
        sprintf(outb, "$PDCLU,");
        hstat.neutral = !!(hex[3] & 0x20);    // ! & 0x02
        hstat.clutch = !!(hex[3] & 0x80);
        switch (hex[3]) {
        case 0x02:
            sprintf(outb, "$PDCLU,xx,");
            break;
        case 0x82:
            sprintf(outb, "$PDCLU,xC,");
            break;
        case 0x20:
            sprintf(outb, "$PDCLU,Nx,");
            break;
        case 0xA0:
            sprintf(outb, "$PDCLU,NC,");
            break;
        }
    } else if (x == 0x68881003
      || x == 0x68FF1003 || x == 0x68FF4003 || x == 0x68FF6103 || x == 0xC888100E || x == 0xC8896103 || x == 0xE889610E) {
        sprintf(outb, "$PDPNG,");
    } else if ((x & 0xffffff7f) == 0x4892402a || (x & 0xffffff7f) == 0x6893612a) {
        sprintf(outb, "$PDOFF,");       // shutdown - lock
    } else if (x == 0x68881083) {
        sprintf(outb, "$PDMIL,");       // check engine
    } else {
        sprintf(outb, "$PDMSG,");
    }

    c = inb;
    while (*c) {
        if (*c == ' ')
            *c = ',';
        c++;
    }

    strcat(outb, inb);
    c = outb;
    while( *c )
        if( *c <  ' ' )
            *c = 0;
        else
            c++;
    addnmeacksum(outb);

}
Last edited by tz on Mon Dec 28, 2009 4:12 pm, edited 1 time in total.
By beast
#88202
tz,
Thanks for the quick response! Bummer that HD bikes aren't ODB2 compliant. You've done quite a bit of reverse engineering to get as far as you have. I'm assuming you just ran your bike through a series of actions and recorded all the messages on the bus. Then went through all the output and determined what was contained in the various messages. Then you had to determine the units as well, how long did it take you to do all this? Very impressive!!!
What kind of bike do you have? My vrod is fuel injected and has a security system, so both of those will likely generate some new data. I don't have ABS or cruise control, but I'd bet those would do some chatting as well.
After looking at your code (the decoder) I recognize some of the messages I've already observed on the bus, but I also have some that aren't handled in your code yet, so I suspect I'll be adding to your decoder.

Thanks again for your time. I'll keep you posted on my progress.

-Bob
By tz
#88204
I have a 2007 Night Train.

The decoder was not that hard for me. First, my area of engineering is automotive so I've written OBD-II and proprietary protocol implementations from before J1850 (e.g. Chrysler Collision Detection). My last job was at SPX working on their next-generation diagnostic tool. And I've done my share of disassembly. They aren't hiding very many things on the bus, and there are standards about how the message should look and the priority (google is your friend).

There is a security handshake at the start and end (the turn signals flash three times and you can see the other messages). I don't have that broken out. The check engine light is on another message, as it entering the security code from the speedometer. There are what appear to be keep-alives or I'm alive messages.

The easiest way is to collect a large blob, and keep tweaking the decoder, with stdout for decoded, and stderr for unknowns, or just grep or search for PDUNK or the equivalent and you can get a fairly good idea.

The units weren't that hard. Originally I just used about a gallon and tracked the mileage and calculated it out. My last run back from Yuma to San Diego (nice to have a contract in a place that is warm in the winter) was continuous so I have every J1850 message. I filled up in Yuma, then back here, so I had the mileage and fuel used over a large period (for more accuracy), so I just had to extract the counts/deltas from the message stream. I add in the millisecond time stamp since I get both distance and fuel, but adding time makes doing miles per gallon easier to calculate as (dist/time)/(fuel/time), though it is still a little choppy and needs a little more calibration.

The data I have is interspersed with GPS information so I know exactly when and where and (via GPS) how fast I was going. It is in a KML file (with time!) with the data as XML comments. www.zdez.org is my site, there might be a new minigpsd archive up soon. PM me here or post if I forget. (I have a mifi, so my position is updated with only slight delay on gpsgate.com while I listen to internet radio on my modified DockNRock - cyclesounds.com).
By beast
#88220
tz,
I posted a reply but it seems to have entered the ether.

The Night Train is a sweet ride. Any mods?

I have a couple of questions...
While I can see that for the minigpsd code you simply monitor the obd bus and decode, I noticed that in your miniobd2d code you are attempting to send the 01 00 (request for supported pids) onto the bus. I'm assuming that this code doesn't function with your harley. Is that correct?

Secondly, I assume that the ECM and the TSSM communicate with each other over the bus. Would it work to spoof the TSSM to trick the ECM into sending requested info? This way we wouldn't be at the mercy of the ECMs update rate. Sensor info like air temp are probably rarely sent.

Finally, would you happen to have any large blocks of raw data that I could use as fodder for my front end? I'm up here in the "Great White North" and my bike is up for the winter, so the amount of data I'm able to get is severely limited. (ignition, security, heartbeat, etc) Ultimately I want to provide a generic front end to a number of wireless handhelds (n8--, n900, iphone, android phone, etc) Utilizing the touch screens if available and providing displays like instrumentation, diagnostics, performance and navigation. (I can pm you my email if you have any data you can share)

Thanks much.

-Bob
By tz
#88251
No engine mods, just the audio and computer.

The miniobd2 does polling for cars using the OBD2 and only that.

I have an obdpro - probably like yours - which can wake up in monitor mode, so no polling or commands required for the Harley. I just set it to J1850, monitor mode, wake up that way, and the data comes through.

There is no spoofing required - the third part is the speedometer which talks with the TSSM and ECM.

PM me here and I can email you the KML of my Yuma run - you might need to use a script to pull the strings out of the XML comments.
By beast
#88252
PM sent.

One more question... Obviously the speedometer gets messages from the TSSM and ECM, but does it just take them passively or does it request them? If it requests them then one could theoretically pretend to be the speedometer module to make the ecm or tssm put data out onto the bus on command.

The reason I make this suggestion is that then you could be decoupled from the internal update rates and request information whenever it's necessary rather than waiting around for the message to show up at some later time.

To frame it another way, in your experience, have you found that the rates that the various sensor data is available on the bus is sufficient for any of the displays/calculations/etc. that you want to have/do?

I was just thinking about a performance display. i.e. 1/4 mile speed. 0-60mph times, etc. And was wondering if the data currently available would be enough to give fairly accurate results?

Later

-Bob
By tz
#88259
The data rate for speed and RPM is about 16 times per second. That is better than GPS and adequate for most things.

It is all broadcast. J1850 is about 10400 baud, so a request-response would be too slow. Each module sends its status - there are a few messages which are only "interesting" to specific other modules, but it is all broadcast and listen. Even things like the TSSM unlock and there doesn't seem to be a challenge/response. (there may be other hardware or paths).
By tz
#88260
The data rate for speed and RPM is about 16 times per second. That is better than GPS and adequate for most things.

It is all broadcast. J1850 is about 10400 baud, so a request-response would be too slow. Each module sends its status - there are a few messages which are only "interesting" to specific other modules, but it is all broadcast and listen. Even things like the TSSM unlock and there doesn't seem to be a challenge/response. (there may be other hardware or paths).