Passen Sie die Zeitberechnung nach der Frequenzänderung von Timer0 an

7

Ich habe einen Arduino Nano mit einem 328P und brauche alle 6 PWM-Pins.

Daher musste ich den Prescaler und den WGM-Modus von Timer0 anpassen.

Es befindet sich jetzt im phasenkorrekten PWM-Modus mit einem Vorteiler von 1.

TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
TCCR0B = _BV(CS00);

Jetzt brauche ich eine Arbeitszeitberechnung für andere Bibliotheken, aber da Timer0 diese Aufgabe hatte, ist jetzt alles außer Betrieb.

Ich habe versucht, die Verkabelung anzupassen. C.

// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

dazu

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 510))

Aber es ist, als hätte ich nichts geändert. (testete andere Einstellungen, die geändert wurden, damit sie neu kompiliert wurden)

Ganzer Code:

void setup() {

  // Set Timer 0, 1 and 2
  // Register A: Output A and B to non-inverted PWM and PWM mode to phase correct.
  // Register B: Pre Scaler to 1.
  TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
  TCCR0B = _BV(CS00);

  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
  TCCR1B = _BV(CS10);

  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(CS20);

  pinMode(8, OUTPUT);


}
void loop() {

  digitalWrite(8, LOW);
  delay(65000);
  digitalWrite(8, HIGH);
  delay(65000);

}
Splitframe
quelle
Alle Ihre Einstellungen sind korrekt. Meine beste Vermutung, wenn Sie möglicherweise die falsche Verkabelung geändert haben. C. Der beste Weg, dies zu diagnostizieren, besteht darin, unter Datei> Einstellungen das Kontrollkästchen Kompilierung unter "Ausführliche Ausgabe anzeigen während" zu aktivieren. Klicken Sie dann auf Überprüfen, kopieren Sie die Ausgabe in einen Texteditor und suchen Sie nach wiring.c. Sehen Sie, woher sie stammt. Öffnen Sie dann die Datei und überprüfen Sie, ob Ihre Änderungen vorhanden sind.
Jake C
Wenn ich den tmp-Ordner bereinige und erneut überprüfe, zeigt er Folgendes: Dies. Und das ist auch die Verkabelung.c Ich habe korrigiert :(
Splitframe
Ich weiß nicht, was ich sagen soll, das sollte auf jeden Fall funktionieren. Eine andere Möglichkeit wäre, nur die Zeitskala selbst . Sie könnten dies in ein Makro a la wickeln #define SCALE_UP(x) x<<6und es dann so verwendendelay(SCALE_UP(1000))
Jake C
Das ist das Seltsame, der wahre Wert scheint wirklich willkürlich zu sein. Ich habe zwei Nanos und einen Uno ausprobiert, beide haben die gleichen Ergebnisse. Und wenn ich versuche, den Wert für die Verzögerung zu berechnen, erhalte ich 64000, aber wenn ich einfüge, dass es nur 800 ms statt 1000 ms sind. Zu Testzwecken habe ich bereits alle #includes rausgeschmissen, es ist nur die Registeränderung und digitalWrite und Verzögerung jetzt.
Splitframe
Wie messen Sie? Haben Sie zufällig ein Oszilloskop?
Jake C

Antworten:

5

Das Korrigieren der Zeitnehmungsfunktionen mit Ihren PWM-Einstellungen ist nicht so einfach. Sie sollten zumindest versuchen , neu zu schreiben ISR(TIMER0_OVF_vect), micros()und wahrscheinlich delay(). Hier ist warum:

Erstens gibt es ein Rundungsproblem. Die Zeit wird mit zwei globalen Variablen gehalten:

volatile unsigned long timer0_millis;
static unsigned char timer0_fract;

Der erste ist, was millis()zurückkehrt. Der zweite zeichnet auf, wie viel Zeit seit der letzten vollen Millisekunde vergangen ist, und zwar in Einheiten von 8 µs. Die beiden Variablen werden folgendermaßen inkrementiert ISR(TIMER0_OVF_vect):

m += MILLIS_INC;  // temporary copy of timer0_millis
f += FRACT_INC;   // temporary copy of timer0_fract

Bei einer normalen Uno-Konfiguration wird der ISR alle 1024 µs aufgerufen. Dann MILLIS_INCist 1 und FRACT_INCist 3. Bei Ihrer Timer-Konfiguration wird der ISR alle 31,875 µs (510 Zyklen) aufgerufen, MILLIS_INCsollte dann 0 und FRACT_INC3,984375 sein. Da es sich jedoch um Ganzzahlen handelt, wird diese auf 3 abgerundet, und Sie millis()ticken etwa 25% zu langsam.

Eine einfache Lösung wäre zu

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512))

