Forum: Mikrocontroller und Digitale Elektronik Problem mit Timer Overflow interrupt


von Jörg M. (jrgm)


Lesenswert?

Ersteinmal ein herzliches Hallo an dieses Forum.

Ich beschäftige mich seit nicht allzulanger Zeit mit µCs und habe sehr 
viel Freude daran, mich in diese Welt einzuarbeiten - aber aller Anfang 
ist schwer.

Aber nun zu meinem Problem: Ich möchte Impulszeiten messen von ca. 1,6ms 
bis zu ca. 100ms mit einer Auflösung von 1µs und diese auf einem LCD 
(4x20 mit einem üblichen Kontroller)ausgeben. Nun passiert folgendes. 
Die Anzeige schwankt regelmäßig um 4,096ms und dies  bei 
unterschiedlichen Frequenzen. Dies entspricht genau einem Timeroverflow.
Ich habe einen Atmega32u4 auf einem Arduinoboard (16MHz), den ich an 
einem I2C-LCD-Display angeschlossen habe. Die Anzeige funktioniert sehr 
gut und dort kann ich u.a. die gemessene Impulszeit ablesen. An dem 
ICP1-Eingang habe ich einen Frequenzgenerator angeschlossen, der mir 
entsprechende 5V-Impulse generiert.

Ich habe schon sehr viel durchstöbert, mir die Tutorials angesehen und 
bin aber noch nicht auf einen grünen Zweig gekommen und bin mir fast 
sicher, etwas triviales übersehen zu haben. Mit der Zeit kommt die 
Erfahrung und die Übersicht über bestimmte Problematiken.

Ich habe das Listing mal beigefügt. Vielen Dank für eure Hilfe.
1
#include <avr/io.h>
2
#include <stdint.h>
3
#include <stdio.h>
4
#include <Wire.h>  
5
#include <LiquidCrystal_I2C.h>
6
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
7
8
#define PD4 4
9
10
unsigned long cnthigh;
11
word Counter1;
12
word Capture1;
13
unsigned long CNTDiff;
14
volatile float to;
15
16
void setup() 
17
{
18
    DDRD = 0 << PD4;                               
19
    PORTB = 1 << PD4;                              
20
    
21
    cli();                                        
22
    TCCR1A = 0;                                    
23
    TCCR1B = 0;                                    
24
    TIMSK1 = 1<<ICNC1 | 1 << TOIE1 | 1 << ICIE1;   
25
    TCCR1B = 1 << CS10 | 1 << ICES1;               
26
    sei();                                         
27
    cnthigh = 0;
28
29
    lcd.begin(20, 4);
30
    lcd.clear();
31
    lcd.home (); 
32
 
33
}
34
void loop()
35
{
36
    lcd.setCursor(0,0);lcd.print("to:");
37
    lcd.print(to);
38
    delay(1000);
39
}
40
ISR(TIMER1_CAPT_vect)
41
{
42
    Capture1 = ICR1;
43
    CNTDiff = Capture1 - Counter1;
44
    Counter1 = Capture1;
45
    to = ((cnthigh * 65535 + CNTDiff) * 0.0625);
46
    cnthigh = 0;
47
}
48
49
ISR(TIMER1_OVF_vect)
50
{
51
     cnthigh++;
52
}

von OldMan (Gast)


Lesenswert?

Du solltest erst einmal Dein vollständiges Programm zeigen, sonst wird 
das nix mit der Hilfe.

von Mitlesa (Gast)


Lesenswert?

Der Dativ ist dem Genitiv sein Tod.

Übersetzt heisst das

Die Floating Point Rechnung in der ISR wird dir die 
Rechengeschwindigkeit
deines Prozessors zunichte machen.

Dazu kommt noch dass ein Interrupt von der Arduino IDE per Default
eingerichtet wird, der wird es auch nicht schneller machen.

Mache die Berechung so oft wie du die Anzeige aktualisierst, ausserhalb
der ISR, dann ist die Rechenlast deutlich geringer und die Interrupts
können alle abgearbeitet werden

von MWS (Gast)


Lesenswert?

Du hast eine Racecondition, ein weiteres Problem des nichtatomaren 
Zugriffs, sowie eine Floatberechnung in einer ISR, die recht lang dauern 
dürfte, obwohl die ISR so kurz wie möglich sein sollte.

