Output Gps PGNs to tractor Isobus?

I’m looking to transition to using AOG in my seeding tractor but I have a few hang ups with ISOBUS terminals that need to be sorted first.

My air seeder cart is run though an isobus virtual terminal but needs gps messages sent over the isobus in order for the turn compensation to work.

  1. Has anyone sent j1939 gps messages from AOG into a vehicles canbus?

  2. Could the Teensy output the correct PGN’s as well as the curve messages to steer a canbus ready tractor?

Looks like Copperhill Tech makes almost what I’m looking for; rs232 to j1939 module. The update rate is too slow at 1hz for the messages though.

Currently the tractor (Claas Xerion) is steered with a Trimble TMX-2050, and a Pro 700 display runs my CNH P series aircart through ISOBUS. The cart uses gps messages sent from the Trimble though the isobus in order to enable turn compensation on the aircart. It will not work feeding it nmea though the serial port on the back of the Pro 700.

Should we add them in and see what happens?

There is a few people that say they have done this but don’t think they share much more than that.

Did you know what messages it needs? Just position, speed, heading & time?

I have a call into my dealer to see if they will tell me exactly what PGN’s it needs. I’ll update if they get back to me.

I will have to run out and check in the tractor when it warms up, as I cant remember which PGN’s I enabled but I think it was:
PGN 65267 - Vehicle Position (5hz)
PGN 65256 - Vehicle Speed & Direction (5hz)
PGN 65254 - Time & Date (1hz)
PGN 129029 - GNSS Position Data (1hz)

1 Like

Yes those PGNs (not sure about the last one) just need to be broadcast on the bus to the 255 j1939 destination address.

Remember in j1939 a CAN ID is broken up into sub fields. PGNs only make sense in this context (in other words a CAN ID can be different with the same PGN, depending on the destination address). Inside a j1939 message is payload (up to 8 bytes), divided into fields according to a formal SPN specification, see below for a pdf.

Here’s a couple of functions to compose and decompose CAN IDs:

void j1939_decode(long ID, unsigned long* PGN, byte* priority, byte* src_addr, byte *dest_addr)
	/* decode j1939 fields from 29-bit CAN id */
	*src_addr = 255;
	*dest_addr = 255;

	*priority = (int)((ID & 0x1C000000) >> 26);

	*PGN = ID & 0x00FFFF00;
	*PGN = *PGN >> 8;

	ID = ID & 0x000000FF;
	*src_addr = (int)ID;

	/* decode dest_addr if a peer to peer message */
	if( (*PGN > 0 && *PGN <= 0xEFFF) ||
	    (*PGN > 0x10000 && *PGN <= 0x1EFFF) ) {
		*dest_addr = (int)(*PGN & 0xFF);
		*PGN = *PGN & 0x01FF00;

long j1939_encode(unsigned long pgn, byte priority, byte src_addr, byte dest_addr)

	long id;
	id = (priority & 0x07) << 26; //three bits only
	/* if a peer to peer message, encode dest_addr */
	if ((pgn > 0 && pgn <= 0xEFFF) ||
	    (pgn > 0x10000 && pgn <= 0x1efff)) {
	    	pgn = pgn & 0x01ff00;
		pgn = pgn | dest_addr;
	id = id | (pgn << 8);
	id = id | src_addr;

	return id;

Here’s some code from my project (which uses my custom CAN frame wrapper, so it would need adaptation) that creates some of those CAN messages. I’m not sure if the sender’s address matters that much. For the most part the multi-byte fields used in j1939 are little endian, which is why my CAN frame wrapper can deal with uint64s, uint32s, uint16s, and individual bytes, depending on what’s needed. I can post my CAN Frame wrapper if needed (makes it nice for accessing the individual j1939 SPN fields). My code uses 28 because that’s what John Deere receivers use:

	//gps position
	//create pgn 65267, priority 3, source 28, dest 2555
	msg.set_id(j1939_encode(65267, 3, 28, 255));
	msg.get_data()->uint32[0] = (latitude + 210.0) * 10000000;
	msg.get_data()->uint32[1] = (longitude + 210.0) * 10000000;

	//PGN 65254, priority 3, src 28, dest 255
	//date and time
	datetime = (uint64_t)(seconds * 4);
	datetime |= ((uint64_t)minutes << 8);
	datetime |= ((uint64_t)hours << 16);
	datetime |= ((uint64_t)month << 24)
	datetime |= (((uint64_t)day * 4) << 32);
	datetime |= (((uint64_t)year - 1985) << 40);
	datetime |= ((uint64_t)125 << 48); //zero
	datetime |= ((uint64_t)125 << 56); //zero
	msg.get_data()->uint64 = datetime;

	// vehicle direction and speed
	// heading is uint16[0] / 128.0
	// speed is uint16[1] / 256.0 for km/h
	// pitch is uint16[2] / 128.0 - 200.0 for angle, pos is climbing, neg is sinking
	// altitude is uint16[3] / 0.125 - 2500 for metres.
	//vehicle direction and speed
	//pgn 65256, priority 3, src 28, dest 255
	msg.get_data()->uint16[0] = heading * 128; //degrees
	msg.get_data()->uint16[1] = speed * 256; //kph
	msg.get_data()->uint16[2] = 200 * 128; //not sure how critical vehicle pitch is 200 is zero
	msg.get_data()->uint16[3] = (altitude + 2500) * 8; //metres

Here’s a good resource https://gurtam.com/files/ftp/CAN/j1939-71.pdf
My CAN frame wrapper object is at: gps_j1939/canframe.h at master · torriem/gps_j1939 · GitHub

PGN 129029 appears to be non-standard and possibly proprietary. I think it’s a peer to peer message, actual PGN 129024 destination address 5. Do you have any examples of what the entire CAN frame looks like?

I could have sworn 129029 was the one that populated the GPS status page on the pro700 showing correction type, # of SATs etc. But I certainly could be mistaken. You are much more versed in it than me, so you are probably correct that it is looking for the first 3 listed. I still haven’t heard back from my dealer either about the exact messages it’s looking for.

Yes I’m sure you’re correct. I don’t have the isobus standard so I can’t determine if it’s part of it. It’s not a message that is listed in the j1939 standard is all I meant.. It’s a standard NMEA 2000 message. The other three messages are, so they are well-known. I am interested to know more about it because I would also like to feed GPS into the Pro 700 or Pro 600 via CAN for mapping purposes.

129029 is just standard NMEA 2000, pretty much the GGA equivalent in NMEA 0183 I think.

I would be totally guessing that the actual ISOBUS controller is only using the 3 PGN and the info in the 129029 is just for information.

I will copy some bits of code and build these 4 messages and see what happens. Will use corrected position from AgOpen so it will work with any GPS type feeding AgOpen.


@torriem I have been having a look at this topic and think I have 3 of the 4 messgaes working. I did notice this line in your decoder for the bigger PGN and I think its just a typo needs to be PGN = ID & 0x01FFFF00 (that will use the 17bits of the 18bit PGN) .

Your decoder / encoder works very well and the can wrapper is very nice too. I will change the way the the current setup works because its fine for a few messages but when you start doing too much on the ISOBUS between brands the wheels come off pretty quick. Scrapping the filters and just let everything in and filter the PGN will work better for that side of it.

Yes I think you’re correct. It should be 0x01ffff00. And I verified that by looking at someone else’s j1939 decode implementation. Good catch.

Yes I like being able to filter on plain PGNs. Does make it easier, provided the MCU has enough horsepower to deal with all the messages, which Teensy certainly does. Hardware CAN filtering is nice because it only lets in some of the messages to the microcontroller so even an 8-bit arduino can work. But yes filtering is only on the whole CAN ID, so looking for a PGN is harder that way the way j1939 IDs are encoded.

I think I have 65267, 65256, 129029 working with corrected postion from AgOpen. When i get more time just need to finish 65254 (Date/Time). I do still need to test on a machine as I have only guessed in the office. Im not sure if you have parts to test or still ordering parts?


Wow! you work fast! Parts are in the mail so hopefully I will be able to test and report back soon.