NMEA 2000 Shield

In NMEA2000: Date is 2 bytes days since 1970-01-01 Time is 4 bytes seconds since midnight resolution 0.0001 s

There seem to be parser for that PGN on my library.

@timolappalainen: Do you know a WiFi module for teensy 3.2 with possibility to connect to IDE via WiFi?

I do not know is it possible with Arduino IDE. But with e.g. Platform IO IDE e.g. ESP8266 can be used. I do not remember how.

Other maybe simpler option is to use ESP32 modules. They have also CAN inside and I have tested at least some examples. The CAN is simpler than on Teensy, but works.

I have been using ESP32 with Timo's library successfully. I'm using MCP2652 CAN transceiver and ESP32 internal CAN controller. Also nmea0183 to wifi tcp is working quite good. Sometimes it gets stuck if there is too much data.


On Github - there is mention of the timing of the ISO address claim timing being altered this year. "03.03.2019

Added: Charger status PGN 127507.

Added: Possibility to delay ISO address claim. Due to some devices CAN priority, it was sent too fast.

Added: Count for tDeviceList


Has that change actually happened? or is it left up to the end user to construct a small delay (? 50mSec ?)

How might that be done? I don't want to break your excellent code!

Thanks, Graeme

Shortly answer: it is implemented, but you do not need to do anything.

The explanation is right, but a bit misleading. I found problem on one device NMEA 2000 certification tests and it appeared only, when test device used complex group function to change device instances. The complex group function handler calls function tNMEA2000::SetDeviceInformationInstances and due to change, it has to send SendIsoAddressClaim at the end of routine. The handler and response was too fast and I could see response on the bus, but test device did not see it and the test fails. This may not be real problem and may be just caused by old design of test device, but I had to modify code so that tests passes. Currently the delay is used only on tNMEA2000::SetDeviceInformationInstances.

Better not to break code, since that passes NMEA 2000 certification tests.


I have a question regarding calibration of sensors through NMEA2K bus as well as how to upgrade the firmware of devices through NMEA2K.

In detail, I purchased the NMEA2K trim tab indicator kit from Bennett to make my tabs visible. The sensors follow an NMEA2k device which should drop the position on the bus to be picked up by MFD's for example. My Raymarine Axiom 9 should be able to show the trim tab position but the sender has to be calibrated first. Of course, this function is not supported by the Raymarine Axiom according to Raymarine. I have read that it should be possible to run the calibration through the NMEA2k but I have no idea how to start. At least the device is shown as small craft status at the diagnostic screen of the Axiom.

The second part of my question, the Lowrance Link-8 should be able to upgrade through MFD of Lowrance OR through NMEA2k. Same situation, where to start?

Can somebody help out?


These setup and calibration for different devices uses proprietary N2k messages. So if you do not know them or can not get document for them, the only way is to use reverse engineering. Unfortunately you need for that also device, which is capable to do calibration.

For Lowrance Link-8 I prefer to go dealer or to buy proper upgrade tool. That has been also done with proprietary messages.

Hi Timo,

I had the hope that there exist standardized calibration sentences to talk to senders. I have sent now an email to the Bennett support but I do not expect help from there.
In case I do not get this information, are you known with the type of sensors used in trim tabs? It is an iron stang moving up and down, according to the position of the tabs, inside a coil. Do you know how to interface them?

Rudder Position example:

// Demo: NMEA2000 library. Read rudder angle from the Analog sensor

#include <NMEA2000.h>
#include <N2kMessages.h>
#include <NMEA2000_teensy.h>
#include <CircularBuffer.h>

// CircularBuffer<double, 5> buffer

unsigned long time = 0;

double rudderValueAVG;
#define rudderPosSens A1

tNMEA2000_teensy NMEA2000;
// List here messages your device will transmit.
const unsigned long TransmitMessages[] PROGMEM = {127245L, 0};

