Interrupts zu schnell?

Moin,

ich hab mal eine Frage zum Thema Interrupts. Im Rahmen eines Projekts muss ich alle 0,8ms einen Sensor auslesen und nach draussen kommunizieren. Um die Taktung zu erreichen war meine Idee mit Interrupts zu arbeiten. Nachdem ich mich in die Thematik eingelesen hatte und meinen ersten Versuch ausprobieren wollte hatte ich mein erstes Problem. Zuerst wollte ich während des Interrupts den Sensorwert auslesen und direkt an den Serial Monitor schicken, was nicht funktionierte. Dann habe ich versucht nur den Sensorwert während des Interrupts auszulesen und in der loop Funktion dann den Wert per Serial.println nach draussen zu geben. Auch wieder nix. Ich habe mit dem Vergleichswert rumgespielt und die Abstände zwischen den Interrupts größer gemacht. Auch das war keine Lösung.

Nun glaube ich dass evtl etwas mit meinen Interrupt Einstellungen nicht stimmt, finde aber partout keinen Fehler. Hat jemand eine Idee woran das sonst liegen könnte, oder bin ich mit meinem Lösungsansatz komplett auf dem Holzweg.

Grüße und dankeschön im Vorraus!

Hier mein Code

int sensorValue = 0;
int sensorPin = A0;
int i;
void setup() 
{
  cli(); // Interrupt abschalten

  //Timer 0 zurücksetzen
  TCCR0A = 0; // TCCR0A auf Null setzen
  TCCR0B = 0; // TCCR0B auf Null setzen
  TCNT0  = 0; // Zählerwert auf NUll setzen
  
  // Vergleichswert setzen
  OCR0A = 50; // 
  
  //Prescaler einstellen  
  TCCR0B |= (1 << CS02) | (1 << CS00); // CS02 & CS00 Bits setzen um 1:1024 Prescaler einzustellen
  TCCR0A |= (1 << WGM01); // TCCR0A ist das Timer/Counter Kontroll Register, ist jetz auf CTC Modus eingestellt
  TIMSK0 |= (1 << OCIE0A); // Vergleichs-Interrupt A ist aktiviert
  
  Serial.begin(115200);
  
  sei(); // Interrupt wieder aktivieren  
}

ISR(TIMER0_COMPA_vect)
{
  //das geschiet bei einem Interrupt
  //sensorValue = analogRead(sensorPin);
  Serial.println("A");
 }


void loop ()
{
  //Serial.println(sensorValue);
  Serial.println("B");
  //i++;
  
}

Im Anhang noch das was ich sehe per SerialMonitor

Da die serielle Ausgabe selber Interrupts benötigt, ist eine solche Ausgabe innerhalb der ISR verboten/unmöglich.

Der Fachbegriff: Deadlock

Hallo,

man soll generell keine komplizierten Sachen in einer ISR erledigen, weil sie so kurz wie möglich/nötig gehalten werden sollte. Den sie könnte und wird andere ISR's während ihrer Ausführung behindern. Darum klappt das mit serial in einer ISR nur rein zufällig, je nach zeitlicher Beanspruchung.

Versuch mal eine status Variable in der ISR zu schalten und diese in der loop auszuwerten. An "volatile" denken.

bensch247:
... oder bin ich mit meinem Lösungsansatz komplett auf dem Holzweg.

Das nun nicht, aber wenn Du in loop alle 800 Milli Mikrossekunden den Wert abfragst, geht das viel einfacher. Schau mal in Beispiele/02.Digital/BlinkWithoutDelay. Anstelle LED blinken zu lassen, den Wert abfragen und ausgeben.

EDIT: Sorry, ich meinte natürlich 800 Mikrosekunden, also die Funktion micros(), benutzt wie millis() im Beispiel.

agmue:
Das nun nicht, aber wenn Du in loop alle 800 Millisekunden den Wert abfragst, geht das viel einfacher. Schau mal in Beispiele/02.Digital/BlinkWithoutDelay. Anstelle LED blinken zu lassen, den Wert abfragen und ausgeben.

das Problem ist ja, dass ich alle 0,8ms (!) den Wert abfragen muss. Aber ich werde mir die anderen Stichworte und Hinweise mal angucken und ein Feedback geben.

Danke schon einmal :slight_smile:

Hallo,

wie genau oder präzise sollen denn die 0,8ms eingehalten? Je nachdem gibts die eine oder andere Lösung dafür.

Und wenn du einen Timer programmierst solltest du dafür nicht Timer0 verwenden! Der wird von der Arduino Software für millis() und delay() gebraucht

Also statt dessen Timer2 nehmen

