Forum: Mikrocontroller und Digitale Elektronik input capture funktioniert nicht - Verständnisfrage


von Egon M. (kpc)


Lesenswert?

Hallo,

trotz vieler Suche bin ich mit meinem Ergebnis für eine 
input-capture-Abfrage nicht erfolgreich.
Ich wollte gern die Zeit zwischen ansteigender und fallender Flanke 
messen.

Ich hatte angenommen, wenn ich dem Atmega16 in das Register TCCR1B das 
ICSE1-Bit = 1 schreibe, startet input capture sofort, indem es den 
TCNT1-Wert in ICR1 kopiert und auf das nächste Event wartet, das ich in 
der Zwischenzeit geändert habe (ICSE1_Bit gelöscht, also fallende 
Flanke).

Wenn ich von dem bei letzterem Event in ICR1 kopierten TCNT1-Zählerstand 
den Anfangswert abziehe, sollte ich die gewünschten Counts erhalten.
Leider geschieht dies nicht, obwohl der Sensor passende Signale liefert 
und diese auch vom ICP detektiert werden.

Mache ich mir vielleicht das mit dem Auslesen der 16-bit-Werte aus TCNT1 
oder meinen Variablen zu einfach?

Ob sich mal jemand den Code ansehen könnte?
Vielen Dank schon mal.
Egon


uint16_t inpucap(void)
{
   int16_t mw, mw_alt=0, mw_neu=0;
   DDRD &= 191;           // ICR=PORTD6 wird Eingang
   TCNT1 = 0;             // vermeidet zunächst overflow-handling
   TCCR1B = (1<<CS10);    // CPU-Takt/8
   TCCR1B = (1<<ICES1);   // steigende Flanke gewählt
                          // automatischer Start ???
                          //  TCNT1-Wert wird in IRC1 kopiert
   mw_alt = ICR1;
   TCCR1B &= 191;         // fallende Flanke (ICES1-bit gelöscht)
   mw_neu = ICR1;
   mw = mw_neu - mw_alt;
   return (mw);
 }

von STK500-Besitzer (Gast)


Lesenswert?

Du solltest warten, bis das ICP-Interrupt aufgetreten ist.
Das kann man entweder in einer ISR mache oder per Polling.

von Johannes M. (johnny-m)


Lesenswert?

Oha, Du hast überhaupt nicht kapiert, wie Input Capture funktioniert! 
Du musst schon auf das Capture-Ereignis warten, bevor Du das Register 
ausliest!

Abgesehen davon: Steuerregister in Dezimalzahlen zu setzen ist die 
denkbar allerschlechteste Methode. Gewöhne Dir sofort grundsätzlich 
die Schreibweise mit Bitnamen oder wenigstens Hexadezimalzahlen an!

von Egon M. (kpc)


Lesenswert?

STK500-Besitzer wrote:
> Du solltest warten, bis das ICP-Interrupt aufgetreten ist.
> Das kann man entweder in einer ISR mache oder per Polling.

Die Interrupts sind abgeschaltet und sollten es auch bleiben. Ich habe 
genügend Zeit für Polling.  Ich hatte gedacht, man startet Zähler und 
wählt die Art der Flanke und die input capture Einheit wartet 
automatisch auf die passende Flanke und zählt.
Was muß ich jetzt noch tun?
Gruß
Egon

von Johannes M. (johnny-m)


Lesenswert?

Egon Müller wrote:
> Was muß ich jetzt noch tun?
Das Flag abfragen!

Und beim Polling dran denken, das Flag nach dem Abfragen auch wieder zu 
löschen!

von Egon M. (kpc)


Lesenswert?

Johannes M. wrote:
> Egon Müller wrote:
>> Was muß ich jetzt noch tun?
> Das Flag abfragen!
>

Also, ich habe jetzt gleich nach dem Löschen des Flankenauswahlbits 
eingefügt

 TIFR = (1<<ICF1);  //  (Manual S. 94)

