Interrupt Routine hängt sich auf!

Hallo habe ein Problem.
Meine Interrupt Routine hängt sich auf wenn ich 1kHz oder mehr über einen Funktionsgenerator rauf gebe. Im Terminalprogramm bleibt die Messung dann einfach stehen. Wie kann ich es schaffen, dass Sie normal wieder weiterarbeitet, wenn ich die Frequenz wieder unterhalb 1 kHz einstelle? Ohne das ich jedesmal den Rest Knopf am Board betätigen muss?

Schnix:
Wie kann ich es schaffen, dass Sie normal wieder weiterarbeitet, wenn ich die Frequenz wieder unterhalb 1 kHz einstelle?

Ausgaben mit "Serial.println(Spannungsmittelwert);" auf Serial sind nicht Interrupt-sicher und dürfen innerhalb einer ISR nicht verwendet werden.

Du mußt wenn, dann Deine Daten innerhalb Deiner ISR in eine geeignete Datenstruktur wegspeichern (z.B. FIFO-Puffer, oder einfach nur den aktuellen Mittelwert als volatile-Variable) und normal aus der loop heraus über Serial senden. Und NICHT mit Serial.print aus der ISR heraus.

Was willst du da mit dem EEPROM? Du hast das RAM um Daten kurz zwischenzuspeichern.

Schnix:
Kann ich nicht einfach den Energiewert ins EEPROM schreiben und dann in der loop auslesen und ausgeben?

Das EEPROM ist ein extrem langsam beschreibbarer Speicher, bei dem der Hersteller zudem nur für 10000 Schreibvorgänge pro Speicherzelle garantiert. Wenn Du Dein EEPROM nicht kaputtschreiben möchtest, beschreibst Du den besser nicht in der loop.

Du kannst in der Interrupt-Routine die Werte in "volatile" deklarierte globale Variablen schreiben und diese Variablen von der loop aus auslesen.

Exterm langsam heißt daß ein Schreibvorgang ca 5mS dauert also Du nur ca max 200 mal pro Sekunde schreiben kannst.

Grüße Uwe

Mußt Du wirklich so oft den Meßwert ausgeben?

Bei 115200 Baud und Ausgabe con zwei 7-Stelligen Zahlen mit Komma und NL kommen da schon fast 20 Zeichen die über die serielle Schnittstelle gesendet werden. Bei 115200 Baud und angenommenen 8N1 Protokoll sind das schon fast 2mS.
grüße Uwe

Beim Auslesen der Daten, also außerhalb der ISR ein noInterrupts(); Am Ende der Funktion wieder enablen.
Es werden dadurch keine ISR "verschluckt", die werden dann etwas später abgearbeitet.

Innerhalb der ISR sollte man sich kurz fassen, also nichts einbauen was viel Zeit braucht.
Ich mache das gerne über ein Flag (bool), das auf in der ISR auf true gesetzt wird wenn die Daten komplett sind.

In der main loop frage ich dann ab, ob die das Flag true ist, und verarbeite die Daten. An Ende das Flag wieder löschen.

Keine Ahnung ob es perfekt ist, aber bei mir funktioniert das.

Du darfst dafür nicht das EEPROM verwenden. Das ist um Werte dauferhaft abzuspeichern. Wenn du etwas speichern willst um es kurze Zeit zu verarbeiten gehört das ins RAM.

Schnix:
@jurs: habe das neu hinzugefügte in der loop markiert, meinst du es so?
Energiewert ist ja als volatile deklariert am Anfang des Programms.

Ja, serielle Ausgaben von der loop aus aufrufen, nicht in der Interrupt-Routine, genau das meinte ich.
Nein, in Codeabschnitten kannst Du nichts "markieren", außer mit Kommentaren.

Schnix:
Problematisch ist auch das meine loop() nicht hinterherkommt was die Abarbeitung der FunktionSerielle_Eingabe_string() betrifft, dieich nach deinem Ansatz auf gebaut habe.

Einleseroutinen mit "delay(100)" oder ähnliches sind nur für Programme sinnvoll, die als Mindestanforderung haben, dass sie "interaktiv auf Eingaben reagieren" sollen. Für Programme, mit erhöhten Anforderungen an schnelle Signalverarbeitung ist JEDES delay() im Programm unbedingt zu vermeiden. In Programmen mit schneller Signalverarbeitung brauchst Du statt so einer Anfänger-Einleseroutine eine anders aufgebaute, delayfreie Routine zum Einlesen von der seriellen Schnittstelle.

