Forum: Mikrocontroller und Digitale Elektronik AVR exakt Millisekunden zählen


von Stefan F. (Gast)


Lesenswert?

Hi alle,
in vielen meiner Projekte zähle ich Millisekunden um Zeiten zu messen. 
Allerdings nur ungefähr, weil mir noch nicht eingefallen ist, wie ich 
das elegant genauer hinbekommen soll. So mache ich es zur Zeit:
1
    void initSystemTimer(void)
2
    {
3
        cli();
4
        TCCR0A = (1<<WGM01); // CTC Mode
5
        #if F_CPU>16000000
6
            TCCR0B = (1<<CS02); // Prescaler 256 
7
            OCR0A = (F_CPU/256+500)/1000;
8
        #elif F_CPU>2000000
9
            TCCR0B = (1<<CS01)+(1<<CS00); // Prescaler 64 
10
            OCR0A = (F_CPU/64+500)/1000;
11
        #else
12
            TCCR0B = (1<<CS01); // Prescaler 8
13
            OCR0A = (F_CPU/8+500)/1000;
14
        #endif 
15
        TIMSK0 |= (1<<OCIE0A); // Interrupt on compare match
16
        sei();
17
    }
18
19
    ISR(TIMER0_COMPA_vect)
20
    {
21
        timerCounter++;
22
    }

Wenn die Quartz-Frequenz nicht glatt durch 8.000, 64.000 oder 256.000 
teilbar ist, bekomme ich aber nur ungefähr Millisekunden. Ich suche nach 
einem generischen Lösungsansatz, der mit allen gängigen Quarzen 
funktioniert und nicht allzu viel CPU Zeit in der ISR verbraucht.

Kennt jemand dazu eine elegante Lösung?

von Knut B. (Firma: TravelRec.) (travelrec) Benutzerseite


Lesenswert?

Stefan U. schrieb:
> Kennt jemand dazu eine elegante Lösung?

Xmega nehmen und ggf. 2 16Bit-Timer über das Event-System kaskadieren. 
Über die Timer-Periode kannst Du die gewünschten Teiler sehr fein 
einstellen.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@ Stefan Us (stefanus)

>    void initSystemTimer(void)
>    {
>        cli();

Wozu? Nach dem Reset sind die Interrupts sicher aus.

>einem generischen Lösungsansatz, der mit allen gängigen Quarzen
>funktioniert und nicht allzu viel CPU Zeit in der ISR verbraucht.

AVR - Die genaue Sekunde / RTC

von Peter D. (peda)


Lesenswert?

Warum nimmst Du nicht einfach einen der 16Bit-Timer?
Z.B. der ATmega328PB hat 3 16Bit-Timer.
Exakt wird das aber auch nicht, da ja die Quarze nicht exakt 16MHz 
haben, sondern z.B. 16,000001MHz.
Ist aber auch kein großes Problem, man kann die Quarzfreqeunz messen und 
dann im Interrupt mit gebrochenen Zahlen eine Korrekturrechnung 
durchführen. Dann sind z.B. 1000 Interrupts a 1ms auf 1Hz genau.

Du solltest also zuerst mal spezifizieren, wie genau Du es haben 
möchtest.

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Stefan U. schrieb:
> Wenn die Quartz-Frequenz nicht glatt durch 8.000, 64.000 oder 256.000
> teilbar ist, bekomme ich aber nur ungefähr Millisekunden.
Eines vorweg: mit jeder Frequenz, die nicht glatt durch 1ms teilbar ist, 
wirst du nur "ungefähr Milliskeunden" bekommen...

Ich würde analog zur DDFS diesen Weg gehen:
1. irgendein hinreichend schneller Timer-Interrupt. "Hinreichend" ist im 
einfachsten Fall einer, der schneller als 1ms ist und so oft aufgerufen 
wird, dass der resultierende Jitter nicht mehr stört.
Im etwas komplizierteren Fall kann das aber auch ein Interrupt mit z.B. 
7,65ms sein. Nur wird dann die ms-Uhr immer mal einige ms 
"überspringen"...

2. z.B. ein 8 oder 16 Bit Akkumulator, bei dessen Überlauf das 
Millisekunden-Register um 1ms hochgezählt wird.

Und wenn jetzt z.B. der Timerinterrupt alle 780us aufgerufen wird, dann 
addiere ich addiere ich pro Interrupt 780us/1000us * 65536 = 51118 auf 
den 16-Bit Akkumulator drauf. Und wie gesagt: wenn der überläuft ist 
wieder eine ms vergangen...

Diese Vorgehensweise ist aber in dem von Falk verlinkten Artikel auch 
mit drin... ;-)

: Bearbeitet durch Moderator
von Wolfgang H. (Firma: AknF) (wolfgang_horn)


Lesenswert?

HI, Stefan,

> in vielen meiner Projekte zähle ich Millisekunden um Zeiten zu messen.
> Allerdings nur ungefähr, weil mir noch nicht eingefallen ist, wie ich
> das elegant genauer hinbekommen soll.

Sobald das Target per JTAG geflasht werden kann, kann dessen 
Quarzfrequenz gemessen - und per JTAG in das EEPROM geschrieben werden.

Ciao
Wolfgang Horn

von U. C. (Gast)


Lesenswert?

Wie wäre es mit einer RTC mit 1ms SQ Ausgang...?
Oder einem UhrenQuarz direkt am ATMega(wenn möglich)?

von Knut B. (Firma: TravelRec.) (travelrec) Benutzerseite


Lesenswert?

U. C. schrieb:
> Wie wäre es mit einer RTC mit 1ms SQ Ausgang...?
> Oder einem UhrenQuarz direkt am ATMega(wenn möglich)?

Das ist doch viel zu einfach! 6, setzen!

von U. C. (Gast)


Lesenswert?

Knut B. schrieb:
> 6, setzen!
Ja, dann ist ja wirklich die -1 bei der Bewertung der Antwort richtig.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

U. C. schrieb:
> Oder einem UhrenQuarz direkt am ATMega(wenn möglich)?

Wie kommst du von 32768 Hz auf 1000 Hz (1 ms)?

von Peter D. (peda)


Lesenswert?

U. C. schrieb:
> Wie wäre es mit einer RTC mit 1ms SQ Ausgang...?

Und der wäre?

Der bekannte PCF8563 kann ja nur 0,98ms (1/1024Hz).

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Peter D. schrieb:
> Und der wäre?
>
> Der bekannte PCF8563 kann ja nur 0,98ms (1/1024Hz).

 DS3231 hat 8.192KHz, 4.096KHz, 1.024KHz und 1Hz output.
 1KHz kann meines Wissens keiner.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Ich merke gerade, dass ich ein wichtiges Detail in meiner Frage 
vergessen habe:

Es ist mir nicht wichtig, dass die Intervalle alle exakt 1ms lang 
dauern, aber im Durchschnitt sollte man auf 1ms kommen, damit sich die 
Fehler nicht aufaddieren.

Eine Uhr soll es nicht werden, dazu hätte ich einfach Quarz + Prescaler 
so kombiniert, dass ich auf glatte Sekunden komme.

Der Artikel 
https://www.mikrocontroller.net/articles/AVR_-_Die_genaue_Sekunde_/_RTC 
hat einen schönen Ansatz, wie man auf genaue Sekunden kommen kann.

