Forum: Mikrocontroller und Digitale Elektronik Frage zu Timereinsatz (Atmega8/Frequenzzähler/Drehgebercode)


von H. G. (hgruner)


Angehängte Dateien:

Lesenswert?

Hallo,

Entschuligungg erstmal für den x-ten Beitrag dazu.
(Ich arbeite mit einem Atmega8 auf StK500 und schreibe in C)

Nun ein paar Anfängerfragen zum Thema:

Ich benutze zum Ausprobieren einen alten TTL Inkrementalgeber mit einer 
1000er Auflösung. Kanal 1 und Kanal 2 funktionieren noch einwandfrei.
(Kanal0 hat sich laut Oszilloskop etwas zickig mit regelmäßigen 
Signalen)
Also habe ich Kanal 1 an PD2 und Kanal 2 PD3 des Atmega8 angeschlossen.


Mein erstes Ziel war, den Geber zu drehen und am Display etwas 
hochzuzählen zu lassen.
So habe ich nach erfolglosen Versuchen einen eigenen Code zu schreiben, 
das Beispiel aus dem Tutorium verwendet. Und es funktioniert.
Nur habe ich kaum Erfahrungen beim Anwenden des Timers.
Darum habe ich anstatt des 16bit timers den 8bit Timer0 des Atmega8 
verwendet und hoffen den Code dadurch nicht alzu sehr verkrüppelt zu 
haben.

FRAGE1: -> Habe ich mir dadurch irgendwie ein noch nicht bemerktes 
Eigentor geschossen oder könnte man das so machen?


Der jetzt verwendete Code ist im Anhang (Routinen zum Ansteuern des 
max7219 mit fünf 7-Segmentanzeigen sind zur besseren Übersicht 
herausgelöscht)


Mein nächstes Ziel wäre eine Drehzahl auslesen zu können:
Mein Gedanke nach dem Durchforsten des Forums dazu ist, mit einem Timer 
die Zeit zwischen zwei (ansteigenden) Flanke zu messen.
Also die erste Flanke startet den Timer und die zweite stoppt ihn.
Nun TCNTO von 0 bis 256 hochzählen lassen und dann die ausgelösten 
Interrupts zählen, den "TCNT0 Endwert" dazuaddieren und den vorher 
gemerkten TCNT0 Startwert abziehen.
Das ganz dann in eine Zeit umrechnen und durch den Kehrwert die 
Frequenz/Drehzahl ermitteln. (natürlich die 1000 Inkremente dabei 
beachten)

FRAGE2-> klappt das so?


Wenn ja, bin ich mir jetzt nur nicht ganz sicher wie das umzusetzen ist.

FRAGE3:-> Kann ich dazu den vorhandenen Timer0 benutzen und nur 
erweitern?
Oder sollte ich einen zweiten Timer einfügen, der das übernimmt?
(und sollte der dann höhere oder niedrigere Priortät haben)

FRAGE4:
Jetzt kommt noch mein Verständnisproblem zum tragen. Denn eigentlich 
kann der Prozessor doch zu gleichen nur eine Sache machen. Das heißt 
wenn er einen Timer behandelt, macht er doch nix anderes. Wie 
funktioniert dann "gleichzeitig" der zweite Timer?


FRAGE5:
Wenn z.B. ein "Drehzahltimer" durch die erste steigende Flanke aktiviert 
und durch die Zweite wieder deaktiviert wird, kommen danach zusätzlich 
noch die Rechnungen und Displayroutinen, die der Prozessor bearbeiten 
muss.
Heißt das er kann gar nicht alle steigenden Flanken mitbekommen?

Bsp:
-> Flanke 1  Start -> Zeit über Timer messen -> Flanke 2 Ende -> 
auswerten -> Flanke 3 Start -> Zeit über Timer messen -> usw.

ergo würde die Zeit zwischen Flanke 2 und 3 nicht mitgezählt. Oder geht 
das so schnell, das Flanke 2 sowohl Ende (incl. auswerten) und Start 
sein kann.



