Problem mit Timer Overflow interrupt

Ersteinma ein herzliches Hallo an die, die mir helfen können.

Ich beschäftige mich seit nicht allzulanger Zeit mit Arduino und den µCs und habe sehr viel Freude daran, mich in diese Welt einzuarbeiten.
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. Nun passiert folgendes. Die Anzeige schwankt regelmäßig von der exakten Zeit bis zu einer um 4,096ms längeren bei unterschiedlichen Frequenzen. Dies entspricht genau einem Timeroverflow.
Ich habe einen Arduino Micro (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 das Listing mal beigefügt. Vielen Dank für die Hilfe.

#include <avr/io.h>
#include <stdio.h>
#include <Wire.h>  // Comes with Arduino IDE
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

#define PD4 4
word cnthigh;
word Counter1;
word Capture1;
word CNTDiff;
float to;

void setup() 
{
    DDRD = 0 << PD4;                               // Pin PD4 als ICP1 als Eingang 
    PORTB = 1 << PD4;                              // Pin PD4 Pullup aktiviert
    
    cli();                                         // alle Interrupts deaktivieren
    TCCR1A = 0;                                    // Bits von TCCR1A löschen
    TCCR1B = 0;                                    // Bits TCCR1B löschen
    sei();                                         // alle Interrupts aktivieren
    TIMSK1 = 1 << TOIE1 | 1 << ICIE1;              // TimerInterruptOverflow + Capture-Interrupt
    TCCR1B = 1 << CS10 | 1 << ICES1;               // Timer starten ohne Prescaling und steigende Flanke

    lcd.begin(20, 4);
    lcd.clear();
    lcd.home (); 

 void loop()
{
    Fo = 1000000 / to;
    Vo = (3.6 * 400000 / to);
    lcd.setCursor(0,0);lcd.print("to:");lcd.print(to);
    lcd.setCursor(8,0);lcd.print("  ");

    delay(1000);
}

ISR(TIMER1_CAPT_vect)
{
    Capture1 = ICR1;
    CNTDiff = Capture1 - Counter1;
    Counter1 = Capture1;
    to = ((cnthigh * 65536 + CNTDiff) * 0.0625);
    cnthigh = 0;
}

ISR(TIMER1_OVF_vect)
{
    ++cnthigh;
}

Hallo,

bestimmt passen Deine Timer Settings nicht. Ich tippe auf den falschen Prescaler. Man sollte, denke ich, von der längsten benötigten Zeit ausgehen die man benötigt. Darauf den Timer einstellen. Ist ähnlich wie beim Meßgerät, wenn man den falschen Meßbereich gewählt. Folge "Overload".

http://www.engblaze.com/microcontroller-tutorial-avr-and-arduino-timer-interrupts/

http://letsmakerobots.com/content/arduino-101-timers-and-interrupts

Edit:

Vo und Fo ist bei Dir nicht definiert. Die sollen aber mit einer Fließkommazahl berechnet werden. Vielleicht ist hier ein Rechenfehler drin, weil der Wertebereich nicht paßt. Eigentlich sollte der Compiler meckern, wenn er nicht meckert nimmt er glaube ich standardmäßig int. Das können dir andere besser erklären.

Da sollte der Compiler nicht nur meckern sondern direkt das kompilieren verweigern, worauf der TE aber nicht hinweist.

Desweiteren werden hier auch Fehler ersichtlich. to sollte zwingend als

volatile float to; defeniert werden.

Es sollte das Einlesen von to unter geschlossenem Interrupt erfolgen:

void loop()
{
   float _to;
   noInterrupts();
   _to = to;
   interrupts();
   
   float Fo = 1000000 / _to;
   ...
}

Ich würde die float Berechnung /0.0625 aus der ISR rausnehmen, aber das sollte eigentlich egal sein...

[Nachtrag] Die integer-Multiplikation (cnthigh * 65536) gefällt mir auch nicht

Vielen Dank für die schnellen Antworten.

sschultewolter Du hast recht, es waren noch Überbleibsel von einigen Test, die im Programm dann nicht enthalten sind.

Doc_Arduino Ich denke in meinem bescheidenen Wissen, dass es an dem Prescaler nicht liegen kann, da ich ohne diesen arbeite - und somit dann ein Overflow 4,096ms dauert

Michael_X Ich habe deinen Vorschlag ausprobiert hinsichtlich der Interruptabschaltung im Loop - aber keine Besserung. Die Anzeige schwankt immer noch um diese 4ms. Mir war schon klar, die ISR möglichst kurz zu halten und die Berechnungen ins Loop zu schreiben - aber da bekomme ich nur laufende Werte. Du meinst, dass die Integermultiplikation ungünstig wäre. Hast du einen Vorschlag, wie es optimaler wäre? Um die Overflows auszuwerten muß ich diese doch erst zählen und dann entsprechend multiplizieren, um letzendlich auf den to-wert zu kommen.

Ja, das Interrupt zu machen sollte wenn überhaupt nur äusserst selten helfen.

int * 65536 könnte allerdings immer 0 ergeben, weil das Ergebnis wieder int wird.

Probier mal (cnthigh * 65536L)

Hab ich ausprobiert - aber auch hier kein Erfolg.
Mir erschließt sich nicht die Logik. Ich habe eine ganz normale Word-Variable, die einfach hochgezählt wird und mit einer Zahl multipliziert wird.
Ich vermute, dass zeitweise ein Interrupt zu viel gezählt wird und sich vielleicht der Capture- mit dem Timer-Interrupt nicht vertragen.

Hallo,

vielleicht liege ich auch völlig falsch. Aber Du zählst doch die Anzahl der Overflows vom Timer? Wenn ein Overflow 4,096x ms dauert, kannst Du auch nur eine Genauigkeit haben von einem Vielfachen dessen. Setz mal den Overflow runter, ich glaube das funktioniert mit dem Prescaler. ;)

Nicht ganz, denn ich zähle diese Overflows, aber dazu addiere ich die Differenz der Zählerstande vom 1. und 2. Captureinterrupt.

Ja, nun habe ich alle Tipps ausprobiert und stehe nun irgendwie auf dem Schlauch :sob: . Ich habe so das Gefühl, dass es irgendeine triviale Sache ist mit den Interrupts, so dass der Endstand der cnthight Variable bei gleicher Frequenz immer um 1 schwankt. Vielleicht findet sich ja noch jemand, der mir weiterhelfen könnte :)