Allerdings habe ich hier den hässlichen Seiteneffekt, dass jede Sekunde 
ein 1ms Intervall stark verzerrt wird (eben um die Abweichung 
auszugleichen). Außerdem geht das nur, wenn die Abweichung pro Sekunde 
<1ms sind, was ich nur mit einem 16bit Counter machen könnte. Ich suche 
aber nach einer Lösung, die mit 8bit Counter geht, weil ich sie auch auf 
den ganz kleinen ATtinys anwenden möchte.

Lieber wäre mir, wenn jedes einzelne Intervall nur so viel verzerrt 
(nennt man das Jitter?) wird, wie unbedingt nötig. In etwa so, wie der 
NTP Daemon unter Linux die Uhr "gerade zieht". Der macht auch keine 
Zeitsprünge, sondern korrigiert die Uhr in ganz kleinen Intervallen, so 
dass es nicht auffällt.

Nicht vergessen: Es geht gar nicht um eine Uhr. Es geht mir nur darum, 
dass nicht jede millisekunde etwas länger dauert, als sie soll und sich 
so die Fehler aufaddieren.

Ich stelle mir vor, dass ich in der ISR ab und zu einen Korrekturwert in 
das Zählregister lade. Aber da fürchte ich ein Problem: Die Berechnung, 
wann das Intervall  einen Takt mehr oder weniger lang sein soll, dauert 
womöglich schon länger, als dieser Takt. Bzw. sie reduziert die 
verbleibende Rechenleistung so sehr, dass das Zählen von Millisekudnden 
sinnlos wird.

Ich habe keinen Plan, wie ich das anstellen kann.

Zum Glück war das in meinen bisherigen Anwendungen immer Scheißegal. Die 
ungefähren Millisekunden waren gut genug. Aber ich hätte es gerne einen 
Lösungsansatz parat, für den Fall dass es mal genauer sein soll.

von Stefan F. (Gast)


Lesenswert?

> 1KHz kann meines Wissens keiner.

Nicht? ich dachte jeder PC hat sowohl unter Linux als auch unter Windows 
einen 1kHz System Zähler. Genau den will ich nachbilden.

von Wolfgang (Gast)


Lesenswert?

Stefan U. schrieb:
> Allerdings nur ungefähr, weil mir noch nicht eingefallen ist, wie ich
> das elegant genauer hinbekommen soll.

Genau (im Mittel) wirst du es nur hinbekommen, wenn du ggf. etwas Jitter 
in Kauf nimmst. Der Bresenham-Algorithmus aus der Computergraphik 
erlaubt jede Geradensteigung. Entsprechend läßt sich auch aus jedem 
Prozessortakt ein (mittlerer) 1ms-Takt generieren.

von Falk B. (falk)


Lesenswert?

@Stefan Us (stefanus)

><1ms sind, was ich nur mit einem 16bit Counter machen könnte. Ich suche
>aber nach einer Lösung, die mit 8bit Counter geht, weil ich sie auch auf
>den ganz kleinen ATtinys anwenden möchte.

Die ist auch im Artikel, nämlich die 32 kHz Variante.

>Ich habe keinen Plan, wie ich das anstellen kann.

Und scheinbar auch keine Augen im Kopf um zu lesen. Viel Geschrei um 
nix, das Problem wurde schon vor Urzeiten gelöst.

von Falk B. (falk)


Lesenswert?

@Stefan Us (stefanus)

>Nicht? ich dachte jeder PC hat sowohl unter Linux als auch unter Windows
>einen 1kHz System Zähler. Genau den will ich nachbilden.

Ja und? Nimm einen passenen Quarz und gut!

von Stefan F. (Gast)


Lesenswert?

Ich habe das mit der 32kHz Variante gelesen, und es gibt auch einige 
andere Quarze (z.B. 8Mhz und 16Mhz) wo die Rechnung glatt auf geht.

Schön wäre es, auch eine Lösung für andere (beliebige) Taktfrequenzen 
parat zu haben.

Aber wir müssen daraus keine Wissenschaft machen. Wenn das nicht "mal 
eben so" geht, dann geht es eben nicht. Das würde ich akzeptieren.

von Falk B. (falk)


Lesenswert?

@Stefan Us (stefanus)

>Ich habe das mit der 32kHz Variante gelesen,

Aber nicht verstanden?

>und es gibt auch einige
>andere Quarze (z.B. 8Mhz und 16Mhz) wo die Rechnung glatt auf geht.

Nö. Wenn man mit einem BINÄREN Zähler auf ein DEZIMALE Frequenz kommen 
will, muss der Quarz logischerweise ein Frequenz haben, die das PRODUKT 
einer Binärzahl und Dezimalzahl ist. Das war mal in der Schule dran, 
Teilbarkeit, kleinstes gemeinsames Vielfaches etc.

Für 1ms und einen 8 Bit Zähler braucht man mindestens N*1 kHz, wenn der 
Zähler den CTC-Modus beherrscht (alle "neueren" AVRs). Bei alten ATmega8 
& Co, die keinen CTC mit dem 8 Bit Zähler können, braucht man 1kHz * 256 
* Vorteiler.

>Schön wäre es, auch eine Lösung für andere (beliebige) Taktfrequenzen
>parat zu haben.

Jaja, die beliebige Beliebigkeit. Anything goes . . .

>Aber wir müssen daraus keine Wissenschaft machen.

Warum? Es gibt eine Lösung, man muss sie nur an die 1kHz anpassen. 
Kneifst du davor?

von Stefan F. (Gast)


Lesenswert?

>>Ich habe das mit der 32kHz Variante gelesen,
>Aber nicht verstanden?

Ich habe das verstanden. Nützt mir aber nichts, wenn der Systemtakt zum 
Beispiel 20Mhz ist.

>> es gibt auch einige
>>andere Quarze (z.B. 8Mhz und 16Mhz) wo die Rechnung glatt auf geht.
> Nö.

Doch!
8MHz mit Prescaler 64 ergibt 125kHz.
125kHz geteilt durch 125 ergibt 1kHz. Und da wollte ich hin.

> Das war mal in der Schule dran
Hmmm, das sieht man.

>> Schön wäre es, auch eine Lösung für andere (beliebige)
>> Taktfrequenzen parat zu haben.
> Jaja, die beliebige Beliebigkeit. Anything goes . .
> Es gibt eine Lösung, man muss sie nur an die 1kHz anpassen.
> Kneifst du davor?

Nein ich kneife nicht.

Ich wollte nur wissen, ob es für mein Problem einen einfachen 
Lösungsansatz gibt, der für beliebige Quarze geeignet ist. Denn wenn es 
den gibt, möchte ich ihn gerne kennen. Denn ich möchte praktische 
Lösungen kennen lernen.

Ich wäre ein schlechter Programmierer, wenn ich nicht ab und zu mal 
andere nach Lösungsansätzen fragen würde. Wenn ich nur auf die Sachen 
vertrauen würde, die ich mir selbst ausgedacht habe.

Letzte Woche hat unsere Tochter vorgeführt, wie man eine 
durchgeschüttelte Dose Cola ohne Sauerei schnell öffnet. Ich habe 
hingegen in den vergangenen 40 Jahren immer gewartet, bis sich der Druck 
von selbst abgebaut hat. Ich hätte ein bequemeres Leben gehabt, wenn ich 
diese simple Lösung schon eher gekannt hätte.

Und eben solche einfachen und nützlichen Lösung möchte ich kennen 
lernen, bevor ich dumm sterbe. Deswegen frage ich andere.

