Tracker One CANbus + J1939 questions

Hey, I’m planning on connecting the Tracker One to a vehicle that uses the J1939 standard.

I see that in the tracker firmware, there is this piece of code that gets executed at every loop:

canInterface.readMsgBufID(&rxId, &len, rxBuf);      // Read data: len = data length, buf = data byte(s)

Now, I believe the vehicle I’m connecting the Tracker to is sending messages periodically (this happens with engine speed, and other messages).

Now my questions:

1- I see CAN_INT goes down when there is a message in the queue, does it make sense to keep reading messages until there are no more in the buffer? I guess this can be done with can.available(), but the Tracker uses the MCP_CAN lib, and I could not find in it an available() fx.

2- when reading a message and specifying an ID with this call:

canInterface.readMsgBufID(&rxId, &len, rxBuf)

what happens with the messages that do not match that ID, are they discarded or do they remain in the queue?

Thanks!

I don’t think that’s how a vehicle OBD-II CAN interface normally works. J1962 connector under the dash connects to the ECU. That connection is not a full stream of everything that is going on in the vehicle.

Instead, you use sendMsgBuf to request a specific PID like engine RPM or vehicle speed. The ECU then responds with a reply with the data for that specific PID, which you read using readMsgBufID.

Because the data is typically not a constant stream you normally won’t get multiple message back. But it’s probably not a bad idea to call it in a loop. The return value of readMsgBufID will be CAN_NOMSG if there is no message waiting, and you should exit the loop.

The ID in rxId is not a filter. It’s filled in with the ID that the first packet in the queue.

1 Like

I'm going to get to it soon, but from what I read, J1939 could be a stream of some messages:

source

Apparently, the engine speed is one of those messages transmitted all the time.
I'll come back with more info or perhaps a confirmation about this - thanks for all the details.

Hey all, I’m a bit new to particle, but i’ve done J1939 and CANbus for over 15 years.

The most reliable vehicle to vehicle way to interface is using the PIDs in the OBDII connector. This is kind of a request and answer protocol. rickkas7 explained it well in a previous post.

gusgonnet is also correct, there could be a stream of messages right there in the same connector (but not always!) this really depends on how the vehicle manufacturer decided to implement their systems. Most modern vehicles have multiple can bus networks. Typically there is a high speed one to control precise engine timing and high importance messages, a general purpose one that might control dash display and general stuff in the cab, and often a dedicated add-on network for 3rd party integrators (this one is usually provided in the frame behind the cab of the vehicle, or down near the transmission).

Very often the cab or general purpose canbus will do double duty and also service the OBDII messages, so when you plug in under the dash you will get a stream of messages. It will also respond if you query for the PIDs in the OBD protocol.

The engine speed is transmitted in J1939 message 61444 (Hex: F004). The full message identifier will look something like 0CF00400. Where OC contains some info about the message such as the priority, and 00 at the end is the source address (the node number of the transmitting device). Typically the priority and source address of a given message on a specific vehicle won’t change, but it can vary from vehicle to vehicle.

So to reliably read the speed, you would read all the can traffic, mask the priority and source address and check if it matches the F004 you are looking for. Then since multiple devices may be transmitting that same message, you will check that the data is valid for the engine speed. bytes 4 and 5 contain the engine speed with a resolution of 0.125 rpms/bit as an unsigned 16 bit integer. if you read 8000, that means the engine speed is 1000rpm. If bytes 4 and 5 are bitstuffed with 0xFFFF, then the data is invalid and the message does not contain a value for the engine rpm. you would just ignore it. Likely there is a different node that is responsible for that data that will transmit it at another time.

hope this is helpful!

6 Likes

WOW, this is VERY helpful.
Thank you for letting me know about these details, which confirm what I read so far and help me understand even more.
And welcome to the community!
Best,
Gustavo.

Gustavo,

I am beginning a project that will be implementing some J1939. Maybe we could do some collaboration.
I intend to listen for a set of engine parameters such as RPM, fuel usage, engine load… etc
Also planning to listen for active engine fault codes (DM1) messages.

Hi, I’ll be happy to collaborate.

This is how far I got:

Or you can buy two here.
I thought one board would be enough in my case, but my understanding is that we need two nodes to establish a CAN network, hence the need for two. I could not make it work with only one board and the tracker one. I suspect I would need to run the ARD1939 stack (more on it below) on the tracker for that to work, but in my case, the tracker one will only be listening, hence I do not think I need to invest time in it.


  • these two boards give me a CAN bus now operational. Searching the internet I was able to find this paper and change some settings with masks and filters on the MCP25625 CAN controller that the tracker one has (basically disabling them, I think). They have screenshots of how they did it on Arduino. I’ll be doing the same on the tracker.

There is some integration Particle has done with what this person, Wilfried Voss at Copperhill Tech, has done: ARD1939, and you can find it here. I did not use this yet.

Let me know if that helps.
Thanks

It will work with just one of your boards, plus the tracker one.

CANbus is a broadcast bus network. All of the ‘nodes’ are capable of listening to the traffic that all of the other nodes send simultaneously. So you can run the windows analyzer software to control your first board and simulate traffic, and listen with your tracker one.

I have a peak pcan-usb that I monitor and simulate traffic with. It will basically do the same thing.

If you are using linux, the socket-can package can do all of the same stuff. monitor, simulate, record, playback… etc…

The Tracker One software comes with the can-mcp25x library as a submodule (with a test.cpp example). I expect this is functional in the Tracker one. My 8 pin M8 cable shipped out today, so by early next week i’ll have it connected up and I’ll know for sure.

I found a couple of pretty good J1399 libraries, I’m sort of debating if i want to use them or just implement some basic can read simplified version of them. Most of the stack is handling stuff that isn’t typically used very much (like address negotiation) , and also doesn’t handle the stuff i need (like reading the RPM value).

This library almost makes me want to use it, but i think i’m more likely to borrow from it and simplify some things:

One problem the library has is that it doesn’t seem to be able to report more than one DM1 error message at a time. (it does report how many are active, but only reads in the codes for the first one). Cool, but one of my goals is to be able to report error codes, so this doesn’t quite do it.

I also don’t have much use for the ag standard protocols implemented, and some of the official J1939 stack stuff.

2 Likes

Oh, don’t forget your termination resistors!

When you install this on a vehicle it’s typically already going to have the termination resistors in place. But setting up a little network on your bench, you will need two 120ohm termination resistors. just jump them between can high and can low, one on both ends of the network.

For such a small network with limited traffic, often just one resistor is enough to get it to work, and you can probably use something close if you don’t have a 120 ohm resistor ( a single 100 ohm resistor might do the trick in a pinch). but it’s best to stick to the correct values and have two.

1 Like

How could I, they are... unforgettable :musical_note:
Yup, thanks for the reminder, the simulator boards come with them already.
Thanks

1 Like

@TPTsys Victory!

I was able to receive, mask and filter out the PGN I needed.

setup() code:

    // Most vehicles use 500 kbit/sec for OBD-II, j1939 is 250 kbit/sec
    // Make sure the last parameter is MCP_20MHZ; this is dependent on the crystal
    // connected to the CAN chip and it's 20 MHz on the Tracker SoM.
    byte status = canInterface.begin(MCP_RX_STDEXT, CAN_250KBPS, MCP_20MHZ);
    if (status == CAN_OK)
    {
        Log.info("CAN initialization succeeded");
    }
    else
    {
        Log.error("CAN initialization failed %d", status);
    }

    // Change to normal mode to allow messages to be transmitted. If you don't do this,
    // the CAN chip will be in loopback mode.
    // canInterface.setMode(MCP_MODE_LISTENONLY);
    canInterface.setMode(MCP_MODE_NORMAL);

    // set up the mask and filters for RXB0 (reception buffer 0)
    // init_mask(): I believe the second parameter is the extended ID flag
    // since we want to receive extended IDs, we set it to 1
    // a mask bit 0 means let it through, 1 means pay attention to filters
    // filters 0 and 1 are used for RXB0
    // canInterface.init_Mask(0, 1, 0); 
    // FILTER: 0x03FFFF00 => we filter out the priority bits at the beginning with the 03,
    //  and the address bits at the end with the 00
    // MASK: 0x00f00400 => we mask out the f004 pgn, which in decimal is 61444
    canInterface.init_Mask(0, 1, 0x03FFFF00);
    canInterface.init_Filt(0, 1, 0x00f00400);
    canInterface.init_Filt(1, 1, 0);

    // set up the mask and filters for RXB1 (reception buffer 1)
    // filters 2 to 5 are used for RXB1
    // canInterface.init_Mask(1, 1, 0);
    // THIS ONE IS NOT USED!
    canInterface.init_Mask(1, 1, 0xFFFFFFFF);
    canInterface.init_Filt(2, 1, 0);
    canInterface.init_Filt(3, 1, 0);
    canInterface.init_Filt(4, 1, 0);
    canInterface.init_Filt(5, 1, 0);

