NMEA 2000 Shield

Hi, @southern_cross: I'm most interested in your work. Myself looking at the possibilities to use Arduino + shield to prototype a marine monitoring interface. Basically I would want to monitor and convert to NMEA2000 the engine data you mention plus some additional ones, like PGN 127508 - Battery Status. Possibly also some environmental data and in future also read and parse the AIS PGNs. I've previously only been working in the NMEA0183 domain though now believe that the transition to 2000 is inevitable. If you are willing to share your progress and also specify how you would want to co-operate I'd be very happy to join.

Hi, I'm new to this forum. Just wondering if there has been anything more on the subject of interfacing Arduino and NMEA2000?

BTW, there is an open-source NMEA 2000 decoder project over at canboat · GitHub that looks useful.

I've just spent the weekend decoding a NMEA 2000 GPS sensor. Garmin's manual specified the PGNs the unit transmits and accepts but no detail on any of the payload. I've managed to come across documents which have been helpful, but whats been most helpful is Maretron's product manual. They get into detail, at least with their GPS sensor, almost as heavily as most NMEA0183 devices I've had the pleasure to use. Only problem is, I need to figure out how to configure my Garmin sensor since differential corrections are disabled be default... I've been talking with a Garmin representative about the lack of technical documentation and am waiting for a follow-up.

When it comes to standards, the manufacture might be the entity who purchases the standard, but we, the customer, are actually paying for it.

http://www.nmea.org/Assets/july%202010%20nmea2000_v1-301_app_b_pgn_field_list.pdf

I've been active over at Seeed studio's forums. I've been actively developing and ironing out the MCP2515 library. I would recommend checking out my github if you end up using the MCP2515 with an Arduino.

Cory would you be able to provide an example of writing a broadcast of engine data PGN 127489 using your library?

I am working on receiving these values from my boat's computer (delphi ecm MEFI3) and I would now like to have them appear on my NMEA 2000 network.

I'll be releasing my full project's source code and hardware when completed (for now: by request).

Nevermind Cory.

I have successfully broadcasted engine data (PGN 127488 + PGN 127489) to my raymarine MFD.

1 Like

I am very interested in doing just this. If you are willing to share what you have done, I would very much appreciate it. Not sure if if want PGN 127489 or 130311 for my task. There doesn't seem to be an appropriate PGN for engine vacuum (not boost).

Thanks!

allen652:
Nevermind Cory.

I have successfully broadcasted engine data (PGN 127488 + PGN 127489) to my raymarine MFD.

I'd be happy to help. PM sent.

I replied. Thanks very much! I don't have much to offer in kind, other than a parts list and schematic for what I plan to do with it.

allen652:
Nevermind Cory.

I have successfully broadcasted engine data (PGN 127488 + PGN 127489) to my raymarine MFD.

Hello Allen!

How did You manage to broadcast PGN 127489? It isnt covered by standard or extended CAN formats due to more than 8 databyte (=26) in it - I found that its only supported by CAN FD devices - MCP2515, AT90CAN128 family and all PICs are supports only the 8 databyte mode... Im really interested in it, can You please share Your findings?

Best regards,
Peter

The way more than 8 data bytes is sent is a little confusing. They send it 7 bytes at a time with the first byte of every message incrementing by 1, which is called the sequence identifier. So to send more than 8 data bytes, you would essentially be sending multiple messages, each having the sequence identifier one greater than the previous. Three such messages would give you 21 bytes and four would give you 28. I think the simplest way to do this would be with a 'for' loop, putting the data bytes in an array, using the the loop count variable as the sequence ID, and multiplying the loop variable by seven for the offset needed by the array.

Sorry, its been a while since I've been around and active. I've been busy with life things and not had any time for my toys.

@allen652:
I'm very impressed you managed to generate engine data on NMEA 2000 so the Raytheon MFD could display it. Is there a way you could share your results or can I approach you via a PM? /Jay

@allen652: I also look to broadcast information to a Triton T41 MFD.