Und wenn es keine Lösung in meinem Sinne gibt, dann ist das auch Ok. 
Hatte ich bereits geschrieben.

von Stefan F. (Gast)


Lesenswert?

> Bei alten ATmega8 & Co, die keinen CTC mit dem 8 Bit Zähler
> können, braucht man 1kHz x 256 x Vorteiler.

Auch falsch. Da kann man im Overflow Interrupt den Zähler manipulieren.

Betrifft übrigens IMHO nur den ATmega8, ATtiny22 und ATtiny26.
1
    void initSystemTimer(void)
2
    {
3
        cli();
4
        #if F_CPU>16000000
5
           TCCR0 = (1<<CS02); // Prescaler 256
6
           TCNT0 = 256-(F_CPU/256+500)/1000; 
7
        #elif F_CPU>2000000
8
           TCCR0 = (1<<CS01)+(1<<CS00); // Prescaler 64
9
           TCNT0 = 256-(F_CPU/64+500)/1000; 
10
        #else
11
           TCCR0 = (1<<CS01); // Prescaler 8
12
           TCNT0 = 256-(F_CPU/8+500)/1000; 
13
        #endif  
14
        TIMSK |= (1<<TOIE0); // Interrupt on overflow
15
        sei();
16
    }
17
18
    ISR(TIMER0_OVF_vect)
19
    {
20
        #if F_CPU>16000000
21
           TCNT0 = 256-(F_CPU/256+500)/1000; 
22
        #elif F_CPU>2000000
23
           TCNT0 = 256-(F_CPU/64+500)/1000; 
24
        #else
25
           TCNT0 = 256-(F_CPU/8+500)/1000; 
26
        #endif   
27
        timerCounter++;
28
    }

von c-hater (Gast)


Lesenswert?

Lothar M. schrieb:

> Eines vorweg: mit jeder Frequenz, die nicht glatt durch 1ms teilbar ist,
> wirst du nur "ungefähr Milliskeunden" bekommen...

Das ist wohl wahr. Die böse Mathetik kennt da kein Pardon.

> Ich würde analog zur DDFS diesen Weg gehen:
[suboptimale Lösungen]

Wenn's keine ganzzahligen Lösungen unterhalb einer Millisekunde gibt, 
dann benutzt man einfach den guten alten Bresenham zur Fehlerverteilung, 
das ist doch trivial. Damit hat man dann neben dem sowieso 
unvermeidlichen Jitter wenigstens eine langfristig exakte Frequenz.

Blöd ist allerdings, wenn der Bresenham-Zähler dazu sehr breit werden 
muss. Das kostet dann erheblich Performance. Zum Glück ist das bei allen 
üblichen Quarzfrequenzen nicht der Fall, i.d.R. ist die Sache also mit 
einem 8Bit-Zähler abzuhandeln (was für eine optmierte Routine zur 
Verwaltung dieses Zählers auf einem AVR8 genau 10 Takte Laufzeit 
erfordert), nur in einigen wenigen Fällen muss man einen 16Bit-Zähler 
benutzen, was allerdings auf einem AVR8 schon erheblich mehr reinhaut. 
In diesen Fällen muss man dann genau überlegen, wie genau der ms-Takt 
tatsächlich sein muss, also ob sich der Aufwand zu seiner (wenigsten 
langfristig) exakten Gewinnung tatsächlich lohnt. Kommt man hier zu dem 
Entschluss: nein, dann ist der fixed point "Phasenakku" angesagt, den du 
vorgeschlagen hast. Aber eben nur dann...

von Stefan F. (Gast)


Lesenswert?

Ich finde beim Stichwort Bresenham nur Beschreibungen, wie man damit 
Linien zeichnet. Die Parallele zu meinem Counter sehe ich, allerdings 
ist die dahinter steckende Mathematik für mich zu hoch. Das kann ich 
nicht in Code umsetzen.

10 Takte schreibst du, na das ist ja in dem Rahmen, den ich erhofft 
hatte.

Ich glaube ich muss in der ISR den Counter um +1, -1 oder 0 anpassen. 
Und der Bresenham Algorithmus sagt mir den konkreten Wert pro Interrupt. 
Richtig?

Kann ich Irgendwo von einer Implementierung abgucken? Wie gesagt 
verstehe ich die Mathematische Formel nicht, ich kann nicht einmal diese 
ganzen Symbole lesen.

von Falk B. (falk)


Lesenswert?

@ Stefan Us (stefanus)

>Ich finde beim Stichwort Bresenham nur Beschreibungen, wie man damit
>Linien zeichnet. Die Parallele zu meinem Counter sehe ich, allerdings
>ist die dahinter steckende Mathematik für mich zu hoch. Das kann ich
>nicht in Code umsetzen.

Ist doch schon fertig im Artikel, sogar in der Überschrift! Schon wieder 
übersehen?

https://www.mikrocontroller.net/articles/AVR_-_Die_genaue_Sekunde_/_RTC#Bresenham_f.C3.BCr_RTCs

von Mein grosses V. (vorbild)


Lesenswert?

Stefan U. schrieb:
> Kennt jemand dazu eine elegante Lösung?

Elegant ist meistens das, wo am wenigsten dran rumgenörgelt wird.

Mit den üblichen Quarzfrequenzen geht es ohne Korrekturen mit einem 
8-Bit-Timer bis 18,432MHz. 20MHz ist zu schnell. Kann aber 0,5 ms 
erzeugen. Und darauf kann man dann ja aufbauen.

Stefan U. schrieb:
> #if F_CPU>16000000
>            TCCR0 = (1<<CS02); // Prescaler 256
>            TCNT0 = 256-(F_CPU/256+500)/1000;
>         #elif F_CPU>2000000
>            TCCR0 = (1<<CS01)+(1<<CS00); // Prescaler 64
>            TCNT0 = 256-(F_CPU/64+500)/1000;
>         #else
>            TCCR0 = (1<<CS01); // Prescaler 8
>            TCNT0 = 256-(F_CPU/8+500)/1000;

Du mußt es andersrum machen:
1
#define TimerConfig(interval) do{\
2
\
3
if((F_CPU / interval / 1) <= 256)\
4
{\
5
  TCCR0A = (unsigned char)((1 << WGM01));\
6
  TCCR0B = (1 << CS00);\
7
  OCR0A = (unsigned char)(F_CPU / interval / 1) -1);\
8
}\
9
\
10
else if((F_CPU / interval / 8) <= 256)\
11
{\
12
  TCCR0A = (unsigned char)((1 << WGM01));\
13
  TCCR0B = (1 << CS01);\
14
  OCR0A = (unsigned char)(F_CPU / interval / 8) -1);\
15
}\
16
\
17
else if((F_CPU / interval / 64) <= 256)\
18
{\
19
  TCCR0A = (unsigned char)((1 << WGM01));\
20
  TCCR0B = (1 << CS01) | (1 << CS00);\
21
  OCR0A = (unsigned char)(F_CPU / interval / 64) -1);\
22
}\
23
\
24
else if((F_CPU / interval / 256) <= 256)\
25
{\
26
  TCCR0A = (unsigned char)((1 << WGM01));\
27
  TCCR0B = (1 << CS02);\
28
  OCR0A = (unsigned char)(F_CPU / interval / 256) -1);\
