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.
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
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.
>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
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
>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
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 :-)
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.
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 :)
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.
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.
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.
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
volatileuint32_tdisplayoverflow=0;
2
volatileuint8_tdisplayausgabe=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?