I have been crawling on the web to find out some raw binary file, that I could use to check my code but no luck.
Therefore, what I have is the NMEA decoder ( Good that this was made open, really a huge piece of work) that will help me to understand what the Triton sends out initially when powered up, but I will have to reverse the decoder, and assemble some PGNs ( like boat COG , wind direction) and send that to the Triton.

It would be of great help if you could accept to share the piece of SW you did to boradcast PGNs... Please.

What I am concerned about is the address claim, and also device capabilities / ISO aCK stuff.

Thanks in advance.

Hello everyone,
I have trouble with PGN 127489 + MFDe7 Raymarine and I have not response from "allen652" .
Can not display engine temperature water.
Can someone please help me?
thank you
jp

I am using an arduino MEGA and the MCP2515 library as mentioned and worked on by coryjfowler GitHub - coryjfowler/MCP_CAN_lib: MCP_CAN Library.

I built my own shield for a few dollars by purchasing the components from digikey.com. The design is based on seeedstudio's canbus http://www.seeedstudio.com/wiki/images/7/78/CAN-BUS_Shield_v0.9b.pdf.

IC CAN Transceiver: MCP2551-I/P-ND
IC CAN Controller: MCP2515-I/P-ND
16 MHZ Crystal: XC1748-ND
Resistors: 10kohm & 4.7kohm
Capacitors: (2) 22pf & (2) 100nf

I am using non-certified NMEA2000 cabling products that I had manufactured for my current project. I have several available for sale below if interested. See images.
2 meter cable (Male to Female) - $7.5 each
T adapter - $7 each
Male rear panel mount connector - $5 each

I'll put some sample code in my next post.

#include <mcp_can.h>
#include <SPI.h>

MCP_CAN CAN0(53);                                      // Set CS to pin 53
//MCP_CAN CAN0(2); //for my new prototype shield
unsigned char stmp[8] = {0, 0xA0, 0x41, 0, 0, 0, 0, 0};
unsigned char extpgn[26] = {0, 0xA0, 0x41, 0, 0, 0, 0, 0};
void setup(){
  Serial.begin(115200);
  // init can bus, baudrate: 250k
  if(CAN0.begin(CAN_250KBPS) == CAN_OK) Serial.print("can init ok!!\r\n");
  else Serial.print("Can init fail!!\r\n");
  //testPGN127489();
  //transmitPGN127489(0,55.5,164,171,13.54,15.8,221,11.2,32.1,38,44,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);
  testRPM();
}

