Forum: Mikrocontroller und Digitale Elektronik Input Capture ISR optimieren


von Input (Gast)


Lesenswert?

Hi,

ich versuche gerade die Zeit zu messen, die ein Signal im Zustand "high" 
ist. Das Signal liegt am Input Capture Pin 1 (ICP1) an.

Es handelt sich hierbei um einen Ultraschall Entfernungsmesser, aber das 
sollte nichts zur Sache tun.

Im Wesentlichen läuft Timer1 im CTC Modus und löst alle 50 ms einen 
Interrupt aus. Darin wird der Trigger Pin für das Ultraschall Modul für 
10 uS auf High gesetzt sowie der Input Capture Trigger so eingestellt, 
dass er auf eine Rising Edge reagiert (Bit ICES1 gesetzt). Wie im 
Datenblatt angegeben lösche ich außerdem das Input Capture Flag (ICF1).

Nun gibt es die eigentliche ISR für den Input Capture. Hier wird 
zunächst geprüft, ob es sich um eine Rising Edge handelt. Sofern dies 
der Fall ist speichere ich den aktuellen Zählerstand in einer Variable, 
und setze den Input Capture Trigger auf "Falling Edge" (und lösche das 
Input Capture Flag wieder entsprechend).

Im Fall, dass die ISR bei einer Falling Edge aufgerufen wird, 
subtrahiere ich den aktuellen Zählerstand vom vorher festgehaltenen und 
habe damit die Zeit, die das Signal "high" war.

In der Theorie ganz einfach. In der Praxis habe ich aber stets ein 
"paar" Takte Versetzung, da das Einlesen des Zählerstandes (16 Bit 
Wert), sowie der Vergleich innerhalb der ISR, ob es sich nun um eine 
Rising bzw. Falling Edge handelt ja auch nicht "umsonst" ist.

Insbesondere die Fallunterscheidung gefällt mir nicht so recht. Gibt es 
hier eine bessere Alternative? Ich arbeite zum Ersten Mal mit dem Input 
Capture, insofern ist es durchaus möglich, dass ich etwas Grundlegendes 
übersehe?

Ich hänge hier mal noch die beiden ISRs in ihrer aktuellen Form an. In 
der Hauptschleife passiert nichts wirklich Interessantes. Dort wird nur 
bei Bedarf etwas auf einem LCD ausgegeben.

Ich hoffe, ich konnte den Ablauf halbwegs klar machen und freue mich auf 
eure Vorschläge!
1
static uint16_t echoHighLevelTime = 0;
2
ISR(TIMER1_COMPA_vect) {
3
4
  /*
5
   * Reset echoHighLevelTime in case there was an overflow
6
   */
7
  echoHighLevelTime = 0;
8
9
  /*
10
   * Send Trigger pulse (according to datasheet needs to be 10 uS at least)
11
   */
12
  PORT(TRIGGER) |= _BV(BIT(TRIGGER));
13
  _delay_us(10);
14
  PORT(TRIGGER) &= ~(_BV(BIT(TRIGGER)));
15
16
  /*
17
   * Set Input Capture trigger to rising edge
18
   */
19
  TCCR1B |= _BV(ICES1);
20
21
  /*
22
   * Clear Input capture flag
23
   */
24
  TIFR1 |= _BV(ICF1);
25
26
}
27
28
ISR(TIMER1_CAPT_vect) {
29
30
  static uint16_t highRiseStartCounterValue;
31
32
  /*
33
   * Check whether ISR was triggered by a rising edge
34
   */
35
  if (TCCR1B & _BV(ICES1)) {
36
37
    /*
38
     * Save the current counter value
39
     */
40
    highRiseStartCounterValue = TCNT1;
41
42
    /*
43
     * Set Input Capture trigger to falling edge
44
     */
45
    TCCR1B &= ~(_BV(ICES1));
46
47
    /*
48
     * Clear Input capture flag
49
     */
50
    TIFR1 |= _BV(ICF1);
51
52
  /*
53
   * ISR was triggered by a falling edge
54
   */
55
  } else {
56
57
    /*
58
     * Substract the counter value from the start in order to get
59
     * the duration of high level
60
     */
61
    echoHighLevelTime = TCNT1 - highRiseStartCounterValue;
62
63
    /*
64
     * Set update flag
65
     */
66
    updateLCD = true;
67
68
  }
69
70
}

von Karl H. (kbuchegg)


Lesenswert?