loop() code:

    // Handle received CAN data
    if (!digitalRead(CAN_INT))
    {
        Log.info("CAN data available");

        long unsigned int rxId;
        unsigned char len = 0;
        unsigned char rxBuf[8];

        // on a 29 bit can extended frame identifier, which we get on rxId
        // priority is the first 3 bits
        // 0x1C0 is 110 0000 000
        // j1939 Parameter Group Number or pgn is the next 18 bits
        // address is the last 8 bits
        int priority = 0;
        int pgn = 0;
        int address = 0;

        canInterface.readMsgBufID(&rxId, &len, rxBuf); // Read data: len = data length, buf = data byte(s)

        priority = (rxId >> 26) & 0x07;
        pgn = (rxId >> 8) & 0x3FFFF;
        address = (rxId & 0xFF);

        Log.info("j1939 rxId: %08lx msg: %02x %02x %02x %02x %02x %02x %02x %02x", rxId,
                 rxBuf[0], rxBuf[1], rxBuf[2], rxBuf[3], rxBuf[4], rxBuf[5], rxBuf[6], rxBuf[7]);

        Log.info("j1939 priority: %d " PRIO_TO_BINARY_PATTERN, priority, PRIO_TO_BINARY(priority));
        Log.info("j1939 pgn: %d " PGN_TO_BINARY_PATTERN, pgn, PGN_TO_BINARY(pgn));
        Log.info("j1939 address: %d " BYTE_TO_BINARY_PATTERN, address, BYTE_TO_BINARY(address));

        // EEC1 electronic engine controller 1 PGN 0xF004 => decimal 61444
        int J1939engineSpeed = 61444;
        if (pgn == J1939engineSpeed)
        {
            // RPM comes from SPN 190 (it's called engine speed)
            // uses bytes 4 and 5 (indexed starting at 1)
            // so in this code (index starts at 0) uses bytes 3 and 4
            // little endian, so we need to reorder the byte sequence
            // PGN is 0xF004, decimal 61444
            // scale is 0.125 (that's why is divided by 8)
            // offset is 0 (nothing to do here)
            // https://www.csselectronics.com/pages/j1939-explained-simple-intro-tutorial
            lastRPM = (rxBuf[4] << 8) | rxBuf[3];
            lastRPM /= 8; // scale is 0.125

            Log.info("j1939 received rpm: %d", lastRPM);

            // We don't process the RPM here, it's done below (with an explanation why)
        }
    }

Some defines for printing the bits (these go at the beginning of the main.cpp):

#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte)       \
    (byte & 0x80 ? '1' : '0'),     \
        (byte & 0x40 ? '1' : '0'), \
        (byte & 0x20 ? '1' : '0'), \
        (byte & 0x10 ? '1' : '0'), \
        (byte & 0x08 ? '1' : '0'), \
        (byte & 0x04 ? '1' : '0'), \
        (byte & 0x02 ? '1' : '0'), \
        (byte & 0x01 ? '1' : '0')

#define PRIO_TO_BINARY_PATTERN "%c%c%c"
#define PRIO_TO_BINARY(byte)       \
    (byte & 0x04 ? '1' : '0'),     \
        (byte & 0x02 ? '1' : '0'), \
        (byte & 0x01 ? '1' : '0')