Doc_Arduino:
Hallo,

wie genau oder präzise sollen denn die 0,8ms eingehalten? Je nachdem gibts die eine oder andere Lösung dafür.

Die o,8ms sollten schon ziemlich genau eingehalten werden, weshalb ich ja an Interrupts dachte. Was schwebt dir denn vor?

Wenn "ziemlich genau" tatsächlich "ziemlich genau" sein soll, dann schau Dir auch ziemlich genau den Takt des Oszillators an.

Hallo,

wenn es nur ungefähr genau sein soll, dann kann man das wie schon erwähnt wurde in der loop mit micros() machen. Die Wiederholgenauigkeit hängt dann ab wieviel die loop noch so an Code abarbeiten muß. delay() ist hier sowieso verboten.

Wird der Code noch größer? Von welchen Toleranzen reden wir?

Wenn es ganz präzise sein soll, dann gehts nur mit Timer ISR. Und weil Klaus das anspricht, die Quarzabweichung kann man dann nachträglich nach ausmessen anpassen mittels dem Timer.

Ok, also im Endeffekt sollen in der loop später 6 Sensoren ausgelesen werden. Ob analog oder digital ist noch nicht raus. Allerdings hatte ich mal mit der Micros() abgeschätzt dass eine analoge Messung ca 0,1ms dauert. Das könnte also machbar sein. Ich würde das Programm mal eine definierte Zahl an Sekunden laufen lassen und über die Anzahl an Messungen die Dauer mitteln. Je nachdem was dann dabei herauskommt kriege ich ja eine Abschätzung wie lange eine durchschnittliche Messung dauert und wie groß die Abweichung zu den anvisierten 0,8ms is

das hier ist mein gerade funktionierender Code den ich ma testen möchte wie lange das tatsächlich dauert:

unsigned long zeitVergangen = 0;
const long intervall = 800; //Abstand zwischen 2 Messungen in Microsekunden
int sensorPin = A0;
int sensorValue = 0;
int i = 0;
void setup()
{
Serial.begin(115200);
}

void loop()
{
unsigned long zeitAktuell = micros();

if(zeitAktuell - zeitVergangen >= intervall)
{
zeitVergangen = zeitAktuell;
sensorValue = analogRead(sensorPin);
Serial.print(i);
Serial.print(": ");
Serial.println(sensorValue);
i++;
}

}

Wenn du Zeitnot hast, ist analogRead(sensorPin); vielleicht nicht das richtige Mittel.
analogRead() blockiert, bis die Messung abgeschlossen ist

Der AVR bietet Möglichkeiten die Messung parallel laufen zu lassen.
google("free running mode adc avr")

Hallo,

zur Loop Zeitmessung hatte mal GuntherB eine geniale Idee.
Denn deine serielle Ausgabe verfälscht dir deine eigentliche Zeitmessung. Außer man will bedingte seriellen Ausgaben mit messen.

Funktion:

/*********************************************************************************
** LoopTiming() v06 by GuntherB & Doc_Arduino @ german Arduino Forum        	**
**********************************************************************************
** Funktion um die Dauer der Loop-Zeiten zu ermitteln				**
** wird in der Loop am Anfang und Ende aufgerufen				**
** benötigt ca (AL * 4 + 16) Byte RAM						**
*********************************************************************************/

void LoopTiming()
{
  const int AL = 200;        // Arraylänge, NUR GERADE Zahlen verwenden!
  static unsigned long LoopTime[AL];
  static unsigned int Index=0, Messung=0, Min=0xFFFF, Max, Avg;
  
  if (Messung % 2 == 0)     // wenn Messung X gerade (0,2,4,6 usw.), entspricht immer Anfang der Loop
    {
     LoopTime[Index] = micros();
     Messung++;
     Index++;    
     return;	            // Funktion sofort beenden, spart bestimmt Zeit
    }

  if (Messung % 2 == 1)     // wenn Messung X ungerade (1,3,5,7 usw.), entspricht immer Ende der Loop
    {
     LoopTime[Index] = micros();
     LoopTime[Index-1] = LoopTime[Index] - LoopTime[Index-1];  	// Loopdauer einen Index niedriger einspeichern wie aktuell
     Messung++;
    }	
	    
  if (Index >= AL) 	// Array voll, Daten auswerten
    {  
     for (int i = 0; i<AL; i++)
       {
        Min = min(Min, LoopTime[i]);
        Max = max(Max, LoopTime[i]);
        Avg += LoopTime[i];
       }
	
     Avg = Avg / AL;
     Serial.print(F("Minimal       "));Serial.print(Min);Serial.println(" µs");
     Serial.print(F("Durchschnitt  "));Serial.print(Avg);Serial.println(" µs");
     Serial.print(F("Maximal       "));Serial.print(Max);Serial.println(" µs");
     Min = 0xFFFF;
     Max = 0;
     Avg = 0;
     Messung = 0;
     Index = 0;
    }
}

