micros ist schneller als erlaubt ?

Hallo,

habe für andere Zwecke die micros Funktion auf den Arduino nachgebaut und nochmal getestet, zum Glück, obwohl der letzte Test i.O. aussah. Aber irgendwas stimmt noch nicht.

Der Intervallvergleich stimmt manchmal nicht. Er wird auch kleiner 999µs ausgeführt.
Nehme ich 9600 Baudrate, haut es hin bzw. fällt nicht auf, weil er dann eh nicht mehr hinterher kommt.
Aber mit 250k Baudrate ist er schneller wie er soll.
Nur warum?
Gibts in der uint32_t micros_() Funktion ein Problem mit dem Variablentyp beim rechnen oder ein ISR race condition Problem?
Meistens stimmts, aber manchmal eben nicht. Paar Bsp. Ausgaben …
linke Spalte die laufenden micros und rechts die Differenz jeweils zum vorherigen Wert, was dem Ausgabeintervall entsprechen soll. Alles was kleiner 1000 ist kann nicht stimmen.

63528		1012
64536		1008
65012		476
66024		1012
67036		1012
68044		1008
69056		1012
70068		1012
71012		944
72024		1012
...
228228		1012
229240		1012
230012		772
231024		1012
232036		1012
233048		1012
234060		1012
235064		1004
236072		1008
237012		940
238024		1012
239036		1012
240044		1008
241056		1012
242068		1012
243080		1012
244092		1012
245104		1012
246012		908
247024		1012
248036		1012
/*
 * Created: 09.10.2016 21:58:34
 * Author: Doc_Arduino
 */ 

#include <util/atomic.h>    // für cli() und sei() mit SREG Sicherung

volatile unsigned long millis_count;
volatile unsigned long second_count;

unsigned long last_micros;

void setup()
{  
    
  Serial.begin(250000);
  set_Timer2(); 
}
      
void loop()
{ 
        
  if (micros_() - last_micros > 999) {
    last_micros = micros_();
    Serial.println(last_micros);
  }     
  
}


/* *** Funktionen *** */

ISR(TIMER2_COMPA_vect)
{
  static uint32_t local_millis = 0;
    
  millis_count++;
  local_millis++;

  if(local_millis > 999) {
    second_count++;
    local_millis = 0;
  }
}


void set_Timer2 ()    // millis
{
  cli();                    // Interupts ausschalten
  TCNT2 = 0;                // Register Reset
  TCCR2A = 0;
  TCCR2B = 0;
  TIMSK2 = 0;
  TCCR2A = (1 << WGM21);          // CTC Modus
  OCR2A = 249;                    // TOP = (CPU Takt / 2 / Prescaler / Takt) - 1
  TIMSK2 |= (1 << OCIE2A);        // Compare Match ISR einschalten, aller 1ms
  TCCR2B |= (1 << CS22);          // Prescaler 64, Timer starten
  sei();
}


uint32_t micros_()
{
  uint32_t m = 0;   
  uint32_t t = 0;   
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)    // cli und sei mit SREG Sicherung
  {
    m = millis_count;       // ms Variable
    t = TCNT2;              // ein Timer Count entspricht 4µs,
  }
  return (m*1000)+(t*4);    // weil Prescaler 64 bei 16MHz
}


uint32_t millis_()
{
  uint32_t value = 0;
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)    // cli und sei mit SREG Sicherung
  {
    value = millis_count;
  }
  return value;
}


uint32_t second_()
{
  uint32_t value = 0;
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)    // cli und sei mit SREG Sicherung
  {
    value = second_count;
  }
  return value;
}

Hallo,

ich kann es eingrenzen, sehe aber den Fehler noch nicht. Lasse ich den micros Wert ohne Pause ausgeben, sehe ich sogar negative Differenzen zum vorherigen Wert. Rechne ich die letzten 3 Stellen zurück in den TCNT2 Counter Wert, sehe ich das es mit dem Compare Match ISR zu tun haben muß. Weil es immer bei TCNT2 = 0 auftritt. Nur warum?

