NMEA 2000 Shield

If your pump is pusation pump, you will need it. If it is rotation pump (very rare), it may work without.

It does not need to be those, but idea is important. They must be between engine and sensor on both input and return lines.

Hello,

may a stupid question, but kind of datatype I have to use for FluidType in:
SetN2kFluidLevel(N2kMsg, Instance, FluidType, Level, Capacity);

I get the hole time whatever I tried the message error: invalid conversion from 'WHATEVER' to 'tN2kFluidType' [-fpermissive]

You must use tN2kFluidType type define in N2kMessages.h. E.g.

SetN2kFluidLevel(N2kMsg, Instance, N2kft_Water, Level, Capacity);

or

tN2kFluidType FluidType=N2kft_Water;
SetN2kFluidLevel(N2kMsg, Instance, FluidType, Level, Capacity);

Hey Timo,

got it! I just forgot that. Had a break of some month.

Another question, RapidEngineData how often do this need to be send? Is once or twice a second enough? When I think about RPM, for example, it needs to be updated a lot to get a smooth result?

Hi,

I just got the sketch for fuel rate and fuel tank finished but can not test it.

Do anybody have a couple of minutes to look through if this would work? I don´t mean to fix everything or show every the best way. Since I am not that genius in arduino programming I would already be happy if that will work before I start to optimize everything.

EDIT
" I just found some things,I would need smoothing for the tank volume as thoose are moving a lot when the boat is rolling and so on!"

Thanks a lot!

// Get tankvolume (pin 20 Teensy3.2) and fuelrate (pin 19), calculate and send through NMEA2K CAN

#include <Arduino.h>
#include <NMEA2000.h>
#include <N2kMessages.h>
#include <NMEA2000_teensy.h>
tNMEA2000_teensy NMEA2000;

// *********************************************
// NMEA2K related setup
// *********************************************

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

// *********************************************
// Variables to setup function reading fuelrate
// *********************************************

volatile int pulsCountValue; // Storage for counted pulses through interupt function
unsigned int FuelRate; // Calculated litres/hour
unsigned char flowsensor = 19; // Sensor Input
unsigned long currentTime;
unsigned long startTime;
#define FuelRateUpdatePeriod 1000


// *********************************************
// Variables to setup function reading fuel tank level
// *********************************************

unsigned char tankVolumeSens = A20;
#define FuelLevelUpdatePeriod 1000

// *********************************************
// Variables to setup sending of NMEA2K message
// *********************************************


double Instance = 0;
double Level;
int Capacity;
tN2kFluidType FluidType = N2kft_Fuel;

void setup() {
  // Set Product information
  NMEA2000.SetProductInformation("00000010", // Manufacturer's Model serial code
                                 100, // Manufacturer's product code
                                 "Fuel Information System",  // Manufacturer's Model ID
                                 "1.1.0.22 (2016-12-31)",  // Manufacturer's Software version code
                                 "1.1.0.0 (2016-12-31)" // Manufacturer's Model version
                                );
  // Set device information
  NMEA2000.SetDeviceInformation(10, // Unique number. Use e.g. Serial number.
                                150, // Device function=Atmospheric. See codes on http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
                                75, // Device class=External Environment. See codes on  http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
                                2046 // 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
  //Serial.begin(115200);
  //NMEA2000.SetForwardStream(&Serial);
  // 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_NodeOnly, 23);
  // NMEA2000.SetDebugMode(tNMEA2000::dm_Actisense); // Uncomment this, so you can test code without CAN bus chips on Arduino Mega
  NMEA2000.EnableForward(false);
  NMEA2000.ExtendTransmitMessages(TransmitMessages);
  NMEA2000.Open();

  // Void Setup for Fuel Rate
  pinMode(flowsensor, INPUT_PULLDOWN);
  digitalWrite(flowsensor, LOW); // Optional Internal Pull-Up

  attachInterrupt(0, pulsCounter, RISING); // Setup Interrupt

  sei(); // Enable interrupts
  currentTime = millis();
  startTime = currentTime;

  Serial.begin(9600);

  // Setup for Fuel Rate END
}

void loop() {

  // *****************************************************************************
  // Call the looping functions
  // *****************************************************************************

  SendN2kFuelRate();
  SendN2kFuelLevel();
  NMEA2000.ParseMessages();
}



// *****************************************************************************
// 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;
}