Hoffe die Fragen sind nicht allzu zu dumm und jemand kann diese 
beantworten. (auch wenn das Thema bestimmt schon einigen zum Hals 
raushängen)

MfG H.

von gast (Gast)


Lesenswert?

in den interruptroutinen sollten keine verzögerunegn oder schleifen 
abgearbeitet werden


also alles was zeit hat und brauch in die MAIN packen
in die ISR baust du dir reine  erkennung des signals ein

von Karl H. (kbuchegg)


Lesenswert?

H. Gruner schrieb:

> Mein Gedanke nach dem Durchforsten des Forums dazu ist, mit einem Timer
> die Zeit zwischen zwei (ansteigenden) Flanke zu messen.

Guter Plan

> Also die erste Flanke startet den Timer und die zweite stoppt ihn.

Auch noch ok

> Nun TCNTO von 0 bis 256 hochzählen lassen und dann die ausgelösten
> Interrupts zählen, den "TCNT0 Endwert" dazuaddieren und den vorher
> gemerkten TCNT0 Startwert abziehen.

Hä?

Das klingt danach, als ob du eines noch nicht verinnerlicht hast:
Ein Timer zählt ganz von alleine.

Da kommt also eine Flanke daher.
Als Reaktion darauf startest du den Timer.
Der Timer zählt jetzt eigenständig vor sich hin, während die CPU was 
anderes macht.
Jetzt kommt die nächste Flanke und du stoppst den Timer wieder.

-> Die Zahl die jetzt im Timerregister steht ist ein Mass für die Zeit 
die zwischen den Flanken vergangen ist.

> FRAGE4:
> Jetzt kommt noch mein Verständnisproblem zum tragen. Denn eigentlich
> kann der Prozessor doch zu gleichen nur eine Sache machen. Das heißt
> wenn er einen Timer behandelt, macht er doch nix anderes. Wie
> funktioniert dann "gleichzeitig" der zweite Timer?

Die Timer sind unabhängig voneinander und vom Rechenwerk.
Das ist nichts anderes als ein Zählerbaustein, der von irgendwo seinen 
Takt bezieht.

> Heißt das er kann gar nicht alle steigenden Flanken mitbekommen?

Kommt drauf an, wie kurz hintereinander die Flanken kommen.
So ein µC ist ja unglaublich schnell. In der Zeit die du für ein 
Augenzwinkern brauchst erledigt der eine Menge Dinge.

von Oliver (Gast)


Lesenswert?

>Mein nächstes Ziel wäre eine Drehzahl auslesen zu können:
>...
>FRAGE2-> klappt das so?

Das könnte so ähnlich klappen. Allerdings kommt es etwas auf die 
Drehzahlen an, die du da messen möchtest. Immerhin hast du zum einen ein 
Verzögerung zwischen Auftreten des Interrupts und Auslesen des Timers, 
und, wesentlich schlimmer, falls noch andere ISR's aktiviert sind, ist 
diese Verzögerung nicht konstant. Bei kleine Drehzahlen ist der Fehler 
sicherlich noch zu verschmerzen, falls du den Geber ab z.B an einen 
Motor mit 3000 1/min schraubst, kommen die Impulse mit 50kHz, und dann 
wird es eng. Das die Auswertung mit Drehzahlberechung und Anzeige auch 
noch Zeit braucht, ist dagegen nicht weiter schlimm. Alles schneller als 
15 Bilder/sek ist für den Menschen eh Film, so oft brauchst du die 
Anzeige also gar nicht zu aktualisieren.

Der Mega8 hat aber (wie die allermeisten Megas) extra für solche 
Anwendungen auch eine spezielle Hardware eingebaut, die dir einen 
Großteil der Arbeit abnimmt. Timer1 (geht leider nicht mit Timer0) hat 
einen "Input Capture" Modus, der automatisch bei Auftreten einer Flanke 
am ICP-Pin den Zählerstand in ein spezielles Register kopiert, und zwar 
in Hardware, unabhängig davon, was der Prozessorkern gerade macht, und 
(fast) ohne Verzögerung. Lies mal im Datenblatt den Abschnitt dazu 
durch.