Und wie schon von Serenifly mit ähnlicher Aussage geschrieben, sind Deine EEPROM-Schreibaktionen im Sketch irgendwie sinnfrei.

EEPROM-Speicher ist nur begrenzt oft beschreibbar, Atmega garantiert nur für 10000 mal. D.h. EEPROM-Speicher sollte nur für die seltene Speicherung von längerfristig gültigen Daten verwendet werden, sagen wir mal Einstell- und Konfigurationswerte. Beim ständigen Beschreiben immer derselben Speicherstellen hast Du EEPROM-Speicher in kürzester Zeit kaputtgeschrieben und der Speicher ist defekt. Lediglich das Auslesen von EEPROM-Speicher ist unbegrenzt oft möglich.

Nur RAM-Speicher, also normale Variablen im RAM, darfst Du beliebig oft ändern, ohne dass es dabei zu Abnutzungserscheinungen kommt, aber für EEPROM-Speicherstellen gilt das nicht.

Ist ein ständiges Ausgeben Serial.println(Energiewert); in der main loop wirklich nötig ?
Das läuft "Vollgas" und verbraucht unnötig Zeit.

Würde mit millies den Traffik etwas reduzieren. http://arduino.cc/en/Tutorial/BlinkWithoutDelay

Auch das Delay beim Einlesen der Parameter ist wie schon mehrfach angesprochen suboptimal.

Schnix:
momentan ist es so das er mir auch 0.00 ausgibt, wenn ich das Triggersignal abmache. Da er in der loop ja permanent Serial.println(Energiewert) ausgibt.
Wie schaffe ich ne vernünftige Alternative, das er in der loop immer nur den Energiewert ausgibt wenn ich mein Signal auch anliegen habe.

Kann Dein Energiewert auch negativ werden oder sind die Werte immer 0 oder größer Null?

In dem Fall brauchst Du nach dem Auslesen den Wert nur auf einen negativen Wert setzen und gibst ihn nur dann aus, wenn er größer oder gleich null ist. Den Wert kann er dann nur von der ISR erhalten haben:

  noInterrupts(); // ist das hier an der richtigen Stelle?
  if (Energiewert>=0) Serial.println(Energiewert);
  Energiewert=-1;
  interrupts();

Falls Energiewert auch negative gültige Werte annehmen kann, benötigst Du eine boolean-Hilfsvariable, dann setzt Du z.B. in der ISR den Wert auf gültig:
EnergiewertValid=true;
und beim Auslesen:

  noInterrupts(); // ist das hier an der richtigen Stelle?
  if (EnergiewertValid) Serial.println(Energiewert);
  EnergiewertValid=false;
  interrupts();

Die Anzahl der "Zeichen pro Sekunde", die Du über die serielle Schnittstelle maximal ausgeben kannst, ist immer Baudrate geteilt durch 10. D.h. bei 115200 Baud kannst Du maximal 11520 Zeichen pro Sekunde ausgeben.

Beim Ausgeben von "Zeilen" daran denken, dass auch die Steuerzeichen am Zeilenende mitzählen, das sind üblicherweise immer zwei Steuerzeichen für CR (Carriage Return) und LF (Linefeed). D.h. wenn Du Zahlen mit einer Länge von 10 ausgibst und am Ende einen Zeilenvorschub mit 2 Zeichen, dann sind das 12 Zeichen pro Zeile, dann schaffst Du bei 115200 Baud maximal:
11520/12 = 960 Zeilen pro Sekunde

Wie oft kommt eigentlich der Interrupt? Man könnte vielleicht statt einen Wert einen ganzen Puffer anlegen, den man in der ISR voll schreibt. Und dann in loop() diesen Puffer alle x Zeiteinheiten ausgeben. Dann werden alle Werte ausgegeben, aber es muss nicht sofort geschehen. Das Problem dabei wird sein, was passiert wenn ein Interrupt während der Ausgabe kommt.

@rudirabbit hast du ein Beispiel für diese Überprüfung mit einem Flag, ob die ISR fertig ist?!
Hab mit Flags bisher noch nicht gearbeitet, weiß nur das dies Flipflops mit 1 Bit Speicher sind. Ein gesetztes Flag wäre in dem Fall true und könnte dann in der loop ausgelesen werden und wird danach wieder gelöscht(false)??