29
}\
30
\
31
else if((F_CPU / interval / 1024) <= 256)\
32
{\
33
TCCR0A = (unsigned char)((1 << WGM01));\
34
TCCR0B = (1 << CS02) | (1 << CS00);\
35
OCR0A = (unsigned char)(F_CPU / interval / 1024) -1);\
36
}\
37
\
38
else\
39
{\
40
  TCCR0A = 0;\
41
  TCCR0B = 0;\
42
  TIMSK0 |= 0;\
43
}\
44
}while(0)

Mit diesem Makro kannst du Timer0 auf Atmega48...328 automatisch 
konfigurieren. Die exakte Millisekunde bekommst du mit allen 
"Normalquarzen" bis 16MHz, die durch 1000 teilbar sind sowie mit 
18,432MHz Baudratenquarz.

Ich habe das Beispiel massiv gekürzt. Deswegen so rudimentär in der 
Funktion. Kann auch sein, daß irgendwo die eine oder andere Klammer 
fehlt. Aber ich denke, es ist klar, wie ich das meine.

Auf diese Art und Weise konfiguriere ich alle Timer-Funktionen bei allen 
Timern, der von mir verwendeten AVRs. Der erzeugte Code ist dabei immer 
exakt der gleiche wie bei "händischer" Eingabe.

Meistens interessiert mich nur ein regelmässiges Interval. Das muß 
selten eine Zehnerpotenz von 1s sein. Wenn ich es brauche, nehme ich 
einen passenden Quarz. Normalerweise nehme ich sowieso 18,432MHz. Damit 
passen nicht nur Baudraten, sondern auch Millisekunden.

Ist ein bißchen Fleißarbeit mit 97% Copy & Paste. Ausgedruckt könnte man 
mit diesem Makromachwerk ein Zimmer tapezieren. Wer das Scheisse findet, 
kann das gerne tun. Es ist mir egal.

Aber wenn ich ein Programm mit einem CTC-Timer von einem Atmega48 mit 
8MHz auf einen Attiny4313 mit 18,432MHz oder auf einen Attiny13 mit 
9,6MHz portiere, rufe ich in allen drei Fällen den Timer so auf:

1
InitTimer(TIMER0, CTC, TIMER0_INTERVAL, COMPA, NORMALPORTS);

und kümmere mich weiter um gar nichts. Das ist mir nicht egal.

: Bearbeitet durch User
von Michael B. (laberkopp)


Lesenswert?

Stefan U. schrieb:
> Allerdings habe ich hier den hässlichen Seiteneffekt, dass jede Sekunde
> ein 1ms Intervall stark verzerrt wird (eben um die Abweichung
> auszugleichen).

Unsinn, du sollst die Lösung, mit der Artikel 1s macht, auf deine 1ms 
anwenden, also mal 1 mehr (1.001ms) mal knapp zuwenig (0.998ms) auf den 
erwarteten nächsten Timerwert draufaddieren damit im zeitlichen Mittel 
1ms rauskommt.

Ja, der Algorithmus ähnelt dem Bresenham, aber der ist 2d, 
eindimensional heisst er wohl DDFS.

von Jakob (Gast)


Lesenswert?

ALLGEMEINGÜLTIG dürfte es schwierig sein, eine Formel anzugeben,
mit der du für jeden Quarztakt, bei jedem Tiny und Mega, mit
jedem Timer eine Lösung findest.

Wer sich da auf "Die genaue Sekunde" beruft, vergisst, dass da schon
einige Einschränkungen vorgegeben sind.

Der grundsätzliche Algorithmus ist schon OK. Ein bis 2 konstante
Speicherplätze für die Korrekturrechnung lassen sich auch meist
bereitstellen. Aber:

Vorteiler und Comparewert für das CTC-Verhalten eines beliebigen Timers
eines beliebigen µCs (Tinyxx, Megaxx) bei beliebiger Quarz-Frequenz
müssen ja schon mal auf die vorhandenen und praktisch nutzbaren Werte 
eingeengt sein.
- Der Comparewert darf nicht zu klein sein: entweder ist er in der IRS
  vielleicht schon abgelaufen, oder seine Änderung bringt viel zu großen
  Jitter...

Du wirst also für jeden µC-Typ + Quarzfrequenz einige Berechnungen
vorher anstellen müssen, um die passenden Parameter als
Konstanten für den gewählten Timer an das Programm zu übergeben.

von Falk B. (falk)


Lesenswert?

@ Jakob (Gast)

>ALLGEMEINGÜLTIG dürfte es schwierig sein, eine Formel anzugeben,
>mit der du für jeden Quarztakt, bei jedem Tiny und Mega, mit
>jedem Timer eine Lösung findest.

Nö. Das kringt man mit ein, zwei Formeln locker hin, man braucht keiner 
Monster-Makros.

>Wer sich da auf "Die genaue Sekunde" beruft, vergisst, dass da schon
>einige Einschränkungen vorgegeben sind.

Welche denn?

>Du wirst also für jeden µC-Typ + Quarzfrequenz einige Berechnungen
>vorher anstellen müssen, um die passenden Parameter als
>Konstanten für den gewählten Timer an das Programm zu übergeben.

Auch das kann man automatisch per Prepräzessor klaren. Der Beweis folgt 
im Laufe des Tages.

von M. K. (sylaina)


Lesenswert?

Falk B. schrieb:
> Nö. Das kringt man mit ein, zwei Formeln locker hin

Oh die würde ich gern sehen...oder bist du jetzt so kleinlich und sagst: 
"...er hat ja nicht 'passende Lösung' gesagt..."?

Falk B. schrieb:
> Auch das kann man automatisch per Prepräzessor klaren.

Ja, aber ist das nicht auch eine Berechnung? ;) Ich mach das beim UART 
auch immer via Prepräzessor um zu sehen ob die Abweichung nicht zu groß 
ist.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@ Michael Köhler (sylaina)

>> Nö. Das kringt man mit ein, zwei Formeln locker hin

>Oh die würde ich gern sehen...oder bist du jetzt so kleinlich und sagst:
>"...er hat ja nicht 'passende Lösung' gesagt..."?

Abwarten.

>> Auch das kann man automatisch per Prepräzessor klaren.

>Ja, aber ist das nicht auch eine Berechnung? ;)

Ja und? Ist das mittlerweile verboten?

OMG!

von M. K. (sylaina)


Lesenswert?

Falk B. schrieb:
> Ja und? Ist das mittlerweile verboten?

Nein, das ist natürlich nicht verboten. Es klang nur so, fand ich jetzt, 
als ob das der Präprozessor ohne Berechnung machen würde.

von Stefan F. (Gast)


Lesenswert?

@Michael

Ja du hast Recht, dass ich nicht aufmerksam gelesen habe. Ich habe mir 
das nochmal angeschaut. Mal sehen, wie das bei mir passt. Ich wähle mal 
willkürlich Werte, bei denen es vermutlich schwierig wird.

- Systemtakt 7,3728Mhz (UART-freundlich)
- Geteilt durch Prescaler 64 ergibt 115200 Hz
- Geteilt durch 8bit Counter 116 ergibt 993,1034482759 Interrupte pro 
Sekunde

Mir ist sonnenklar, wir ich jede Sekunde einen Korrekturwert dazu 
addiere, um auf exakte Sekunden zu kommen. Aber dann hat die Sekunde 
weniger als 1000 Interrupte, also auch weniger als 1000 Intervalle. Das 
ist weit von meinem Ziel entfernt.

