Arduino hängt - Hilfe bei Fehlersuche

Moin,
ich habe mich nach langer Zeit mal wieder mit der Arduino Programmierung beschäftigt und folgendes Problem:
Ich habe ein Arduino UNO welches ein paar Wärmemengenzähler angeschlossen hat. Diese geben beim Durchfluss von X Litern Wassern einen Impuls raus, den das Arduino protokolliert und mittels HTTP Request an meinen Server schickt.

Die Zähler haben unterschiedliche Durchflussmengen, deshalb wird im Code definiert, nach wie vielen Impulsen tatsächlich geschickt werden soll.

Dieser Prozess funktioniert auch soweit gut, teilweise läuft das Board wochenlang fehlerfrei durch. Manchmal hingegen stürzt es z.B. nach ein paar Tagen ab und lässt sich nur durch Stromtrennung am PC wieder erkennen (Ansonsten erhalte ich die Fehlermeldung, dass auf COM3 nichts angeschlossen sei).

Meine Vermutung ist, dass die nächtliche Trennung der Internetverbindung und die allgemein instabile LAN Verbindung (DLAN) den Zwischenspeicher irgendwann “überlaufen” lassen, da die Impulse zwar protokolliert aber nicht verschickt werden können. Wenn ich das Arduino direkt an den Router anschließe läuft das Board gefühlt fehlerfrei durch, aber das Patchkabel quer durch’s Haus stört :slight_smile: und ich bin mir nicht sicher, ob das wirklich die Ursache ist. Da die INT Werte nur maximal 32k Werte haben, könnte ich mir vorstellen, dass das Problem da liegen könnte.

Ich habe den Code mal etwas aufgeräumt und soweit kommentiert, wäre sehr cool wenn mir jemand einen Denkanstoß geben könnte, wo ich da den Programmierfehler habe oder wie ich sinnvoll verhindern kann, dass eine weitere Protokollierung stattfindet, wenn keine Internetverbindung existiert.

//Zähler wie viele Impulse bereits auf dem PIN gekommen sind
int impulsCounters[] = {
  0,0,0,0,0,0,0,0
};
//Zwischenspeicher um Zustandsänderungen zu erkennen
static byte oldVals[8];

//Die am Arduino angeschlossenen PINS. Pin 0 und 1 sind vom Ethernetshield blockiert, die Pins 6 und 7 nicht angeschlossen.
int impulsPins[] = {
  0,  //Impulspin 0 = deaktiviert
  0,  //Impulspin 1 = deaktiviert
  2,  //PIN 2 = Gaskessel
  3,  //PIN 3 = Heizkreis unten
  4,  //PIN 4 = Heizkreis oben
  5,  //PIN 5 = Holzofen
  0,  //Impulspin 6 = deaktviert 
  0   //Impulspin 7 = deaktiviert
  };

//Hilfsvariable für Modulooperation, damit nur alle X Impulse gesendet wird.
//Impulspin 2 sendet bei jedem 10ten Impuls, alle anderen bei jedem IMpuls
 int impulsModulo[] = {1,1,10,1,1,1,1,1};

#include <SPI.h>
#include <Ethernet.h>

//Netzwerkeinstellungen
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
char server[] = "192.168.11.214";
EthernetClient client;
IPAddress ip(192, 168, 11, 215);

//Prüft ob ein Impuls auf pin X erfolgt ist
boolean impulsEingabe(int pin)
{
	byte val= digitalRead(pin);
	byte result=false;
	if (val==LOW && oldVals[pin]==HIGH) result=true;
	oldVals[pin]=val;
	return result;
}

void setup() {
	Serial.begin(9600);
	if (Ethernet.begin(mac) == 0) {
		Serial.println("Failed to configure Ethernet using DHCP");
		Ethernet.begin(mac, ip);
	}
	Serial.println("Started  ");
	for (int i = 2; i <=5;i++){
		pinMode(i, INPUT_PULLUP); // Pins 2-5 aktivieren/lesend
		impulsEingabe(i);
	}
}

void loop() {
	for (int i = 2; i <= 5;i++){
		//Prüfen ob ein Impuls auf i kam
		if (impulsEingabe(i)){
			Serial.println("Got impuls on Pin " +String(i) + " = "  + String (impulsPins[i]));
			impulsCounters[i]++;
		}
		//Ergibt die aktuelle Anzahl der Impulse mit Modulo einen Restwert von > 0
		// && ist die Anzahl der Impulse > 0
		// && ist die Netzwerkverbindung frei? 
		//Wenn ja, verbinden, den Impuls an den Server melden, Anzahl der Impulse reduzieren
		if (impulsCounters[i] % impulsModulo[i] == 0 && impulsCounters[i] > 0 && !client.connected()){
		   Serial.println("Found impuls and send" + String (impulsPins[i]));
		   if (client.connect(server, 80)) {
				Serial.println("connected");
				client.println("GET /getImpulse.php?pulseId="+String(impulsPins[i])+" HTTP/1.1");
				client.println("Host: 192.168.11.214");
				client.println("Connection: close");
				client.println();
				impulsCounters[i] -= impulsModulo[i];
				Serial.println("New Value for impulsCounters["+String(i)+"]=" + String (impulsCounters[i]));
				client.stop();
		   }else{
				Serial.println("Verbindung zum Server nicht möglich");
		   }
		}
	}
}

