MaxxEcu CAN-Anzeige im KFZ, Programm friert ein

Hallo, liebe Gemeinde.

Folgendes Setup: Arduino Uno, MCP2515 Can-Modul, SSD1306 OLED 164x32 und ein Drehregler, versorgt von einem StepDown-Wandler 12V->5V.
Eingebaut in meinem KFZ um CAN-Botschaften mitzulesen und einige davon im Display anzuzeigen.
Grundsätzlich funktioniert es, jedoch friert das Programm/der Uno in unregelmäßigen Abständen ein. Das Display zeigt weiterhin einen Wert an, reagiert aber nicht mehr.
Alles auch bei nicht laufendem Motor, nur Zündung. Das Display (I2C) sowie der Drehregler sind ca. 50cm vom Arduino entfernt.
Gibt es "übliche Verdächtige" um das Einfrieren zu unterbinden? Freue mich über jeden Hinweis.
Vielen Dank fürs Lesen.

Roland

Anbei noch mein Code:

#include <Arduino.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <mcp_can.h>

//rotary
#define encoderPinA 2
#define encoderPinB 3
#define encoderSwitch 4
int encoderPos = 0;                // a counter for the dial
unsigned int lastReportedPos = 1;  // change management
static boolean rotating = false;   // debounce management
unsigned long lastButtonPress = 0;
// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;

//display
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE, /* clock=*/SCL, /* data=*/SDA);
char TempStr[20];

//menu
int Modus = 0;
int Abgasmodus = 0;
bool Abgas = false;
int MAXMENU = 8;
int MAXABGAS = 4;

//FahrzeugCAN
long unsigned int rxId;
unsigned char len = 0;
unsigned char rxBuf[8];
long unsigned int canId = 0x000;
MCP_CAN CAN0(10);

float Lambda;
float BoostPressure;
float IntakeTemp;
float CoolantTemp;
int RPM;
float OilPressure;
float TEMP;
float Volt;
float FuelPressure;
int Lambdabox;
int Lambdapeak;
int combined;
int EGT;
int EGT1;
int EGT2;
int EGT3;
int EGT4;

void setup() {
  //rotary
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);
  pinMode(encoderSwitch, INPUT_PULLUP);
  digitalWrite(encoderPinA, HIGH);         // turn on pullup resistors
  digitalWrite(encoderPinB, HIGH);         // turn on pullup resistors
  attachInterrupt(0, doEncoderA, CHANGE);  // encoder pin on interrupt 0 (pin 2)
  attachInterrupt(1, doEncoderB, CHANGE);  // encoder pin on interrupt 1 (pin 3)

  //can bus
  CAN0.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ);  // init can bus : baudrate = 500k
  CAN0.setMode(MCP_NORMAL);
  pinMode(5, INPUT);  //(CAN)

  //display
  u8g2.begin();
  u8g2.setFont(u8g2_font_10x20_tf);   // choose a suitable font
  u8g2.drawRFrame(0, 0, 128, 32, 5);  //Rahmen außen
  u8g2.sendBuffer();

  //Serial.begin(9600);  // output
}