// *****************************************************************************
// Interupt function. Count pulses from interupt.
// *****************************************************************************

void pulsCounter() {
  pulsCountValue++;
}

// *****************************************************************************
// Read and calculate fuelrate based on pulses from pin 19
// *****************************************************************************

double ReadFuelRate() {

  currentTime = millis();
  // Every 3 second, calculate and print litres/hour
  if (currentTime >= (startTime + 3000))
  {
    startTime = currentTime; // Updates startTime
    
    FuelRate = (pulsCountValue * 3.6); // (Pulse frequency x "3600/1000" 3.6 = flowrate in L/hour

    //    Serial.print(FuelRate, DEC); // Print litres/hour
    //    Serial.println(" L/hour");
  }
  return FuelRate;
}

// *****************************************************************************
// Read and map current fuel tank volume based on pin 20
// *****************************************************************************

double ReadTankVolume() {
  double Level= dMap(analogRead(tankVolumeSens), 60, 900, 0, 100);
  return Level;
}

// *****************************************************************************
// Send NMEA2K message for Fuel rate
// *****************************************************************************

void SendN2kFuelRate() {
  static unsigned long FuelRateUpdated = millis();
  tN2kMsg N2kMsg;

  if ( FuelRateUpdated + FuelRateUpdatePeriod < millis() ) {
    SetN2kEngineDynamicParam(N2kMsg, Instance, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, FuelRate, N2kDoubleNA);
    FuelRateUpdated = millis();
    NMEA2000.SendMsg(N2kMsg);
    pulsCountValue = 0;
  }
}

// *****************************************************************************
// Send NMEA2K message for fuel tank level
// *****************************************************************************

void SendN2kFuelLevel() {
  static unsigned long FuelLevelUpdated = millis();
  tN2kMsg N2kMsg;

   if ( FuelLevelUpdated + FuelLevelUpdatePeriod < millis() ) {
    SetN2kFluidLevel(N2kMsg, Instance, FluidType, Level, Capacity);
    FuelLevelUpdated = millis();
    NMEA2000.SendMsg(N2kMsg);
  }
}

NMEA2000 defination for RapidEngineData is 10 times/second.

If you are not sending WindSpeed PGN, you should not list it on TransmitMessages. If you are sending EngineDynamic parameters, you should list that on TransmitMessages.

Set the SetProductInformation and SetDeviceInformation right.

What kind of sensor (pulses/litre) you have and how much you expect to burn fuel in hour? As quick calculation your code accuracy can not be very good.

For analog read you can increase accuracy by increasing avaraging with analogReadAveraging(32); and/or putting readings to ring buffer and averaging that. Fuel level does not need to be very quick, so you could have e.g. 60 readings long ring buffer where you update reading every second. Then you have 1 minute averaging value.

Hello Timo,

Thx.

The sensor for fuel rate is a G12 OF 201. I tested it out and it gives 7.13 ml/puls. I let 2 liters flow 20 times through the sensor and measured & calculated the average with result 7.13ml/puls.

If I am right then fuel rate is a rapid date, right? So it will be sent every 100 msec. I do not think it is a problem at full rate but if I average the value lets say for 2 or 3 seconds I would send values from the past? This is not a problem I know.
Other more important rapid data like rpm, in one-second huge changes can happen here. What is here a better solution then count the pulses and average them?