void loop(){  
  //PGN 127488 binary 011111001000000000
  //for id try 110 + above + 00000000 = 11001111100100000000000000000 = hex 19F20000
  //highest priority = 000 try 00001111100100000000000000000 = hex 1F20000 = dec 32636928
  
  //PGN 127489 binary 011111001000000001
  //for id try 00001111100100000000100000000 = hex 1F20100 = dec 32637184
  
  // send data:  id = 0x00, extended frame, data len = 8, stmp: data buf
  CAN0.sendMsgBuf(32636928L, 1, 8, stmp);  
  
  //testPGN127489();
  //transmitPGN127489(0,55.5,164,171,13.54,15.8,221,11.2,32.1,38,44,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);  //with checkEngine flag set to true
  transmitPGN127489(0,55.5,164,171,13.54,15.8,221,11.2,32.1,38,44,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0);  //with checkEngine flag set to false
  
  delay(1000);                       
}
void testPGN127489(){
  //if an engine parameter is not available, set it to 255
  unsigned char extpgn[26] = {0, 208, 14, 170, 13, 151, 136, 236, 4, 37, 2, 112, 207, 11, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 255, 255};  //decimal values
  unsigned char temp[8] = {0,0,0,0,0,0,0,0};
  //
  
  int cur=0;
  for(int i = 0; i<=3; i++){
      temp[0] = i; //frame counter
      if (i==0){
          temp[1] = 26; //total bytes in fast packet
          //send the first 6 bytes
          for(int j = 2; j<8; j++){  
               temp[j]=extpgn[cur];
               cur++;   
           } 
      } else{
           for(int j = 1; j<8; j++){  
               temp[j]=255;
           }
           //send the next 7 data bytes
           for(int j = 1; j<8; j++){  
               temp[j]=extpgn[cur];
               cur++;   
           } 
      }
      CAN0.sendMsgBuf(32637184L, 1, 8, temp);  
      delay(1);
  }
}
void testRPM(){
  unsigned char stmp[8] = {0, 0xA0, 0x41, 0, 0, 0, 0, 0};
  int rpm;
  for(int i = 0; i<=4200; i=i+100){
      rpm=(i*4);
      stmp[2]=rpm/256;
      stmp[1]=rpm-(stmp[2]*256);
      CAN0.sendMsgBuf(32636928L, 1, 8, stmp);  
      delay(100);
  }
}
void transmitPGN127489(int engineInstance, float engineOilPress, float engineOilTemp, float engineCoolantTemp, float battery, 
  float fuelRate, float engineHours, float engineCoolantPress, float engineFuelPress, float engineLoad, float engineTorque, bool flagCheckEngine, 
  bool flagOverTemp, bool flagLowOilPress, bool flagLowOilLevel, bool flagLowFuelPress, bool flagLowSystemVoltage, bool flagLowCoolantLevel,
  bool flagWaterFlow, bool flagWaterInFuel, bool flagChargeIndicator, bool flagPreheatIndicator, bool flagHighBoostPress, bool flagRevLimitExceeded,
  bool flagEgrSystem, bool flagTPS, bool flagEmergencyStopMode, bool flagWarning1, bool flagWarning2, bool flagPowerReduction, 
  bool flagMaintenanceNeeded, bool flagEngineCommError, bool flagSubThrottle, bool flagNeutralStartProtect, bool flagEngineShuttingDown){
  //if an engine parameter is not available, set it to 255
  unsigned int extpgn[26]; // {0, 208, 14, 170, 13, 151, 136, 236, 4, 37, 2, 112, 207, 11, 0, 255, 255, 255, 255, 1, 0, 0, 0, 0, 255, 255};  //decimal values
  unsigned char temp[8]; // {0,0,0,0,0,0,0,0};
  float v;
  extpgn[0]=engineInstance;
  
  v=engineOilPress;
  v=v*68.94757;
  //extpgn[2]=v/256;
  //extpgn[1]=v-(extpgn[2]*256);
  extpgn[2]=highByte((int) v);
  extpgn[1]=lowByte((int) v);  //this works well. I should change the code below to use this format.
  
  v=engineOilTemp;
  v=(((v-32)*5/9)+273)*100;
  extpgn[4]=v/256;
  extpgn[3]=v-(extpgn[4]*256);
  
  v=engineCoolantTemp;
  v=(((v-32)*5/9)+273)*100;
  extpgn[6]=v/256;
  extpgn[5]=v-(extpgn[6]*256);
  
  v=battery;
  v=v*100;
  extpgn[8]=v/256;
  extpgn[7]=v-(extpgn[8]*256);
  
  v=fuelRate;
  v=v*37.8541;
  extpgn[10]=v/256;
  extpgn[9]=v-(extpgn[10]*256);
  
  v=engineHours;
  v=v*3600;
  //extpgn[14]=v/16777216;
  //extpgn[13]=(v-(extpgn[14]*16777216))/65536;
  //extpgn[12]=(v-((extpgn[14]*16777216)+(extpgn[13]*65536)))/256;
  //extpgn[11]=v-((extpgn[14]*16777216)+(extpgn[13]*65536)+(extpgn[12]*256));
  int tempVar = (long) v >> 16;
  extpgn[14]=highByte(tempVar);
  extpgn[13]=lowByte(tempVar);
  tempVar = (long) v & 0xFFFF;
  extpgn[12]=highByte(tempVar);
  extpgn[11]=lowByte(tempVar);
  
  v=engineCoolantPress;
  v=v*68.94757;
  extpgn[16]=v/256;
  extpgn[15]=v-(extpgn[16]*256);
  
  v=engineFuelPress;
  v=v*689.4757;
  extpgn[18]=v/256;
  extpgn[17]=v-(extpgn[18]*256);
  
  extpgn[19] = 255; //nmea2000 reserved byte
  
  int engineStatus1P1 = B00000000;
  int engineStatus1P2 = B00000000;
  int engineStatus2 = B00000000;
  if (flagCheckEngine) engineStatus1P1 |= B00000001;
  if (flagOverTemp) engineStatus1P1 |= B00000010;
  if (flagLowOilPress) engineStatus1P1 |= B00000100;
  if (flagLowOilLevel) engineStatus1P1 |= B00001000;
  if (flagLowFuelPress) engineStatus1P1 |= B00010000;
  if (flagLowSystemVoltage) engineStatus1P1 |= B00100000;
  if (flagLowCoolantLevel) engineStatus1P1 |= B01000000;
  if (flagWaterFlow) engineStatus1P1 |= B10000000;
  
  if (flagWaterInFuel) engineStatus1P2 |= B00000001;
  if (flagChargeIndicator) engineStatus1P2 |= B00000010;
  if (flagPreheatIndicator) engineStatus1P2 |= B00000100;
  if (flagHighBoostPress) engineStatus1P2 |= B00001000;
  if (flagRevLimitExceeded) engineStatus1P2 |= B00010000;
  if (flagEgrSystem) engineStatus1P2 |= B00100000;
  if (flagTPS) engineStatus1P2 |= B01000000;
  if (flagEmergencyStopMode) engineStatus1P2 |= B10000000;
  
  if (flagWarning1) engineStatus2 |= B00000001;
  if (flagWarning2) engineStatus2 |= B00000010;
  if (flagPowerReduction) engineStatus2 |= B00000100;
  if (flagMaintenanceNeeded) engineStatus2 |= B00001000;
  if (flagEngineCommError) engineStatus2 |= B00010000;
  if (flagSubThrottle) engineStatus2 |= B00100000;
  if (flagNeutralStartProtect) engineStatus2 |= B01000000;
  if (flagEngineShuttingDown) engineStatus2 |= B10000000;
  
  extpgn[20]=engineStatus1P1;
  extpgn[21]=engineStatus1P2;
  extpgn[22]=engineStatus2;
  extpgn[23]=0;
  
  extpgn[24]=engineLoad;
  
  extpgn[25]=engineTorque;
  
  int cur=0;
  for(int i = 0; i<=3; i++){
      temp[0] = i; //frame counter
      if (i==0){
          temp[1] = 26; //total bytes in fast packet
          //send the first 6 bytes
          for(int j = 2; j<8; j++){  
               temp[j]=extpgn[cur];
               cur++;   
           } 
      } else{
           for(int j = 1; j<8; j++){  
               temp[j]=255;
           }
           //send the next 7 data bytes
           for(int j = 1; j<8; j++){  
               temp[j]=extpgn[cur];
               cur++;   
           } 
      } 
      CAN0.sendMsgBuf(32637184L, 1, 8, temp);  
      delay(1);
  }
  //for debugging
  /*
  Serial.println();
  Serial.print("Data = ");
  for(int j = 0; j<26; j++){  
    if (j<25){
      Serial.print(extpgn[j]);
      Serial.print(", ");
    } else {
      Serial.println(extpgn[j]);
    }
  }
  Serial.print("HexData = ");
  for(int j = 0; j<26; j++){  
    if (j<25){
      Serial.print(extpgn[j],HEX);
      Serial.print(", ");
    } else {
      Serial.println(extpgn[j],HEX);
    }
  }
  */
}

