Go Down

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

RadApl

Hello Timo,
Is there plan to support Teensy 4.0 https://www.pjrc.com/store/teensy40.html? I try to compile small examples with no success, even with adding pins definition in FlexCan library for new chip.

timolappalainen

Yes, but I not got my Teensy 4 yet. I ordered 2, but got a Teensy 3.6 in package. Took month to get those, will take month or two, before I can test.

keith-i

Hi Timo

With the success of my first device based on your fantastic code I'm now starting on something a bit more challenging.

My engine transmits data to the dashboard gauges via can bus so I intend to use a Teensy 3.6 to extract some of the data such as rpm, temperature, boost etc and put this onto the N2K network.

I have successfully identified the can bus messages and data bytes of interest but am struggling to understand how FlexCAN should be set up with filters and masks to select this data. As you obviously have very good knowledge of FlexCAN is there any chance you could give me some pointers please.

I am only interested in messages with ID 0x280, 0x288, 0x380 and 0x588 (they are all 11 bit standard ID). Should I be setting up 4 individual mailboxes with filters or is there another way? From my research if I use mailboxes I need a default mask of 0x7FF and then each mailboxes will have a filter of 0x280, 0x288 etc. Is this correct?

If this is the way to do it, how do I then get the data bytes of interest out of the mailboxes? Does the data automatically get put into buffers?

Any way you can help would be appreciated as there are so few examples on the net that make sense to me.

Keith

timolappalainen

I do not see that there is any reason to setu FlexCAN filtering. You could just catch all messages and filter them on sw - Teensy 3.6 has enough and lot more power to do that. Also if you use other CAN on Teensy 3.6 just for connecting it to engine, it will not have much messages on that bus. I have been testing Teensy 3.6 on bus, where I have over 100 frames/s on the bus and it still do filtering and graphics at same time.

Take a look for NMEA2000_Teensy module and create new module for handling standard CAN messages with interrupts. Then just poll the buffer like on NMEA2000 library and filter messages you are interested.

keith-i

Thanks Timo, that sounds like a sensible way forward. There's a lot I don't understand yet so I might be back!

keith-i

Then just poll the buffer like on NMEA2000 library and filter messages you are interested.
Humour me please: I'm still very much a noob and need some high level pointers :)

Presumably the frames from the engine canbus are being put into buffers within an array as they arrive.

Presumably your NMEA code sends out its packets to the N2K network at preset intervals. Faster for some things like Engine Rapid PGN's I suppose and slower for Temperature PGN's.

Presumably I need to get your N2K code to look for the correct frame ID (e.g. 0x288) in the array and then extract the data I'm interested in (e.g. byte 1 for coolant temp).

How do I do this last part? Is it some form of If ID=0x288 then get the data from buf[2] or some such into a variable.

Your help, as ever, is appreciated.

kenobiben

Hi Timo, thanks for your hard work.
I would like to make an arduino project who's light a lamp when it see on the nmea2000 bus the alarm msg PGN126983.
Do you think I can use your library for that? and could you suggest me how?
Thanks
Bye
Benoit :)

timolappalainen

keith-i,

The simplest way is just poll other CAN on Teensy 3.6. It would be something like this:

Code: [Select]
void PollEngineCAN() {
  CAN_message_t Msg;

  if ( Can1.available()>0 ) {
    Can1.read(Msg);
    if ( Msg.flags.extended==0 ) { // Standard message
      //Filter and parse message here to global data EngineRPM, EngineOilPressure, etc.
    }
  }
}

void SendN2kEngineRapid() {
  static unsigned int NextSendTime=5000;

  if ( NextSendTime<millis() ) {
    NextSendTime+=100;
    tN2kMsg N2kMsg;
   
    SetN2kEngineParamRapid(N2kMsg,0,EngineRPM);
    NMEA2000.SendMsg(N2kMsg);
  }
}

void SendN2kEngineDynamic() {
  static unsigned int NextSendTime=5000;

  if ( NextSendTime<millis() ) {
    NextSendTime+=500;
    tN2kMsg N2kMsg;
   
    SetN2kEngineDynamicParam(N2kMsg,0,EngineOilPressure,EngineOilTemp,EngineCoolantTemp,
                                               N2kDoubleNA,N2kDoubleNA,N2kDoubleNA);
    NMEA2000.SendMsg(N2kMsg);
  }
}

void loop() {
  NMEA2000.ParseMessages();
  PollEngineCAN();
  SendN2kEngineRapid();
  SendN2kEngineDynamic();
}


Note that you should provide NA value for each data, which is not available. Also you should take care that if your Engine CAN will not send some data anymore, you should update that data value to NA after some timeout. This can be easily done by using class for values.

timolappalainen

kenobiben,