I have seen a couple of status "flags" in the n2kmessages.h like flagOverTemp, flagLowOilPress, flagLowOilLevel and so on. I have some sensors on that engine which are not connected anywhere like for example right under the RTC for the coolant temp. I think these are alert "switches" / sensors to give an overheat signal for example. If I am right with this sensor then those flags are for this unconnected sensors right? If not, they could be calculated and set when the temp is going over a fixed defined limit? Its just a thought, I do not know yet if mayAxiom have some functions regarding this flags.

Are you using this flags? I mean it is very cool to create a complete digital engine control panel for older engine for example.

How much your engine is eating fuel/h?

So if you have 8 ml/pulse you will have 1000 / 8 pulse/l = 125 pulse/liter. If your engine takes 40 l/h, it means 5000 pulse/h = 1.39 pulse/s This means that you can not calculate pulses with 1 s period. Not even with 3 s - as you do in ReadFuelRate - giving you sometimes 3 pulses and sometime 4 pulses causing 33% changes. And by the way you newer call ReadFuelRate and do not update FuelRate variable. On idle engine takes so little fuel, that you can not measure it.

If the fuel stream is steady, you can get some results by measuring distance of each pulse in microseconds. But even that value you have average e.g. by using ring buffer.

Fuel rate is not on rapid PGN. It is on Engine Dynamic Parameters PGN, which you update once per second.

I have not used flags under engine dynamic parameters. Some MFD:s can show alarm according to them.

Hi Timo,

of course, you are right if I think about this now! I also saw this type of calculation/measurement where the time between the pulses is base for the calculation. I couldn´t figure out how that person who wrote that code came on the value of 7.5 puls per liter per hour.

the other errors.. before I posted this code I had sent you a PM a couple of days before. May you didn´t saw that. I don´t want to spam the thread with my code full of failures because I am not able yet ( or maybe never) to get this correct done.

EDIT: I jus realized what you have mentointed that fuel rate is not rapid data. So the next part is not right.

/ wrong

Regarding SetProductInformation and SetDeviceInformation, I am sure I didn´t think the whole thing to the end before I started with this device(s). EngineRapid data like FuelRate and Fluid level are different classes and functions. So this will then by a multidevice, right?
If so I will remove the fuel rate and put that first into its own device and may later with into the main device for the engine where all rapid- & dynamic data shall be generated.

wrong /

I will try to correct all you mentioned. Can I send it to you by PM or shell I post it here again?
I am sure you have better things to do than teaching me all this and correct all I try to create. Unfortunately, I have no idea where to post or ask somewhere else since this is tight together with your nmea2k library.

Regards & thanks

Just found a good fitting flow sensor for an affordable price (about 100 €).

It is the GEMS FT-210 made of Polyamid which is gasoline and diesel nonreacting.

This sensor delivers 22.000 puls / liter and goes official according to the specs from 0.1L/min to 2.5L/min which are from 6L/hr to 150L/hr. But I guess if accuracy do not count as much under 6L/hr then it would deliver 367 puls/sec at 1L/hr.

This would be great for the flow meter application.

Here is the link to the manufacturer if somebody is interested: GEMS FT-210 0.1 to 2.5 L/min 22.000 puls/L

I think with a so high puls rate simple smoothing would be enough to update the flow rate once per 2-3 seconds?

autopilotNOR:
I will try to correct all you mentioned. Can I send it to you by PM or shell I post it here again?
I am sure you have better things to do than teaching me all this and correct all I try to create. Unfortunately, I have no idea where to post or ask somewhere else since this is tight together with your nmea2k library.

Regards & thanks

Sorry, I did not notice PM. I had marked it read somehow. Yes you can send me PM.

You do not need to use MultiDevice. The Class and Function are informative and does not effect for device functionality.

I can do some guiding with coding, but currently I have reasonably big project under coding.

About other sensor. The problem with counting is syncronizating system. I used external counter chip, but actually still better may be just measure time between pulses. GEMS FT-210 provides maximum 150 * 22000 /3600 = 917 p/s, which is not much - 1 ms / p ans so could be measured by using us timer. There is also FreqCounter and FreqMeasure libraries for Teensy.