Ich muss also wohl bei jedem einzelnen Interrupt einen Korrekturwert 
berechnen und anwenden. Der Fehler pro Interrupt ist 0,0068965517ms. 
Diese Werte addiere ich solange auf, bis über 0,0086806ms Abweichung 
erreicht habe. Das entspricht einem Counter-Takt. In diesem Moment 
ändere ich das Compare Register, so dass der Zähler ausnahmsweise einen 
Takt weniger zählt.

Wenn wir uns die Zahlen anschauen, wird offensichtlich, dass ich dazu 
eine 32 Bit Berechnung brauche. Ansonsten wird es wieder ungenauer, als 
der Quarz selbst ist. Ich bezweifle, dass ich ohne großartige 
Schwierigkeiten 32 Bit Berechnungen in einer ISR machen kann, die jede 
ms aufgerufen wird.

Immerhin soll dieser Counter nicht die ganze Leistung des µC 
verschlucken. Auch nicht die halbe Leistung.

Mir ist da noch ein Haken bewusst geworden: Wenn ich Interrupt für 
länger als 1ms sperre, wird mein Timer ungenau. Im Fall des Atmega 8 (wo 
ich keinen CTC verwende) wird er sogar schon ungenau, wenn ich 
Interrupte für wenige Takte (ca. 60) sperre.

Das lässt sich aber kaum vermeiden, besonders bei meiner Soft-Serial 
implementierung, die ganz borniert mit _delay_us() arbeitet.

Also ist mein Millisekunden Timer ohnehin ungenau und keinesfalls 
geeignet, genaue Sekunden zu messen. Was mich zurück zur Frage bringt: 
Was will ich eigentlich erreichen?

Ich wollte einen möglichst genauen Millisekunden Zähler, mit dem ich 
Zeiten messen kann. Am Besten so genau, dass ich damit auch eine Uhr 
realisieren könnte. Diese Idee kollidiert aber mit anderem primitivem 
Code, der Interrupt sperrt. Außerdem kann ich Uhren einfach passende 
Quarze wählen, oder einen RTC.

Kurz gesagt, mein gestecktes Ziel war wohl unrealistisch. Vielleicht 
machbar, aber es würde in Overkill ausarten.

Ich denke, Jakob hat es auf den Punkt gebracht.

Ich bin mit meinem aktuellen Code, der nur ungefähre Millisekunden 
zählt, nun doch zufrieden. Aus folgenden Gründen:

- Wenn ich genaue Millisekunden brauche, kann ich einen passenden Quarz 
nehmen.

- In den allermeisten Fällen muss es aber gar nicht genau sein. Das war 
nur so eine möchtegern Anforderung in meinem perfektionisten Hirn.

- Ein sparsamer Algorithmus, der in allen Fällen auf allen AVR's mit 
Hilfe von berechnetem Jitter dafür sorgt, dass 10000 ungefähre 
Millisekunden Interrupte exakt eine Sekunde ergeben ist übermäßig 
aufwändig.

Ich wollte eine einfache allgemein gültige Lösung. Die scheint es nicht 
zu geben.

Danke für eure Antworten, ihr habt mir geholfen, meine Entscheidung zu 
treffen.

von Michael B. (laberkopp)


Lesenswert?

Stefan U. schrieb:
> Mir ist sonnenklar, wir ich jede Sekunde einen Korrekturwert dazu
> addiere, um auf exakte Sekunden zu kommen

Du hast immer noch nicht verstanden, daß nicht jede Sekunde der Fehler 
korrigiert wird, sondern bei jedem 1ms Interrupt, in dem manche bis 116 
und manche bis 115 laufen.

Jede Sekunde wird er nur bei 'Die genaue Sekunde' korrigiert.

von Falk B. (falk)


Lesenswert?

@ Stefan Us (stefanus)

>- Systemtakt 7,3728Mhz (UART-freundlich)
>- Geteilt durch Prescaler 64 ergibt 115200 Hz
>- Geteilt durch 8bit Counter 116 ergibt 993,1034482759 Interrupte pro
>Sekunde

>Mir ist sonnenklar, wir ich jede Sekunde einen Korrekturwert dazu
>addiere, um auf exakte Sekunden zu kommen.

Falsch! Die Korrektur erfolgt bei JEDEM Interrupt.

> Aber dann hat die Sekunde
>weniger als 1000 Interrupte,

Nö, es sind 1000, wenn man es richtig macht.

>Ich muss also wohl bei jedem einzelnen Interrupt einen Korrekturwert
>berechnen und anwenden.

Genau.

> Der Fehler pro Interrupt ist 0,0068965517ms.
>Diese Werte addiere ich solange auf, bis über 0,0086806ms Abweichung
>erreicht habe. Das entspricht einem Counter-Takt.

Richtig.

> In diesem Moment
>ändere ich das Compare Register, so dass der Zähler ausnahmsweise einen
>Takt weniger zählt.

Yupp!

>Wenn wir uns die Zahlen anschauen, wird offensichtlich, dass ich dazu
>eine 32 Bit Berechnung brauche.

Nicht zwangsläufig.

> Ansonsten wird es wieder ungenauer, als
>der Quarz selbst ist.

Nein.

> Ich bezweifle, dass ich ohne großartige
>Schwierigkeiten 32 Bit Berechnungen in einer ISR machen kann, die jede
>ms aufgerufen wird.

Ach herje, der arme, kleine AVR. Kann nicht mal ne 32 Bit Addition 
ausführen, ohne stehen zu bleiben.

>Immerhin soll dieser Counter nicht die ganze Leistung des µC
>verschlucken. Auch nicht die halbe Leistung.

Tut er nicht, auch nicht bei 32 Bit, die man aber möglicherweise auch 
nicht braucht.

>Mir ist da noch ein Haken bewusst geworden: Wenn ich Interrupt für
>länger als 1ms sperre, wird mein Timer ungenau.

Das macht man ja auch nicht!

> Im Fall des Atmega 8 (wo
>ich keinen CTC verwende) wird er sogar schon ungenau, wenn ich
>Interrupte für wenige Takte (ca. 60) sperre.

Dann machst du grundlegend was falsch.

>Das lässt sich aber kaum vermeiden, besonders bei meiner Soft-Serial
>implementierung, die ganz borniert mit _delay_us() arbeitet.

Ist halt so.

>geeignet, genaue Sekunden zu messen. Was mich zurück zur Frage bringt:
>Was will ich eigentlich erreichen?

Eine gute Frage.

>Ich wollte einen möglichst genauen Millisekunden Zähler, mit dem ich
>Zeiten messen kann. Am Besten so genau, dass ich damit auch eine Uhr
>realisieren könnte.

Ja und? Das wird jeden Tag tausendfach gemacht. Nur du macht ein Ding 
der Unmöglichkeit draus. Typisch deutsch!

> Diese Idee kollidiert aber mit anderem primitivem
>Code, der Interrupt sperrt. Außerdem kann ich Uhren einfach passende
>Quarze wählen, oder einen RTC.

EBEN!

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Stefan U. schrieb:
> Der Fehler pro Interrupt ist 0,0068965517ms.
Du lügst dir mit diesen dahergerechneten Zahlenkolonnen selber was in 
die Tasche, denn dein Quarz ist niemals so genau und stabil.

Stefan U. schrieb:
> Der Fehler pro Interrupt ist 0,0068965517ms. Diese Werte addiere ich
> solange auf, bis über 0,0086806ms Abweichung erreicht habe.
Machst du das mit Float-Variablen? Die sind auch nicht so arg genau, das 
ist dir hoffentlich bewusst...