Die derzeitige “Lösung” des Problems ist übrigens eine Zeitschaltuhr, die das Arduino einmal am Tag für 15min vom Strom trennt. Aber das hat eigentlich nicht die Bezeichnung “Lösung” verdient…

Meine Fragen sind letztendlich:

  • Liege ich mit meiner Vermutung richtig (dass der Speicher irgendwann voll läuft, da die Daten nicht rausgeschickt und somit gelöscht werden)?
  • Wie kann ich das sauber lösen?
  • Wo sonst ist der Code bescheiden?
  • Ist es eine (sinnvolle) Lösung die nicht erfolgreich gesendeten Requests zu verwerfen? Ich möchte eigentlich ungern bei der nächtlichen Zwangstrennung die Werte verlieren
  • Hilft hier eventuell Watchdog?

Über jede Verbesserung, Anregung bin ich dankbar. Jetzt wo es langsam kalt wird, möchte ich mal sehen wohin meine Wärme geht :slight_smile:

Du könntest auch ein usigned long nehemen da passen dann 4294967295 Impulse rein das sollte dann ne Weile gehen falls es aus dieser Ecke kommt kann man das ja so testen.
Statt der Schaltuhr könntest du auch ne Watchdog nehmen der/die ? ist auch schon onboard.
Schaue mal >HIER<.
Immer wenn ein neuer Puls kommt evtl im EEPROM speichern dann verlierst du auch nix.
Gruß
DerDani

Ein Überlauf der Zähler behindert den Programmablauf nicht, es können dann höchstens falsche oder negative Werte herauskommen. Ich habe nicht den ganzen Code durchgeschaut, aber

if (impulsCounters[i] % impulsModulo[i] == 0 && impulsCounters[i] > 0 && !client.connected()){

scheint nichts auszugeben, sobald der Zähler < 0 wird (nach ca. 32000 Impulsen). Da aber die Zähler nach jeder Ausgabe quasi zurückgesetzt werden, sollte dieser Fall eigentlich nie auftreten.

Zum Hängen der Ethernet Verbindung kann ich nichts sagen, bin kein Netzwerk-Experte. Vielleicht ist da nach einer Zwangstrennung mehr notwendig, evtl. ein neues Ethernet.begin()?

Oder bleibt client.connected() irgendwann True? Dazu würde ich obige Bedingung auftrennen

if (impulsCounters[i] < 0)
  Serial.println("Counter Overflow!!!");
else if (impulsCounters[i] >= impulsModulo[i]) //Ausgabe sobald genügend Impulse gezählt wurden
  if (client.connected()
   Serial.println("Client still connected!!!");
  else { ... 
    impulsCounters[i] = 0; //einfach löschen, Anzahl wurde ja vollständig ausgegeben, ohne Modulo
}

Mit unsigned int Zählern kann man den Sonderfall vermeiden, daß negative Zählerstände auftreten und ggf. irgendwas durcheinanderbringen könnten.

Die Verwendung von String könnte ggf. den Speicher zumüllen, das könnte das Programm tatsächlich zum Abstürzen bringen - die dynamische Speicherverwaltung der Arduinos ist bekannt problematisch. Ich würde einfach ein Statement wie

Serial.println("Got impuls on Pin " +String(i) + " = "  + String (impulsPins[i]));

aufteilen in

Serial.print("Got impuls on Pin "); Serial.print(i); Serial.print(" = "); Serial.println(impulsPins[i]);

Ansonsten kann man den Watchdog benutzen, um den Controller im Falle eines echten Hängers neu zu booten.

Vielen Dank für die Anregungen & Informationen.
Das mit der String-Verkettung habe ich mal umgesetzt, auch den Datentyp des Zählers angepasst und den Wert nicht um Modulo reduziert, sondern auf 0 gesetzt. Das auf 0 setzen kann zwar zu etwas unsauberen Werten führen (z.B. wenn gerade ein anderer Request durchgeführt wird oder das Internet nicht da ist), aber in der Praxis werden alle 10-15 Sekunden Werte gesendet. Sollte also nicht wirklich schlimm sein - und optimieren kann man dann ja noch immer.

Bis jetzt läuft alles seit gestern Abend sauber durch. Im nächsten Schritt werde ich mich mal mit Watchdog beschäftigen.