Go Down

Topic: NMEA 2000 Shield (Read 398259 times) previous topic - next topic

autopilotNOR

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:

Code: [Select]
// 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);
  }
}

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
Code: [Select]
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
Code: [Select]
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.

autopilotNOR

#512
Aug 25, 2018, 10:27 am Last Edit: Aug 25, 2018, 11:53 am by autopilotNOR
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
Code: [Select]
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
Code: [Select]
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.
Quote
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! :-)

THX for all your time Timo!

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.

autopilotNOR

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.

Quote
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.

keith-i

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:
 
Quote
'SendN2kTemperature' was not declared in this scope
Here is my code:

Code: [Select]
// 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

timolappalainen

You have missing } on function ReadDriveTemp:
Code: [Select]
double ReadDriveTemp() { //N2K command
  return CToKelvin(thermocouple.readCelsius());   //N2K command incorporating Max31855 temp(c)
} // <--- this is missing

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

keith-i

Thank you Timo. Sorry for asking such a basic question!

timolappalainen

Missing wrong positioned } or ; causes sometime very strange errors. This is example, that when everyhing seems right, you have to start comparing {} pairs.

keith-i

#519
Aug 27, 2018, 08:00 pm Last Edit: Aug 27, 2018, 08:01 pm by keith-i
I'd like to understand whether I've got the loop part of my code correct. I've used the temperature example and tried to adjust it to suit my device; the difference being that my second temperature is not an 'outside' environmental parameter, it is another engine room temperature just from a different sensor.

What I don't quite understand is how the network will 'see' the two temperatures. Could you explain what the first and second lines of code below do.

Do I need to change the second parameter in the final line of code from a '1' to '2' to imply a second instance? Or how else will the MFD know to show two different temperatures?

Code: [Select]
SetN2kTemperature(N2kMsg, 1, 1, N2kts_EngineRoomTemperature, ReadDriveTemp());
    NMEA2000.SendMsg(N2kMsg);
   
    SetN2kEnvironmentalParameters(N2kMsg, 1, N2kts_EngineRoomTemperature, ReadDriveTemp());
    NMEA2000.SendMsg(N2kMsg);
   
    SetN2kEnvironmentalParameters(N2kMsg, 1, N2kts_EngineRoomTemperature, ReadEngRmTemp());
    NMEA2000.SendMsg(N2kMsg);


Thanks again.

timolappalainen

It can not. SetN2kEnvironmentalParameters = SetN2kPGN130311, which is obsolete PGN. Better is SetN2kTemperature = SetN2kPGN130312. It has instance parameter TempInstance, which you can set different. Unfortunately PGN 130312 is also obsolete. The newest one is SetN2kTemperatureExt = SetN2kPGN130316. Unfortunately that is supported only on new MFD:s, so you may anyway need to use SetN2kTemperature. In example I just send different PGNs for testing. In real solutions I use either SetN2kTemperature or SetN2kTemperatureExt depending od MFD support.

If your device sends several temperatures, it is important to have different instance number for each of them even they have different TempSource. So if you send 2 engine room temperatures and one cabing, you have to use
    SetN2kTemperature(N2kMsg, 1, 1, N2kts_EngineRoomTemperature, ReadDriveTemp());
    NMEA2000.SendMsg(N2kMsg);
   
    SetN2kTemperature(N2kMsg, 1, 2, N2kts_EngineRoomTemperature, ReadEngRmTemp());
    NMEA2000.SendMsg(N2kMsg);
   
    SetN2kTemperature(N2kMsg, 1, 3, N2kts_MainCabinTemperature, ReadCabinTemp());
    NMEA2000.SendMsg(N2kMsg);

keith-i

Brilliant, thanks so much for explaining. I think I'm starting to get the hang of it now.

autopilotNOR

Hei Timo,

Damn Fuel rate...
I just realized that my engine has to fuel pipes one for delivery and a return line. If I really want to measure the fuel rate than I would need to subtract the returning fuel from the delivering value.

Getting expensive now since the new flow rate sensor costs already 100 €.

Have you been in this scenario before? What flow sensor are you using?

Regards!

timolappalainen

I wrote you earlier that I have two sensors. And unfortunately good sensors are expensive. Mine costs about 100 € each. Also you may have same problem with pulsating diesel pump, which causes that dies flow is not static. I also mentioned that and to prevent that problem you will need dampers to both line - exmple link was somewhere.

autopilotNOR

I wrote you earlier that I have two sensors. And unfortunately good sensors are expensive. Mine costs about 100 € each. Also you may have same problem with pulsating diesel pump, which causes that dies flow is not static. I also mentioned that and to prevent that problem you will need dampers to both line - exmple link was somewhere.
Hey Timo,

I know that you wrote that and I am aware of this details. My question was which sensors you are using.
I just forgot that I have a return line. I am still not in that state to test the stuff inside my boat.

Regards..

Go Up