von M. K. (sylaina)


Lesenswert?

Lothar M. schrieb:
> Machst du das mit Float-Variablen? Die sind auch nicht so arg genau, das
> ist dir hoffentlich bewusst...

Und nur um vorzugreifen: Doubles sind auf dem AVR auch nicht genauer als 
Floats...um nicht zu sagen dass der Compiler (oder wars der Linker) aus 
Doubles im Quellcode eh Floats macht da der AVR nix anderes kann 
diesbezüglich. ;)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Michael K. schrieb:
> da der AVR nix anderes kann diesbezüglich. ;)

Der AVR kann nur Integers (8 und 16 Bit), alles andere macht der
Compiler.

Allerdings wurde bei der Implementierung des Compilers vor mehr als
15 Jahren die Entscheidung getroffen, sich auf 32-Bit-Gleitkommazahlen
zu beschränken, vermutlich aus Performancegründen ("double" ist ja
der Wertebereich, auf den bei Gleitkomma die default promotion greift).
Eigentlich ein bisschen schade, 48 Bit wäre besser gewesen - ein guter
Kompromiss zwischen Genauigkeit und Geschwindigkeit, gab's schon bei
Turbo-Pascal auf dem Z80.  Hätte sogar die Minimalvoraussetzungen für
C99-Kompatibilität erfüllt. ;-)

von M. K. (sylaina)


Lesenswert?

Jörg W. schrieb:
> Der AVR kann nur Integers (8 und 16 Bit), alles andere macht der
> Compiler.

Na wenn wir so kleinlich werden wollen: Intergers kann der auch nicht, 
er kann nur Bits.
However, ändert nichts daran, dass auf einem AVR kein Unterschied 
zwischen Double und Float besteht. Beide werden als Float behandelt ;)

von c-hater (Gast)


Lesenswert?

Lothar M. schrieb:

> Du lügst dir mit diesen dahergerechneten Zahlenkolonnen selber was in
> die Tasche, denn dein Quarz ist niemals so genau und stabil.

Das ist garnicht der Punkt. Der Punkt ist einfach, dass man die 
Fehlerakkumulation vermeiden muss, um wenigstens so genau sein zu 
können, wie der Muttertakt es ist.

Capisce?

Aber genau deswegen ist der Ansatz mit floats jeglicher Art schon von 
vornherein ziemlicher Unsinn, insofern hast du natürlich vollkommen 
Recht.

@ Stefan Us (stefanus)

Übrigens: In dem Thread "The secret of WS2812B" findest du in dem 
Funktionsdemo eine Bresenham-Anwendung für einen schon recht 
komplizierten Fall, der sogar einen 24Bit-Bresenham-Zähler erfordert.

21 Takte. Und das bei dem Luxus, den verschissenen Zähler nicht in 
Registern zu halten, sondern im im RAM. Wenn ich den in Registern halten 
würde, was problemlos möglich gewesen wäre, hatte das Teil 9 Takte 
gekostet, denn 12 Takte von den 21 gehen für das Laden aus dem RAM und 
das Rücksichern in den RAM drauf.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Michael K. schrieb:
>> Der AVR kann nur Integers (8 und 16 Bit), alles andere macht der
>> Compiler.
>
> Na wenn wir so kleinlich werden wollen: Intergers kann der auch nicht,
> er kann nur Bits.

Er kann sie native als Integer addieren, subtrahieren und teilweise
multiplizieren.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

c-hater schrieb:
> Das ist garnicht der Punkt. Der Punkt ist einfach, dass man die
> Fehlerakkumulation vermeiden muss, um wenigstens so genau sein zu
> können, wie der Muttertakt es ist.
>
> Capisce?
Ich hatte dazu schon das Wort DDFS ins Rennen geworfen. Damit kann mit 
simplen Integeroperationen leicht den Summationsfehler bis zur 
Quarzgenauigkeit reduzieren.

von Stefan F. (Gast)


Lesenswert?

Macht euch keine Sorgen um Fließkomma Zahlen. Nur weil ich 0,xxxxx 
Millisekunden geschrieben habe, heißt das noch lange nicht, dass ich 
Fließkommazahlen im C Programm verwenden wollte. In der Tat habe ich auf 
Mikrocontrollern in 20 Jahren noch nie Fließkomma Zahlen verwendet.

Naja, einmal vielleicht. Aber ein Blick in das Assembler-Listing brachte 
mich dazu, das ganz schnell anders zu lösen und nie wieder zu versuchen.

von c-hater (Gast)


Lesenswert?

Lothar M. schrieb:

> Ich hatte dazu schon das Wort DDFS ins Rennen geworfen. Damit kann mit
> simplen Integeroperationen leicht den Summationsfehler bis zur
> Quarzgenauigkeit reduzieren.

Was zum Teufel soll "DDFS" sein? Nichtmal Google kennt den Begriff 
(jedenfalls nicht für diesen Zshg. auf den ersten zwei Seiten Hits).

Vermutlich handelt es sich also um einen selbstgestrickten Kunstbegriff 
eines Re-Inventors, der im Kern meint: Bresenham. Nur dass der 
Wiedererfinder den entweder garnicht kannte oder auch nur nicht 
begriffen hat, dass er da seit 60 Jahren bekanntes Terrain erneut 
erforscht...

von Falk B. (falk)


Lesenswert?

@ c-hater (Gast)

>Was zum Teufel soll "DDFS" sein?

Sowas wie DSDS ;-)

DDFS: Direct Digital Frequency synthesizer.

> Nichtmal Google kennt den Begriff
>(jedenfalls nicht für diesen Zshg. auf den ersten zwei Seiten Hits).

http://www.lothar-miller.de/s9y/categories/31-DDFS

Der 9. Treffer bei einer Googlesuche . . .

von Jakob (Gast)


Lesenswert?

@ Falk Brunner (falk)

Jakob (Gast) schrieb
>>Du wirst also für jeden µC-Typ + Quarzfrequenz einige Berechnungen
>>vorher anstellen müssen, um die passenden Parameter als
>>Konstanten für den gewählten Timer an das Programm zu übergeben.

Falk Brunner (falk) schrieb:
>Auch das kann man automatisch per Prepräzessor klaren. Der Beweis folgt
>im Laufe des Tages.

Keine 2 Stunden mehr, ich werde langsam neugierig...  :-)

von Jakob (Gast)


Lesenswert?

Ansonsten:

Meine Güte, "der Bresenham" wird ja verfochten, als hätten den hier
manche selbst erfunden. Die sind wohl sehr stolz darauf, ihn kapiert
zu haben...

Der Bresenham hat doch nur das in einen Computer gehackt, was sich
Aloisius Lilius schon vor 500 Jahren ausgedacht hat, um den "Timer"
KALENDER mit minimalen, gleichmäßig verteilten Korrekturschritten
auf den "Quarz" ERDUMLAUF zu synchronisieren...

von m.n. (Gast)


Lesenswert?

Michael K. schrieb:
> However, ändert nichts daran, dass auf einem AVR kein Unterschied
> zwischen Double und Float besteht.

Unfug, auch wenn er immer gerne wiederholt wird!

von Falk B. (falk)


Lesenswert?

@ m.n. (Gast)

>> However, ändert nichts daran, dass auf einem AVR kein Unterschied
>> zwischen Double und Float besteht.

