NMEA2000 icon indicating copy to clipboard operation
NMEA2000 copied to clipboard

Program hangs after a while on crowded N2K bus

Open eriksenfredrik opened this issue 6 years ago • 6 comments

Thanks for a great library. This has helped us a lot in prototyping stages and still is.

We have an issue where the program hangs and becomes unresponsive after a while on a crowded bus. We have an idle state on the bus where the amount of messages are limited and then after activation of a switch the data increased and this seems to trigger some kind of hang state in the arduino.

Board is Mega2560 Rev3, using CAN-BUS Shield from Sparkfun. Example code is shown below for reference.

Any way we can make the chip work less on incoming PGNs to avoid this? Or is it something that we are overlooking?

`// Demo: NMEA2000 library. Read some data and their show values. // See also DataDisplay2.ino, which handles more data.

// #define USE_DUE_CAN 1 #define N2K_SOURCE 15 #define N2k_CAN_INT_PIN 21

#include <Arduino.h> #include <NMEA2000_CAN.h> // This will automatically choose right CAN library and create suitable NMEA2000 object #include <N2kMsg.h> #include <NMEA2000.h> #include <N2kMessages.h>

#include <avdweb_VirtualDelay.h>

#define ACCRELAY 2 #define IGNRELAY 3 #define CRANKRELAY 4

#define STATE_ALL_OFF 0 #define STATE_ACC 1 #define STATE_IGN 2 #define STATE_CRANK 3 #define STATE_CRANK_ALL 4 #define STATE_STOP_ALL 5 #define ERR_NOT_IN_IGN_OR_ACC 10 #define UNKNOWN_COMMAND 11

#define MYADDRESS 0xF2 #define BROADCAST 0xFE

unsigned char currentState = STATE_ALL_OFF; unsigned char myAddress = MYADDRESS; unsigned char State = 0;

//Prepare TX message tN2kMsg N2kMsgTX; tN2kMsg N2kMsgTX2;

VirtualDelay delay1; VirtualDelay delay2; VirtualDelay delay3; VirtualDelay delay4; VirtualDelay delay5;

typedef struct { unsigned long PGN; void (*Handler)(const tN2kMsg &N2kMsg); } tNMEA2000Handler;

void IgnitionControl(const tN2kMsg &N2kMsg);

tNMEA2000Handler NMEA2000Handlers[]={ {65522L,&IgnitionControl}, {0,0} };

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

Stream *OutputStream;

void setup() {

//Setup relays pinMode(ACCRELAY, OUTPUT); pinMode(IGNRELAY, OUTPUT); pinMode(CRANKRELAY, OUTPUT);

digitalWrite(ACCRELAY, HIGH); digitalWrite(IGNRELAY, HIGH); digitalWrite(CRANKRELAY, HIGH);

Serial.begin(115200); OutputStream=&Serial; // Set Product information NMEA2000.SetProductInformation("00000001", // Manufacturer's Model serial code 100, // Manufacturer's product code "", // Manufacturer's Model ID "1.0.0.0 (2019-01-20)", // Manufacturer's Software version code "1.0.0.0 (2019-01-20)" // Manufacturer's Model version ); // Set device information NMEA2000.SetDeviceInformation(112231, // 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 2040 // Just choosen free from code list on http://www.nmea.org/Assets/20121020%20nmea%202000%20registration%20list.pdf
);

NMEA2000.SetForwardType(tNMEA2000::fwdt_Text); NMEA2000.SetForwardStream(OutputStream); NMEA2000.SetMode(tNMEA2000::N2km_NodeOnly,22); // Set false below, if you do not want to see messages parsed to HEX withing library NMEA2000.EnableForward(true); NMEA2000.SetMsgHandler(HandleNMEA2000Msg); // NMEA2000.SetN2kCANMsgBufSize(2); // Here we tell library, which PGNs we transmit NMEA2000.ExtendTransmitMessages(TransmitMessages); NMEA2000.Open(); OutputStream->print("Ignition Box v1.0\nBoot Complete\nNMEA OK\n"); }

//*****************************************************************************

void IgnitionControl(const tN2kMsg &N2kMsg) { //Variables for receiving unsigned char SID; unsigned char Address; unsigned char Command;

if (ParseN2kPGN65522(N2kMsg,SID,Address,Command) ) {
  OutputStream->print("Ignition command received!\n");
  OutputStream->print(SID);
  OutputStream->print("\n");
  OutputStream->print(Address);
  OutputStream->print("\n");
  OutputStream->print(Command);
  OutputStream->print("\n");

  if (Address == MYADDRESS) {
    OutputStream->print("Address match!\n");
    switch (Command) {
      case STATE_ACC:
        digitalWrite(ACCRELAY, LOW);
        digitalWrite(IGNRELAY, HIGH);
        digitalWrite(CRANKRELAY, HIGH);
        currentState = STATE_ACC;
        State = STATE_ACC;
        OutputStream->print("STATE = ACC\n");
        break;
      case STATE_IGN:
        digitalWrite(ACCRELAY, LOW);
        digitalWrite(IGNRELAY, LOW);
        digitalWrite(CRANKRELAY, HIGH);
        currentState = STATE_IGN;
        State = STATE_IGN;
        OutputStream->print("STATE = IGN\n");
        break;
      case STATE_CRANK:
        if (currentState == STATE_IGN) {
          State = STATE_CRANK;
          OutputStream->print("STATE = CRANKING\n");
          digitalWrite(CRANKRELAY, LOW);
          delay1.start(1000);
          break;
        } else {
          OutputStream->print("ERROR: Trying to crank from wrong state:");
          OutputStream->print(State);
          State = ERR_NOT_IN_IGN_OR_ACC;
          break;
        }
      case STATE_ALL_OFF:
        digitalWrite(ACCRELAY, HIGH);
        digitalWrite(IGNRELAY, HIGH);
        digitalWrite(CRANKRELAY, HIGH);
        State = STATE_ALL_OFF;
        currentState = STATE_ALL_OFF;
        OutputStream->print("STATE = ALL OFF\n");
        break;
      default:
        State = UNKNOWN_COMMAND;
        OutputStream->print("UNKNOWN COMMAND\n");
        break;
    }
  } else if (Address == BROADCAST) {
    //Address for all units
      switch (Command) {
      case STATE_ACC:
        digitalWrite(ACCRELAY, LOW);
        digitalWrite(IGNRELAY, HIGH);
        digitalWrite(CRANKRELAY, HIGH);
        currentState = STATE_ACC;
        State = STATE_ACC;
        OutputStream->print("STATE = ACC\n");
        break;
      case STATE_IGN:
        digitalWrite(ACCRELAY, LOW);
        digitalWrite(IGNRELAY, LOW);
        digitalWrite(CRANKRELAY, HIGH);
        currentState = STATE_IGN;
        State = STATE_IGN;
        OutputStream->print("STATE = IGN\n");
        break;
      case STATE_CRANK_ALL:
        if (currentState == STATE_IGN) {
          State = STATE_CRANK;
          OutputStream->print("STATE = IGN, Waiting for crank\n");
          if(MYADDRESS == 0xF2) {
            delay2.start(2000);
            break;
          }
          OutputStream->print("STATE = CRANKING\n");
          SetN2kPGN65521(N2kMsgTX, 1, myAddress, State);
          NMEA2000.SendMsg(N2kMsgTX);
          digitalWrite(CRANKRELAY, LOW);
          delay3.start(2000);
          break;
        } else {
          OutputStream->print("ERROR: Trying to crank from wrong state:");
          OutputStream->print(State);
          State = ERR_NOT_IN_IGN_OR_ACC;
          break;
        }
      case STATE_STOP_ALL:
      if (currentState == STATE_IGN) {
        State = STATE_CRANK;
        OutputStream->print("STATE = IGN, Stopping engine\n");
        OutputStream->print("STATE = Stopping\n");
        SetN2kPGN65521(N2kMsgTX, 1, myAddress, State);
        NMEA2000.SendMsg(N2kMsgTX);
        digitalWrite(CRANKRELAY, LOW);
        delay5.start(2000);
        break;
      } else {
        OutputStream->print("ERROR: Trying to stop from wrong state:");
        OutputStream->print(State);
        State = ERR_NOT_IN_IGN_OR_ACC;
        break;
      }
      case STATE_ALL_OFF:
        digitalWrite(ACCRELAY, HIGH);
        digitalWrite(IGNRELAY, HIGH);
        digitalWrite(CRANKRELAY, HIGH);
        State = STATE_ALL_OFF;
        currentState = STATE_ALL_OFF;
        OutputStream->print("STATE = ALL OFF\n");
        break;
      default:
        State = UNKNOWN_COMMAND;
        OutputStream->print("UNKNOWN COMMAND\n");
      break;
  }
      OutputStream->print(MYADDRESS);
      OutputStream->print(State);
      SetN2kPGN65521(N2kMsgTX, 1, myAddress, State);
      NMEA2000.SendMsg(N2kMsgTX);
      OutputStream->print("Ignition state sent!\n");
  }
} else {
  OutputStream->print("Failed to parse PGN: "); OutputStream->println(N2kMsg.PGN);
}

}

//***************************************************************************** //NMEA 2000 message handler void HandleNMEA2000Msg(const tN2kMsg &N2kMsg) { int iHandler;

// Find handler if(N2kMsg.PGN == 65522) { OutputStream->print("In Main Handler: "); OutputStream->println(N2kMsg.PGN); }

for (iHandler=0; NMEA2000Handlers[iHandler].PGN!=0 && !(N2kMsg.PGN==NMEA2000Handlers[iHandler].PGN); iHandler++);

if (NMEA2000Handlers[iHandler].PGN!=0) { NMEA2000Handlers[iHandler].Handler(N2kMsg); } }

//***************************************************************************** void loop() { if(delay1.elapsed()) { digitalWrite(CRANKRELAY, HIGH); OutputStream->print("CRANK DONE \n STATE = IGN\n"); currentState = STATE_IGN; State = STATE_IGN; } if(delay2.elapsed()) { OutputStream->print("STATE = CRANKING\n"); SetN2kPGN65521(N2kMsgTX, 1, myAddress, State); NMEA2000.SendMsg(N2kMsgTX); digitalWrite(CRANKRELAY, LOW); delay4.start(2000); } if(delay3.elapsed()) { digitalWrite(CRANKRELAY, HIGH); OutputStream->print("CRANK DONE \n STATE = IGN\n"); currentState = STATE_IGN; State = STATE_IGN; } if(delay4.elapsed()) { digitalWrite(CRANKRELAY, HIGH); OutputStream->print("CRANK DONE \n STATE = IGN\n"); currentState = STATE_IGN; State = STATE_IGN; } if(delay5.elapsed()) { digitalWrite(CRANKRELAY, HIGH); OutputStream->print("STOP DONE \n STATE = IGN\n"); currentState = STATE_IGN; State = STATE_IGN; }

NMEA2000.ParseMessages(); } `