Mit Flag meinte ich eine Variable vom typ boolean. (Flag ist ein hochtragendes Wort für eine einfache Sache)
In deinem Fall könnte ich mir vorstellen das innerhalb der ISR die Werte pufferst in einem Array z.b
Wenn du dann z.b 100 Werte hast, setzt du in der ISR diese Variable auf true.

In der main loop fragst du nach dieser Variable ob die true ist, und gibst dann die 100 Werte in einem Rutsch aus.
Dann setzt du diese wieder auf false.
Könnte performanter sein als jedesmal nur einen Wert auszugeben.
Also in etwa das was Serenifly beschreibt.
Wenn vor dem Serial Print ein noInterrupts() werden die Interrupts vorher gesperrt, sollte schon gehen.

Aber das ganze natürlich auch Grenzen, ist halt nur ein 16 mhz CPU.

Schnix:
mit der Variante fährt er sich leider genauso fest bei 1kHz, wie vorher bei der Ausgabe in der ISR.

Ja, sorry, die Interrupts während der Serial-Ausgabe zu sperren ist wahrscheinlich genau so eine schlechte Idee wie die Serial-Ausgaben innerhalb der ISR zu machen.

Wenn Du mit extremen Interruptraten die Serial-Ausgaben überlaufen möchtest, so dass das Programm mehr Interrupts generiert als es an Daten über Serial ausgeben kann, muß die Serial-Ausgabe außerhalb der gesperrten Interrupts stattfinden. Innerhalb der gesperrten Interrupts dürfen die Daten nur in eine andere Variable umkopiert werden, die dann danach zur Serial-Ausgabe verwendet werden kann, wenn die Interrupts wieder freigegeben worden sind.

Ungefähr so:

  noInterrupts();
  float Energiewert_gelesen=Energiewert;
  Energiewert=-1;
  interrupts();
  if (Energiewert_gelesen>0) Serial.println(Energiewert_gelesen);

Damit sollte der Sketch am Laufen bleiben, auch bei höchster Interruptrate.

Eine Erkennung, ob die Interruptrate so hoch ist, dass nicht mehr jeder Wert auch auf Serial ausgegeben wird, brauchst Du oder brauchst Du nicht?

Es werden die Strings sein, die Du verwendest.
Ist ein ATMega8 soviel billiger daß es sich auszahlt diesen zu vewenden? Bist Du sicher daß der Sketch auf dem ATmega8 läuft da dieser andere periferie und weniger Speicher (auch RAM) hat.
Grüße Uwe

Schnix:
wenn man sich den Sketch jetzt mal betrachtet an welcher stelle wird so viel Speicher verbraucht das 10.5 kBytes zustande kommen?

Du hast im Programm völlig falsche Variablentypen gewählt, wenn Du ein möglichst kleinen Programmcode bekommen möchtest.

Ein guter Teil Speicher geht bei Dir drauf, sobald Du eine Variable als Gleitkommazahl "float" deklarierst, damit herumrechnest und diese dann auch wieder in Text umwandelst.

AVR 8-Bit Controller haben keine Gleitkommarecheneinheiten im Controller und jeder Code zum Rechnen mit und umwandeln von Gleitkommazahlen wird per Softwareemulation als Library in den Code gepackt.

Wenn das Programm keine einzige Rechnung mit und Umwandlung von/zu einer Gleitkommazahl drin hätte, wäre es gleich um einiges kleiner, weil dann überhaupt kein Gleitkomma-Softwareemulationscode ins Programm eingebunden werden braucht.

Du könntest Deine "Energiewerte" zum Beispiel als "MilliEnergiewert" betrachten und statt mit Zahlen, die drei Nachkommastellen haben, mit Zahlen (ggf. "long") rechnen, die tausendmal so groß sind.

Außerdem hat Uwe damit Recht, dass auch die Deklaration von Objekten des Typs "String" Dir Programmspeicher wegfressen. "String" sind aufgesetzte Objekte oberhalb der normalen AVR libc Library, die stattdessen mit C-Strings (Char Arrays, nullterminierten Strings) arbeitet. Wenn Du aber immer noch eine Softwareschicht auf eine bereits vorhandene Softwareschicht draufsetzt, dann verballerst Du damit nur Programmspeicher und machst die Programme außerdem langsam in der Verarbeitung.

