Forum: Mikrocontroller und Digitale Elektronik Input Capture funktioniert nicht richtig


von Martin Neumann (Gast)


Lesenswert?

allo zusammen,

ich bin dabei, mit einer Capture Unit ein Frequenz zu messen. die
Frequenz ist recht gering und spielt zwischen 0 und ca. 250Hz.
Verwendet wird dabei ein Atmega 8. Ein Optokoppler schaltet dabei bei
jedem Signal Masse an den ICP Pin durch. Sonst liegt der ICP Pin durch
einen externen pullup (10K) auf "high". Eine LED hängt an PB1 auf active 
low
(zur Frequenzdarstellung).
Das Hauptprogramm soll später einmal die Frequenz dann aus den Timer
werten berechnen, aber das Einlesen klappt im Moment noch nicht ganz.
Gedacht ist, zunächst bei fallender Flanke einzulesen, Wert sichern eine
LED einschalten, die Flanke wechseln, darin dann die LED ausschalten,
nächste fallende einlesen, Endwert sichern, LED wieder anschalten,
wieder auf steigende Flanke wechseln und darin die LED ausschalten.
Durch den Start und Endwert kann ich dann später meine Frequenz
errechnen.

Nur das Problem ist, die LED müsste mit steigender Frequenz immer
kräftiger erscheinen, bis sie für das Auge ab ca. 30-40 Hz in
"Dauerleuchten" übergeht. Leider "hängt" die LED manchmal zwischendrin,
als ob sie nicht ausgeschaltet wird, also deutlich länger und dadurch 
auch heller leuchtet/flackert.
Die Singale sind vom Optokoppler logischerweise prellfrei, daher vermute 
ich das Problem im Code:
1
#include <stdint.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <inttypes.h>
5
6
// ----------------------------------------------------------------------------
7
// DEFINES
8
// ----------------------------------------------------------------------------
9
#ifndef F_CPU
10
#define F_CPU           8000000UL                   // processor clock frequency 8Mhz
11
#endif
12
13
#define Input    PB0    //Pin des Input Captures PB0=ICP
14
#define LED      PB1    //Zeigt Frequenz
15
16
17
#ifndef TRUE
18
#define TRUE 1
19
#define FALSE 0
20
#endif
21
22
23
24
// ----------------------------------------------------------------------------
25
// globale Variablen 
26
// ----------------------------------------------------------------------------
27
volatile unsigned char NrOverflowsTC1 = 0; 
28
volatile unsigned int StartTime = 0;      
29
volatile unsigned int EndTime = 0;        
30
volatile unsigned char Messung_vollstaendig = 0;//JobFlag
31
volatile unsigned char ErsteFlanke = TRUE; 
32
33
//Timer1 (16-Bit) Capture Interrupt Service Routine
34
35
ISR( TIMER1_CAPT_vect ){
36
    if ( !(TCCR1B & (1<<ICES1) )){        //fallende Flanke?
37
      if ( ErsteFlanke ){            //Ist es die erste Flanke oder Wurde schon eine Flanke eingelesen? 
38
        StartTime = ICR1;          //StartWert sichern
39
        PORTB &= ~( 1 << LED );      //LED an PB1 einschalten  
40
        NrOverflowsTC1 = 0;          //Setzt die Overflows des Timer1 (16-bit) zurück (Timeout Sicherheit)
41
        ErsteFlanke = FALSE;             //Die naechste Flanke ist das Ende der Messung
42
      }
43
44
      else{                  //dies ist die zweite Flanke
45
        EndTime = ICR1;            //EndWert
46
        PORTB &= ~( 1 << LED );      //LED an PB1 einschalten  
47
      }
48
    }
49
    else{                    //steigende Flanke
50
      PORTB |= ( 1 << LED);          //schaltet die LED an PB1 wieder aus
51
      Messung_vollstaendig++;          //Wenn Wert=2, Messung vollständig
52
      if (Messung_vollstaendig == 2){      
53
        ErsteFlanke = TRUE;               //Neue Messung kann starten
54
        Messung_vollstaendig = 0;
55
      }
56
    }
57
  TCCR1B ^= ( 1 << ICES1 );            //Schaltet die zu erkennende Flanke um, von fallend auf steigend oder umgekehrt
58
  TIFR |= ( 1 << ICF1 );              /*Nachdem ein Flankenwechsel gesetzt wird, muss das Interrupt Flag von der Software gelöscht werden
59
                            dies geschieht durch Schreiben einer logischen 1 in das ICF1 Register*/
60
}
61
62
63
//Timer1 (16-Bit) Overflow Interrupt Service Routine
64
65
ISR (TIMER1_OVF_vect){
66
NrOverflowsTC1++;
67
68
}
69
70
//Hauptprogramm
71
int main()
72
{
73
74
  
75
76
  DDRB = 0x00;                    //Datenrichtungsregister zunächst alle Eingang 
77
  DDRB |= (1 << DDB1);                 //PB1 als Ausgang schalten für LED
78
  PORTB |= (1 << LED);
79
  
80
  TCCR1B = (1 << CS10) | (1 << CS11);          // Prescaler 64
81
  TCCR1B &= ~(1<<ICES1);                  // Interrupt Capture wird zunächst auf fallende Flanke eingestellt
82
  TIMSK = (1<<TICIE1) | (1<<TOIE1);             // Aktiviert Input Capture Interrupt und Timer1 Overflow Interrupt
83
  
84
  sei();                  //setzt globales Interrupt enable
85
  
86
  while(1){
87
  }
88
  
89
}