You still have not answered how much your engine will take fuel / hour. Also it is important that does it have only one fuel line or like I have 2 - one for in and one for return. So I need two sensors and need to calculate difference.

timolappalainen:
About other sensor. The problem with counting is syncronizating system. I used external counter chip, but actually still better may be just measure time between pulses. GEMS FT-210 provides maximum 150 * 22000 /3600 = 917 p/s, which is not much - 1 ms / p ans so could be measured by using us timer. There is also FreqCounter and FreqMeasure libraries for Teensy.

You still have not answered how much your engine will take fuel / hour. Also it is important that does it have only one fuel line or like I have 2 - one for in and one for return. So I need two sensors and need to calculate difference.

I figured out now how this would work with using the time between the pulses instead. Thats why I came to the conclusion that the flow sensor I have do not fit at all since it would need about 25 seconds to get a puls at a rate of 1L/hr. I was thinking about to block all counting until it reaches about 4-5 L/hr, but this wouldnt be effective since I wanted this sensor data because I would like to use the calculation functions of my MFD when it comes for example to if I would have enough fuel to come to the next waypoint and so on. So it wouldn`t make sense if I do not have the data down to the minimum of use of fuel from the engine.

However, I will start over again and take out the fuel tank level, for now, just to have more overview and it is better to get one by one working before I start combining everything. The new sensor should arrive may tomorrow so I can test a bit.

I also need to get the heading data out of a Bosch BNO055. This looks complicated for me so I even didn`t try it yet. I bought that sensor once to create a DIY automatic control for the trimtabs. I understood fast to drop that completely for a longer period. :slight_smile:

Engine RPM (EngineParameterRapid)

Will this work?
Based on pulsin()

To the function, I need to convert the input frequency from the engine (one pulse per rpm) to 4 pulses per RPM to be able to use may one year old RPM gauge (gasoline and now I have a diesel engine).

As well shall the data be send through NMEA2k.

The input frequency should be average by 30 readings.
Here is the code:

// Demo: NMEA2000 library. Calculate Engine RPM

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

float frequency;
byte EngineInstance = 0;
byte rpmGaugeOut = 6;
#define RapidDataUpdatePeriod 167
byte freqDivider = 4;
volatile float freqOut;

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

// *****************************************************************************
void setup() {
  // Set Product information
  NMEA2000.SetProductInformation("000001001", // Manufacturer's Model serial code
                                 101, // Manufacturer's product code
                                 "Engine RPM",  // Manufacturer's Model ID
                                 "1.0.0.0 (2018-08-24)",  // Manufacturer's Software version code
                                 "1.0.0.0 (2018-08-24)" // Manufacturer's Model version
                                );
  // Set device information
  NMEA2000.SetDeviceInformation(000001001, // Unique number. Use e.g. Serial number.
                                140, // Device function=Engine. See codes on http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
                                50, // Device class=Engine. See codes on  http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
                                2046 // 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
  //Serial.begin(115200);
  NMEA2000.SetForwardStream(&Serial);
  // If you want to use simple ascii monitor like Arduino Serial Monitor, uncomment next line
  NMEA2000.SetForwardType(tNMEA2000::fwdt_Text); // Show in clear text. Comment 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_NodeOnly, 23);
  // NMEA2000.SetDebugMode(tNMEA2000::dm_Actisense); // Uncomment this, so you can test code without CAN bus chips on Arduino Mega
  NMEA2000.EnableForward(false); // Comment this, if you want to see bus traffic on your serial.
  NMEA2000.ExtendTransmitMessages(TransmitMessages);
  NMEA2000.Open();

}

// *****************************************************************************
void loop() {
  NMEA2000.ParseMessages();
  SendN2kRapidData();
  FreqCount();
}

void FreqCount() {
  FreqMeasure.begin();
  double sum = 0;
  int count = 0;
  if (FreqMeasure.available()) {
    // average several reading together
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 30) {
      frequency = FreqMeasure.countToFrequency(sum / count);
      freqOut = (frequency / freqDivider);
      sum = 0;
      count = 0;
    }
  }
}

