Kann man nen Interrupt im CTC-Modus unterbrechen (für andere Aktionen)?

Hallo liebe Forum-Mitglieder! Bin hier gang neu versuche seit ca 2 Mon in die Geheimnisse von C einzutauchen. Als Board hbe ich mir nen Atmega 2560 zugelegt und mir ein bischen eingearbeitet.

Ich hätte mal ne allgemeine Frage zu meinem Programmcode:

z.ZT. läuft zwei Rechtekgeneratoren zeitversetzt im 1 Ms Zyklus. Diese Procedur läüft im ISR CTC-Modus.
Nach Oszilloskopierung der Pins 30,und 32 funzt es auch.
Nun möchte ich diesen Interrupt unterbrechen, sagen wir mal für alle 7 sekunden um für ca 1,5sec Impulse zu detektieren und die zeitdifferenz ( zwischen 2 Impulsen )zu bestimmen. Z.ZT. funktionert nur der ctc aber die Zeitmessung nicht, vermutlich durch die Unterbrechungen?

Ist dies übrhaupt möglich?

Die Zeitdifferenz kann man ja über die Funktionen (millis,micros) bestimmen, die glaub beim 2560 an den Timer 1 gebunden ist…
Benötige ich weitere Untebrechnungen?

Über Tipps und Anregungungen wäre ich sehr dankbar und hoff auf etwas Unterstützung :slight_smile:

Hier mein bisheriger Entwurf

int U=0;
volatile byte wechsler = 0;                       // counter für switsch ISR 
byte PEAK_SUCH=0;                                // Variable für Pulsdetektion 
unsigned long start=0;                           // Meßstart 
unsigned long dauer=0;                          // Differenz Zeit 