void loop() {

  // CAN auslesen
  if (!digitalRead(5)) {                   // If pin 5 is low, read receive buffer
    CAN0.readMsgBuf(&canId, &len, rxBuf);  // Read data: len = data length, buf = data byte(s)
    //Serial.println(canId);
    if (canId == 1328) {                   //0x530
      //Volt
      combined = ((int)rxBuf[1] << 8) | rxBuf[0];
      Volt = (float)combined / 100;

      //BaroPress
      combined = ((int)rxBuf[3] << 8) | rxBuf[2];
      BoostPressure = (float)combined / 1000;

      //IntakeTemp
      combined = ((int)rxBuf[5] << 8) | rxBuf[4];
      IntakeTemp = (float)combined / 10;

      //CoolantTemp
      combined = ((int)rxBuf[7] << 8) | rxBuf[6];
      CoolantTemp = (float)combined / 10;

    } else if (canId == 1329) {
      //EGT1
      EGT1 = ((int)rxBuf[7] << 8) | rxBuf[6];

    } else if (canId == 1330) {

      EGT2 = ((int)rxBuf[1] << 8) | rxBuf[0];
      EGT3 = ((int)rxBuf[3] << 8) | rxBuf[2];
      EGT4 = ((int)rxBuf[5] << 8) | rxBuf[4];
      EGT = ((int)rxBuf[7] << 8) | rxBuf[6];

    } else if (canId == 1335) {

      combined = ((int)rxBuf[1] << 8) | rxBuf[0];
      FuelPressure = (float)combined / 1000;

      //combined = ((int)rxBuf[7] << 8) | rxBuf[6];
      //BoostPressure = (float)combined / 10;

    } else if (canId == 1312) {

      combined = ((int)rxBuf[7] << 8) | rxBuf[6];
      Lambda = (float)combined / 1000;

    } else if (canId == 1312) {

      RPM = ((int)rxBuf[1] << 8) | rxBuf[0];
    }
  }

  //Rotary handling
  rotating = true;  // reset the debouncer
  if (lastReportedPos != encoderPos) {
//    Serial.print("Abgasmodus:");
//    Serial.println(Abgasmodus);
//    Serial.print("Modus:");
//    Serial.println(Modus);
//    Serial.println(encoderPos);
    lastReportedPos = encoderPos;
  }

  if (Modus == 1) {  //Abgasmodus aktiv und Knopf wird gedrückt
    // Read the button state
    int btnState = digitalRead(encoderSwitch);
    //If we detect LOW signal, button is pressed
    if (btnState == LOW) {
      //if 50ms have passed since last LOW pulse, it means that the
      //button has been pressed, released and pressed again
      if (millis() - lastButtonPress > 50) {
        if (Abgas == false) {
          Abgas = true;
        } else {
          Abgas = false;
          Abgasmodus = 0;
        }
      }
      // Remember last button press event
      lastButtonPress = millis();
    }
  }

  switch (Modus) {
    case 0:  //Batteriespannung
      //Volt = random(11, 14);
      strcpy(TempStr, "Batt: ");
      dtostrf(Volt, 5, 2, &TempStr[strlen(TempStr)]);  //add to end of str
      strcat(TempStr, "V");
      draw(TempStr);
      //Serial.println(TempStr);
      break;

    case 1:  //Abgastemperatur
      switch (Abgasmodus) {
        case 0:  //Fühler vor Turbo
          strcpy(TempStr, "Abgas:");
          dtostrf(EGT, 4, 0, &TempStr[strlen(TempStr)]);  //add to end of str
          strcat(TempStr, "\260C");
          draw(TempStr);
          //Serial.println(TempStr);
          break;

        case 1:  //Zylinder1
          strcpy(TempStr, "EGT1:");
          dtostrf(EGT1, 5, 0, &TempStr[strlen(TempStr)]);  //add to end of str
          strcat(TempStr, "\260C");
          draw(TempStr);
          //Serial.println(TempStr);
          break;

        case 2:  //Zylinder2
          strcpy(TempStr, "EGT2:");
          dtostrf(EGT2, 5, 0, &TempStr[strlen(TempStr)]);  //add to end of str
          strcat(TempStr, "\260C");
          draw(TempStr);
          //Serial.println(TempStr);
          break;

        case 3:  //Zylinder2
          strcpy(TempStr, "EGT3:");
          dtostrf(EGT3, 5, 0, &TempStr[strlen(TempStr)]);  //add to end of str
          strcat(TempStr, "\260C");
          draw(TempStr);
          //Serial.println(TempStr);
          break;

        case 4:  //Zylinder4
          strcpy(TempStr, "EGT4:");
          dtostrf(EGT4, 5, 0, &TempStr[strlen(TempStr)]);  //add to end of str
          strcat(TempStr, "\260C");
          draw(TempStr);
          //Serial.println(TempStr);
          break;
      }

      break;

    case 2:  //Lambda

      u8g2.clearBuffer();                 // clear the internal memory
      u8g2.drawRFrame(0, 0, 128, 32, 5);  //Rahmen außen
      u8g2.drawBox(63, 30, 2, 2);         //Punkt 2x2 Pixel in der Mitte, der Lmabda 1 markiert
      Lambdabox = map(Lambda * 1000, 650, 1455, 5, 119);
      if (Lambdapeak < Lambdabox) {         // if the peak is below the current bar size
        Lambdapeak = Lambdabox;             // move peak to the new max. position (i.e. size of the bar)
      } else if (Lambdapeak > Lambdabox) {  // if the bar is lower than the peak
        Lambdapeak--;                       // slowly move the peak down, one pixel every frame
      }
      u8g2.drawBox(5, 28, Lambdabox, 2);
      u8g2.drawBox(Lambdapeak, 28, 2, 2);
      u8g2.setFont(u8g2_font_10x20_tf);  // choose a suitable font
      strcpy(TempStr, "Lambda:");
      dtostrf(Lambda, 4, 2, &TempStr[strlen(TempStr)]);  //add to end of str
      u8g2.drawStr(5, 22, TempStr);                      // write something to the internal memory
      u8g2.sendBuffer();                                 // transfer internal memory to the display
      //Serial.println(TempStr);
      break;

    case 3:  //BoostPressure

      strcpy(TempStr, "Boost: ");
      dtostrf(BoostPressure, 4, 2, &TempStr[strlen(TempStr)]);  //add to end of str
      // strcat(TempStr, "kpa");
      draw(TempStr);
      //Serial.println(TempStr);
      break;

    case 4:  //IntakeTemp

      strcpy(TempStr, "Luft: ");
      dtostrf(IntakeTemp, 3, 0, &TempStr[strlen(TempStr)]);  //add to end of str
      strcat(TempStr, "\260C");
      draw(TempStr);
      //Serial.println(TempStr);
      break;

    case 5:  //CoolantTemp

      strcpy(TempStr, "Wasser:");
      dtostrf(CoolantTemp, 3, 0, &TempStr[strlen(TempStr)]);  //add to end of str
      strcat(TempStr, "\260C");
      draw(TempStr);
      //Serial.println(TempStr);
      break;

    case 6:  //RPM

      strcpy(TempStr, "RPM:");
      dtostrf(RPM, 4, 0, &TempStr[strlen(TempStr)]);  //add to end of str
      strcat(TempStr, " min");
      draw(TempStr);
      //Serial.println(TempStr);
      break;

    case 7:  //OilPressure

      strcpy(TempStr, "\326l: ");                             //Öl
      dtostrf(OilPressure, 3, 2, &TempStr[strlen(TempStr)]);  //add to end of str
      strcat(TempStr, " bar");
      draw(TempStr);
      //Serial.println(TempStr);
      break;

    case 8:  //FuelPressure

      strcpy(TempStr, "Rail: ");
      dtostrf(FuelPressure, 3, 1, &TempStr[strlen(TempStr)]);  //add to end of str
      strcat(TempStr, "bar");
      draw(TempStr);
      //Serial.println(TempStr);
      break;
  }
}