| micros_____ | Diff____ | TCNT2 | | - | - | - | | 319604 | | 151 | | 319952 | 348 | 238 | | 320304 | 352 | 76 | | 320656 | 352 | 164 | | 320000 | -656 | 0 | | 321348 | 1348 | 87 | | 321696 | 348 | 174 |

| micros______ | Diff____ | TCNT2 | | - | - | - | | 16276088 | | 22 | | 16276556 | 468 | 139 | | 16276000 | -556 | 0 | | 16277456 | 1456 | 114 | | 16277916 | 460 | 229 |

Hallo,

Ursache gefunden, aber noch nicht behoben. Im Fall des Falles wird noch der alte millis Wert verwendet. Der müßte aber bei TCNT2 Compare Match und 0 um eines weiter sein. Deswegen ist der Gesamtwert micros im Fall des Falles kleiner wie vorher. Ich muß wohl oder übel noch Flags abfragen ... Deswegen wird wohl die Arduino micros Funktion so umfangreich sein. ;)

Edit: kann ich da nicht einfach den TCNT2 auf 0 abfragen und wenn ja temporär millis um eins erhöhen? nein das geht natürlich nicht

Hallo,

falls das irgend jemanden außer mir noch interessieren sollte, mit Flag Abfrage funktioniert das aller erste Sahne. :slight_smile:

uint32_t micros_()
{
  uint32_t m = 0;   
  uint32_t t = 0;   
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)    // cli und sei mit SREG Sicherung
  {
    m = millis_count;       // ms Variable
    t = TCNT2;              // ein Timer Count entspricht 4µs,
                            // weil Prescaler 64 bei 16MHz
    if ( (TIFR2 & _BV(OCF2A)) && (t < 249) ) {  // Compare Flag gesetzt?
      m++;                                      // ja, millis inkrementieren
    }
  }
  return (m*1000)+(t*4);    // weil Prescaler 64 bei 16MHz
}

;-) Ja, ich bin noch da, und lese aufmerksam mit.

Danke für den Vortrag!

Hallo combie,

da ich aber froh nicht nur Selbstgespräche geführt zu haben. ;) Wenn ich "laut" denke kommen mir öfters bessere Ideen. Im Grunde ist das nun fast der Nachbau der Arduino micros Funktion, nur das ich den Compare Match ISR verwende und damit gleich auf 1ms genau auslöse. Damit spare ich mir die zusätzliche Korrektur ein. Als netter Nebeneffekt wurde nun das Rätsel der micros Funktion aufgedeckt. ;) Jetzt bin ich glücklich und trinke erstmal Kaffee ...

Glückwunsch zum geretteten Sonntag :-)

Ich würde noch die Variablen kleiner machen, insbesondere die im ISR verwendeten.

Doc_Arduino: Im Grunde ist das nun fast der Nachbau der Arduino micros Funktion, nur das ich den Compare Match ISR verwende und damit gleich auf 1ms genau auslöse. Damit spare ich mir die zusätzliche Korrektur ein.

Die Korrektur kann auch für andere Quarzfrequenzen verwendet werden, und zum Ausgleich von Quarzabweichungen.

Hallo,

du meinst t kann ich als byte definieren. Ja, kann ich machen. Aber nur solange OCR2A <= 255 ist. Ich dachte bei der Fehlersuche das er damit falsch rechnet. Hatte auch UL an die 1000 und 4 geschrieben. Brachte aber alles nichts. Tests mit byte sind auch fehlerfrei. Habe 250.000 Excelzeilen neu nach Anomalien durchsucht.
Eine Nebenfrage. Warum rechnet er ausgerechnet hier fehlerfrei im unsigned long Wertebereich?
Weil m unsigned long ist in der Formel?