Yes you can use library to handle PGN 126983 alert messages as any NMEA 2000 message. In N2kMessages module there are set and parser functions for most common messages. So for PGN 126983 you can write your own parser function. Then you make handler functions like in DataDisplay example.

The one problem is that I do not have field description for PGN 126983 or currently any device sending that to do reverse engineering.

keith-i

Thanks Timo that's very helpful to me.

kenobiben

Thanks Timo, I will try and tell you

keith-i

#806
Oct 12, 2019, 10:52 am Last Edit: Oct 12, 2019, 10:20 pm by keith-i
Hi Timo

I've set up a bit of test code to send some dummy data to the N2k network and have put N2kDoubleNA and N2kInt8NA as appropriate in the unused fields of PGN 127489 (engine dynamic).

However when I look at the NMEA guide for 127489 it shows 14 data fields whereas your messages.h file only seems to refer to 12 fields. When I check everything on Actisense reader it puts odd data into fields 13 and 14 which are engine load and torque. Does your messages.h file perhaps need some more data fields adding to comply? I might be misunderstanding how the engine status flags are set as these appear to me to be the missing fields.

Keith

Edit: I was trying to be clever and putting fields in the order of the NMEA PGN rather than your N2kMessages.h file. I would still be keen to know how you set engine flags though.

keith-i

Ok, well here's my attempt at coding this ECU to N2k gateway. I'd appreciate any comments you might have about it, particuarly the variable declarations and calculations on the data bytes as they are the most likely sources of any mistakes. I'm waiting on delivery of a Teensy 3.6 to be able to try it for real.

It would also be good to have some ideas on how to set a timeout such that the N2k fields get populated with NA if the engine is not on.

Code: [Select]

// Convert VW Marine ECU canbus data to N2k. Oct 2019
// based on Collin80 and Timo Lappalainen code

#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 <FlexCAN.h>


#ifndef __MK66FX1M0__
  #error "Teensy 3.6 with dual CAN bus is required to run this example"
#endif

// List below N2k messages your device will transmit.
const unsigned long TransmitMessages[] PROGMEM={127488L,127489,0}; //engine rapid and dynamic

static CAN_message_t msg; //sets up a storage buffer?
static uint8_t hex[17] = "0123456789abcdef";
 
//-----------------------------------------------------------------------------------------
void setup() {
 
  delay(5000);
  // Set Product information
  NMEA2000.SetProductInformation("000300", // Manufacturer's Model serial code
                                 200, // Manufacturer's product code
                                 "Arduino VW Gateway",  // Manufacturer's Model ID
                                 "1.1 (2019)",  // Manufacturer's Software version code
                                 "1.A (2019)" // Manufacturer's Model version
                                 );
  // Det device information
  NMEA2000.SetDeviceInformation(303030, // Unique number. Use e.g. Serial number.
                                160, // Device function=engine gateway.
                                50, // Device class=propulsion.
                                443 // Just choosen free from code list - VDO                               
                               );
 
  // Uncomment 2 rows below to see, what device will send to bus. Use e.g. OpenSkipper or Actisense NMEA Reader                           
  //Serial.begin(115200); // uncomment to send to serial for PC use
  //NMEA2000.SetForwardStream(&Serial); // uncomment to send to serial - default is Actisense format for PC use

  // If you want to use simple ascii monitor like Arduino Serial Monitor, uncomment next line
  //NMEA2000.SetForwardType(tNMEA2000::fwdt_Text); // uncomment to send to serial as plain text - overides above 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,22);
 
  //NMEA2000.SetDebugMode(tNMEA2000::dm_Actisense); // Uncomment this, so you can test code without CAN bus chips on Arduino Mega
  // Comment out next line for Actisense PC use
  NMEA2000.EnableForward(false); // Disable all forward messaging from N2K bus to USB (=Serial) - default is True. Comment for PC use
 
  NMEA2000.ExtendTransmitMessages(TransmitMessages);
  NMEA2000.Open();

  //Can0 should be automatically set at default 250kbs for N2k
  Can1.begin(500000); //set baud for VW ECU.
  Can1.setListenOnly (true); //hopefully set listen only mode. Set after begin()
  }

  /*
   * 0x280 byte 1 = torque (*0.39)%
   * 0x280 byte 2low&3high = rpm (*0.25)/min
   * 0x288 byte 1 = coolant ((*0.75)-48)C
   * 0x380 byte 1 = IAT ((*0.75)-48)C
   * 0x480 byte 2&3 = fuel rate ul
   * 0x588 byte 4 = boost (*0.01)bar
   */
     
  //maybe use int not uint8_t
  uint8_t torque = 0; //torque %
  uint16_t rpm = 0; //rpm
  uint8_t coolant = 0; //coolant temp in degC
  uint16_t fuel = 0;  //fuel rate in ul
  uint8_t boost = 0; //boost pressure in Pa. 1bar = 100,000Pa