Oliver

von H. G. (hgruner)


Lesenswert?

Erstmal vielen Dank euch Dreien für eure Mühe.

@gast:
Ok guter Hinweis, werde ich beim Ausprobieren explitit beachten.



@Karl heinz Buchegger:

>Der Timer zählt jetzt eigenständig vor sich hin, während die CPU was
>anderes macht.
>....
>Die Timer sind unabhängig voneinander und vom Rechenwerk.
>Das ist nichts anderes als ein Zählerbaustein, der von irgendwo seinen
>Takt bezieht.

Ahh danke, ich hatte den entsprechenden Satz im Tutorial falsch 
interpretiert. Ich dachte der Prozessor gibt dem Timer nur ein höhere 
Priorität und lässt alles andere erstmal liegen.

entsprechende Stelle Tutorial:
>um ein Register regelmäßig und vor allen Dingen unabhängig vom restlichen 
>Programmfluss (!) hochzählen zu lassen.

Und jetzt zum:
>Hä?
>Das klingt danach, als ob du eines noch nicht verinnerlicht hast:

Da hast du gar nicht mal so unrecht ;)

Grundidee war diese:
Ich starte den Timer und die Welle dreht sich z.B langsam, das 
Zählregister zählt bei 8bit von 0 bis 255 hoch. Nun gab es in dieser 
Zeit aber noch kein Endsignal. Also wird dieses Erreichen des Overflows 
in einer Variable gespeichert.
z.B. so:
1
ISR( TIMER0_OVF_vect )
2
{
3
  overflowcounterT0++;
4
}
den Wert dieser Variablen könnte man doch bei einem prescaler von 1 
einfach mit 256 multiplizieren, um analog zum Prozessortakt eine Zeit zu 
ermitteln.
Jetzt muss ich aber doch davon ausgehen, dass das Endsignal nicht zur 
gleichen Zeit wie der Overflow kommt. Ergo müsste ich die Schritte, die 
das Zählregister noch gemacht hat (meinetewegen 174) dazuaddieren.
Und wenn das Zählregister immer stur durchzählt, müsste ich bei der 
nachfolgenen Zeitmessung noch die Schritte abziehen, die es vor dem 
erneuten Startsignals gemacht hat.

in etwa so:
zeit = (overflowcounterT0 * 256) + zaehlerstandendeT0 - 
zaehlerstandanfangT0;

-> hoffe ich konnt mich diesmal verständlicher ausdrücken



@Oliver

Was sich bei mir dreht, macht eigentlich nur 500U/min aber es geht ja 
darum, etwas zu lernen. Also sollte man so wenig Timer wie möglich 
einsetzen, wenn man z.B. mit der gemessen Drehzahl irgendwas steuern 
möchte (je nach geforderter Genauigkeit)

Zum ICP:
Den habe ich beim Überfliegen der Pinbelegung auch schon gesehen. Aber 
leider ist das nur einer und der Inkrementalgeber hat zwei (drei mit 0 
Signal) Anschlüsse.
Aber könnte ich Phase 1 an PB0 (ICP) und Phase 2 an PB1 anschließen, 
dann eben nur Phase 1 für das Drehzahlsignal über ICP nutzen und 
gleichzeitig noch mit dem vorhanden Code eine Ortung der Welle über 
Phase 1 und Phase 2 gewährleisten? Das geht doch dann nicht mehr, oder?


Tante Edit:
Da niemand etwas gesagt hat, gehe ich mal davon aus, dass mein Code im 
Groben in Ordnung ist

von Oliver (Gast)


Lesenswert?

>Also sollte man so wenig Timer wie möglich
>einsetzen, wenn man z.B. mit der gemessen Drehzahl irgendwas steuern
>möchte (je nach geforderter Genauigkeit)