// Interrupt on A changing state
void doEncoderA() {
  if (rotating) delay(1);                   // wait a little until the bouncing is done
  if (digitalRead(encoderPinA) != A_set) {  // debounce once more
    A_set = !A_set;
    // adjust counter + if A leads B
    if (A_set && !B_set) {
      if (Abgas == false) {
        encoderPos -= 1;
        Modus -= 1;
        if (Modus < 0) Modus = MAXMENU;
      } else {
        encoderPos -= 1;
        Abgasmodus -= 1;
        if (Abgasmodus < 0) Abgasmodus = MAXABGAS;
      }
    }
    rotating = false;  // no more debouncing until loop() hits again
  }
}

// Interrupt on B changing state, same as A above
void doEncoderB() {
  if (rotating) delay(1);
  if (digitalRead(encoderPinB) != B_set) {
    B_set = !B_set;
    //  adjust counter - 1 if B leads A
    if (B_set && !A_set) {
      if (Abgas == false) {
        encoderPos += 1;
        Modus += 1;
        if (Modus > MAXMENU) Modus = 0;
      } else {
        encoderPos += 1;
        Abgasmodus += 1;
        if (Abgasmodus > MAXABGAS) Abgasmodus = 0;
      }
    }
    rotating = false;
  }
}

void draw(char what[20]) {
  u8g2.clearBuffer();                // clear the internal memory
  u8g2.setFont(u8g2_font_10x20_tf);  // choose a suitable font
  u8g2.drawRFrame(0, 0, 128, 32, 5);
  u8g2.drawStr(5, 22, what);  // write something to the internal memory
  u8g2.sendBuffer();          // transfer internal memory to the display
}