>     /*
>     * Save the current counter value
>     */
>    highRiseStartCounterValue = TCNT1;

Der Charme des Input Capture Interrupts besteht darin, dass der Timer 
selber den Zählerstand exakt beim Eintreten des Capture Ereignisses in 
einem eigenen Register sichert.

-> In der ISR holst du dir diesen Zählerstand aus diesem Register und 
nicht aus TCNT1. Denn dann ist es ziemlich egal (im Rahmen), wieviele 
Takte vom Capture Event vergehen, bis du dir den Wert abholst. Im 
Register ICR1 steht der Zählerstand, zu dem der Input Capture ausgelöst 
hat, egal wie weit der Zähler in der Zwischenzeit weiter gezählt hat.

   highRiseStartCounterValue = ICR1;

...

   echoHighLevelTime = ICR1 - highRiseStartCounterValue;


tatsächlich ist es genau dieses Feature, welches den Input Capture 
Interrupt interessant macht. Sonst könnte man ja auch gleich einfach nur 
einen externen Interrupt nehmen und bräuchte den Input Capture gar 
nicht.

von Cube (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> In der ISR holst du dir diesen Zählerstand aus diesem Register und
> nicht aus TCNT1.

Klar, da habe ich mich extreme trottelig angestellt. Vielen Dank für die 
Aufklärung ;).

Um die "Weiche" innerhalb der ISR komme ich aber nicht herum, oder? Auch 
wenn es das Ergebnis nicht verfälscht ...

von Karl H. (kbuchegg)


Lesenswert?

Cube schrieb:

> Um die "Weiche" innerhalb der ISR komme ich aber nicht herum, oder?

Nachdem du auf beide Flanken, steigend und fallend, reagieren willst: 
nein. Da führt kein Weg vorbei. Irgendwie musst du das unterscheiden.

Kostet doch fast nichts. Die 2 Assembler Instruktionen tun nicht weh.

von Cube (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Da führt kein Weg vorbei. Irgendwie musst du das unterscheiden.

Ok, vielen Dank. Und ist es denn wenigstens üblich das so zu machen wie 
ich, sprich innerhalb der ISR auf das Bit "ICES1" hin zu prüfen, oder 
hat sich da etwas anderes bewährt?

von Karl H. (kbuchegg)


Lesenswert?

Cube schrieb:
> Karl Heinz Buchegger schrieb:
>> Da führt kein Weg vorbei. Irgendwie musst du das unterscheiden.
>
> Ok, vielen Dank. Und ist es denn wenigstens üblich das so zu machen wie
> ich, sprich innerhalb der ISR auf das Bit "ICES1" hin zu prüfen, oder
> hat sich da etwas anderes bewährt?

Ich würde das als die beste Alternative ansehen.

Welche Möglichkeiten gibt es

* am Pin nachsehen ob der Pegel 0 oder 1 ist.
  WEnn 0, dann muss es eine fallende Flanke gewesen sein
  Wenn 1, dann muss es eine steigende Flanke gewesen sein

  Problem: Vom Zeitpunkt der Flanke aus gerechnet, bis dann
  endlich der Pin abgefragt wird, vergeht Zeit. Zeit, in der
  der Pin seinen Zustand schon wieder geändert haben könnte.

  -> ist also nicht 100% zuverlässig

* in einer Variablen mitzählen, der wievielte Aufruf das war
  (oder, was aufs gleiche rauskommt: eine Variable abwechselnd
   0 bzw. 1 setzen und anhand der entscheiden)
  Problem: Wird auch nur 1 Flanke verpasst (aus welchem Grund auch
  immer), kommt alles durcheinander. Anstatt der Länge des Pulses
  misst man die Länge der Pause.

* Beim INput Capture nachfragen, worauf eigentlich reagiert wurde.
  Also deine Methode
  Vorteil: Man hat die Gewissheit, dass die 'Auskunft' auf jeden
  Fall korrekt ist. Die ISR wurde aufgerufen, WEIL eine steigende
  Flanke detektiert wurde. Befrage ich die Cature Unit, wie sie
  gerade eingestellt ist, dann lautet die Auskunft: steigende Flanke
  Wäre da eine fallende Flanke gewesen, hätte die Capture Unit mit
  der Einstellung nicht reagiert.
  Kommt also der Interrupt und sagt mir die Capture Unit, das sie
  auf steigend konfiguriert ist, dann war da auch eine steigende
  Flanke. 100% sicher.

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.