void rpm() {
  unsigned long highTime = 	((1000000 / freqOut) / 2);			// calculate the time of high level in microseconds
  unsigned long currentTime = micros();
  if (currentTime - (highTime * 2) <= currentTime) {
    digitalWrite(rpmGaugeOut, HIGH);
  }
  else
    digitalWrite(rpmGaugeOut, LOW);
}
void SendN2kRapidData() {
  static unsigned long RapidDataUpdated = millis();
  tN2kMsg N2kMsg;

  if ( RapidDataUpdated + RapidDataUpdatePeriod < millis() ) {
    RapidDataUpdated = millis();
	int int_frequency = (int)frequency;
    SetN2kEngineParamRapid(N2kMsg, EngineInstance, int_frequency, N2kDoubleNA, N2kDoubleNA);     //SetN2kEngineParamRapid(N2kMsg, EngineInstance, frequency, EngineBoostPressure, EngineTiltTrim);
    NMEA2000.SendMsg(N2kMsg);
  }
}

If I read it right, it does not work. On FreqCount() you have double sum; and int count; These are local and will be reset on every new call. Instead you can use

 static double sum = 0;
 static int count = 0;

Then those will be static inside function. Better would be make own class for measure.

The average may work, but it restarts over after 30 measure. Now if you have max RPM 4000 and you get 4 p/r, you will get 4000 * 4 / 60 = 266 Hz. So you will get 26 p/ (1/10)s, which is RadidDataPeriod. For 1000 RPM you get only 8 p This means that your data is bit late, since you average over 30.

As I mentioned it could be better to make 30 value ring buffer and calculate average over those by every measure. With clever logic you can skip error values and if RPM changes quickly from 800 to 2500, you can reset average ring.

Do not convert frequency to int int int_frequency = (int)frequency; SetN2kEngineParamRapid takes frequency as double in RPM. I think FreqMeasure give it as Hz, which you then need to multiply by 60 to get RPM.

timolappalainen:
If I read it right, it does not work. On FreqCount() you have double sum; and int count; These are local and will be reset on every new call. Instead you can use

 static double sum = 0;

static int count = 0;



Then those will be static inside function. Better would be make own class for measure.

The average may work, but it restarts over after 30 measure. Now if you have max RPM 4000 and you get 4 p/r, you will get 4000 * 4 / 60 = 266 Hz. So you will get 26 p/ (1/10)s, which is RadidDataPeriod. For 1000 RPM you get only 8 p This means that your data is bit late, since you average over 30.

As I mentioned it could be better to make 30 value ring buffer and calculate average over those by every measure. With clever logic you can skip error values and if RPM changes quickly from 800 to 2500, you can reset average ring.

Do not convert frequency to int 


int int_frequency = (int)frequency;



SetN2kEngineParamRapid takes frequency as double in RPM. I think FreqMeasure give it as Hz, which you then need to multiply by 60 to get RPM.

double sum & int count should be global according to the original example. I placed it wrong.
The conversion to int of frequency I did because there is float used for frequency and since floating point rpm is not used in nmea2k for rpm I thought it is better to convert it to int to et the floating point away. Good if not necessary.

About the ring buffer (circular buffer), yes, I did research on that for a couple of days even on youtube but could not find a good example which I could adapt to this I need. I think as well this would be the best solution, I just don`t know how?!

about the rapid data period, I just didn`t think so far and copied this from one of your examples thinking this is a static value and can not be changed.

Now if you have max RPM 4000 and you get 4 p/r, you will get 4000 * 4 / 60 = 266 Hz.

The 266 Hz is "just" the frequency I need to output to get the result shown on the external gauge.
So for the NMEA2K sending the 1p/RPM is relevant as the engine delivers 1p per RPM. So this will be then about 16,67 Hz. how many readings of the frequency shall I now take per second, at 600RPM (expected minimum) 10 times as 600rpm are 10 Hz?