//Ports
#define setBit(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
#define clearBit(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT)) 
//ADC
#define ADC 1
//  Bits für ADC setzten
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
void setup()
{
#if ADC
  // Prescaler64  ADC
  sbi(ADCSRA,ADPS2) ;
  sbi(ADCSRA,ADPS1) ;
  cbi(ADCSRA,ADPS0) ;
#endif

  cbi(PORTC, 5); // Pin 32 LOW
  cbi(PORTC, 7); // Pin 30 LOW

  cli();
  // Timer 2 CTC Modus,Vorteiler 128
  TCCR2B = (1<<CS22)  | (1<<CS20); // CS Bits für 128
  TCCR2A = (1<<WGM21);            
  OCR2A = 124;                    // (16M/128)*1ms -1
  TIMSK2 = (1<<OCIE2A);
  sei();
}
void loop(){
  U= analogRead(0);                         
  if(U>1000)                                 
  {
    PEAK_SUCH=1;   
  }
  else{
    PEAK_SUCH=0;
  }
  if(PEAK_SUCH !=0) {                            
    start = micros();
  }
  else{                                         
    dauer = micros() - start;
  }                        
}
ISR(TIMER2_COMPA_vect) {  // ISR CTC alle 1ms
  wechsler = (wechsler + 1 ) % 2;
  switch(wechsler) {
  case 0: 
    sbi(PORTC, 5);         // Pin 32 HIGH
    cbi(PORTC, 7);         // Pin 30 LOW
    break;
  case 1: 
    cbi(PORTC, 5);         // Pin 32 LOW
    sbi(PORTC, 7);         // Pin 30 HIGH
    break;
  }// Ende ISR

So wie ich verstehe willst du den Timer weiter zählen lassen und das Auslösen des Interrupts unterbinden. Dafür ist das Output Compare Interrupt Enable Flag OCIEn im Timer Interrupt Mask Register TIMSKn da.

Das kannst du mit TIMSK2 &= ~(1<<OCIE2A); löschen
Das invertiert die Maske rechts und durch das UND wird das entsprechende Bit auf 0 gesetzt

Oder du nimmst die Makros cbi und sbi die du dafür hast :slight_smile:

Das ging ja schnell… :slight_smile:

ok ich denke dass " TIMSK2 &= ~(1<<OCIE2A)" vernünftigt kling. Kann ich diese Funktion dierekt ins Loop einbinden
oder immer so schreiben:

  TCCR2B = (1<<CS22)  | (1<<CS20); // CS Bits für 128
  TCCR2A = (1<<WGM21);            
  OCR2A = 124;                    // (16M/128)*1ms -1
  TIMSK2 &= ~(1<<OCIE2A);

?
Wenn den ctc nach ca 1,5sec wieder aktivieren möchte , denke ich dass dat Bit wieder auf " TIMSK2 = (1<<OCIE2A);" gesetzt wird, oder?

Du solltest TIMSK2 |= (1<<OCIE2A); machen
Mit dem ODER. Damit werden die anderen Bits nicht berührt. In diesem Fall spielt das keine Rolle (da das Register nur das zweite Output Compare Enable Bit für Kanal B und das Overflow Interrupt Enable Bit enthält), aber bei anderen Registern und in bestimmten Timer Modi ist es wichtig. Wenn man sich dann konsequent angewöhnt immer nur einzelne Bits und nicht das ganze Register zu ändern passieren da weniger Fehler.

Wenn du die Makros schon hast, verwende die vielleicht auch. Liest sich besser und ist sicherer:
cbi(TIMSK2, OCIE2A);
sbi(TIMSK2, OCIE2A);

Geht aber so oder so. Das Makro ist einfach eine Textersetzung vor dem Kompilieren. Der Code der rauskommt ist demnach identisch. Das _BV Makro wiederum macht das (1 << n) das du bei deinen Port Makros hast.

Aber ja, du solltest das einfach hin und herschalten können. Ob das dann insgesamt so läuft wie du willst ist was anderes :slight_smile:

Nachtrag:
Die Clock Select Bits und das Compare Register musst du aber nur einmal in setup() setzen. Es bringt nichts das nochmal zu machen.

Nachtrag 2:
Das toggeln der I/Os geht auch kürzer so:
PORTC ^= 0xA0

Das liest PORTC ein, invertiert die Bits durch ein XOR mit 1010 0000 und schreibt das Ergebnis zurück

Thanks!
Also cbi(TIMSK2, OCIE2A); und sbi(TIMSK2, OCIE2A); habe ich jetzt mal einzeln probiert und funzt, danke. dat mit den Makros ist für mich einfacher. Nun gilt es dass die bits entsprechend nach meinen Zeitvorgaben zu setzten. Kann ich einfach nen 7sec von Timer 1 (millis();)benutzen, den dann CTC deaktiveren( mit den Makros..) den timer1 auf null setzten und 1,5 sec läufen lassen, dazu die zeitmessung der Impulspausen messen , und nach den 1,5 sec wieder den ctc aktivieren, und dann wieder von vorn? Dann bebötige ich doch 2 timerfunktionen oder?

Das mit den toggeln mit Schreibweise PORTC ^= 0xA0 probiere ich demnächst aus.

Du kannst mit millis() beliebig viele Zeiten zählen, wenn du dir nur entsprechend viele previousMillis und interval Variablen anlegst. Den zeitlichen Ablauf kann man über booleans regeln. Also z.B. wenn Zeit1 abgelaufen einen boolean auf true setzen und erst wenn dieser true ist eine zweite Variable abfragen.

Sowas in der Art. Sollte irgendwie gehen. Genauer müsste ich mir das auch erst mal im Detail überlegen.

klingt einleuchtend, was mir aber kopfzerbrechen bereitet, wenn der ctc aktiv ist, dann wird das loop ständig unterbrochen, somit wäre meine Zeitmessung bis ca 7 sec (zum deaktivieren des ctc) erreicht werden, verfälscht oder?
Muss ich erstmal probieren... Vielen Dank schon mal für die Unterstützung

millis() läuft auf Timer0. Die ISR ist so kurz, dass das bei den ewig langen Zeiten keine Rolle spielen sollte. Theoretisch. Aber im Prinzip hast du natürlich recht. Es ist sehr doof wenn ständig das Programm unterbrochen wird nur um einen I/O zu schalten. Das wollte ich dir noch hinschreiben, aber ich musste dann erst mal fort :slight_smile:

Das macht man daher besser anders. Statt die Pins per Software zu schalten, lässt man das die Hardware erledigen.

Schau dir da parallel zu dem was hier schreibe im Datenblatt ab Seite 187 für die Register Beschreibungen an:

Die Timer haben dazu alle zwei oder drei Ausgangs Pins. Diese sind mit OCnA/B/C bezeichnet:
http://www.pighixxx.com/pgdev/Temp/ArduinoMega_b.png
Die Ausgangspins für Timer2 sind Pin 10 für Kanal A und Pin 9 für Kanal B (oben rechts auf dem Board)

Du hast auf Timer2 zwei Output Compare Register. Schreibe in OCR2A und OCR2B den gleichen Wert.

Dann gibt es im Timer/Counter Control Register 2A (TCCR2A) die 4 Compare Match Output Mode Pins.

Wenn COM2A1 = 0 und COM2A0 = 1, dann toggelt der Pin bei einem Compare Match
Das gleiche für Kanal B mit COM2B1 = 0 und COM2B0 = 1
Im Datenblatt gilt hier Table 20-2 und Table 20-5: non-PWM mode

Hier auch beachten, dass die Register Anfangs alle auf 0 stehen. Das heißt du musst A1 und B1 nie anfassen.

Was mir jetzt nicht 100% sicher ist, ob da der Ausgangszustand in Betracht gezogen wird. Also die Pins müssen auf OUTPUT geschaltet werden. Aber man sollte auch den Zustand als HIGH oder LOW vorbesetzten können, so dass sie sich gegenläufig verhalten.

Diese Funktionalität ist unabhängig von den zwei Interrupt Enable Bits OCIE2A und OCIE2B!! Also du kannst das Toggeln nicht durch die IE Bits abschalten, sondern musst das jeweilige COM2(A/B)0 Bit auf 0 setzen. Bei 00 gilt dann wieder:
"Normal port operation, OC2A disconnected"

Lies dir da generell dazu mal das hier durch:

Da ist das für Timer1 gemacht. Geht aber ähnlich für alle anderen Timer (wobei Timer2 da vor allem bei den Modi und dem Prescaler anders geht. Also nicht einfach die WGM und CS Bits aus dem Code kopieren!). Der Kram mit den Pins per Hardware ist unten beschrieben, nachdem es erst mal per Hand in der ISR gemacht wurde, so wie du es jetzt hast.

Since we need to toggle the LED, we choose the second option (01). Well, that’s all we need to do! No need to check any flag bit, no need to attend to any interrupts, nothing. Just set the timer to this mode and we are done! Whenever a compare match occurs, the OC1A pin is automatically toggled!

Wie du siehst ist im Code ganz unten nie ein Interrupt Enable Bit gesetzt. Das darfst du in diesem Modus also nicht in setup() setzen. Dann wird auch nicht dein Programm unterbrochen.

Vollziehe das mal selbst nach. Ich habe damit jetzt nicht extrem viel Erfahrung. Dann merkst du auch ob in meiner Beschreibung irgendwo ein Fehler ist. Aber im Prinzip geht das so. :slight_smile:

Dann gibt es noch Fast PWM mit einem Tastverhältnis von 50%, aber das ist komplizierter:

Jedoch kann man hier explizit sagen ob man das invertierend oder nicht-invertierend habe möchte. Wenn man da 127 als Compare Wert nimmt (auf einem 8 Bit Timer) toggelt der Pin glaube ich wenn er 50% des Maximums erreicht und beim Maximum selbst (255). Dann kann man über die Compare Match Bits den Pegel bestimmen. Dazu ist in Teil 2, etwa in der Mitte Code für einen Kanal auf Timer0. Das kann man leicht anpassen auf andere Timer und 2 Kanäle.

Die Periode kann man da soweit ich das verstehe über den Prescaler einstellen. 980 Hz (ca. 1ms) sind auch leicht möglich:
http://playground.arduino.cc/Main/TimerPWMCheatsheet

Bei PWM Blicke ich aber nicht 100%ig durch.

huh starker Tobak :slight_smile: Dein Aufwand verdient Respekt . Da muss mich mich wohl erst mal einarbeiten.

Andererseits stell ich mir die Frage, ob ich nicht einfach im ctc nen counter abwärts zahlen lasse ( Beispiel I bis 444 Zählen
-->entspricht ca 7 sec gestoppt, diese müssen eh nicht genau sein) dann deaktivieren. Nen timer bis 1,5 sec laufen lassen.
Und dann die Zeit über 2 detectierte impulse über die Input capture ISR zu messen? Nach dem Timer ca. 1,5 ec. fertig, dann den i wieder zurücksetzten + ctc wieder aktivieren. Dann wieder von vorn.
Wäre dat ne Alternative?

Was willst du überhaupt allgemein machen? Ich habe da bisher Details erklärt, aber mir fehlt ein genereller Überblick was das alles bewirken soll. :slight_smile:
Wozu brauchst du die zwei Rechteck-Signale? Sowie ich es verstehe ist der ganze CTC Kram ja bisher nur zu deren Erzeugung.

Dafür Timer zu verwenden ist völlig ok, aber wenn du einfach nur eine bestimmte Zeit warten willst, dann geht das wie gesagt einfach mit millis(). Da braucht man auf dem Arduino nicht extra Timer zu starten. Siehe hier:

EDIT:
Der Input Capture Modus ist zwar sehr elegant, aber auf dem Arduino eigentlich für alles bis auf die genauesten Messungen überflüssig. Du kannst dir fast jederzeit mit millis() und micros() einen Zeitstempel zu einem Ereignis holen, der auf 1ms, bzw. 4-8µs genau ist. Das geht auch am Anfang einer ISR, auch wenn der Zähler selbst in der ISR nicht upgedatet wird. Aber man sollte sich ja auch nicht lange darin aufhalten.

Ich mache da gerade sowas, wo nach einer Aktion eine bestimmte Zeit gewartet werden soll bis was anderes anfängt oder die gleiche Aktion wiederholt werden soll. Vereinfacht:

void loop()
{
  static unsigned long previousDelayAfterProgramMillis;
  static boolean programFinished;
  static boolean delayAfterProgram;

   ..... //hier wird was gemacht das immer ausgeführt werden soll

   if(delayAfterProgram == true)   //die Variable ist automatisch mit false initialisiert, dadurch wird das erst mal nicht gemacht
   {
        if(millis() - previousDelayAfterProgramMillis >= delayAfterProgram * 100UL)
	{
	    delayAfterProgram = false;
	    
            //Code der ausgeführt wird wenn die Zeit ebgelaufen ist
	}
        else
	    return;		//loop fängt wieder von vorne an. Der Code darunter wird nicht ausgeführt bis dieses Delay abgelaufen ist
    } //endif

   programFinished = function();     //die Funktion meldet zurück wenn sie fertig ist. Im realen Code wird diese wiederholt zyklisch aufgerufen.
   
    if(programFinished == true)      //Aktion fertig
    {
	delayAfterProgram = true;   //oberen if-Block aktivieren
	previousDelayAfterProgramMillis = millis();  //damit die Differenz zur aktuellen Zeit bei 0 anfängr
   }
}

Da geht noch komplizierter. Ich führe da ständig zwei Zeit-Abfragen durch um regelmäßig Dinge zu tun. Eine davon ist der Aufruf der Funktion. Das andere ist alle 1 ms eine LED-Anzeige zu multiplexen (das packe ich vielleicht noch in eine Timer ISR, aber im Moment geht es auch ohne). Darüber liegt dann noch eine zweite Verzögerungs-Abfrage mit einem anderen Wert und anderen Aktionen wenn die Zeit abgelaufen ist.

Das da eine Funktion meldet wenn sie fertig ist, ist für dich glaube ich nichts, aber man könnte da genauso eine millis() Abfrage machen und wenn dieser Wert abgelaufen, setzt man den boolean so dass dann die obere Abfrage läuft. Erst wenn diese dann fertig ist kommt wieder die untere dran.

Da kann man z.B. direkt nach if(delayAfterProgram == true) aber vor dem Abfragen von millis() Code ausführen. Dieser wird dann immer ausgeführt wenn der boolean true ist und das solange bis die Zeit abgelaufen sind. So kann man zwei Blöcke (oder mehr) dieser Art schreiben und mit booleans gegeneinander verriegeln. Wenn die Zeit abgelaufen ist setzt man in einem Block den boolean um den andere freizugeben und umgekehrt.