Ich habe es auch schon mit dem Noise Cancellor versucht, trotzdem das 
gleiche Problem. Rauscht der Optokoppler überhaupt?

von Karl H. (kbuchegg)


Lesenswert?

Was hast du zum Testen auf der anderen Seite des Optokopplers hängen?

von Ulrich (Gast)


Lesenswert?

Die Zeile
  TIFR |= ( 1 << ICF1 );
hat ein Problem:  damit werden alle aktiven Interupt flags gelöscht 
(Wenn das Time Register nicht mehr per SBI ansprechbar ist, was der Fall 
sein sollte). Beisser zu löschen nur
  TIFR  = ( 1 << ICF1 );
Die Verknüpfung macht ja die Logic im Register.


Es fehlt noch die Behandlung des Sonderfalls wenn ICP und Overflow 
Interrupt fast gleichzeitig auftreten: Da kann es passieren das der 
Overflow Interrupt zu spät kommt. Das ist aber ein eher selten 
auftretendes Problem, das aber zu sletenen Ausreißern bei den Messwerten 
führt.

Die LED sollte eine Kopie des Eingangssignals sein. Die helligkeit ist 
dabei nicht unbedingt von der Frequenz abhängig.

von Martin Neumann (Gast)


Lesenswert?

Hallo,

heute habe ich das ganze mal nicht auf dem Breadboard versucht und siehe 
da, mit einer Lochrasterplatine habe ich dieses Flackern nicht mehr. 
Aber ich MUSS den Noise Cancellor aktivieren. Ohne diesen flackert es 
noch mächtig.
Werde nun demnächst noch das Hauptprogramm schreiben und testen, ob die 
eingelesenen Frequenzen auch stimmen, indem ich einfach ab einer 
Grenzfrequenz eine LED einschalte.

Ulrich schrieb:
> Die Zeile
>   TIFR |= ( 1 << ICF1 );
> hat ein Problem:  damit werden alle aktiven Interupt flags gelöscht
> (Wenn das Time Register nicht mehr per SBI ansprechbar ist, was der Fall
> sein sollte). Beisser zu löschen nur
>   TIFR  = ( 1 << ICF1 );
> Die Verknüpfung macht ja die Logic im Register.


Dies verstehe ich nicht ganz (meine C-Kenntnisse sind für 
Mikrocontroller nicht nicht ganz optimal).
Kann mir jemand mal den Unterschied erklären?