Genauer, beim Compiler avr gcc. Andere (kommerzielle) Compiler ala IAR 
machen das ggf. anders.

>Unfug, auch wenn er immer gerne wiederholt wird!

Wie meinen?

von Falk B. (falk)


Lesenswert?

@Jakob (Gast)

>>Auch das kann man automatisch per Prepräzessor klaren. Der Beweis folgt
>>im Laufe des Tages.

>Keine 2 Stunden mehr, ich werde langsam neugierig...  :-)

Ich und hab im Moment keine Lust. Man muss halt das Konzept aus dem 
Artikel anpassen. ;-)

von Jakob (Gast)


Lesenswert?

@ Falk Brunner (falk)

Na super! Und ich dachte, jetzt kommt mal was, an dem
ich was lernen kann!

Mit den "vorherigen Berechnungen" hatte ich auch an den
Präprozessor gedacht - was mir aber selbst für nur mega,
oder tiny bei beliebigem Quarz SEHR aufwändig erschien.

Wird wohl auch nicht mit 2 Zeilen zu erledigen sein, wenn man
bedenkt, dass manche Timer nur wenige Prescaler-Faktoren und
mal 8, mal 16 Bit haben - bei tiny261 auch noch 10 Bit...

OK, man müsste sich für den eigentlichen (Hardware-)Ablauf auf
den kleinsten gemeinsamen Nenner (falls es den gibt) festlegen.

Dazu für jeden µC die Register-Namen, samt Position der
Mode-, Prescaler-, F_In-Bits etc. vorhalten.

Und - und - und...

Damit ist aber auch die anderweitige Nutzung dieser Timer
blockiert. - Wie vermittle ich das dem Programmierer?

2 Zeilen?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Jakob schrieb:
> Wird wohl auch nicht mit 2 Zeilen zu erledigen sein, wenn man bedenkt,
> dass manche Timer nur wenige Prescaler-Faktoren und mal 8, mal 16 Bit
> haben
Ich würde sagen, deine Denkrichtung ist verkehrt herum.
Die Idee hier ist: du sagst dem Makro, welche Frequenz der Interrupt hat 
und das Makro berechnet dann den Summationswert für die DDS/DDFS.

: Bearbeitet durch Moderator
von eProfi (Gast)


Lesenswert?

Du brauchst keinen 1ms-Interrupt, um eine Sekunde zu "erzeugen".
Es kann auch ein unregelmäßiger IRQ sein, solange gewährleistet ist, 
dass der Timer zwischen zwei IRQs keinen Überlauf macht. In der ISR 
subtrahierst du die Anzahl der Zyklen oder Ticks, die seit dem letzten 
IRQ vergangen sind, in eine z.B. 24-bit-Variable, wenn diese 0 
unterschreitet (geht sehr einfach mit dem Negativ-Flag), ist die Sekunde 
vorbei. Dann addierst Du die genaue Zahl der Ticks pro Sekunde, damit 
die nächste Sekunde um die bereits vergangene Zeit korrigiert wird.

Das ganze dauert nur ein paar Zyklen (3 loads, 3 subc und ein 
conditional branch).  Wenn die Sekunde vorbei ist, nochmal 3 loads und 3 
adc.

von Falk B. (falk)


Lesenswert?

@ Jakob (Gast)

>Na super! Und ich dachte, jetzt kommt mal was, an dem
>ich was lernen kann!

Das kannst du jetzt schon. Der Abschnitt mit dem 32 KHz Uhrenquarz ist 
von mir.

https://www.mikrocontroller.net/articles/AVR_-_Die_genaue_Sekunde_/_RTC#Echtzeituhr_mit_Uhrenquarz

>Dazu für jeden µC die Register-Namen, samt Position der
>Mode-, Prescaler-, F_In-Bits etc. vorhalten.

>Und - und - und...

Man kriegt das schon hin, je nach Aufwand auch vollautomatisch.

>Damit ist aber auch die anderweitige Nutzung dieser Timer
>blockiert. - Wie vermittle ich das dem Programmierer?

Mein Gott, niemand will ein neues Super-Duper Sonstwas Framework 
erschaffen, bei dem jeder Hirntote nur dreimal clicken muss, um die Next 
Generation Killer App zu "erschaffen".

Ach ja. Nur selber denken macht geistig fett. Dazu gehört auch, sich mal 
an ein kniffliges Problem ranzusetzen und es SELBER hinzukriegen! Los 
gehts!

von Jakob (Gast)


Lesenswert?

@ eProfi (Gast)

Der TO möchte einen SO GUT WIE MÖGLICH genauen 1 ms Takt.
Wahrscheinlich so, dass 1000 davon ungefähr eine Sekunde
dauern.

Für alle AVR tiny und mega und jeden Quarz.

Setze doch einfach deine klugen Gedanken in etwas dafür
Brauchbares um - und schon bist du der Held!

Blabla hatten wir hier schon mehrfach...

von eProfi (Gast)


Lesenswert?

> Der TO möchte einen SO GUT WIE MÖGLICH genauen 1 ms Takt.
Der TO Stefanus glaubt, einen solchen für eine möglichst exakte 
Zeitmessung zu brauchen.
Das genaue Vorgehen hängt von den Wünschen (z.B. der maximal messbare 
Zeit und der Auflösung) ab:
Bis zu 2^31 / 20000000 = 107,37 Sekunden (bei 20 MHz) reicht es, statt 
der 24-bit-Zählvariable eine mit 32 Bits zu verwenden.
(^31, weil eine signed Variable den Algorithmus vereinfacht).

Dann ist die Zeitmessung sehr einfach: Bei Beginn den Zählerstand 
merken, bei Ende die gemerkte Zeit vom aktuellen Zählerstand 
subtrahieren.
Wenn man es sehr genau haben will, rechnet man noch die beiden 
Timer-Stände an den beiden Ereignissen mit dazu.

Nun hat man die Ticks zwischen den beiden Ereignissen.
Mit einer Division durch (Ticks pro Einheit) kommt man auf die 
gewünschte Einheit, z.B. ms oder s.

Ein anderer Lösungsansatz ist ein leicht erweiterter Algorithmus mit 
zwei kaskadierten Zähler. Damit kann man beliebig lange Zeiten 
bestimmen.
Der eine Zähler zählt Ticks, der andere z.B. ms oder s, je nach 
gewünschter Auflösung.

> Für alle AVR tiny und mega und jeden Quarz.
Mir ist kein AVR bekannt, der nicht einen Timer und die Befehle adc, sbc 
und bmi hätte.


Ein Beispiel:
Fxtal = 1,234 MHz, freilaufender 8bit-Timer, d.h. IRQs mit 4822,xx Hz.
Wir subtrahieren in der ISR 256 und vergleichen auf Null.
  int16_t ticks; uintxx_t millis;
  ticks -= 256; if (ticks < 0) {ticks += 1234; millis ++;}

Das geht in Asm besonders elegant, da nur ein dec auf das High-Byte von 
ticks nötig ist und danach das Ergebnis des Compares schon intrinsic im 
Sign-Bit und im Carry-Bit steckt.
  dec high(ticks)
  bnc done   ;oder bpl
  addw ticks, #1234
  inc millis
done:
  reti

Aufpassen muss man, wenn das "1234" kleiner wird als "256", die ISR also 
seltener aufgerufen wird als milli hochgezählt, dann heißt es:
 ticks -= 256; while (ticks < 0) {ticks += xxx; millis++;}