Möglicherweise ist das "delay(1)" in den Interrupt-Routinen keine gute Idee...

Leider nein, das Auskommentieren hat keine Verbesserung gebracht.

Ich würde jetzt mal auf den I2C Bus tippen.
Der ist seht empfindlich und kann schon mal, auch bei einer Kabellänge von 50 cm ein System zum Absturz bringen.
Mein Tipp: Verwende für die Verbindung abgeschirmte Kabel.

Danke, probiere ich morgen aus. Schirm einseitig auf Masse sollte reichen, oder?

  1. UNO bei schlechter Programmierung als Laufzeitfehler
  2. CAN-Bus mit eigener Hardware nur bei blockierender Bibliothek
  3. I²C (meine Vermutung) bei zu vielen Daten

Wenn Du eine Herzschlag-LED einbaust, siehst Du, ob der UNO hängen bleibt. Auch der serielle Monitor kann Dir Hinweise liefern.

Einen I²C-Bus habe ich schon mal zum Einfrieren gebracht. Du scheinst den mit Informationen zuzuschütten, könnte ein Grund sein. So schnell kannst Du vermutlich nicht die Wechsel wahrnehmen, also eventuell nur alle 500 ms das Display mit neuen Infos füllen.

Die Displays haben meist 10 kΩ PullUp-Widerstände, könntest Du auf bis zu 1 kΩ verringern.

So meine Überlegungen :slightly_smiling_face:

Ja, einseitig GND ist ok.

Eigentlich macht eine Abschirmung unter anderem genau das Falsche. Sie erhöht die Leitungskapazität gegenüber Masse. Wenn der I2C Bus wegen zu hoher Kabelkapazität schlecht funktioniert führt eine Kapazitäterhöhung zu einem überhaupt nicht mehr funktionierenden Bus.

Ok, eine Abschirmung verhindert die Einstreuung von Störungen

Grüße Uwe

Da gebe ich dir Recht. Bei längeren Leitungen wäre ich auch mit der Aussage zurüchhalend geblieben.
Allerdings bei richtiger Anwendung kann das schon sehr nützlich sein.

Bei I2C Bus hat mir das verdrillen der Leitungen schon mal zu stabilerem Verhalten verholfen.

Das Stimmt, jedoch bin der Meinung bei so einer EMV Versauter umgebung wie ein Fahrzeug wurde trotz dem zum Extender greifen und den so nach an der MCU, so wie Display setzen wie es geht bei 2X P82B715 brauch man nur auf den Enden jeweils 2Widerstände = 4 x 470Ohm, danach ist Ruhe im Kasten und das Display ( nicht nur) kann man setzen wo man will.
Ach ja der P82B715 arbeitet schon ab 3V wen nötig.

Danke für den Input. Ab morgen erstmal 14 Tage im Urlaub. Melde mich danach mit dem Ergebnis mit der geschirmten Leitung.

Gruß, Roland

Schönen Urlaub