Ulrich schrieb:
> Es fehlt noch die Behandlung des Sonderfalls wenn ICP und Overflow
> Interrupt fast gleichzeitig auftreten: Da kann es passieren das der
> Overflow Interrupt zu spät kommt. Das ist aber ein eher selten
> auftretendes Problem, das aber zu sletenen Ausreißern bei den Messwerten
> führt.

Wie kann ich an das Problem rangehen? Der Overflow löst doch direkt nach 
Verlassen der Capture ISR dann trotzdem aus und ist so kurz, dass er 
wohl nicht viel bremst. Die Anzahl der Overflows spielen dann ja später 
nur bei der Berechnung der Frequenz eine Rolle oder habe ich einen 
Denkfehler?

von Tim (Gast)


Lesenswert?

>Wie kann ich an das Problem rangehen? Der Overflow löst doch direkt nach
>Verlassen der Capture ISR dann trotzdem aus und ist so kurz, dass er
>wohl nicht viel bremst. Die Anzahl der Overflows spielen dann ja später
>nur bei der Berechnung der Frequenz eine Rolle oder habe ich einen
>Denkfehler?

Der Overflow IRQ hat beim AVR eine geringere Priorität als der
Capture IRQ. (PDF Seit 14 "Reset and Interrupt Handling")

Stell dir Folgende Situation vor:
cli();
Overflow IRQ wird ausgelöst
Capture IRQ wird ausgelöst
sei();

Overflow UND Capure IRQ stehen an.
Zuerst wird der Overflow IRQ bearbeitet.
Wenn der ebenfalls anstehenden Overflow IRQ
nicht beachtet wird kommt er ein falscher Messwert raus.
Auch der nächte Capure IRQ Misst dann Schrot,
da er einen Overflow zu viel drin hat.

Also im Capture IRQ prüfen ob Overflow ansteht UND
ob der eher zur aktuellen Messung gehört (Zäherstand klein)
oder schon zur nächsten Messung (Zählerstand groß).
Im 1. Fall den Overflow mit zählen und das IRQ Flag löschen.

von Ulrich (Gast)


Lesenswert?

Der Teil mit TIFR hat nichts mit C Kenntnissen, sondern einer 
spezialität der REstister TIFR bein AVR zu tun:

Das Register TIFR ist kein normales Register. Um ein Bit zu löschen 
schreibt man eine 1 in genau das Bit. Wenn eine Null reingeschrieben 
wird, bleibt ein Flag erhalten.
Wenn man die oder verknüpfung macht, leißt die CPU erst TIFR aus, fügt 
ggf. noch ein Bit hinzu und schreibt dann wieder zurück nach TIFR. Damit 
werden alle gesetzten Flags gelöscht.

von Martin Neumann (Gast)


Lesenswert?

Tim schrieb:
> Overflow UND Capure IRQ stehen an.
>
> Zuerst wird der Overflow IRQ bearbeitet.
>
> Wenn der ebenfalls anstehenden Overflow IRQ
>
> nicht beachtet wird kommt er ein falscher Messwert raus.
>
> Auch der nächte Capure IRQ Misst dann Schrot,
>
> da er einen Overflow zu viel drin hat.

Du meinst ja sicherlich, dass zuerst der Capture IRQ bearbeitet wird.
Ok, ich verstehe den 1. Fall, dass wenn ein Overflow Interrupt währned 
des Capture Interrupts eintritt, dass der Messwert falsch ist.
Aber wieso wirkt sich das auf das nächste Capture Interrupt auch aus 
bzw. was meinst du damit, dass dann ein Overflow zuviel drin ist? Das 
verstehe ich noch nicht. Die zu messenden Frequenzen sind ja so niedrig, 
das zwischen zwei Capture Interrupts auf jedenfall der Overflow 
abgearbeitet wird. Daher ist mir das noch nicht ganz klar.