Für möglichst kleine und effektive Programme sind die Datentypen "float" und "String" völlig unbrauchbar. Also Zahlen lieber mit "long" statt "float" rechnen und Strings mit Char-Arrays statt mit String-Objekten verwalten, dann klappt es auch mit der Programmgröße.

Globale Variablen auf 0 zu initialisieren kann je nach Compiler auch ein klein wenig Speicher fressen:
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_varinit

Globale und lokale statische Variablen werden standardmäßig schon nach dem Reset des Controllers auf 0 initialisiert (was am C Standard liegt, nicht an der AVR Architektur selbst). Dann muss nur die Existenz und die Größe der Variable gespeichert werden. Wenn man sie dann noch explizit initialisiert, muss der Initialisierungswert unnötigerweise im Flash gespeichert werden. Wobei es auch Compiler gibt, die das Erkennen und optimieren, aber es steht nicht dabei was genau mit "recent versions of gcc" gemeint ist.

Das fällt aber im Gegensatz dem float und String Kram kaum ins Gewicht :slight_smile:

Serenifly:
Das fällt aber im Gegensatz dem float und String Kram kaum ins Gewicht :slight_smile:

Anbei mal zwei kurze Beispielprogramme, von denen eines “long” und das andere mit “float” rechnet und am Ende die Ausgabe auf dem seriellen Monitor in jedem Fall in Float-Formatierung mit drei Nachkommastellen ausgibt.

Funktionell sind beide Programme gleichwertig, aber die kompilierte Programmgröße unterscheidet sich stark.

Mit float:

#define pin A0

void mitFloatGemacht()
{
  float zahl=0.123;               // Gleitkommazahl definieren
  zahl = zahl* analogRead(pin);   // mit einem Analogwert multiplizieren
  Serial.println(zahl,3);         // Ergebnis auf Serial ausgeben
  zahl= zahl/3;
  Serial.println(zahl,3);         // Ergebnis auf Serial ausgeben
  zahl= zahl+2.000;
  Serial.println(zahl,3);         // Ergebnis auf Serial ausgeben
  zahl= -zahl;
  Serial.println(zahl,3);         // Ergebnis auf Serial ausgeben
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  mitFloatGemacht();
  delay(1000);
}

Mit long:

#define pin A0

void mitLongGemacht()
{
  long zahl= 123;                 // Ganze Zahl definieren (1000 mal so gross)
  zahl = zahl* analogRead(pin);   // mit Analogwert multiplizieren
  printAsFakeFloat(zahl);
  zahl= zahl/3;
  printAsFakeFloat(zahl);
  zahl= zahl+2000;
  printAsFakeFloat(zahl);
  zahl= -zahl;
  printAsFakeFloat(zahl);
}

void printAsFakeFloat(long value)
// Ausgabe einer "long" Zahl geteilt durch 1000 
// (ohne "float" zu verwenden)
{
  Serial.print(value/1000);Serial.print('.');
  value= abs(value)%1000;
  if (value<10) Serial.print('0');
  if (value<100) Serial.print('0');
  Serial.println(value);
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  mitLongGemacht();
  delay(1000);
}

Obwohl für das Programm, das mit long rechnet, extra eine Ausgabefunktion für die formatierte serielle Ausgabe geschrieben werden mußte, ist dessen Code um ca. 1,3 KB kleiner als der Code des Programms, das mit float rechnet.

Das ist auch der Grund weshalb sprintf() und sscanf() auf dem AVR nicht für float implementiert sind. Das würde den Code stark vergrößern. Und das glaube ich auch selbst wenn man diese Funktionen gar nicht mit floats verwendet.

Schnix:
Gibt es eine Methode die sich dabei an diesen Code anlehnt oder komme ich mit den Strings und float Zahlen einfach nicht auf diese Frequenz?

Du kannst gar nicht auf die Frequenz kommen, wenn Du in der Interrupt-Routine dreimal hintereinander analogRead() machst.

Ein einzelnes analogRead() dauert ungefähr 0,0001 Sekunden, also dreimal analogRead() dauern 0,0003 Sekunden.
Die ADC-Wandlung mit analogRead() ist eine der langsamsten Funktionen auf dem Arduino.

Rechne Dir mal aus, auf welche Frequenz Du maximal kommen kannst, wenn jeder Interrupt mindestens 0,0003 Sekunden dauert!