um FRACT_INC4 und millis()0,4% zu schnell zu sein. Oder Sie können timer0_fracteine 16-Bit-Variable erstellen und Taktzyklen zählen lassen, um diesen Fehler zu vermeiden. Beide Optionen sollten behoben werden millis(), aber Sie haben immer noch ein Problem mit micros().

micros()funktioniert durch Lesen sowohl des timer0_overflow_count(im ISR um 1 inkrementierten) als auch des tatsächlichen Zählerwerts. Da Ihr Zähler jetzt alternativ auf und ab geht, ist es schwieriger, aus diesen Messwerten eine Mikrosekundenzahl zu berechnen. Vielleicht könnten Sie zwei aufeinanderfolgende Ablesungen des Zählers vornehmen, um zu wissen, ob er nach oben oder unten geht ...

Und dann gibt es das delay(), worauf man sich verlässt micros(). Wenn Sie beheben micros(), delay()sollte gut funktionieren. Wenn nicht, können Sie die delay()Verwendung millis()stattdessen umschreiben , was einfach sein sollte, aber Sie verlieren etwas an Genauigkeit.

Edgar Bonet
quelle
Danke für die raffinierte Antwort! An dieser Stelle auch danke an @jakec für die Diskussion des Problems mit mir!
Splitframe
Since your counter is now going alternatively up and downWarum geht das TCNT0rauf oder runter? Es steigt und jedes Mal, wenn es überläuft, wird der ISR aufgerufen. Vermisse ich etwas Ich habe versucht, dies zu lösen, indem ich die timer0_overflow_countZeit nur alle 64 Mal erhöht habe. Bei der micros()Funktion wird die Zeit als zurückgegeben. return ((m << 8) + (t % 64)) * (64 / clockCyclesPerMicrosecond());Also mache ich einen Mod auf dem TCNT0Zähler, da mein Timer 64-mal schneller tickt. Aber obwohl das micros()scheinbar in Ordnung delay()ist, läuft es immer noch schneller und ich verstehe nicht warum ...
Lefteris
@Lefteris: Der Zähler geht auf und ab, weil das OP ihn im Modus „Phasenkorrektes PWM“ konfiguriert hat.
Edgar Bonet
Korrektur oben, Modulo war falsch, ich dividiere jetzt durch 64 bei der Rückgabefunktion: return ((m << 8) + (t/64) ) * (64 / clockCyclesPerMicrosecond());Trotzdem bleibt das Problem bei der Verzögerungsfunktion, die ich nicht verstehe
Lefteris
@EdgarBonet Wenn ich richtig verstanden habe, TCTN0erhöht sich das Register mit jedem CLK / 64-Tick. Es geht also nur hoch. Es wird jedes Mal zurückgesetzt, wenn es überläuft und TIMER0_OVF_vectISR aufgerufen wird. Ich verstehe also immer noch nicht, welche phasenrichtige PWM damit zu tun hat.
Lefteris
0