I have found now a ring buffer library which I understand and can use for this project.

This is an overview of the period times and the frequency:

RPM Frequency PeriodTime
600 10,00 Hz 100000 µs
1000 16,67 Hz 60000 µs
2000 33,33 Hz 30000 µs
3000 50,00 Hz 20000 µs
4000 66,67 Hz 15000 µs
5000 83,33 Hz 12000 µs
6000 100,00 Hz 10000 µs

I will start over again, i stopped counting how often now! :slight_smile:

THX for all your time Timo!

Since FreqCount is one function calculating frequency, it is best to keep variables as its own. So leave them inside function:
static double sum = 0;
static int count = 0;

Then I do not understand your meaning "So for the NMEA2K sending the 1p/RPM is relevant..". RPM is value for revolutions per minute. And NMEA 2000 library takes double for RPM. NMEA 2000 has internally 0.25 RPM accuracy. So you can provide double. If youconvert it int, then compiler will do internally new conversion from int to double anyway, since function parameter has been defined to be double. And you should not provide values in Hz. As function description on N2kMessages.h says: EngineSpeed RPM (Revolutions Per Minute). So if you like to show 1200 RPM on MDF gauge, you have to provide value 1200.

timolappalainen:
Since FreqCount is one function calculating frequency, it is best to keep variables as its own. So leave them inside function:
static double sum = 0;
static int count = 0;

Then I do not understand your meaning "So for the NMEA2K sending the 1p/RPM is relevant..". RPM is value for revolutions per minute. And NMEA 2000 library takes double for RPM. NMEA 2000 has internally 0.25 RPM accuracy. So you can provide double. If youconvert it int, then compiler will do internally new conversion from int to double anyway, since function parameter has been defined to be double. And you should not provide values in Hz. As function description on N2kMessages.h says: EngineSpeed RPM (Revolutions Per Minute). So if you like to show 1200 RPM on MDF gauge, you have to provide value 1200.

Then I do not understand your meaning "So for the NMEA2K sending the 1p/RPM is relevant..". RPM is value for revolutions per minute. And NMEA 2000 library takes double for RPM. NMEA 2000 has internally 0.25 RPM accuracy.

I do multiply the rpm by 4 dou to the external analog gauge I have. That one needs 4 pulses per RPM. That`s why I said "for NMEA2K is 1p/RPM relevant", not the multiplied by 4 value.

I used now another Arduino UNO to simulate the pulses. It is generating a PWM between 10 Hz and 100 Hz.
I get values shown with the ring buffer, but not the right one. Also, there is a huge delay between the frequency change of the input and the result getting shown from this code with ring buffer.

Can this be due to the 50% cycle why the result is completely different from the input signal? I will test all that out practically now and hopefully come to a usable result. Ring Buffer (circular buffer library) I do understand now and could use it. Wasn`t that complicated after I tried it practically.

Hi Timo

I would appreciate a little bit of guidance please.

I am trying to set up a single Teensy 3.2 (on an SK-Pang canbus breakout board) to read two different temperatures from my engine room and put the data onto the N2K newtork. One sensor is a onewire DS18B20, the other is a themocouple on a Max 31855. Individually I can get them both to compile and work, but I am struggling to combine them both into one piece of Arduino code. When compiling I get an error message at the start of void loop:

'SendN2kTemperature' was not declared in this scope

Here is my code:

// NMEA2000 Send temperature to the N2K bus using onewore and thermocouple.

#include <Arduino.h>
#include <NMEA2000_CAN.h>  // This will automatically choose right CAN library and create suitable NMEA2000 object
#include <N2kMessages.h>
#include <NMEA2000_teensy.h>
#include <SPI.h>
#include "Adafruit_MAX31855.h"
#include <OneWire.h>
#include <DallasTemperature.h>