und aufgerufen wird diese 2x. Einmal genau am Anfang der loop und einmal genau am Ende der loop.

Wenn AL auf 200 gesetzt ist, bedeutet das, es wird 100 mal hintereinander die loop vermessen und danach die Werte ausgespuckt und dann wieder 100 mal hintereinander gemessen. Je nach RAM kannste das auch erhöhen.
millis oder micros sind immer bis auf 4 genau.

Man kann analogRead() auch schneller machen wenn man den ADC Takt erhöht:
http://www.microsmart.co.za/technical/2014/03/01/advanced-arduino-adc/

Das schlägt sich aber negativ auf die Genauigkeit der Messung nieder.

Wenn man es per Hand macht kann man auch mit einem Timer direkt den ADC triggern (entweder per Compare Match oder Overflow). Ohne den Umweg über eine ISR und vollständig in Hardware. Aber meistens wird es wohl reichen die Messung in einer ISR zu starten.

Wichtig ist eben dass dir klar wird, dass du Dinge wie Serial oder auch delay() nicht in einer ISR ausführen kannst. Statt dessen musst du eine Variable setzen um loop() mitzuteilen dass ein Ereignis eingetreten ist.

Serenifly:
Man kann analogRead() auch schneller machen wenn man den ADC Takt erhöht:
http://www.microsmart.co.za/technical/2014/03/01/advanced-arduino-adc/

Das schlägt sich aber negativ auf die Genauigkeit der Messung nieder.

Wenn man es per Hand macht kann man auch mit einem Timer direkt den ADC triggern (entweder per Compare Match oder Overflow). Ohne den Umweg über eine ISR und vollständig in Hardware. Aber meistens wird es wohl reichen die Messung in einer ISR zu starten.

Wichtig ist eben dass dir klar wird, dass du Dinge wie Serial oder auch delay() nicht in einer ISR ausführen kannst. Statt dessen musst du eine Variable setzen um loop() mitzuteilen dass ein Ereignis eingetreten ist.

ah ok. kannst du mir nen link schicken bei der das mit den variablen zufällig erklärt wird? eine kurze google recherche hat mir grad nich sooo viel erklärt :slight_smile:

ansonsten schon einmal dickes dankeschön für die hilfe hier

Der Fachbegriff ist "Flag setzen".

Beachte: google("Variable Volatile")

Darf ich mal eine Frage dazwischenwerfen? Was willst Du mit den Daten tatsächlich machen? Doch wohl nicht in so kurzen Abständen auf den Monitor malen. Zumindest ich kann da nicht mitlesen.

Einfach sowas:

volatile bool interruptTriggered;

void loop()
{
   if(interruptTriggered)
   {
      interruptTriggered = false;

      ...
   }

}

ISR(...)
{
   interruptTriggered = true;
}

Der Grund ist ganz einfach dass beim ISR die Interrupts global abgeschaltet werden damit die sich nicht gegenseitig unterbrechen können.

Aber ja, alle 1ms einen Messwert auf Serial zu schreiben ist so oder so Unsinn

Serenifly:
Aber ja, alle 1ms einen Messwert auf Serial zu schreiben ist so oder so Unsinn

Das meine ich, ein analoger Messwert muß ja irgendwie verarbeitet werden.

Ich habe in loop ohne ISR alle 800 µs sechs analoge Meßwerte erfaßt und dabei einen digitalen Ausgang geschaltet. Mein Schätzeisen zeigt mir Werte zwischen 798 und 802 µs. Das geht also. Mit Serial.print wird es dann aber kritisch.

Es geht darum Daten von einem Teststand auszuwerten. Die Daten sollen vom Teststand nach aussen (drahtlos) gesendet werden, wo sie dann gespeichert werden sollen. Der Plan war das ganze mit einem Bluetooth Modul zu realisieren und das kommuniziert nunmal mit einer (max.) Baudrate von 115200 seriell mit dem Arduino. Bevor ich das ganze mit dem Bluetooth Modul probiere wollte ich erst einmal gucken ob das von der "Leistung" bzw. Programmstruktur überhaupt funktioniert. Ich werde mir die hier beherzigten Ratschläge angucken (muss mich allerdings in das eine oder andere erst einmal kurz einlesen) und meinen Ansatz dementsprechend anpassen. Haltet ihr das Vorhaben für überhaupt realistisch so weit?

Grüße