eriksenfredrik avatar Feb 06 '19 15:02 eriksenfredrik

I did not totally understood your problem. Do you mean that bus is pretty quiet until you activate some of your device?

I have not tested mega with really full bus. I have test it by filling bus with my NMEA Simulator and it can still handle data.

And one warning. avdweb_VirtualDelay seem to be under GNU. If you are developing anything for selling later, stay out of GNU.

ttlappalainen avatar Feb 07 '19 04:02 ttlappalainen

Thank you for answering. There is another external factor on the machine this module is on, mainly that if the RPM increases on the engine it sends out more data on the CAN Bus and this seems to make the arduino hang after a little while.

We are still debugging but this seems like the most probable cause as we can keep it running for hours on low RPM with less traffic on the CAN Bus.

Do you have any suggestions on how we can lighten the load on incoming PGN processing which is not relevant to us? Settings in the NMEA setup? Or if we should reduce any writing to serial or similar?

We will also be setting up the same code now on Arduino Due to see if the same happens or not.

Any help is greatly appreciated, thank you.

eriksenfredrik avatar Feb 07 '19 09:02 eriksenfredrik

There are default transfer periods for each messages. The RPM message (PGN127488) has default period 100 ms and it should be independent of RPM. It it works in this way, it should not have problem with 2-3 engines. I have been filling bus with several 100 ms messages and Mega does not hang and can still forward data to serial in Actisense format.