// *****************************************************************************
void setup() {
        // Set Product information
        NMEA2000.SetProductInformation("40155", // Manufacturer's Model serial code
                                       100, // Manufacturer's product code
                                       "Rudder Position", // Manufacturer's Model ID
                                       " (15-09-2018)", // Manufacturer's Software version code
                                       " (15-09-2018)" // Manufacturer's Model version
        // Set device information
        NMEA2000.SetDeviceInformation(40155, // Unique number. Use e.g. Serial number.
                                      155, // Device function=Rudder. See codes on http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
                                      40, // Device class=Steering and Control Surfaces. See codes on  http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
                                      1851 // Just choosen free from code list on http://www.nmea.org/Assets/20121020%20nmea%202000%20registration%20list.pdf
        // Uncomment 2 rows below to see, what device will send to bus. Use e.g. OpenSkipper or Actisense NMEA Reader
        // If you want to use simple ascii monitor like Arduino Serial Monitor, uncomment next line
        //NMEA2000.SetForwardType(tNMEA2000::fwdt_Text); // Show in clear text. Leave uncommented for default Actisense format.

        // If you also want to see all traffic on the bus use N2km_ListenAndNode instead of N2km_NodeOnly below
        NMEA2000.SetMode(tNMEA2000::N2km_ListenAndNode, 23);
        //NMEA2000.SetDebugMode(tNMEA2000::dm_Actisense); // Uncomment this, so you can test code without CAN bus chips on Arduino Mega

// *****************************************************************************
// Double replacement for standard map function
double dMap(double x, double in_min, double in_max, double out_min, double out_max) {
        return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;

// *****************************************************************************
// Function reads rudder postion from A0 input and scales it to angle in degrees
double ReadRudderPosition() {
        double rudderValue = dMap(analogRead(rudderPosSens), 60, 900, -45, 45);
   // We actually would need here some filter function to prevent noise on analog.
   for (unsigned int i = 0; i < buffer.size(); i++) {
    rudderValueAVG += buffer[i] / buffer.size();
        return rudderValue;
// *****************************************************************************

#define RudderSendPeriod 100
#define RudderSamplePeriod 1
#define RudderSampleCount 40

void SendRudder() {
        static unsigned long RudderSendTime = millis() + RudderSendPeriod;
        static unsigned long RudderSampleTime = RudderSendTime - RudderSampleCount * RudderSamplePeriod;
        static double RudderSampleSum = 0;
        static size_t RudderSamplesDone = 0;

        tN2kMsg N2kMsg;
        if ( RudderSampleTime < millis() ) {
                RudderSampleSum += ReadRudderPosition();
                if ( RudderSendTime < RudderSampleTime ) {
                        RudderSendTime += RudderSendPeriod;
                        RudderSampleTime = RudderSendTime - RudderSampleCount * RudderSamplePeriod;
                        double rudderValue = RudderSampleSum / RudderSamplesDone;
                        RudderSampleSum = 0;
                        RudderSamplesDone = 0;
//      Serial.print("Rudder value: "); Serial.println(rudderValueAVG);
                        SetN2kRudder(N2kMsg, DegToRad(rudderValue));
                } else {
                        RudderSampleTime += RudderSamplePeriod;

void loop() {

No idea. May also depend of manufacturer like calibration.

If you are making some NMEA 2000 device with Teensy, you can fool the system: - Read trim tab positions from Bennett to Teensy. - Make calibration on Teensy (your device) - Send calibrated values back to NMEA 2000 bus from your device. - On Axiom select your device for trim tab instead of Bennett.

It should be possible to have several devices to sent trim tab and Axiom should also hangle that and show options, which device to use. "Should" is again good word here!

Hello Timo,

I jjust saw that there is missing text I wrote in my last post. It was about the rudder position sketch I posted. This causes a problem in the network, or at least I think it is a problem. If this device is part of the network than the MFD is giving the message "It is not possible to manually choose devices while Rudder Position with SN 40155 is part of the network".

For example while I try to change the depth sensor this message comes up. What causes this "block"?

Another thing is driving me nuts, I just programmed the TeensyActisenseLitenerSender from the example and I can not see anything at all. I tried it now on two devices, changed the MCP2562 twice and tried different baud rates. Not the NMEA reader can see something (Serial port is open by the reader) nor the Serial Monitor of Arduino IDE is showing something. I also modified to show data in clear text on serial. What do I make wrong, is there more to be modified of the TeensyActisenseLitenerSender when using with Teensy3.2 and MCP2562? Both devices where working before.


On SetDeviceInformation you have set manufacturer code to be Raymarine code. Maybe it checks that this is not right Raymarine rudder sensor and refuces to work.

Note that TeensyActisenseListenerSender is made for that schematic and uses Serial1 for data output to PC. So you should have USB-serial (3.3V) converter on Teensy Serial1. Use just simple ActisenseListener for spying data on bus with NMEA reader.

I have a question about Timo's library...

I'm trying to extract the manufacturer and industry codes from proprietary PGNs and I'm getting data that I don't think can be right. For reference the manufacturer code is the left most 11 bits of the first two bytes while the industry code is the rightmost 3 bits (the first three fields, with the middle 2 bit field reserved). So my code looks like:

int index = 0;
uint16_t propHeader = 0;

uint16_t manCode;
unsigned char industryGroup;

propHeader = N2kMsg.Get2ByteUInt(index, 0);

industryGroup = industryGroup & 0x0003;
manCode = (manCode & 0xFFE0) >> 5;

Am I extracting the first two bytes correctly?

You do not set manCode and industryGroup. I prefere to turn on all warnings and also define all warnings are errors, so you can cot compile this kind of clear problem.

Also data is in little-endian. So also fields insize value has to be handled in that way. So try:

int index = 0; uint16_t propHeader = N2kMsg.Get2ByteUInt(index);

uint16_t manCode=propHeader & 0x7ff; unsigned char industryGroup=(propHeader >> 13);

I use ESP32 and I’m trying to send engine data to my B&G Triton T41 MFD. For some reason, MFD does not recognize my device as source for engine data. However it recognizes it as source for fuel level. It also recognizes it as device and shows data I am sending to bus.

For engine data I use PGN 127489 and for fuel level PGN 127505.

B&G Zeus2 plotter accepts my device as source for engine data. Confusing.

All data is hardcoded at the moment. Some code:

  NMEA2000.SetProductInformation("112233", // Manufacturer's Model serial code
                                 100, // Manufacturer's product code
                                 "Engine monitor",  // Manufacturer's Model ID
                                 " (2016-12-31)",  // Manufacturer's Software version code
                                 " (2016-12-31)" // Manufacturer's Model version

                                50, // Device class=Propulsion. 

Message sending:

#define EngineDataUpdatePeriod 1000

void SendN2kEngineData() {
  static unsigned long EngineDataUpdated=millis();
  tN2kMsg N2kMsg;

  if ( EngineDataUpdated+EngineDataUpdatePeriod<millis() ) {
    SetN2kEngineDynamicParam(N2kMsg, 0, ReadOilPressure(), N2kDoubleNA, ReadEngineCoolantTemp(), ReadAlternatorVoltage(), ReadFuelRate(), N2kDoubleNA);

#define TankDataUpdatePeriod 2500
#define TankCapacity 75

void SendN2kTankData() {
  static unsigned long TankDataUpdated=millis();
  tN2kMsg N2kMsg;

  if ( TankDataUpdated+TankDataUpdatePeriod<millis() ) {
    SetN2kFluidLevel(N2kMsg, 0, N2kft_Fuel, ReadTankLevel(), TankCapacity);

See attachments. Ideas?






Have you defined PGNs you sent?

... const unsigned long TransmitMessages[] PROGMEM={127489UL ,127505UL,0}; ...

setup() { ... NMEA2000.ExtendTransmitMessages(TransmitMessages); ... NMEA2000.Open(); ... }

Hello Timo,

I'm facing problem that occurs every now and then. I have built test system with two esp32, other is reading data from bus and the other is acting as simulator feeding data to bus. My bus is just short piece of breadboard with 2x 120ohm resistors at the both ends.

My other esp32 is using mcp2562 tranceiver and other esp32 is using SN65HVD230 tranceiver. For Can controller both esp32 uses it's internal controller.

Now however, sometimes my simulator sails to send messages to bus (e.g. "PGN 127488 send failed.."). Sometimes simulator is sending data to bus but my receiving esp32 is not getting this data (Forward type = text and nothing showing in console). Anyway sometimes when I reset few times both Esp's things starts to work for a while and then just stops in less than an hour.

Do you have any ideas what could cause this kind of problems? Do you think this would be hardware related or software related problem?

timolappalainen: Have you defined PGNs you sent?

... const unsigned long TransmitMessages[] PROGMEM={127489UL ,127505UL,0}; ...

setup() { ... NMEA2000.ExtendTransmitMessages(TransmitMessages); ... NMEA2000.Open(); ... }

Yes, I have but with a little difference:

const unsigned long TransmitMessages[] PROGMEM={127489L,127505L,0};

As you can see - without "U" after PGN number. Can it cause this odd behavior?

hirppa10: Do you have any ideas what could cause this kind of problems? Do you think this would be hardware related or software related problem?

Please email me your .ino files so I can test do I have same behaviour.

Where I'm able to find your email? Ill send Ino's right away

edit: found it