Angenommen ich würde die Frequenzberechnung im Hauptprogramm durchführen 
(und nicht in irgendeinem Interrupt), so wäre doch mein Overflow Zähler 
immer aktuell ohne dass ich mich kümmern müsste.

Dein Lösungsvorschlag ist zwar mir klar, doch weiß ich nicht, welchen 
Wert man als kleinen Zähler nimmt. Theoretisch müsste ich vor der 
Frequenzberechnung (angenommen macht macht diese in der ISR) abfragen, 
ob das Overflow Interrupt anliegt. Und dann müsste ich wissen, wieviel 
Timerzyklen seit Beginn der Capture ISR bis zur Frequenzberechnung 
vergangen sind (da wo die Anzahl der Overflows relevant ist), um einen 
Grenzzählerstand abzufragen...

von Ulrich (Gast)


Lesenswert?

Es hilft nichts die Auswertung im Hauptprogramm zu machen. Ein Fehler 
bei der Zeitmessung kann da ja nicht mehr korrigiert werden.

Das Problem ist das ein kurz vor dem ICP Interupt aufgetretender 
Overflow, der nicht mehr vor der ICP ISR ausgeführt werden kann. Ursache 
kann ein Befehl mit mehr Zyken (z.B. RET) oder ein CLI/SIE Block sein. 
Erkennen kann man das am besten in der ICP-ISR, und das ist gar nicht so 
schwer:

Ein noch ausstehender overfow (Flag in TIFR gesetzt) müßte eigentlich 
vor den ICP Interrupt, wenn der Wert im ICP Register klein ist. Ein 
kleiner Wert heißt ja, dass der ICP Interrupt kurz nach dem Overflow 
ausgelöst wurde.

Wenn nicht gerade extrem lange CLI/SEI Blöcke vorkommen, ist bei 
anstehenden Overflow Interrupt der Wert im ICP Register entweder sehr 
sehr klein (High byte 0) oder sehr groß (High Byte 255). Wo man jetzt 
genau die Grenze setzt ist relativ egal, anbieten würde sich 128 fürs 
High Byte.

von Martin Neumann (Gast)


Lesenswert?

Ulrich schrieb:
> Es hilft nichts die Auswertung im Hauptprogramm zu machen. Ein Fehler
>
> bei der Zeitmessung kann da ja nicht mehr korrigiert werden

Das ist der Punkt, den ich vorhin schon nicht verstanden habe.
Angenommen dieser Fall tritt auf. IPC Interrupt erfolgt, obwohl OVF 
Interrupt gesetzt. Nun wird mein TimerStart und TimerEnd Wert im ICP 
eingelesen (dieser ist ja ok). Die ICP wird verlassen, das ausstehende 
OVF Interrupt wird direkt ausgeführt und erhöht den OVF Zähler. Der OVF 
Zähler ist nun wieder aktuell.
Und nun erst erfolgt doch die Auswertung im Hauptprogramm, beide Zähler 
sind aktuell, oder etwa nicht?

Dein Lösungsvorschlag dürfte in Code also so aussehen?
1
if (TIMSK & (1 << TOIE1){      //overflow flag getzt?
2
  if (TCNT1H < 128){        //high byte kleiner 128?
3
  NrOverflowsTC1++;        //erhöhe overflow
4
  TIMSK &= ~(1 << TOIE1);      //lösche interrupt flag
5
  }
6
}

von Karl H. (kbuchegg)


Lesenswert?

Martin Neumann schrieb:

> Und nun erst erfolgt doch die Auswertung im Hauptprogramm, beide Zähler
> sind aktuell, oder etwa nicht?

Damit handelst du dir aber schon wieder das nächste Problem ein.
Der Overflow kann auch während der Ausführung des ICP Interrupts kommen. 
D.h er gehört eigentlich nicht mehr zu dieser Messung, sondern zur 
nächsten.
Dein Hauptprogramm weiß das aber nicht und rechnet dann einen Overflow 
zu viel ein.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.