Und ich glaube ich kann jetzt diese Zeile in normale Worte fassen. Hatte etliche Knoten im Hirn.
if ( (TIFR2 & _BV(OCF2A)) && (t < 249) ) { // Compare Flag gesetzt?

Laut meinem Verständnis passiert folgendes. Dürft mich gern korrigieren.
Das Compare Match (oder Overflow) Flag wird einen µC Takt später gesetzt wie der Compare erkannt wurde.
Der TCNT2 Zähler wird bei Compare sofort auf 0 gesetzt. Das Flag jedoch wird erst wieder gelöscht, wenn der Compare ISR Handler ausgeführt wurde. Und dazwischen können einige Takte vergehen. Man weiß ja nicht wann der µC dafür wirklich Zeit hat. In dieser Situation steht der TCNT2 schon auf 0, das Flag ist aber noch gesetzt, was bedeutet der eigentliche millis_count Zähler wurde im Compare ISR Handler noch nicht erhöht. Der ISR Handler wurde noch nicht abgearbeitet. Deshalb muß man den millis Wert für die Rechnung für diese Situation temporär selbst um +1 korrigieren.

Und schon sahen meine Augen kurzzeitig eine theoretische Falle. Die bei meiner näheren Betrachtung vielleicht noch zu erwähnen wäre. Angenommen man ruft zwischen µC Takt 1009 und 1014 die micros_() Funktion zweimal auf? Dann würde dem Anschein nach millis ein zweitesmal inkrementiert. Das passiert aber nicht, weil vorher immer der alte millis_count eingelesen wird und temporär zu m wird.

Bsp. mit angenommen Prescaler 4, sonst werden es zu viele Zeilen.

Clock____ TCNT2____ OC Flag__ millis_count
1000 247 L 2
1001 247 L 2
1002 247 L 2
1003 247 L 2
1004 248 L 2
1005 248 L 2
1006 248 L 2
1007 248 L 2
1008 249 > 0 L 2
1009 0 H 2 m++ (3)
1010 0 H 2 m++ (3)
1011 0 H 2 m++ (3)
1012 1 H 2 m++ (3)
1013 1 H 2 m++ (3)
1014 1 H 2 m++ (3)
1015 1 L 3 irgendwann wird Compare ISR ausgeführt
1016 2 L 3

Einen Nachsatz noch. Die Abfrage t<249 kann man nicht weglassen. Sonst kommt wieder Müll raus. Warum weiß ich allerdings nicht. Kann ich nicht analysieren. Wüßte auch nicht wie ich das machen sollte.

Unsigned long ist für die meisten Zwischenwerte viel zu groß, 16 Bit würden reichen.

Wenn ich mir die Formel für die CTC Frequenz anschaue, dann wird der Timer bei einem Match nicht sofort zurückgesetzt, sondern erst mit dem nächsten Timer-Takt. Deine Liste müßte also 4 Einträge mit TOP enthalten, bei denen OCF nicht als Üblerlauf interpretiert werden darf.

Hallo,

wegen dem vielleicht weglassen von t<249 ?
Habe noch eine Möglichkeit gefunden und Glück gehabt. Also t<249 Abfrage testweise auskommentiert.
In der micros_() Funktion speichere ich innerhalb atomic den TCNT2 Wert und das Flagregister.

uint32_t micros_()
{
  uint32_t m = 0;   
  uint8_t  t = 0;   
  ATOMIC_BLOCK (ATOMIC_RESTORESTATE)    // cli und sei mit SREG Sicherung
  {
    m = millis_count;       // ms Variable
    t = TCNT2;              // ein Timer Count entspricht 4µs,
                            // weil Prescaler 64 bei 16MHz

    _flag = TIFR2;          // testweise volatile global
    _tcnt2 = t;

    if ( TIFR2 & _BV(OCF2A)) {  // Compare Flag gesetzt?                              
      m++;                      // ja, millis inkrementieren
    }
  }
  return (m*1000)+(t*4);    // weil Prescaler 64 bei 16MHz
}

Das lasse ich mir dann in der loop mit ausgeben. Glück hatte ich das ich das Flag einmal zu sehen bekam.
Hier die Daten. Interpretieren kann ich es noch nicht wirklich, was da im Detail falsch läuft.
Weil einmal haut es mit TCNT2 Wert 249 hin und einmal nicht. Da wurde der ISR Handler noch nicht ausgeführt. Die Differenz lasse ich in Excel berechnen.

@ Dr.
ich nehme das erstmal zur Kenntnis, mal sehen ob ich das doch noch richtig interpretieren kann warum das in dem Fall gegen den Baum läuft.

TCNT2___ micros___ Diff___ TIFR2
28 1.579.112 5
178 1.579.712 600 5
99 1.580.396 684 5
249 1.580.996 600 5
169 1.581.676 680 5
88 1.582.352 676 5
238 1.582.952 600 5
29 1.794.116 5
179 1.794.716 600 5
99 1.795.396 680 5
0 1.796.000 604 7
141 1.796.564 564 5
62 1.797.248 684 5
212 1.797.848 600 5
27 1.946.108 5
181 1.946.724 616 5
98 1.947.392 668 5
249 1.948.996 1.604 5
170 1.948.680 -316 5
87 1.949.348 668 5
240 1.949.960 612 5

Doc_Arduino: falls das irgend jemanden außer mir noch interessieren sollte,

Ich bin zwar erst jetzt auf diesen Thread gestoßen, aber da ich jetzt hier gepostet habe, werde ich über Neuerungen informiert. Interessant ist es auf jeden Fall!

Gruß

Gregor

Hallo,

du kannst auch still informiert werden. Oben rechts "Receive Mail" anklicken und nochmal "receive mail". :)