Thank you for your reply.
Here is my current code for this part :

 // ++++++++++++++++++++++++++++++++++++ Engine Parameters, Dynamic ++++++++++++++++++++++++++++++++++++
  updateRate = 500; // 2 * / s
  priorite = 5;
  PGN = 127489;  // Paramètres environnementaux bus NMEA 2000 (N2K)
  source = 35;   // Airmar = 35
  adresse = priorite * 67108864 + PGN * 256 + source;
  float tempwc =  95; //TWEng;  // Celsius  Engine Water Temp
  long tempwk =  100 * ( tempwc + 273.15 );
  long tempw1 = ( tempwk & 0b1111111100000000 ) >> 8;  // mise en forme format CAN 16bits
  long tempw2 =   tempwk & 0b0000000011111111;

  float PressEng = 200;  // Lecture capteur pression resolution = 8 bits pour essai.
  long PressEng_ = 20 * PressEng;
  long PressEng1 = ( PressEng_ & 0b1111111100000000 ) >> 8;  // mise en forme format CAN 16bits
  long PressEng2 =   PressEng_ & 0b0000000011111111;

  message.id = adresse;
  message.ide = 1;
  message.rtr = 0;
  message.dlc = 8;
  message.data[0] = 0;   //  SID 
  message.data[1] = 0;   //  Engine instance  8bits    0=Single Engine
  message.data[2] = 0;   //  doit etre  à 0 pour lecture 2 octets plus bas. Oil press = 0 si 255
  message.data[3] = PressEng2; //PressEng2;   
  message.data[4] = PressEng1; //PressEng1;   //  Oil   Press huile 1=0,3  2=0,5  3=0,8  4=1b  28=7,2b
  message.data[5] = 255;   //  ????
  message.data[6] = tempw2;   //  Temperature eau moteur - 16 bits (sonde DS18B20)
  message.data[7] = tempw1;   //  Temperature eau 
 /* Serial.print("TEMPERATURE EAU MOTEUR : ");
 Serial.print(tempw2);  Serial.print(" - ");
 Serial.println(tempw1);
 */
  SendPaquet();

  message.data[1] = 1;  // 1 paquet supplémentaire
  message.data[2] = 0;  // Alternateur
  message.data[3] = 0;  // Alt
  message.data[4] = 0;  // Fuel rate
  message.data[5] = 0;  // Fuel rate
  message.data[6] = 1;  // Hour
  message.data[7] = 0;  // Hour
  message.data[8] = 0;  // Hour
 
  SendPaquet(); 

  message.data[1] = 2;  // 1 paquet supplémentaire
  message.data[2] = 4;  // Hour
  message.data[3] = 0;  // Coolant press
  message.data[4] = 0;  // Coolant press
  message.data[5] = 0;  // Fuel press
  message.data[6] = 0;  // Fuel press
  message.data[7] = 0;  // reserved
  message.data[8] = 0;  // Discrete Status 1
 
  SendPaquet();

  message.data[1] = 3;  // 1 paquet supplémentaire
  message.data[2] = 0;  // Discrete Status 1
  message.data[3] = 0;  // Discrete Status 2
  message.data[4] = 0;  // Discrete Status 2
  message.data[5] = 0;  // Percent Engine Load
  message.data[6] = 0;  // Percent Engine Torque
 
  SendPaquet();

  delay(updateRate);

allen652:
I am using non-certified NMEA2000 cabling products that I had manufactured for my current project. I have several available for sale below if interested. See images.
2 meter cable (Male to Female) - $7.5 each
T adapter - $7 each
Male rear panel mount connector - $5 each

I have found that NEMA2000 is hardware wise follows the older DEVICENET so many parts interchange with out issues.

Compliments, Beautiful Code.
But I have to try to translate Arduino uno.

I did not see how you code:
address = priority * 67108864 * 256 + PGN + source;

I should probably comment my code better (and more often).

jpjcb66: The first 3 bits of the id is the priority, the next 18 bits are the PGN, and the last 8 bits are the source address.
See the comments in my code above in void loop().

priority = 000
PGN = 011111001000000000
source = 00000000

00001111100100000000000000000 binary = 32636928 decimal

// send data:  id , extended frame, data length = 8, data buffer
  CAN0.sendMsgBuf(32636928L, 1, 8, stmp);