Das ist ja der Clou an einem Synthesizer: man kann aus einer beliebigen 
Frequenz eine (nahezu) beliebige andere erzeugen.
Das gilt für Hardware- als auch für Software-Synthesizer.

von eProfi (Gast)


Lesenswert?

Korrektur: bei ersten Algorithmus kann man doch 32 Bits nutzen, da das 
Sign-Bit nicht verwendet wird.
Der zweite Algo ist der Bresenham, wobei die Fallunterscheidung if / 
while  beim Linien-Bresenham mit der Geradensteigung <1 / >1 
gleichwertig ist. Beim Klassiker löst man die Fallunterscheidung mit dem 
Vertauschen von x und y, das dürfte hier nicht funktionieren.

Ich weiß, die Theorie ist nicht ganz trivial, ich habe es damals (1986?) 
auch nicht auf Anhieb verstanden, sondern mehrere Wochen / Anläufe 
gebraucht.
Auch bitte ich zu entschuldigen, wenn ich die AVR-Syntax nicht mehr ganz 
korrekt wiedergebe, ihre Verwendung liegt auch schon wieder mehrere 
Jahre zurück. Klar sind Befehle wie "inc millis"  als Makros für 16 / 24 
/ 32 Bit zu verstehen  etc.

von Stefan F. (Gast)


Lesenswert?

Interessant, das immer noch darüber diskutiert wird, was ich vermutlich 
haben wollte und was nicht.

1) Ich brauche einen ungefähren Millisekunden Counter. Der Code soll auf 
möglichst allen ATtiny und ATmega µC laufen und sich auf die Nutzung 
eines 8bit Counters beschränken.

2) Ich habe längst genug Antworten erhalten.

3) Wenn es ohne großen Aufwand machbar ist, dann möchte ich den Timer so 
gestalten, dass 1000 ungefähre 1ms Intervalle zusammen exakt eine 
Sekunde ergeben (unter der Annahme, das der Quarz seine Sollfrequenz 
exakt einhält).

Punkt 1 war schon fertig, sozusagen die Ausgangslage.

Punkt 2 ist erledigt, wirklich.

Punkt 3 ist nicht möglich. Der Aufwand wird im Verhältnis zum Nutzen 
unverhältnismäßig groß und damit hat sich das Thema schon erledigt. Denn 
ich habe eine simple Alternative: Passenden Quarz wählen.

von Uwe K. (ukhl)


Lesenswert?

Der Papst Gregor XIII. hat im 16. Jahrhundert etwas eingeführt, was auch 
hier funktioniert.

Das Schaltjahr.

OK. Wir brauchen eine Schaltmillisekunde. Aber das funktioniert genauso. 
Mit drei 16bit korrekturwerten (meistens kommt man mit 8bit aus), 
bekommt man nahezu jeden Quarz in den Griff. Es ist kein Float oder 
32bit Variable notwendig. Und komplizierte Berechnungen werden zur 
Laufzeit auch nicht gemacht. Die benötigte CPU Power liegt bei wenigen 
Takten. Der Jitter liegt dann bei maximal 1 ms.

Im ersten Wert speichert man die Anzahl der Timer-Takte die eine 
Taktkorrektur benötigt (1 Takt Differenz).

Der zweite Wert ist durch den ersten Wert teilbar. Er speichert die 
Anzahl der Takte die NICHT korrigiert werden, obwohl der erste Wert 
zutrifft.

Der dritte (da sind wir schon im ppm Bereich) ist durch den zweiten Wert 
teilbar und sagt das trotzdem korrigiert wird.

Die Korrektur wird nur in eine Richtung (wie beim Schaltjahr) gemacht. 
Entweder -1 wenn der Timer zu langsam läuft oder +1 wenn er zu schnell 
ist.

Beispiel: Schaljahre mal anders:

Ein Jahr hat 365,2425 Tage. Der Kalender aber nur 365 Tage. Wann müssen 
wir korrigieren? Auf jeden Fall ist der Kalender zu langsam. Wir fügen 
also einen Tag hinzu, wenn wir eine Anpassung vornehmen.

1. Korrektur: 1 Tag / 0,2425 Tage = 4   (wir runden ab)
-  Wir korrigieren 1/4 = 0,25. Dann bleiben 364,9925. Das war zu viel. 
Wir müssen einmal aussetzen. Dafür ist der 2. Wert da.

2. Korrektur: 1 Tag / -0,0075 Tage = -133
-  durch 4 teilbar wird daraus -132 . Bei 132 Takten setzen wir einmal 
aus.
Wir speichern 132 als 2. Wert. Wir korrigieren 1 / (-132) = 
-0,0075757575(Periode). Wir haben dann einen Wert von 365,0000757575 
(Periode). Das ist noch zu viel. Naja, es sind 0,2 ppm Abweichung.

3. Korrektur: 1 Tag / 0,00007575757575 Tage = 13200 (genau!). Der dritte 
Wert bekommt 13200. Damit ist die genaue Millisekunde perfekt Langzeit 
stabil (Rechnerisch jedenfalls).

Damit haben wir die drei Werte:
4
132
13200

Beim Gregorianischen Kalender wird 4, 100 und 400 genommen.

Jetzt machen wird das mit dem OCR0A-Wert:
20 MHz Quarz
OCR0A 78,125 also 78-1 sprich 77 (256 Teiler)
Wert 1: 8 --> Volltreffer; Wert 2 und 3 werden nicht benötigt.
Alle 8 "Millisekunden" den Wert auf 76 absenken.

14,318 MHz Quarz
OCR0A 55,9296875 sind 56-1 sprich 55 (256 Teiler)
Wert 1: 14
Wert 2: 896 --> Volltreffer
Da wir zu schnell sind:
Alle 14 "Millisekunden" den Wert auf 56 erhöhen.
Alle 896 "Millisekunden" NICHT erhöhen.

Anderer Teiler:
OCR0A 223,71875 sind 224-1 sprich 223 (64 Teiler)
Wert 1: 3
Wert 2: 18
Wert 3: 288 --> Volltreffer
Auch hier sind wir zu schnell:
Alle 3 schritte den Wert auf 224 erhöhen
Alle 18 schritte NICHT erhöhen
Alle 288 schritte wieder auf 224 erhöhen

14,7456 MHz Quarz (krummere Werte habe ich nicht gefunden)
OCR0A 57,6 sind 58-1 sprich 57 (256 Teiler)
Wert 1: 2
Wert 2: 10 --> Treffer
Zu schnell also langsamer werden bei der Korrektur.

Anderer Teiler:
OCR0A 230,4 sind 230
Wert 1: 2
Wert 2: 8
Wert 3: 40
Hier sind wir zu langsam, also schneller werden.

2,097152 MHz Quarz
OCRA0A 8,192 sind 8-1 also 7 (256 Teiler)
Wert 1: 5
Wert 2: 125 --> Treffer

Ist das die Lösung?

von c.m. (Gast)


Lesenswert?

wär das nichts?

http://www.ebay.de/sch/i.html?_nkw=rubidium+10MHz

oder soll es "ganz exakt 1ms sein und nichts kosten dürfen"?

von Stefan F. (Gast)


Lesenswert?

Leute, jetzt seid ihr soweit vom Thema abgekommen, euch kann man gar 
nicht mehr zurück holen.

Lasst es gut sein, das Thema wurde ausreichend beleuchtet.

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.