Rechne ein Beispiel durch:
Angenommen der IC schlägt bei 65000 zu, dann trödelt Deine 
Floatberechnung rum und in der Zwischenzeit läuft der Timer über. 
cnthigh wird zwar auf 0 gesetzt, aber sofort nachdem die IC-ISR beendet 
ist, wird die im Overflowflag gemerkte OVF-ISR ausgeführt, cnthigh wird 
1. Nimm an, dass bei Zählerstand 1000 das nächste IC-Ereignis auftritt, 
dann wird gerechnet unsigned 1000 - 65000 = 1536 + cnthigh * 65536 
(warum 65535?). Und schon ist das Ergebnis gar nicht mehr das 
Gewünschte.

Cntdiff würde man nur mit Wortbreite wählen, ein 0 - 65535 wird da 
automatisch = 1 und so will man das, da man die Overflows separat 
berücksichtigt.

Wg. atomar: während der Ausgabe auf dem LCD kann auch ein Teil des 
Ausgabewerts von der ISR verändert werden.

von Jörg M. (jrgm)


Lesenswert?

Vielen Dank für die schnellen Antworten.
Also - mehr Code hab ich nicht und der läuft auch bis auf dieses 
Problem.

Ich habe die Berechnungen mal ausgelagert,aber die Schwankungen wie 
gehabt exakt die 4,096ms.

Hier der geänderte Programmteil.

void loop()
{
    to = ((CNTH * 65535 + Diff) * 0.0625);
    lcd.setCursor(0,0);lcd.print("to:");lcd.print(to);lcd.print("   ");
    delay(1000);
}
ISR(TIMER1_CAPT_vect)
{
    Capture1 = ICR1;
    CNTDiff = Capture1 - Counter1;
    Counter1 = Capture1;
    Diff = CNTDiff;                // word Diff;
    CNTH = cnthigh;                // word CNTH;
    cnthigh = 0;
}

ISR(TIMER1_OVF_vect)
{
     cnthigh++;
}

von Peter D. (peda)


Lesenswert?


von Mitlesa (Gast)


Lesenswert?

Jörg Müller schrieb:
> aber die Schwankungen wie gehabt exakt die 4,096ms.

Versuche doch mal den Interrupt zu disablen den die
Arduino IDE generiert.

Dazu müsstest du aber dann die Rechnerei die in loop()
gemacht wird dann im main (...) in einer selbst geschriebenen
while (1)  Schleife laufen lasssen da die loop() nicht mehr
angesprungen wird.

von MWS (Gast)


Lesenswert?

Normalerweise macht man die Berechnung der Differenz in einer Variable 
mit Timer(Bit)breite. Dann ergibt ein 0 - 65535 eben 1, man hat dabei 
einen natürlichen Überlauf des Timers. Man prüft also, ob Capture_alt 
größer ist als Capture_neu, ist dies der Fall, so lag ein natürlicher 
Überlauf dazwischen, den man abziehen muss, d.h. man zieht von cnthigh 1 
ab.

von Jörg M. (jrgm)


Lesenswert?

Mitlesa schrieb:
> Versuche doch mal den Interrupt zu disablen den die
> Arduino IDE generiert.

Kannst du mir das noch mal näher erklären? Welchen meinst du? Mit "cli" 
werden doch global die Interrupts gesperrt.

In einer Arduino-Umgebung verwende ich void loop(). Warum diese 
Änderung? Ich stecke da leider noch nicht so tief in der Materie.

von Jörg M. (jrgm)


Lesenswert?

MWS schrieb:
> Man prüft also, ob Capture_alt
> größer ist als Capture_neu, ist dies der Fall, so lag ein natürlicher
> Überlauf dazwischen, den man abziehen muss, d.h. man zieht von cnthigh 1
> ab.

Dir noch mal vielen Dank, das war das Problem. Ich hatte mir die 
einzelnen Variablen im Serial.print angesehen und siehe da, die 4ms 
traten immer dann auf, wenn die Overflowvariable = 1 war und dies wenn 
der neue Wert kleiner war als der alte. Ich hab das dann so gelöst.

ISR(TIMER1_CAPT_vect)
{
    Capture1 = ICR1;
    Cpt1 = ICR1;
    Cnt1 = Counter1;
    CNTDiff = Capture1 - Counter1;
    Counter1 = Capture1;
    Diff = CNTDiff;
    CNTH = cnthigh;
    if (Cpt1 < Cnt1)
      {
        CNTH = CNTH - 1;
      }
     else
      {
       CNTH = cnthigh;
      }

    cnthigh = 0;
}

Noch mal Dankeschön für alle Tipps.

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.