Du meinst so läuft das ab oder könnte es ablaufen ... ? Würde bedeuten, die Abfrage muß den TOP Wert herausfiltern, weil das Flag zeitlich gesehen ab TOP bis irgendwann gesetzt bleibt. Die "3 roten Zeilen" sind die Fehlerzustände und müssen rausgefiltert werden. Macht erstmal Sinn im Zusammenhang der letzten Daten. Okay?

| Clock____ | TCNT2____ | OC Flag__ | millis_count | | | - | - | - | - | - | | 1000 | 247 | L | 2 | | | 1001 | 247 | L | 2 | | | 1002 | 247 | L | 2 | | | 1003 | 247 | L | 2 | | | 1004 | 248 | L | 2 | | | 1005 | 248 | L | 2 | | | 1006 | 248 | L | 2 | | | 1007 | 248 | L | 2 | | | 1008 | 249 | L | 2 | compare erkannt, mit next clock | | 1009 | 249 | H | 2 | wird Flag gesetzt, m++(3) | | 1010 | 249 | H | 2 | m++ (3) | | 1011 | 249 | H | 2 | m++ (3) | | 1012 | 0 | H | 2 | m++ (3) | | 1013 | 0 | H | 2 | m++ (3) | | 1014 | 0 | H | 2 | m++ (3) | | 1015 | 0 | L | 3 | irgendwann wird Compare ISR ausgeführt | | 1016 | 1 | L | 3 | | | 1017 | 1 | L | 3 | | | 1018 | 1 | L | 3 | |

So etwa meinte ich das.

Hallo,

habe die Tabelle mit den millis_count korrigiert.
Wenn jemand schreibt “in etwa”, dann sagt mir das, dass etwas noch nicht stimmt. :wink:
War es nur die millis Korrektur oder noch ein anderes Detail?

Was mich noch irritiert ist folgendes. Im Datenblatt steht zum Bsp. 20.5 Output Compare Unit

The 8-bit comparator continuously compares TCNT2 with the Output Compare Register
(OCR2A and OCR2B). Whenever TCNT2 equals OCR2A or OCR2B, the comparator signals a
match. A match will set the Output Compare Flag (OCF2A or OCF2B) at the next timer clock
cycle. If the corresponding interrupt is enabled, the Output Compare Flag generates an Output
Compare interrupt. The Output Compare Flag is automatically cleared when the interrupt is exe-
cuted.

Wenn wirklich der nächste Clock Takt gemeint ist, dann haut die Tabelle noch nicht hin bzw. komme ich dann nicht mehr mit. Wenn aber wirklich alle Takte immer durch den Prescaler müssen, wie im Block-Diagramm, dann paßt das alles wie es ist. Richtig oder falsch?