Sie haben MICROSECONDS_PER_TIMER0_OVERFLOWeinen richtigen Wert eingestellt, dieser wird jedoch immer nur von verwendet MILLIS_INC, was wiederum immer nur von verwendet wird millis(). Dies bedeutet, dass die anderen Timing-Funktionen, wie z. B. micros(), unterbrochen werden delay(), delayMicroseconds()wenn timer0 geändert wird. Dies ist eine Art Fehler, der möglicherweise in einer zukünftigen Version behoben wird. Derzeit erwarten die Arduino-Bibliotheken jedoch, dass Sie timer0 in Ruhe lassen. Die beste Problemumgehung besteht darin, sie nur millis()für Ihre zeitkritischen Funktionen zu verwenden.

Jake C.
quelle
0

Dies ist eine unvollständige Antwort - Edgar weiß, wovon er spricht, hör ihm zu

Ich arbeite derzeit mit der gleichen Sache, aber in einem ATMega2560. Diese Internetseitehat mir geholfen, die millis () -Funktion besser zu verstehen. Der Timer läuft alle 510 Zählungen über (siehe Datenblatt, S. 123 für die atmega2560). Ich glaube, es ist 510 und nicht 512, weil es hoch und runter zählt, aber keine Zählung oben oder unten wiederholt - zum Beispiel, wenn Sie von 1 bis 10 zählen und dann wieder runter (ohne 10 zu wiederholen), haben Sie 19-mal gezählt, nicht 20. Dieser Zähler beginnt bei 0, zählt bis 255 und dann zurück bis 0, wodurch ein Überlauf ausgelöst wird, bevor erneut 0 erreicht wird. Dies ist 256 (Countdown einschließlich 0 und 255) + 254 (Countdown ohne 255 oder 0) = 510. Ich habe ein Python-Skript geschrieben, um die Auswirkungen dieser Änderungen zu visualisieren und zu versuchen, sie zu berücksichtigen. Dies zählt bis zu 10 Millionen, daher dauert die Ausführung einige Zeit, aber am Ende kann der Fehler, selbst wenn die Skalierung einfach nachträglich angepasst wird, selbst bei der Skalierung ~ 7 Sekunden betragen.

#! /usr/bin/env python
import numpy as np

realT = np.zeros(10000000)
timer0_millis = np.zeros(10000000)
timer0_fract = np.zeros(10000000)
i=0

for i in range (1,10000000):
    timer0_millis[i] = timer0_millis[i-1] + 1
    timer0_fract[i] = timer0_fract[i-1] + 3
    realT[i] = i*31875
    if timer0_fract[i-1] >= 125:
        timer0_fract[i] = timer0_fract[i-1] - 125
        timer0_millis[i] = timer0_millis[i-1] + 1

adjusted = timer0_millis*510/(256*64)

print "after 100 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[99], realT[99]/1000000, adjusted[99])
print "after 1000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[999], realT[999]/1000000, adjusted[999])
print "after 10000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[9999], realT[9999]/1000000, adjusted[9999])
print "after 100000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[99999], realT[99999]/1000000, adjusted[99999])
print "after 1000000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[999999], realT[999999]/1000000, adjusted[999999])
print "after 10000000 timer interrupts millis() will read %d, actual millis is %.6f, adjusted millis is %.6f" % (timer0_millis[9999999], realT[9999999]/1000000, adjusted[9999999])
Nerbsie
quelle
Sie haben geschrieben: " Ich glaube, im phasenrichtigen Modus läuft der Timer alle 510 Zählungen über, nicht 512, wie von Edgar angegeben. " Bitte lesen Sie meine Antwort noch einmal und zitieren Sie mich nicht falsch.
Edgar Bonet
Entschuldigung, ich habe auf diese Zeile verwiesen "#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds (1 * 512))" - Ich habe meinen Kommentar aktualisiert, korrigiere mich, wenn ich hier falsch
liege
Es gibt einen sehr guten Grund , 512 statt 510 in dieser Zeile zu haben. Lesen Sie meine Antwort und Sie werden verstehen.
Edgar Bonet
Okay, Sie fügen also zwei Timer-Zählungen mit jeweils 62,5 ns hinzu, um den Fehler zu berücksichtigen, der durch die erzwungene Abrundung verursacht wird?
Nerbsie
1
Edgar, wenn du mir weiterhin helfen möchtest, habe ich beschlossen, anstatt eine Frage mit eigenen und möglichen Fehlinformationen zu beantworten, eine Frage zu stellen
Nerbsie,
0