void PollEngineCAN()
  {
  CAN_message_t inMsg;
   
    if (Can1.available()>0) {//or maybe use while?
      Can1.read(inMsg); //read message into inMsg
      //filter and parse message here to global data
      if (inMsg.id == 0x280){
        torque = (inMsg.buf[1]*0.39);
        rpm = (inMsg.buf[3]<<8)+inMsg.buf[2];}
      if (inMsg.id == 0x288){
        coolant = ((inMsg.buf[1]*0.75)-48);}
//      if (inMsg.id == 0x480){
//        fuel = (((inMsg.buf[4]&0b01111111)<<8)+inMsg.buf[3]);}
      if (inMsg.id == 0x588){
        boost = (inMsg.buf[4]*0.01);}
    }
  }
//might want a timeout to put NA if engine is off

void SendN2kEngineRapid()
  {
  static unsigned int NextSendTime=5000;
  if ( NextSendTime<millis() )
    {
    NextSendTime+=100;
    tN2kMsg N2kMsg;
 
    SetN2kEngineParamRapid(N2kMsg, 0, rpm, boost, N2kInt8NA);
    NMEA2000.SendMsg(N2kMsg);
    }
  }

void SendN2kEngineDynamic()
  {
  static unsigned int NextSendTime=5000;
  if ( NextSendTime<millis() )
    {
    NextSendTime+=500;
    tN2kMsg N2kMsg;
 
    SetN2kEngineDynamicParam(N2kMsg, 0, N2kDoubleNA, N2kDoubleNA, coolant,
    N2kDoubleNA, fuel, N2kDoubleNA, N2kDoubleNA, N2kDoubleNA, N2kInt8NA, torque);
    NMEA2000.SendMsg(N2kMsg);
    }
  }
//-----------------------------------------------------------------------------------------
void loop()
 
{
  NMEA2000.ParseMessages();
  PollEngineCAN();
  SendN2kEngineRapid();
  SendN2kEngineDynamic();
  }

timolappalainen

Some things.
- When you calculate values, with real coefficient, you can not use int. You have to use float or double. With Teensy 3.6 I use just double, since it seem to be fast enough.

- In SetN2kEngineParamRapid you do not need to set EngineTiltTrim, since that has been set to NA as default.

- Your PollEngineCAN now test every time all msg id:s. You can either use if () { } else if () {}... way or in your case when you test against constant you can use switch, which is faster than if-else

so
Code: [Select]

  double torque = N2kDoubleNA; //torque %
  double rpm = N2kDoubleNA; //rpm
  double coolant = N2kDoubleNA; //coolant temp in degC
  double fuel = N2kDoubleNA;  //fuel rate in ul
  double boost = N2kDoubleNA; //boost pressure in Pa. 1bar = 100,000Pa
  unsigned long LastRPM=0;

void PollEngineCAN() {
  CAN_message_t inMsg;
   
    if (Can1.available()>0) {//or maybe use while?
      Can1.read(inMsg); //read message into inMsg
      //filter and parse message here to global data
      switch ( inMsg.id ) {
        case 0x280:
          torque = ( inMsg.buf[1]*0.39 );
          rpm = ( inMsg.buf[3]<<8)+inMsg.buf[2];
          LastRPM=millis();
          break;
        case 0x288:
          coolant = ( (inMsg.buf[1]*0.75)-48);
          break;
//        case 0x480:
//        fuel = (((inMsg.buf[4]&0b01111111)<<8)+inMsg.buf[3]);
//        break;
        case 0x588:
          boost = ( inMsg.buf[4]*0.01 );
          break;
      }
    }
  }


Note that I added LastRPM variable, which you can then use on sending:

Code: [Select]
#define EngineTimeout 5000

void loop() {
  NMEA2000.ParseMessages();
  PollEngineCAN();
  if ( LastRPM+EngineTimeout > millis() ) {
    SendN2kEngineRapid();
    SendN2kEngineDynamic();
  }
}


Other way is to set values to NA on polling:
Code: [Select]
void PollEngineCAN() {
    if (Can1.available()>0) {//or maybe use while?
      ...
    }
    if ( LastRPM+EngineTimeout < millis() ) {
      torque = N2kDoubleNA;
      rpm = N2kDoubleNA;
      coolant = N2kDoubleNA;
      fuel = N2kDoubleNA;
      boost = N2kDoubleNA;
    }
}



You can also have own timeout counter for each message.

Setting NA is a bit how you would it like to work. I have system, which starts to alarm, if value drops to NA for RPM.

keith-i

Thanks Timo. Once again your detailed help is very much appreciated.

Go Up