Nicht unbedingt. Da du ja für eine Anzeige nur ein paarmal pro Sekunde 
tatsächlich die Drehzahl messen musst, reicht es bei den Drehzahlen auch 
aus, während der eigentlichen Messung alle anderen ISR's zu sperren. 
Wenn das Drehzahlsignal für eine mit hoher Taktfrequenz laufende 
Regelung genutzt werden soll, geht das natürlich nicht mehr. Dann dürfen 
tatsächlich keine anderen ISR's mehr aktiv sein, oder du nutzt die input 
capture Funktion. Die wurde extra für diese zeitkritischen Fälle 
erfunden.

>Aber könnte ich Phase 1 an PB0 (ICP) und Phase 2 an PB1 anschließen,
>dann eben nur Phase 1 für das Drehzahlsignal über ICP nutzen und
>gleichzeitig noch mit dem vorhanden Code eine Ortung der Welle über
>Phase 1 und Phase 2 gewährleisten? Das geht doch dann nicht mehr, oder?

Wenn ich das Datenblatt richtig interpretiere, lässt sich PB0 bei 
Konfiguration als ICP1 nicht mehr normal auslesen, aber es ja nicht 
verboten, einen Kanal deines Drehgebers an mehr als einen AVR-Eingang 
anzuschliessen.

Oliver

von Karl H. (kbuchegg)


Lesenswert?

H. Gruner schrieb:

> Grundidee war diese:
> Ich starte den Timer und die Welle dreht sich z.B langsam, das
> Zählregister zählt bei 8bit von 0 bis 255 hoch. Nun gab es in dieser
> Zeit aber noch kein Endsignal. Also wird dieses Erreichen des Overflows
> in einer Variable gespeichert.
> z.B. so:
>
1
> ISR( TIMER0_OVF_vect )
2
> {
3
>   overflowcounterT0++;
4
> }
5
>
> den Wert dieser Variablen könnte man doch bei einem prescaler von 1
> einfach mit 256 multiplizieren, um analog zum Prozessortakt eine Zeit zu
> ermitteln.
> Jetzt muss ich aber doch davon ausgehen, dass das Endsignal nicht zur
> gleichen Zeit wie der Overflow kommt. Ergo müsste ich die Schritte, die
> das Zählregister noch gemacht hat (meinetewegen 174) dazuaddieren.

Bingo.
Jetzt hast du es!

> Und wenn das Zählregister immer stur durchzählt, müsste ich bei der
> nachfolgenen Zeitmessung noch die Schritte abziehen, die es vor dem
> erneuten Startsignals gemacht hat.

Oder du setzt den Zähler auf 0 bevor du den Timer loslaufen lässt :-)
Da du den Timer ja vollständig unter Kontrolle hast, kannst du das 
machen. Die Anzahl der Overflows musst du ja sowieso auch auf 0 setzen, 
da kannst du den Timer auch noch gleich mit auf 0 setzen.

> -> hoffe ich konnt mich diesmal verständlicher ausdrücken

Du bist auf dem richtigen Weg.
Die Grundidee ist schon richtig.

Ich würd sagen: Implementier das jetzt erst mal als Übungsaufgabe. 
Danach studierst du den Input Capture vom Timer1 und änderst dein 
Programm entsprechend um. Dann siehst du auch gleich, welche 
Alternativen dir der Timer anbietet, wie sich das programmtechnisch 
auswirkt und was das für Vor/Nachteile bringt.

> darum, etwas zu lernen. Also sollte man so wenig Timer wie möglich
> einsetzen, wenn man z.B. mit der gemessen Drehzahl irgendwas steuern
> möchte

Ganz im Gegenteil: Man möchte immer möglichst viel dem Timer selbst 
aufbürden. Warum? Weil die Hardware-Einheit Timer autonom arbeitet und 
damit unabhängig vom restlichen Programmfluss autonom eine Fuktionalität 
erledigen kann. Wenn der Timer selbst etwas nicht kann, muss man mit 
Programmcode aushelfen. Programmcode ist aber nie so schnell wie wenn 
die Hardware etwas alleine erledigen kann. Mal abgesehen davon, dass 
jemand den Code schreiben muss :-)