Dank Edgars Antwort und Nebsies Erklärung, wie der Zähler genau funktioniert, konnte ich mir eine eigene Implementierung zur Korrektur von Verzögerungen und Mikros () einfallen lassen, die - bisher - auf meinem Arduino Uno gut zu funktionieren scheint.

Ich sage nicht, dass dies die genaueste Implementierung ist, insbesondere habe ich meine Zweifel an micros (), wenn zum Beispiel ein Timerüberlauf zwischen dem Lesen von t1 und t2 auftritt, aber es ist eine, die bisher für mich gut funktioniert.

Als erstes - gemäß Edgars Empfehlung habe ich definiert:

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512)) 

Dies hat jedoch keinen Einfluss auf das Timing von micros () oder delay ().

Funktionsmikros () Ich habe zweimal von TCNT0 gelesen, um festzustellen, ob es nach oben oder unten zählt. Dies geschieht mit der Klausel "if (t1> t2)". Die Überlaufzahl "m" wird mit 510 multipliziert, da der Zähler nach 510 Schritten abläuft. Dann wird der berechnete Zählerwert "t" addiert, geteilt durch die Anzahl der Takte pro Mikrosekunde. (Hinweis: Prescaler = 1, daher keine weitere Multiplikation).

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t1, t2;
    uint16_t t;

    cli();
    m = timer0_overflow_count;
#if defined(TCNT0)
    t1 = TCNT0;
#elif defined(TCNT0L)
    t1 = TCNT0L;
#else
    #error TIMER 0 not defined
#endif

#if defined(TCNT0)
    t2 = TCNT0;
#elif defined(TCNT0L)
    t2 = TCNT0L;
#else
    #error TIMER 0 not defined
#endif

    if (t1 >= t2) {
        t = 510 - t2;       // counter running backwards
    } else {
        t = t2;             // counter running upwards
    }       

#ifdef TIFR0
    if ((TIFR0 & _BV(TOV0)) && (t2 > 1))        // if overflow flag is set -> increase m
        m++;
#else
    if ((TIFR & _BV(TOV0)) && (t2 > 1))
        m++;
#endif

    SREG = oldSREG;

    return ((m * 510) + t)  / clockCyclesPerMicrosecond();          
}

JEDOCH - dies scheint gut zu funktionieren, aber ich hatte zunächst Probleme (bevor ich dies erneut bearbeitete). Da der Zähler viel schneller läuft, läuft alle 268 Sekunden der vorzeichenlose lange Datentyp von micros () über und beginnt wieder bei Null. Dies führte zu einer unerwünschten Verzögerung von delay (), insbesondere wenn lange Verzögerungszeiten verwendet werden, wie in meinem Fall Verzögerungen von einer Sekunde pro Schleifeniteration. Daher musste ich der delay () - Funktion in arduino verdrahtung.c auch eine Überlauferkennung hinzufügen.

Wenn der Überlauf auftritt, ist das Timing möglicherweise nicht sehr genau. Da dies jedoch einmal mehr als 268 Sekunden sind, kann dies akzeptabel sein. Bisher funktioniert mit dieser Änderung die Verzögerungsfunktion auf meiner Seite wieder einwandfrei.

void delay(unsigned long ms)
{
    uint32_t start = micros();
    uint32_t elapsed = 0;   

    while (ms > 0) {
        yield();
        while ( ms > 0 && (micros() - 1000) >= (start + elapsed)) {         
            ms--;
            elapsed += 1000;            
        }
        if( start > micros() ) {    // overflow detected
            start = micros();       // reset start
            elapsed = 0;            // reset elapsed
        }
    }
}
MJtheK
quelle