aber mein mw ist konstant = 0.

> Und beim Polling dran denken, das Flag nach dem Abfragen auch wieder zu
> löschen!

Sollte ich also oben in die Funktion das  TIFR = (1<<ICF1); 
reinschreiben (das Manual schweigt sich dazu aus)?


Nachtrag:
Ich habe mal das mit dem Flankenwechsel weggelassen - wo man lt. Manual 
das ICF1-Bit nicht löschen muß   das Elend ist unverändert
Grüße
Egon

von STK500-Besitzer (Gast)


Lesenswert?

Du solltest eine While-Schleife einbauen, die das ICP-Interrupt-Flag 
abfragt.

von Johannes M. (johnny-m)


Lesenswert?

Du musst erst warten, bis das Flag gesetzt ist!! Erst dann hat die 
Capture-Einheit eine Flanke registriert! Schau Dir bitte mal das 
AVR-GCC-Tutorial an!

von STK500-Besitzer (Gast)


Lesenswert?

1
uint16_t inpucap(void)
2
{
3
   int16_t mw, mw_alt=0, mw_neu=0;
4
   DDRD &= 191;           // ICR=PORTD6 wird Eingang
5
   TCNT1 = 0;             // vermeidet zunächst overflow-handling
6
   TCCR1B = (1<<CS10);    // CPU-Takt/8
7
   TCCR1B = (1<<ICES1);   // steigende Flanke gewählt
8
                          // automatischer Start ???
9
                          //  TCNT1-Wert wird in IRC1 kopiert
10
   while(!(TIFR &(1<<ICF1)); // auf Interrupt warten
11
   TIFR |= (1<<ICF1);        // Interuptflag löschen
12
   mw_alt = ICR1;
13
   TCCR1B &= 191;         // fallende Flanke (ICES1-bit gelöscht)
14
   while(!(TIFR &(1<<ICF1)); // auf Interrupt warten
15
   TIFR |= (1<<ICF1);        // Interuptflag löschen   mw_neu = ICR1;
16
   mw = mw_neu - mw_alt;
17
   return (mw);
18
 }

von Matthias L. (Gast)


Lesenswert?

So geht das ohne Interrupt. reagiert immer auf dieselbe Flanke
1
void  main ( void )
2
{
3
  //-- lokale variablen ------------------------------------
4
  uint16_t    u16Temp;
5
  uint16_t    u16Period;
6
  uint16_t    u16Last;
7
  //-- Initialisierung -------------------------------------
8
  ...
9
10
  //-- Endlosschleife --------------------------------------
11
  while ( 1 )
12
  {
13
    //-- Ereignis aufgetreten? ------------------
14
    if ( TIFR & (1<<ICF1)  )
15
    {
16
      TIFR |= (1<<ICF1);    // Flag löschen
17
      //-- Dauer errechnen ----------
18
      u16Temp = ICR1;
19
      u16Period = ( u16Temp - u16Last );
20
      u16Last   = u16Temp;
21
      //-- mach was mit u16Period ---
22
      ....
23
    }
24
    //-- mach was anderes zyklisch --------------
25
    ....
26
  }
27
}

von Egon M. (kpc)


Lesenswert?

Johannes M. wrote:
> Du musst erst warten, bis das Flag gesetzt ist!! Erst dann hat die
> Capture-Einheit eine Flanke registriert! Schau Dir bitte mal das
> AVR-GCC-Tutorial an!

Da lese ich doch dauernd darin und finde da als einziges Bit Bit 
speziell für Input capture das ICF1, von dem in Manual zu lesen ist ,das 
es nach Flankenwechsel unverzüglich zu löschen sei, aber ohne 
Flankenwechsel sei das unnötig (S.94). Und dann gibt es noch ein hier 
interessierendes Bit für Zählerüberlauf. Aber sonst finde ich nichts, 
nicht mal über while-Schleifen.

Hast Du mal meinen Code besehen, ist da vielleicht ein Fehler drin?


Hi STK500-Besitzer:
>Du solltest eine While-Schleife einbauen, die das ICP-Interrupt-Flag
>abfragt.
Interrupts wollte ich doch nicht

Gruß
Egon

von Egon M. (kpc)


Lesenswert?

Hi Matthias und STK500-Besitzer

vielen Dank, das probiere ich mal aus

Egon

von STK500-Besitzer (Gast)


Lesenswert?

>Interrupts wollte ich doch nicht

Der Controller meldet "Fertig!" mit setzen des ICF1. Das löscht man dann 
wieder und kann wieder auf das Setzen warten.

Das, wovor du Angst hast sind Interrupt Service Routinen. Aber selbst 
das ist keine Hexerei...

von Egon M. (kpc)


Lesenswert?

Hallo STK500Besitzer

Deine Version habe ich schon ausprobiert,
es sieht insofern gut aus, als ein konstanter Wert ungleich null
angezeigt wird.
Aber mit meinen Variablen scheint irgendetwas schief zu laufen, denn die 
Anzeige ist immer -2. Vielleicht muß man doch high - und low-Byte 
getrennt auslesen?

Egon

von Matthias L. (Gast)


Lesenswert?

>Aber mit meinen Variablen scheint irgendetwas schief zu laufen

Mach mal aus
1
   int16_t mw, mw_alt=0, mw_neu=0;
ein
1
  uint16_t  ...

von STK500-Besitzer (Gast)


Lesenswert?

Um die Pulslänge zu messen, rufst du jedes Mal diese Funktion auf?
Was'n Stress für den Timer...
Einfach irgendwo (Hauptprogramm/Initalisierung) den Timer starten und 
dann zur Pulslängenfeststellung die Routine aufrufen.

von Egon M. (kpc)


Lesenswert?

STK500-Besitzer wrote:
>>Interrupts wollte ich doch nicht
>
>
> Das, wovor du Angst hast sind Interrupt Service Routinen. Aber selbst
> das ist keine Hexerei...

Ich fürchte mich deshalb davor, weil dieses Modul in eine größeres 
Programm eingefügt werden soll, das gespickt ist mit undurchschaubaren 
Interrupts.
Und wenn ich hier irgendetwas habe, dann ist es Zeit. Deshalb wollte ich 
mir zusätzliche Komplikationen ersparen.

von Egon M. (kpc)


Lesenswert?

Matthias Lipinsky wrote:
>>Aber mit meinen Variablen scheint irgendetwas schief zu laufen
>
> Mach mal aus
>
1
>    int16_t mw, mw_alt=0, mw_neu=0;
2
>
> ein
>
1
>   uint16_t  ...
2
>

Schade, war schon drin, ist nur beim Kopieren verloren gegangen.

Aber main ist definiert
>int main(void).
Eigentlich hätte ich gern geschrieben
>uint16_t main(void),
aber  das mag der Compiler nicht. Aber innerhalb von
main ist mw mit uint16_t bezeichnet.

von Matthias L. (Gast)


Lesenswert?

>Und wenn ich hier irgendetwas habe, dann ist es Zeit. Deshalb wollte ich
>mir zusätzliche Komplikationen ersparen.

Muhahaha..

Und du meinst, das wird dan alles funktionieren, wenn du in deiner 
Routine
mit der Zeile
1
while(!(TIFR &(1<<ICF1)); // auf Interrupt warten
Den Prozessor solange anhälst, bis du mal ein Signal ankam?

von Egon M. (kpc)


Lesenswert?

Matthias Lipinsky wrote:
>>Und wenn ich hier irgendetwas habe, dann ist es Zeit. Deshalb wollte ich
>>mir zusätzliche Komplikationen ersparen.
>
> Muhahaha..
>
> Und du meinst, das wird dan alles funktionieren, wenn du in deiner
> Routine
> mit der Zeile
>
1
> while(!(TIFR &(1<<ICF1)); // auf Interrupt warten
2
>
> Den Prozessor solange anhälst, bis du mal ein Signal ankam?

Durchaus. Wenn das Signal nicht kommt, ist alles wertlos. Für den 
Prozessor gilt dann mitgegangen, mitgehangen...

von Egon M. (kpc)


Lesenswert?

STK500-Besitzer wrote:
> Um die Pulslänge zu messen, rufst du jedes Mal diese Funktion auf?
> Was'n Stress für den Timer...
> Einfach irgendwo (Hauptprogramm/Initalisierung) den Timer starten und
> dann zur Pulslängenfeststellung die Routine aufrufen.

Hatte ich auch vor, es gibt da ein main und von da wird dann auch die 
Meßwerterfassung aufgerufen (z.B. einmal täglich).

Nun muß ich nur noch die Variablen hinbiegen, damit ich irgendetwas 
auswerten kann. Leider gibt es input capture nur bei den 16-bit-Zählern. 
Mit 8 bit könnte man es leichter testen.

Grüße
Egon

von Egon M. (kpc)


Lesenswert?

Hallo Matthias
ich habe es für meine Variablen umgeschrieben,
aber das Elend ist nicht anders geworden, es wird für mw immer nur 0 
angezeigt.
Ich glaube, es wird langsam Zeit zum Verzweifeln.

Gruß
Egon


Matthias Lipinsky wrote:
> So geht das ohne Interrupt. reagiert immer auf dieselbe Flanke
>
>
1
> void  main ( void )
2
> {
3
>   //-- lokale variablen ------------------------------------
4
>   uint16_t    u16Temp;
5
>   uint16_t    u16Period;
6
>   uint16_t    u16Last;
7
>   //-- Initialisierung -------------------------------------
8
>   ...
9
> 
10
>   //-- Endlosschleife --------------------------------------
11
>   while ( 1 )
12
>   {
13
>     //-- Ereignis aufgetreten? ------------------
14
>     if ( TIFR & (1<<ICF1)  )
15
>     {
16
>       TIFR |= (1<<ICF1);    // Flag löschen
17
>       //-- Dauer errechnen ----------
18
>       u16Temp = ICR1;
19
>       u16Period = ( u16Temp - u16Last );
20
>       u16Last   = u16Temp;
21
>       //-- mach was mit u16Period ---
22
>       ....
23
>     }
24
>     //-- mach was anderes zyklisch --------------
25
>     ....
26
>   }
27
> }
28
> 
29
>

von Matthias L. (Gast)


Lesenswert?

Poste doch mal den kompletten Code von dir. Kopier ihn mal rein.

Ich bin grad an einer Drehzahlmessung per ICP dran. Die Periodendauer 
wird schon gemessen mit Interrupt und folgendem Code:
1
ISR ( TIMER1_CAPT_vect )
2
{
3
  //-----------------------------------------------------------------
4
  //-- lokale Variablen ---------------------------------------------
5
  uint32_t  u32Temp  = (   (  ( (uint32_t)g_u16TimeTick  ) << 16)
6
                         | (  ( (uint32_t)ICR1           )      )  );
7
8
  //-- Zeitstempel speichern und Periodendauer errechnen ------------
9
  scMeasData.u32Period = ( u32Temp - scMeasData.u32TsLast );
10
  scMeasData.u32TsLast = u32Temp;
11
  //-- Umdrehung merken ---------------------------------------------
12
  scMeasData.u16NbrRev++;
13
}
14
15
ISR ( TIMER1_OVF_vect )  // alle 32.768ms @ 2MHz
16
{
17
  //-----------------------------------------------------------------
18
  //-- Zeitvariable erhöhen -----------------------------------------
19
  g_u16TimeTick++;
20
  ...

von Egon M. (kpc)


Angehängte Dateien:

Lesenswert?

Matthias Lipinsky wrote:
> Poste doch mal den kompletten Code von dir. Kopier ihn mal rein.
>
> [/c]

Mache ich, hoffentlich gelingt es.
Ich habe die endlosen Passagen weggelassen, wo ein I2C angesprochen wird 
und ein PCF8575 (oder so ähnlich), das LCD usw..

Ich habe in der Zwischenzeit getestet, ob vielleicht meine Zahlenausgabe 
die Fehlerursache sein könnte. Ist sie wahrscheinlich nicht.
In der Funktion zahl_lcd (ziemlich am Anfang) ist als einziges, was man 
vielleicht ändern sollte, die char-Variable. Ist vielleicht mit 8 bit 
etwas wenig?
Anderseits werden Zahlen bis zu 0x7FFF korrekt auf meinem LCD angezeigt, 
nur, was darüber ist, nicht mehr.
Ich habe probeweise den TCNT1 simuliert mit 0x7FFF, das wird angezeigt, 
dann habe ich den Original-TCNT1 anzeigen lassen, das fängt mit 2 an und 
zählt sich dann langsam hoch, innerhalb einer Minute auf etwa 100.
Da ist wohl etwas faul, der Zähler scheint nicht zu laufen, aber 
anderseits nimmt er langsam zu.

Fehlt vielleicht in meinem Programm ein Starbefehl? ich habe z.B TCCR1A 
nicht erwähnt, weil es in der richtigen Weise vorkonfiguriert ist.

Bis morgen
Egon

von Karl H. (kbuchegg)


Lesenswert?

> Ich glaube, es wird langsam Zeit zum Verzweifeln.

Das glaub ich nicht.
Es wird Zeit, dass du dir jemanden suchst und bezahlst, der wenigstens 
ein bischen was von programmieren versteht.

Das ist so ziemlich das schrottigste Programm, das ich in 25 Jahren 
gesehen habe.

> In der Funktion zahl_lcd (ziemlich am Anfang) ist als einziges,
> was man vielleicht ändern sollte, die char-Variable. Ist vielleicht
> mit 8 bit etwas wenig?

Du hast keine Ahnung was du eigentlich machst.
Ja an der Funktion muss man Hand anlegen, allerdings nicht an der 
'char-Variablen' (die in Wirklichkeit ein Array ist). Die und der itoa 
Aufruf sind so ziemlich das einzig Korrekte in dieser Funktion.

Nichts für ungut. Aber du versuchst gerade in Schuhen zu laufen, die dir 
mindestens 5 Nummern zu gross sind.

Edit: Korrektur. Der itoa Aufruf ist auch falsch.

von Stefan E. (sternst)


Lesenswert?

Matthias Lipinsky wrote:
1
     TIFR |= (1<<ICF1);    // Flag löschen

Ein einzelnes Flag löscht man mit "=".
Mit "|=" werden gleich alle Flags in dem Register gelöscht.

von Matthias L. (Gast)


Lesenswert?

>Ein einzelnes Flag löscht man mit "=".
>Mit "|=" werden gleich alle Flags in dem Register gelöscht.

Du bist soeben auf das Feld "Gehe zurück zu Anfang gesprungen"

von Stefan E. (sternst)


Lesenswert?

Matthias Lipinsky wrote:

> Du bist soeben auf das Feld "Gehe zurück zu Anfang gesprungen"

Sorry, verstehe ich nicht.

von Karl H. (kbuchegg)


Lesenswert?

Matthias Lipinsky wrote:
>>Ein einzelnes Flag löscht man mit "=".
>>Mit "|=" werden gleich alle Flags in dem Register gelöscht.
>
> Du bist soeben auf das Feld "Gehe zurück zu Anfang gesprungen"

Und dabei darf er €2000 einziehen.

(Er hat recht. So seltsam es auch aussieht, aber zb. mit  TIFR |= 0 
werden alle Flags in TIFR gelöscht. Es spielt keine Rolle was rechts vom 
|= steht. Das |= ist der Übeltäter. Remember: Ein Flag in TIFR wird 
zurückgesetzt, indem man eine 1 reinschreibt. Welche Bits sind bei |= 
auf jeden Fall 1. Genau! Die, die es vorher auch schon waren. Ergo ....

von Egon M. (kpc)


Lesenswert?

Karl heinz Buchegger wrote:

> Das ist so ziemlich das schrottigste Programm, das ich in 25 Jahren
> gesehen habe.
>
Das ist kein Programm, sondern der schnell zusammengezimmerte Versuch, 
den Problemen des input capture auf die Spur zu kommen. Es enthält 
eigene Passagen und parallel eingefügte Sequenzen hier aus dem Forum.
Das hätte man leicht erkennen können.

> Nichts für ungut. Aber du versuchst gerade in Schuhen zu laufen, die dir
> mindestens 5 Nummern zu gross sind.

Bleibt auf dem Teppich; es sind höchstens drei Nummern

> Edit: Korrektur. Der itoa Aufruf ist auch falsch.
itoa ist richtig.

Einen konstruktiven Beitrag hast Du nicht? Eigentlich hättest Du aus der 
Diskussion feststellen müssen, daß der Zähler nicht läuft.
Einen Vorschlag, wie man ihn starten kann?


mfg
Egon

von Karl H. (kbuchegg)


Lesenswert?

Egon Müller wrote:

>> Edit: Korrektur. Der itoa Aufruf ist auch falsch.
> itoa ist richtig.

Das glaub ich nicht, Tim.
Welchen Datentyp hat denn der Eingangsparameter deiner Funktion?
Und welchen Datentyp erwartet itoa?
Und warum gibt es wohl eine Funktion utoa?

> Bleibt auf dem Teppich; es sind höchstens drei Nummern
Gemessen an so manchen deiner Aussagen hier in diesem Thread, sind 5 
Nummern zu gross noch freundlich ausgedrückt.

> Einen konstruktiven Beitrag hast Du nicht?
Doch hatte ich.
Such dir jemanden, der zumindest ein wenig programmieren kann.
Und das ist mein tatsächlicher, wirklicher Ernst! Und ich sag das nicht, 
weil ich mich lustig machen will.

von Johannes M. (johnny-m)


Lesenswert?

Du solltest Dich auch ganz dringend über Gültigkeitsbereiche von 
Variablen informieren! Es gibt in Deinem "Programm" einige Stellen, an 
denen eine Variable namens mw benutzt wird. Diese Variable existiert 
an diesen Stellen aber gar nicht! Das müsste Dir schon allein deswegen 
beim compilieren um die Ohren fliegen...

Ich muss Karl heinz Recht geben, auch wenn es vielleicht tatsächlich ein 
bisschen übertrieben ist, wenn er schreibt, dass es das "schrottigste 
Programm in 25 Jahren" sei. Selbst ich habe schon schlimmeres gesehen, 
aber nichtsdestotrotz ist es in weiten Teilen wirklich Schrott.

Und dass Dein Timer nicht läuft, wundert mich auch nicht. Schließlich 
schaltest Du ihn jeweils eine Zeile nach seiner Aktivierung schon wieder 
aus...

Wenn Du schreibst
1
REGISTER = WERT1;
2
REGISTER = WERT2;
was glaubst Du, was nach der zweiten Anweisung noch von dem übrig ist, 
was die erste Anweisung gemacht hat? Genau, die erste Anweisung kannste 
dann auch direkt weglassen.

Ehrlich: Dir fehlen absolute Grundlagen, nicht nur was den Controller 
und seine Funktionen angeht, sondern auch, was die Programmiersprache 
betrifft. Und einer wie Du hat sicher nicht das Recht, dem Karl heinz, 
der in Sachen didaktisch wertvoller Beiträge hier der Ansprechpartner 
Nummer 1 ist, in einem solchen Ton zu begegnen...

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.