Code for converting a CAN message ID to ISO 11783 / NMEA 2000 fields

I've received a number of requests from people who are trying to get microcontrollers up & running that they want to attach to the N2K bus.

Apart from the logical content of the datafields for each PGN, one of the other things that you will end up doing is filtering out the priority, PGN, source address and destination address from the underlying CAN message.

NMEA 2000 messages are always CAN 2.0B messages with the "Extended Frame" format. It uses the ISO-11783 standard as the physical layer. That standard is where we get the "PGN" term from.

Once you get a ISO-11783 CAN message ID from your device, it will contain an encoding for the PGN number, the priority, the source ID and possibly a destination address. To save bits the PGNs that are not addressable imply a "broadcast" destination of 255. That format is called PDU2. Other PGNs are addressable, and contain an explicit 8 bit destination in the PS field. That format is called PDU1. If you want to know more, try googling for ISO11783 PDU1 for more information.

Once you've got the 32 bit CAN message ID (which you should be able to get from your hardware device pretty easily) you need to convert the message ID to priority, PGN, source and destination. Here is the source code that I came up with to do this:


static void getISO11783BitsFromCanId(unsigned int id, unsigned int * prio, unsigned int * pgn, unsigned int * src, unsigned int * dst)
{
unsigned char PF = (unsigned char) (id >> 16);
unsigned char PS = (unsigned char) (id >> 8);
unsigned char DP = (unsigned char) (id >> 24) & 1;

if (src)
{
*src = (unsigned char) id >> 0;
}
if (prio)
{
*prio = (unsigned char) ((id >> 26) & 0x7);
}

if (PF < 240)
{
/* PDU1 format, the PS contains the destination address */
if (dst)
{
*dst = PS;
}
if (pgn)
{
*pgn = (DP << 16) + (PF << 8);
}
}
else
{
/* PDU2 format, the destination is implied global and the PGN is extended */
if (dst)
{
*dst = 0xff;
}
if (pgn)
{
*pgn = (DP << 16) + (PF << 8) + PS;
}
}

}