von H. G. (hgruner)


Lesenswert?

Ok dank euch, ich werde eure Tipps heute Nachmittag ausprobieren, das 
mit der Wellenortung erstmal weglassen und dann eine Rückmeldung geben, 
ob alles klappt.

von H. G. (hgruner)


Angehängte Dateien:

Lesenswert?

So in Ermangelung von Zeit habe ich mich mal nur an dem ICP probiert. 
Dazu gab es einen guten Thread von vor ca. 2 Jahren. Das vorläufige, um 
die Displayfunktionen gekürzte Ergebnis, ist im Anhang zu finden. Ich 
werde morgen auch mal ausprobieren, ob das überhaupt so im Praktischen 
funktioniert.


Dann gibt es noch eine Unklarheit in meinem Denken zum Einsatz des 
reinen Timer Interrupts, also z.B. über Timer0 (ohne ICP):

den Overflow zu zählen ist klar:
1
ISR( TIMER0_OVF_vect )
2
{
3
  overflow++;
4
}

Aber wo hin schreibe ich das Erkennen von positiven Flanken wie:
1
signalneu = PIND & (1<<PD2);
2
3
if (    signalneu != 0  &&  signalalt  == 0 )
4
{
5
  //aktiviere Timer bzw. löse Interrupt aus
6
}
7
signalalt = signalneu
eigenlich doch in die main- while Schleife oder bin da auf dem Holzweg?
Zumal es in der Vektortabelle zum Timer0 auch keine weiteren ISR() 
Funktionen gibt. Macht sonst eigentlich auch nicht viel Sinn, da er ja 
erst aktiviert werden muss.

Dann habe beim Durchlesen eben jener Vektortabelle auch ein INT0 und 
INT1 gefunden. Wenn ich die Beschreibung richtig gelesen habe, könnte 
ich diese externen Interrupts bzgl. der rising edges doch auch für meine 
Zwecke nutzen, oder?

Fragen über Fragen, ich geh ins Bett :)

von H. G. (hgruner)


Lesenswert?

so nur mal als Update und diesmal bin ich den Code nochmal im wachen 
Zustand durchgegangen:

Fehler eins: man sollte vielleicht den richtigen Port aktivieren
Fehler zwei: 2 mal den Timer zu initialiseren bringt auch nix

mögliches Problem beim späteren Ausprobieren:
wenn ich die ultoa() Funktion richtig verstanden habe, schiebt diese die 
Zahl "rückwärts" von buffer[0] bis buffer[x]. das müsste doch heißen das 
sich folgendes auf dem display dargestellt wird:

Legende: |Display0| |Display1| |Display2| |Display3| |Display4|

bei 12345 -> Displayanzeige: |1| |2| |3| |4| |5|

aber bei z.B. 23 Displayanzeige: |2| |3| |0| |0| |0|

Das ist natürlich ungünstig da den Einern, Zehnern, Hundertern eine 
feste Displaystelle zugewiesen werden soll.

-> ich müsste dann doch eine eigene Funktion schreiben, oder?


Edit:
erstmal gucken wie sich das auswirkt, aber unter Umständen müsste, wie 
Oliver schon erwähnte, noch eine Timerfunktion mit eingebaut werden, die 
das Display nur alle 0,5 Sekunden erneuert.

von Karl H. (kbuchegg)


Lesenswert?

H. Gruner schrieb:

> wenn ich die ultoa() Funktion richtig verstanden habe, schiebt diese die
> Zahl "rückwärts" von buffer[0] bis buffer[x].

Na ja. Eigentlich ist das schon vorwärts :-)

> Das ist natürlich ungünstig da den Einern, Zehnern, Hundertern eine
> feste Displaystelle zugewiesen werden soll.
>
> -> ich müsste dann doch eine eigene Funktion schreiben, oder?