Hallo,

ich habe mich mit Timern noch nicht beschäftigt. Aber warum definierst Den Timer nicht anders? Bsp. auf 1ms Overflow Interrupt. Dann mußt Du nichts rechnen. Dann zählst Du einfach die Interrupts und hast vielfache von 1ms.
Ich vermute in Deiner Rechnung/Formel mit der Differenz liegt der Fehler. Da ich aber nicht weis was das macht kann ich das nicht nachvollziehen.

Oder suche mal nach “Arduino zeit messen” Link zu Timer Settings wurden ja schon gepostet. Haste die mal gelesen?

Mit dem Overflow Interrupt kommt man nicht auf genau 1ms. Da das von einem 16MHz Takt abgeleitet ist.

Man kann natürlich den Compare Match Interrupt im CTC Modus verwenden.

Hallo,

ja, ich meinte auch das er sich einen kleinen gemeinsamen Nenner aussucht in der Nähe um die 1ms. Damit fällt vielleicht die falsche Rechnung weg, vermutlich ist die falsch, oder der Timer mit Differenz Capture ist falsch programmiert. Keine Ahnung. Ich denke ein einfacherer kurzer Interrupt ist leichter für den Anfang. Leider kann ich ihm sonst nicht weiter helfen. Interrupt möchte ich tiefer angehen wenn ich mein Projekt endlich mal fertig habe. Dieses Jahr soll's werden. Danach möchte ich mir einen Frequenzgenerator bauen.

Vielen Dank für eure Antworten,

Arduino Doc, die Overflowzeit zu verkürzen ist ein guter Tipp, wert dich morgen ausprobieren. Rechnen muß aber trotzdem, da ich ja eine wesentlich höhere Auflösung als 1ms brauche. Die Subtraktion mit den Timerwerten funktioniert wunderbar. Auch to wird korrekt gemessen, wenn ich 1. nicht mit dem Timeroverflow arbeite und nur die Timerdifferenz verwende und demzufolge die Messzeit unter einem Timeroverflow liegt. Dann habe ich wirklich eine stabile Anzeige bis 4,096ms.

Serenifly Mit dem Compare Match Interrupt ist es die einzige Möglichkeit die Overflowzeit zu verkürzen, wenn ich eine Auflösung von 1µs erhalten will. Für das Prescaling bräuchte ich 1/16, die leider nicht zur Verfügung steht oder liege ich da falsch? Wie würdest du aus deiner Erfahrung es beurteilen, warum die cnthigh-Werte beim Overflow nicht konstant bleiben, wenn die Ausgangsfrequenz sich nicht verändert?

Mit dem Compare Match Interrupt ist es die einzige Möglichkeit die Overflowzeit zu verkürzen, wenn ich eine Auflösung von 1µs erhalten will. Für das Prescaling bräuchte ich 1/16, die leider nicht zur Verfügung steht oder liege ich da falsch?

Compare Match bedeutet, dass das Timer/Counter Register mit dem Wert im OCR1A Register verglichen wird und dann ein Interrupt ausgelöst wird. Du kannst daher selbst bestimmen wann der Interrupt ausgelöst wird und bist nicht auf die vollständige Breite des Counter Registers beschränkt. Im CTC Modus wird auch automatisch das Counter Register auf 0 gesetzt, d.h. man muss das nicht per Hand machen. Genaugenommen wird auch nur der Interrupt ausgelöst wenn man ihn explizit aktiviert. Gibt auch Anwendungen wo man das gar nicht macht und alles komplett in Hardware schaltet.

Für den Wert von OCR1A gilt diese Formel: gewünschte Zeit in Sekunden * 16 MHz / Prescaler - 1

Hallo,

wenn man darüber redet kommen paar Ideen. ;)

Mal sehen ob das stimmt. Ich glaube mal gelesen zu haben das man das Timer Register auch auslesen kann zu einem bestimmten Zeitpunkt. Also wenn das schnell/langsam genug hochzählt und groß genug ist, kann man den Timer starten und beim Stop kann man den auslesen und hat eine klaren Zählerstand den man dann nur umrechnen muß in seine Zeiteinheit. Der 16Bit Timer wäre dafür bestimmt gut. Vielleicht kann das jemand bestätigen oder verneinen.

Anders ausgedrückt. Man stellt ja den Takt vom Timer ein. Den wählt man so das die 16Bit in Deinem Fall nach etwas mehr wie 100ms erreicht sind. Das wären 65536. Dann hätte man eine Auflösung von 100ms / 65536 = 1,51µs

Kann man, aber das ist hier nichts. Er betreibt den Timer ja im Input Capture Modus. Das heißt je nach einem externen Signal wird der Zählerstand in das Input Capture Register geschrieben. Von da kann man ihn dann auslesen. Der Zähler läuft aber während dessen weiter.

Es gibt übrigens eine fertige Library dafür: https://www.pjrc.com/teensy/td_libs_FreqMeasure.html

Dass man da aber einfach den Overflow zählt ist normal. Das sollte also gehen. Keine Ahnung was hier schief läuft.