// Create a thermocouple instance with hardware SPI on a given CS pin.
#define MAXCS   10
Adafruit_MAX31855 thermocouple(MAXCS);

// Create a one-wire connection on a given pin
#define ONE_WIRE_BUS 9  //Sets pin 9 for data
OneWire oneWire(ONE_WIRE_BUS);  //set up instance
DallasTemperature sensors(&oneWire);  //pass onewire ref to dallas

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

void setup() {
  
  //delay(5000);
  // Set Product information
  NMEA2000.SetProductInformation("000200", // Manufacturer's Model serial code
                                 100, // Manufacturer's product code
                                 "Arduino ER temp",  // Manufacturer's Model ID
                                 "1.1 (2018)",  // Manufacturer's Software version code
                                 "1.A (2018)" // Manufacturer's Model version
                                 );
  // Det device information
  NMEA2000.SetDeviceInformation(202020, // Unique number. Use e.g. Serial number.
                                130, // Device function=Temperature. See codes on http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
                                75, // Device class=Sensor Communication Interface. See codes on  http://www.nmea.org/Assets/20120726%20nmea%202000%20class%20&%20function%20codes%20v%202.00.pdf
                                2046 // 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                           
  // Serial.begin(115200);
  // NMEA2000.SetForwardStream(&Serial);
  
  // 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. 
  
  //NMEA2000.SetForwardOwnMessages(); //was in 2016 example but not 2018 so now commented out

  // If you also want to see all traffic on the bus use N2km_ListenAndNode instead of N2km_NodeOnly below
  NMEA2000.SetMode(tNMEA2000::N2km_NodeOnly,22);
  
  //NMEA2000.SetDebugMode(tNMEA2000::dm_Actisense); // Uncomment this, so you can test code without CAN bus chips on Arduino Mega
  NMEA2000.EnableForward(false); // Disable all forward messaging to USB (=Serial)
  
  NMEA2000.ExtendTransmitMessages(TransmitMessages);
  NMEA2000.Open();

  sensors.begin();   //Dallas/onewire command
}


void loop() {
  SendN2kTemperature();
  NMEA2000.ParseMessages();
}
  
double ReadDriveTemp() { //N2K command
  return CToKelvin(thermocouple.readCelsius());   //N2K command incorporating Max31855 temp(c)

double ReadEngRmTemp() { //N2K command
  return CToKelvin(sensors.getTempCByIndex(0));   //N2K command incorporating onewire sensors command
}

#define TempUpdatePeriod 3000

void SendN2kTemperature() {
  static unsigned long TempUpdated=millis();
  tN2kMsg N2kMsg;

  if ( TempUpdated+TempUpdatePeriod<millis() ) {
    TempUpdated=millis();
    SetN2kTemperature(N2kMsg, 1, 1, N2kts_EngineRoomTemperature, ReadDriveTemp());
    NMEA2000.SendMsg(N2kMsg);
    SetN2kEnvironmentalParameters(N2kMsg, 1, N2kts_EngineRoomTemperature, ReadDriveTemp());
    NMEA2000.SendMsg(N2kMsg);
    
    //SetN2kTemperature(N2kMsg, 1, 2, N2kts_EngineRoomTemperature, ReadEngRmTemp());
    //NMEA2000.SendMsg(N2kMsg);
    SetN2kEnvironmentalParameters(N2kMsg, 1, N2kts_EngineRoomTemperature, ReadEngRmTemp());
    NMEA2000.SendMsg(N2kMsg);
    // Serial.print(millis()); Serial.println(", Temperature send ready");
       
  }
}

Presumably I also somehow need to create two instances for the engine room temps. Might this also be the cause of the compile error?
Thanks, Keith

You have missing } on function ReadDriveTemp:

double ReadDriveTemp() { //N2K command
  return CToKelvin(thermocouple.readCelsius());   //N2K command incorporating Max31855 temp(c)
} // <--- this is missing

double ReadEngRmTemp() { //N2K command
...