Hallo,
ich bastele grad an einer kleinen Servoansteuerung mit den zwei
8-Bit-Timern des Tiny25.
1
volatileintiDeg=200;
2
3
ISR(TIMER0_COMPA_vect){
4
5
PORTB=0b00000011;
6
7
if(iDeg==99)iDeg=200;
8
9
TCCR1=0b10010111;//CTC an & Prescaler 64
10
OCR1A=8000000/((iDeg*5)*64);
11
12
while(TCNT1!=OCR1A){asmvolatile("nop");}
13
/* Nichts tun und warten, bis TCNT1 == OCR1A,
14
da Port dann toggelt. Dann weiter und Timer
15
erstmal deaktivieren */
16
17
TCCR1=0x00;// Timer deaktivieren
18
19
iDeg--;
20
}
Eigentlich gehts mir um die Zeile
1
OCR1A = 8000000 / ((iDeg * 5) * 64);
Verbalisiert: Die Rechnung ergibt, bei iDeg = 200, im ersten Durchgang
125. 8000000 (Takt in Hz.) / 64 (Prescaler) / 125 (Wert OCR1A) = 1000Hz,
also 1ms/Pulslänge. Bei iDeg = 100 sollte 250 im OCR1A stehen, also 2ms
Pulslänge. Sollte im Prinzip also halbwegs zur Servosteuerung hinhauen.
Mir ist klar, dass solche mathematischen Operationen auf einem AVR wenig
sinnvoll ist, merkt man auch daran, dass für diese eine Zeile gut 100
Takte draufgehen, aber was solls. Nur rechnen sollte er halt richtig.
Beim ersten Schleifendurchlauf komme ich, lt.
AVR-Studio-Simulator-IO-Watch, auf den Wert 168. Eigentlich solltens
aber 125 sein. Bei nachfolgenden Durchläufen bleibt die Rechnung nach
wie vor falsch.
In Hardware hab ich das Ganze übrigens noch nicht getestet. Kann mir
aber irgendwie nicht vorstellen, dass der AVR-Simulator (2) sowas nicht
hinbekommt.
Danke & Gute Nacht.
Ich meine hier gleich mehrere Punkte zu sehen die falsch bzw. kritisch
sind.
Zunächst einmal sollte Code in einer Interrupt-Routine so kurz wie nur
irgend möglich sein.
Das ganze
1
2
PORTB=0b00000011;
3
4
if(iDeg==99)iDeg=200;
5
6
TCCR1=0b10010111;//CTC an & Prescaler 64
7
OCR1A=8000000/((iDeg*5)*64);
8
iDeg--;
sollte (und kann vorher erledigt werden. Das verkürzt die Zeit im
Interrupt und vermeidet irgendwelche (esoterisch anmutenden) Effekte.
Dann ist natürlich die Berechnung
1
OCR1A=8000000/((iDeg*5)*64);
wiel aufwendiger geschrieben als nötig und sie benötigt auch mehr
Variablenplatz als nötig.
In dem Ausdruck kann man die Division von 8.000.000 durch 5 und durch 64
vorwegnehmen. Das sollte allerdings auch der Comiler schon tun. Wenn
aber 8.000.000 dastehen soll, dann wenigsten als Long oder Unsigned long
(U resp. UL). Sonst gibt das Murks.
Dann bleibt immer noch 25.000 / iDeg übrig. Das aber passt nicht mehr in
eine 8-Bit Variable. Der Zwischenwert wird also in einem Int gebildet,
das nicht mehr in ein 8-Bit Register zu laden ist. Das sollte mindestens
eine Warning geben. Irre ich mich oder hast Du die ignoriert?
Soweit ich informiert bin kann man Servos (Modellbauservos) mit eine PWM
ansteuern. Dazu gibt es hier auch einige Lösungen und Diskussionen.
Das ist der Hauptpunkt. Nutze eine PWM und nicht diese Art von
Implementierung. Das ist unnötig kompliziert und wenn man noch wenig
Ahnung hat eher kontraproduktiv, wie Dein Code beweist.
Das liegt an einem Überlauf einer Puffervariable bei der Berechnung.
Wenn du diese groß genug (long) wählst, geht es:
OCR1A = 8000000 / (((long)iDeg * 5) * 64);
Abgesehen davon benötigt das jetzt 757 clockcycles.
Das benötigt nur 234 Cycles und macht exakt das gleiche:
OCR1A = 25000 / iDeg;
Und noch viel schneller wäre es mit einer Tabelle, so wie ich das
verstehe, gibt es ja nur 101 gültige Werte für iDeg.
Grüße,
Peter
> Zunächst einmal sollte Code in einer Interrupt-Routine so kurz wie nur> irgend möglich sein.
Danke für den Hinweis, ist aber bekannt.
Ist halt eine recht frühe Version. Wäre vielleicht irgendwann behoben
worden. Ist hierbei aber nicht wirklich kritisch. Der Tiny soll nichts
Anderes machen, als ein wenig am Servo rumdrehen.
> Dann bleibt immer noch 25.000 / iDeg übrig. Das aber passt nicht mehr in> eine 8-Bit Variable.
Das Ergebnis sollte eigentlich schon in eine 8-bit-Variable passen. Die
25000 aber wohl eher nicht. Stimmt.
> Das sollte mindestens eine Warning geben. Irre ich mich oder hast Du die
ignoriert
Nein, gibt keine Warnung. Eben nochmal getestet.
> Soweit ich informiert bin kann man Servos (Modellbauservos) mit eine PWM> ansteuern.
Mag sein dass ich oberflächlich gesucht habe, aber alles was auf
PWM-basierende, was ich finden konnte, setzt 16-Bit-Timer voraus. Die
hat mein Tiny25 leider nicht. Der Wiki-Artikel
(http://www.mikrocontroller.net/articles/Modellbauservo_Ansteuerung)
machts übrigens, soweit ich das überblickt habe, auch ohne PWM.
> Und noch viel schneller wäre es mit einer Tabelle, so wie ich das> verstehe, gibt es ja nur 101 gültige Werte für iDeg.
Ja, gut möglich. Darf ausnahmsweise aber mal ignoriert werden. Wie
gesagt, der Tiny tut nix anderes, als den Servo anzusteuern. Selbst die
Genaugigkeit spielt hierbei kaum eine Rolle. Hauptsache es bewegt sich
überhaupt.
>Der Wiki-Artikel>(http://www.mikrocontroller.net/articles/Modellbaus...)>machts übrigens, soweit ich das überblickt habe, auch ohne PWM.
Und warum machst du es dann nicht so wie im Wiki-Artikel?
>Wie>gesagt, der Tiny tut nix anderes, als den Servo anzusteuern. Selbst die>Genaugigkeit spielt hierbei kaum eine Rolle. Hauptsache es bewegt sich>überhaupt.
Mit an Sicherheit grenzender Wahrscheinlichkeit ändert sich dieser Teil
der Anforderungen an das Programm ziemlich bald :-) Spätestens dann
brauchst du doch eine eingermassen sinnvolle Softwarearchitektur.
Oliver
Oliver wrote:
>>Der Wiki-Artikel>>(http://www.mikrocontroller.net/articles/Modellbaus...)>>machts übrigens, soweit ich das überblickt habe, auch ohne PWM.>> Und warum machst du es dann nicht so wie im Wiki-Artikel?
Weil der mir irgendwie auch nicht allzu optimal vorkommt.
Mal Nachrechnen:
Ein OCR1A-Registerwert von 2375 stellt, lt. Kommentar, die
Servomittelstellung/einen Impuls von 1,5ms dar.
Der erste Compare Match schlängt also nach 1ms auf.
In der ISR wird dann die Differenz aus 20ms (repräsentiert durch den
Wert 2500 im OCR) und der vorherigen Pulslänge berechnet.
Der zweite Compare-Match nach 0,019s (19ms).
20 ms haben wir dann zusammen. Mittelstellung kann das aber eigentlich
nicht sein.
(Ich habe jetzt übrigens angenommen, dass die Variable da schon nicht
ganz optimal initialisiert wurde - wenn wir den ersten Compare Match bei
2375 erwarten, vergehen bis dahin nämlich 19ms. Der eigentliche
High-Impuls kommt erst danach. Und lt. Diagramm des Autors liegt der
Impuls ja zu Anfang der 20ms. Es vergehen also mindestens 19ms, bis
überhaupt etwas verwertbares am Servo anliegt. Dann lieber gleich 2500 -
2375 nehmen. Dann kommt der erste Interrupt nämlich nach 1ms. Siehe
Rechenbsp. oben)
Mit
> Prozessortakt / 8 (1000000Hz / 8 = 250000Hz)
im Fliesstext bin ich irgendwie auch nicht ganz einverstanden.
Versteh ich das Ganze einfach miss? Sollte dem so sein, möchte ich mich
höflichst beim Autoren entschuldigen.
Ansonsten sollte da mal jemand drüberschauen - trägt irgendwie zur
Verwirrung bei.
Fürs EDIT ists leider schon zu spät. Habe den Wiki-Code mal simuliert.
Ich komme auf 1037µS bis zum Eintritt des ersten OCR-Interrupts. Das
kann eigentlich nicht Mittelstellung sein. Funktioniert aber nur, wenn
ich bei der Initialisierung des Registers das obere Byte außen vor
lasse. Also so:
1
OCR1AL=125;
Ansonsten lande ich schon nach ein paar µS im Interrupt. Kann ich
irgendwie nicht nachvollziehen.
Aber meine Vermutung, dass da irgendwas nicht iO ist mit dem Code,
sollte hinhauen, oder?