Inzwischen traue ich dem Datenblatt nicht mehr so richtig. Natürlich würde es (mehr?) Sinn machen, wenn ein Match gleichzeitig als Overflow interpretiert werden könnte, und dann erst auftreten würde, wenn der Zähler auf 0 zurückgesetzt ist. Andererseits gibt es noch ein TOV2 Flag für Overflow, und zwei Flags mit dem gleichen Verhalten machen nur wenig Sinn.

Ich vermute aber, daß micros_() immer noch fehlerhaft ist.

   _flag = TIFR2;          // testweise volatile global
   _tcnt2 = t;

   if ( TIFR2 & _BV(OCF2A)) {  // Compare Flag gesetzt?                              
     m++;                      // ja, millis inkrementieren
   }

Zwar speicherst Du TIFR2 in _flag, aber beim nachfolgenden if greifst Du nochmal auf TIFR2 zu, und das könnte dann ja schon wieder geändert sein. Die Flags ändern sich ja auch bei ausgeschalteten Interrupts. Aber egal wie rum man es macht, die asynchron gelesenen TIFR2 und TCNT2 können immer zu unterschiedlichen Zuständen gehören (vor und nach dem Timer Reset auf 0). Abhilfe vielleicht mit

   _tcnt2 = t;

   if ((flag = TIFR2) & _BV(OCF2A)) {  // Compare Flag gesetzt?                              
   _tcnt2 = t = TCNT2; //nach match
     m++;                      // ja, millis inkrementieren
   }
  return (m*1000)+(t*4);    // weil Prescaler 64 bei 16MHz

Und wenn das auf dem anderen(?) Controller mit 8MHz anders funktioniert, ist der Chip vielleicht ein Fake?

Hallo,

das OCF und TOV Flag sind 2 verschiedene Dinge. OCF löst bei Compare aus und TOV bei Overflow. Ich bin mir langsam sicher das mit "next timer clock" gemeint ist, mit dem nächsten Takt nach Prescaler wird es auf 0 gesetzt. Geht nicht anders laut Blockdiagramm.

Was meine micros Funktion angeht, so würde ich nicht die ganze Funktion als falsch bezeichnen. Das einzigste was nicht ganz stimmt, da gebe ich dir recht, ist das besagte doppelte abfragen des TIFR Registers. Dieses Manko führt aber nur zu einer falschen Debugausgabe auf der seriellen. Das verfälscht nicht die Funktion an sich. Allerdings, befindet sich der Code noch in der gesperrten Interrupt Abarbeitung. Ich werde es nochmal ausgebessert testen.

Hallo,

habe nochmal versucht mit 3 verschiedenen seriellen Ausgaben einen Nachweis zubringen was falsch und richtig rechnet. Eigentlich ging es mir im letzten Teil nur darum herauszufinden warum die Abfrage t<249 so wichtig ist. Um die serielle Ausgabe kurz zu halten habe ich auf die Ausgabe des TNCT2 Wertes verzichtet. Den kann man hinterher auch in Excel berechnen an Hand der letzten 3 Stellen von micros die direkt dem aktuellen Zählerstand entsprechen unter Berücksichtigung des Prescaler und damit dessen Zeitkonstante pro Count.
Ich habe mir also immer nur den Wert von micros und dann noch den Inhalt vom TIFR2 Register anzeigen lassen. Der Wert 7 besagt das Bit 1 mit gesetzt ist in dem Moment. Das Compare Flag eben.

In der letzten “Spalte” wo es richtig rechnet sieht man, dass das Compare Flag beim TCNT2 Wert 249 manchmal noch nicht und manchmal schon gesetzt ist. Das läßt die obige Annahme bestätigen das mit next timer clock doch inkl. Prescaler gemeint sein muß.

Ich denke mehr läßt sich mit der “seriellen Debugschnittstelle” nicht machen. Bin überhaupt schon froh mit halben Millisekunden Verzögerungen den TCNT2 Überlauf und dann noch den Flagwechsel sehen zu können.

neu_micros.png