Filtering is problematic, since that should be done on chip level. NMEA2000 requires so many common messages, that there is often no more room for user messages to select on filter.

I have many times wrote on this, but again I prefer Teensy 3.2 and up. It has better CAN and processor itself is farter.

ttlappalainen avatar Feb 07 '19 10:02 ttlappalainen

Thank you for your thorough response. Maybe we are mistaken in that the NMEA processing in fact is the error. We will keep debugging and make sure to update here with what we find.

Again, thank you for maintaining and supporting this great library.

eriksenfredrik avatar Feb 07 '19 11:02 eriksenfredrik

We were trying to compile code for the Arduino Due. Did a clean install of arduino and installed the due_can and NMEA2000_due. Upon compiling we get an error regarding the class CANRaw. Seems to relate to the way things are included in the files, or maybe the order?

Error:

`NMEA2000_due\NMEA2000_due.cpp:` In member function 'virtual bool tNMEA2000_due::CANOpen()':
NMEA2000_due\NMEA2000_due.cpp:83:36: error: 'class CANRaw' has no member named 'getNumRxBoxes'
   for (mailbox = 0; mailbox < Can0.getNumRxBoxes()-1; mailbox++) {

Include section in code:

#include <Arduino.h>
#include <due_can.h> //(Tried with and without this include)
#include <NMEA2000_CAN.h>  // This will automatically choose right CAN library and create suitable NMEA2000 object
#include <N2kMsg.h>
#include <NMEA2000.h>
#include <N2kMessages.h>
#include <avdweb_VirtualDelay.h>

eriksenfredrik avatar Feb 07 '19 16:02 eriksenfredrik

Did you download due_can library from my git? It has inline uint8_t getNumRxBoxes() { return getNumMailBoxes()-numTXBoxes; } on line 276

ttlappalainen avatar Feb 07 '19 18:02 ttlappalainen