Yep. Ist aber trivial.
1
void my_ultoa( unsigned long value, char* buffer, unsigned char ascii_len )
2
{
3
  unsigned char i = ascii_len;
4
5
  buffer[i] = '\0';
6
7
  while( i > 0 ) {
8
    i--;
9
    buffer[i] = value % 10;
10
    value = value / 10;
11
  }
12
}

von H. G. (hgruner)


Lesenswert?

oh...Dank dir vielmals :)

Programm funktioniert in etwa wie gedacht.
Drehzahlrechnung müsste ich vielleicht nochmal überprüfen (20.00 U/min 
sind bei einer Bohrmaschine etwas wenig :D)
Und das Display zappelt auch ganz schön -> noch einen, von der Drehzahl 
unabhängigen Timer0 einbauen um die "Ausgabefrequenz" an das Display zu 
veringern.

Beim Versuch eben, gabs damit nur ein vollständig erleuchtedes LED 
Display. Da stört sich wohl irgendwas untereinander oder es gibt einen 
ungewollten Overflow.

Da muss ich mir mal am Sonntag nochmal in Ruhe das Tutorial und das 
Datenblatt angucken.

von H. G. (hgruner)


Angehängte Dateien:

Lesenswert?

Hallo,

nachdem ich heute endlich wieder ein bisschen Zeit zum Basteln habe, 
versuche ich schon den ganzen Vormittag 2 Timer parallel laufen zu 
lassen.

Und zwar will ich in das Programm im Anhang, welches wie schon erwähnt 
eigentlich wunderbar funktioniert, einen zweiten drehzahlunabhängigen 
Timer einbauen (in diesem Fall Timer0)der alle 0,X Sekunden den 
aktuellen Wert der Variablen Drehzahl an das Display ausgibt.
In jeweiligen Einzelprogrammen funktioniert jeder Timer für sich so wie 
er soll, also Timer1 wie in den obigen Beiträgen beschrieben und Timer0 
ebenfalls wenn z.B. eine Zahl über drehzahl++; einfach nur hochgzählt 
wird.

Status bis jetzt ist:
In obiges Programm habe ich folgenden Code für Timer0 einfügt:

In die Kopfzeile:
1
volatile uint32_t displayoverflow = 0;
2
volatile uint8_t displayausgabe = 0;

Die Timer0 ISR Funktion:
1
ISR(TIMER0_OVF_vect)
2
{
3
    displayoverflow++;
4
    if (displayoverflow == 10)
5
       {
6
        displayausgabe = 1;  //flag Timer0
7
        displayoverflow = 0;
8
        }
9
}

Timer0 in der main initialisiern:
1
  TCCR0 |= (1<<CS02)|(1<<CS00);    // prescaler 1024
2
  TIMSK |= (1<<TOIE0);      // interrupt definieren
3
  TCNT0 = 0;        // startwert (vorladen)


und in der while schleife folgende Struktur:
1
while(1)
2
{
3
      //vorhandene if Schleife für Timer1
4
      if( updatemessung == 1 )
5
         {
6
         cli();
7
      
8
      //-> hier stehen Berechnungen aber OHNE Ausgabefunktion 
9
10
         sei();      
11
         }
12
    
13
    // hier die zusätzliche if Funktion von Timer0 für die Ausgabe     
14
    
15
    if(displayausgabe==1)
16
    {
17
18
    cli();  
19
20
    my_ultoa(drehzahl,buffer,5); //zahl umwandeln
21
22
    ausgabe();    //an Display ausgeben
23
24
    displayausgabe =0;     //flag für Timer0 auf 0
25
26
     sei();
27
     }
28
}


Es funktioniert am Anfang, aber nach kurzer Zeit bzw höheren Drehzahlen 
leuchten nur noch alle Segmente der 7-Segment Digits permanent auf. (es 
werden also keine zahlen mehr angezeigt)

Frage:
-> Was mache ich hier grundlegend falsch bzw. funktioniert das 
vielleicht so gar nicht?

von H. G. (hgruner)


Lesenswert?

Hat niemand eine Idee in welche Richtung mein Fehler geht?

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.