// 18 bits pattern
#define PGN_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c"
#define PGN_TO_BINARY(byte)           \
    (byte & 0x20000 ? '1' : '0'),     \
        (byte & 0x10000 ? '1' : '0'), \
        (byte & 0x8000 ? '1' : '0'),  \
        (byte & 0x4000 ? '1' : '0'),  \
        (byte & 0x2000 ? '1' : '0'),  \
        (byte & 0x1000 ? '1' : '0'),  \
        (byte & 0x800 ? '1' : '0'),   \
        (byte & 0x400 ? '1' : '0'),   \
        (byte & 0x200 ? '1' : '0'),   \
        (byte & 0x100 ? '1' : '0'),   \
        (byte & 0x80 ? '1' : '0'),    \
        (byte & 0x40 ? '1' : '0'),    \
        (byte & 0x20 ? '1' : '0'),    \
        (byte & 0x10 ? '1' : '0'),    \
        (byte & 0x08 ? '1' : '0'),    \
        (byte & 0x04 ? '1' : '0'),    \
        (byte & 0x02 ? '1' : '0'),    \
        (byte & 0x01 ? '1' : '0')

Sources:
MCP25625-CAN-Controller-Data-Sheet.pdf
j1939-pgn-spn tutorial

This can get you going on your side I believe.

That was quite a fight.
Gustavo.
PS: many thanks to the snowy weather that toasted my outdoor plans.

1 Like

Awesome!

One tip that could save you a headache one day:

if (!(rxBuf[4] == 0xFF && rxBuf[3] == 0xFF)) {
            lastRPM = (rxBuf[4] << 8) | rxBuf[3];
            lastRPM /= 8; // scale is 0.125
}

It’s possible that another device transmits PGN F004, and if so it is likely not responsible for reporting the engine RPM. Any time a J1939 message is sent the sending device bit stuffs any value it is not responsible for. That way the receiving device can differentiate a zero value vs no-data. It’s good practice to verify the received data as valid.

1 Like

oh yes, thank you. I remember now you warned me about this in your first post.
Thanks again!

Ok,

So I did some reading on the mask and the filters.
Am i correct in understanding their only real purpose is to reduce the processing overhead of the application?

So, for example, had you not set any mask or filter you still would have received the PGN just the same, except if the network were very busy it is possible the program would have fallen behind and missed some messages?

Yes, I have the same understanding.
I am not sure what would happen if the network were very busy if the tracker would lose some messages or not.

Hello to everyone, the thread is very interesting.
Gustavo, I was checking you posted some parts of the code you finally applied.
Question is, which libraries did you use?
You mentioned some example codes from Copperhill tech, there is an example for ESP32 but I can't compile.
If you can share some more details about your code I would appreciate.
Best regards,

Juan

Hola Juan,
I am using:

Hi!

I'm able to draw J1939 using built-in tracker one can-mcp25x library but I'm not able to send out the data and received Sendmessage Timeout Error. This is the code I tried for sending out message:

unsigned long OBD_CAN_REQUEST_ID = 0x18EA00FE;
byte PGNRequest[8] = {0xCB, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
byte sndStat = canInterface.sendMsgBuf(OBD_CAN_REQUEST_ID, 1, 8, PGNRequest);

This is the set up code:

status = canInterface.begin(MCP_RX_ANY, CAN_250KBPS, MCP_20MHZ);

if (status == CAN_OK)
{
    Log.info("CAN initialization succeeded");
}
else
{
    Log.error("CAN initialization failed %d", status);
}
canInterface.setMode(MCP_MODE_NORMAL);

I wanted to try out the MCP_CAN_RK but having issue with initialization. The error is:

error: no matching function for call to 'MCP_CAN::MCP_CAN(int, particle::SpiProxy<HAL_SPI_INTERFACE2>&)'
38 | MCP_CAN canInterface(CAN_CS, SPI1);

Any thought would be appreciated!

Thanks

If you have PCANView and an adapter to sniff the CAN bus, do you notice any warnings like "Bus heavy"?

Is there any adapter that can work with pcanview I could buy from Amazon?

I'm using this cooperhill board as the ECU simulator:

1 Like