Hallo,
ich hab mich in den letzten Tagen mal in die Welt der C-Programmierung
von AVRs eingearbeitet und bin zur Zeit dabei, mal ein Servo mit dem µC
zu steuern.
Verbunden habe ich rot mit extra 5 Volt aus einem Netzteil, der
Mikrocontroller ist über mein Testboard versorgt und zwar über die
USB-Schnittstelle.
Verbunden habe ich Masse miteinander und die Signalleitung des Servos
mit PB1 meines µC.
Ich habe mir auch ein kleines Programm geschrieben, mit dem ich den
Servo zum Test über ein Poti steuern kann. Da das nicht funktioniert
hat, habe ich einfach mal diesen Code hier genommen:
http://www.mikrocontroller.net/articles/Modellbauservo_Ansteuerung#Signalerzeugung_f.C3.BCr_1_Servo_.28C.29
Der Servo hat sich dann kurz bewegt und hat dann aber immer leicht hin-
und hergeruckelt. Da dachte ich, der Servo ist vielleicht kaputt und
einen anderen versucht, bei dem das gleiche und bei einem dritten auch
das gleiche, somit glaube ich nicht, dass alle 3 Servos kaputt sind.
Wisst ihr an was das liegen könnte? Könnte irgendwas vom Mikrocontroller
das Signal stören? (Der Mikrocontroller macht sonst nichts anderes.)
Oder liegt es vielleicht daran, dass ich den internen Takt des Atmega8
verwendet habe?
Ich kanns mir einfach nicht erklären.
Haste mal das PWM Signal angeschaut was die Servos bekommen?
Möglicherweise ist es nicht stabil. Auf der verlinkten Seite sind viele
Codes, welcher denn ?
Hab doch im Link en Anker gesetzt, oder? Der erste gleich, der ganz
simple, der auf Mittelstellung stellen soll.
Ne, habe aber auch kein Oszi mit dem ich das anschauen könnte.
Robotico schrieb:> ja müsste er, der interne Takt des mega8 sind ja 1MHz
Das kommt drauf an. Das Datenblatt sagt: "The calibrated internal RC
Oscillator provides a fixed 1.0, 2.0, 4.0, or 8.0 MHz clock". Da ist
also alles möglich, je nach dem, wie die Fuses gesetzt sind.
So schnell sind meine Augen nicht, um 1 Mhz zu erkennen :D
Mike schrieb:> Das kommt drauf an. Das Datenblatt sagt: "The calibrated internal RC> Oscillator provides a fixed 1.0, 2.0, 4.0, or 8.0 MHz clock". Da ist> also alles möglich, je nach dem, wie die Fuses gesetzt sind.
Ja stimmt, da hast du Recht, aber da ich da nichts umgestellt habe,
dürften es noch standardmäßig 1Mhz sein
Habe jetzt ein Wackler bei dem Stecker entdeckt, der die Massen
verbindet, das habe ich schnell gefixt, jetzt passiert leider gar nichts
mehr, die Servos geben kein Mucks mehr von sich, bewegen sich aber auch
nicht auf Ausgangstellung wenn ich sie davor von Hand verdrehe oder
ähnliches.
Na ja, wenn der Takt nicht stimmen würde, dann müsste der Servo an den
Anschlag gehen, oder bei leichter Abweichung nicht mittig stehen.
Mit Zittern hat das aber nichts zu tun.
Also jetzt funktioniert es soweit, dass sich das Servo auf eine
bestimmte Position einstellt.
Jetzt habe ich den Code wieder soweit abgeändert, dass ich es über ein
ADC mit einem Poti steuern kann.
long_delay(adc_wert);// in den 1500 steckt die Lageinformation
30
PORTB&=~(1<<PB1);
31
32
_delay_ms(18);// ist nicht kritisch
33
}
34
35
return0;
36
}
Leider lässt sich es nicht steuern, es bleibt immer auf derselben
Stelle.
Habe es auch mit 19ms probiert, kein Unterschied.
Sieht jemand einen Fehler im Code?
Das ADC-Register ist auf jeden Fall richtig eingestellt, habe es schon
mit einem Lauflicht getestet und den ADCW auch schon auf dem Display
ausgegeben, müsste ein Wert zwischen 0 und 1023 geben, 10 Bit eben.
Robotico schrieb:> So schnell sind meine Augen nicht, um 1 Mhz zu erkennen :D
Du LED soll auch nicht am Oszillator hängen.
Schreib ein kurzes Programm.
while (1)
{
LED an
delay (500ms)
LED aus
delay (500ms)
}
Und 1 Hz wirst du sicher erkennen und von 2, 4 oder 8 Hz sicher
unterscheiden können ;-)
Andreas S. schrieb:> Was für ein Testboard benutzt Du?
Ein selbstgebautes, mit paar Potis, LEDs, Taster, Anschluss für LCD,
usw.
µC-Bastler schrieb:> Robotico schrieb:>> So schnell sind meine Augen nicht, um 1 Mhz zu erkennen :D>> Du LED soll auch nicht am Oszillator hängen.>> Schreib ein kurzes Programm.>> while (1)> {> LED an> delay (500ms)> LED aus> delay (500ms)> }>> Und 1 Hz wirst du sicher erkennen und von 2, 4 oder 8 Hz sicher> unterscheiden können ;-)
Achso meinst du das :D Ja habs mal getestet und die LED blinkt mit
schönen 1 Hertz :)
Upps - jetzt habe ich mich selbst reingelegt - der routinen-parameter
"ms" hat mich aus der Bahn geworfen ...
Vergiß bitte die letzten 2 Beiträge von mir.
1us ist bei 1Mhz Takt eine Winzigkeit.
Wenn Du diesen Winzig-Delay in einer for-Schleife z.B. 1000x aufrufst,
so dürften Dir die Instruktionen, die die for-Schleife ausmachen,
vermutlich das Timing völlig vergurken.
Will sagen: Du kommt mit weit mehr als 1000us Verzögerung aus Deiner
long_delay routine wieder raus.
Viele Grüße
Igel1
Andreas S. schrieb:> Wenn Du diesen Winzig-Delay in einer for-Schleife z.B. 1000x aufrufst,> so dürften Dir die Instruktionen, die die for-Schleife ausmachen,> vermutlich das Timing völlig vergurken.
So ist das, wenn der Prozessor noch was anderes zu tun hat (Schleife
abarbeiten) als delay_us und man sich scheut, solche zeitkritischen
Sachen einem Timer zu überlassen, der die kritische Pulsdauer völlig
selbständig erzeugen kann.
Robotico schrieb:> Wusste ich nicht, dann werde ich es am Wochenende mal mit einem Timer> aufbauen und testen, danke.
Um dein Software Pulsdauer zu retten, kannst du dir auch einfach mal im
Simulator die tatsächliche Dauer der Verzögerungsschleife ansehen und
korrigierend bei den Konstanten eingreifen ;-)
Mal zwei "elektrische" Fragen:
>Verbunden habe ich Masse miteinander und die Signalleitung des Servos>mit PB1 meines µC.
Kommt denn der Servo mit der µP-Spannung aus?
Reicht die Ausgangsleistung des µP überhaupt zum Treiben des Servos aus?
Du kannst das Problem ganz einfach umschiffen*:
Wenn Du den Aufruf "long_delay(adc_wert);" durch "_delay_us(adc_wert)"
ersetzen könntest, wärst Du aus dem Schneider: kein
loing_delay-Funktionsaufruf, keine Schleife => keine Probleme.
... kannst Du aber leider nicht :-)
Warum? Weil die Funktion _delay_us(...) aus der AVR Libc in Deinem Fall
vermutlich nur bis zu einem adc_wert von 768 funktioniert.
Kannst Du lesen hier:
http://www.nongnu.org/avr-libc/user-manual/group__util__delay.html#gab20bfffeacc678cb960944f5519c0c4f
Also paßt Du einfach auf, daß der Übergabewert diese 768 Schwelle nicht
überschreitet.
Dazu teilst Du ganz schlicht den adc_wert durch 4 (oder schiebst
adc_wert zwei Bits nach rechts) und rufst mit diesem geviertelten Wert
den _delay_us(...) dann einfach viermal hintereinander auf.
Könnte so aussehen (Achtung: Code ist ungetestet):
1
while(1)
2
{
3
adc_wert = ADCW;
4
adc_wert += 1000;
5
adc_wert /= 4; // nur 2er Potenzen verwenden, sonst kann der
6
// Compiler nicht in shift-operationen optimieren
7
8
PORTB |= (1<<PB1);
9
_delay_us(adc_wert);
10
_delay_us(adc_wert);
11
_delay_us(adc_wert);
12
_delay_us(adc_wert);
13
PORTB &= ~(1<<PB1);
14
15
_delay_ms(18); // ist nicht kritisch
16
}
Viele Grüße
Igel1
PS: * alles oben Geschriebene ist nur Theorie und nicht wirklich selbst
ausprobiert. Hab' schon länger nicht mehr AVR programmiert - also bitte
Tipps mit Vorsicht genießen ...
Timer1 mit 1MHz rennen lassen,
CTC-Modus, Top on OCR1A=20000 (=20ms)
OCR1A-Int setzt deinen Pin, OCR1B-Int setzt ihn wieder zurück
Bei 1500 im OCR1B-Register bekommst du schöne 1,5ms Signale im
20ms-Abstand, alles ist fein.
Andere Servolage: anderen Wert nach OCR1B schreiben (1000...2000)
Andreas S. schrieb:> Warum? Weil die Funktion _delay_us(...) aus der AVR Libc in Deinem Fall> vermutlich nur bis zu einem adc_wert von 768 funktioniert.> Kannst Du lesen hier:> http://www.nongnu.org/avr-libc/user-manual/group__util__delay.html#gab20bfffeacc678cb960944f5519c0c4f>> Also paßt Du einfach auf, daß der Übergabewert diese 768 Schwelle nicht> überschreitet.
Darauf muss er nicht aufpassen.
Aber du hättest die Doku mal weiter studieren sollen.
Man darf die _delay_xx Funktionen nur mit Konstanten benutzen und nicht
mit zur Laufzeit berechneten Ausdrücken. Sonst stimmt das Timing nicht,
das darauf ausgelegt ist, dass der Compiler die komplette Berechnung der
Anzahl der Schleifendurchläufe im Vorfeld berechnen kann. Mit Variablen
geht das nicht und daher bleibt die Berechnung (die mit Floating Point
gemacht wird) im Programm. Was dir das komplette Timing zerschiesst.
Servo Pulse mit _delay_xx zu erzeugen, ist einfach nur Unsinn. Das kann
man für einen schnellen Test machen, für mehr ist das aber nicht
geeignet. Die Aufgabe wird einem Timer übertragen und gut ists.
> Reicht die Ausgangsleistung des µP überhaupt zum Treiben des Servos aus?
Die ist nicht das Problem.
Auf der Signalleitung zieht das Servo keinen Strom.
Die Frage ist aber: was stellt das Servo mit der Versorgungsspannung an?
Wenn Motoren anlaufen, dann ziehen sie Strom. Und den muss das Netzteil
liefern können.
Amateur schrieb:> Mal zwei "elektrische" Fragen:>>>Verbunden habe ich Masse miteinander und die Signalleitung des Servos>>mit PB1 meines µC.>> Kommt denn der Servo mit der µP-Spannung aus?> Reicht die Ausgangsleistung des µP überhaupt zum Treiben des Servos aus?
Was ist eine µP-Spannung?
Außerdem betreibe ich den Servo extra über ein 5V-Netzteil, wie weiter
oben beschrieben.
@Andreas S.: Ja gute Idee, aber das funktioniert nicht, da das Attribut
nur eine Konstante sein darf wie Karl Heinz erläutert hat.
Ich werds gleich mit nem Timer machen, brauche ich sowieso für das
Programm später, der µC soll sich später nicht ausruhen und nur nen
Servo steuern ;)
Karl Heinz schrieb:> Die ist nicht das Problem.> Auf der Signalleitung zieht das Servo keinen Strom.> Die Frage ist aber: was stellt das Servo mit der Versorgungsspannung an?> Wenn Motoren anlaufen, dann ziehen sie Strom. Und den muss das Netzteil> liefern können.
Den sollte das Netzteil können. Das kann laut Aufdruck 3A und der Servo
ist so winzig, der ist aus nem kleinen Modellflugzeug, der sollte so gut
wie nix ziehen. Das schafft selbst die USB-Schnittstelle.
Zittern kommt meist von einem dieser beiden "Probleme":
- etliche Servos funktionieren erst sauber mit einer Impulsspannung
von > 3.2 bis 3.5V, darunter reagieren sie auf eventuelle
abgerundete Impulsflanken (als Test - keine saubere Lösung - R
von ca. 1k von Impuls zu +5V des AVRs)
- die Plusspannung des Servos ist trotz "3A-Netzteil" nicht sauber
bei Motoranlauf -> mit einem kleinen Elko > 100µF testweise puffern
Gruß
Das "Zitter"problem war schon gelöst. Das lag an dem Wackler des
Steckers, der die Massen verbunden hat, der Servo hat keine Masse somit.
Daher das "Zittern".
Ansonsten habe ich mein Programm jetzt mal mit nem Timer aufgebaut,
funktioniert 1A. Danke an alle für die Hilfe!
Hätte ich aber auch selbst drauf kommen können, wenn ich mir überlegt
hätte wie ein Assemblerprogramm aufgebaut wäre.
Lauter NOPs (1 Takt) und das Dekrementieren einer Variable (1 Takt) in
einer BRNE Verzweigung (1/2 Takte).
Das wären ja schon 3-4 µs pro Schleifendurchlauf...
> @Andreas S.: Ja gute Idee, aber das funktioniert nicht, da das Attribut> nur eine Konstante sein darf wie Karl Heinz erläutert hat.
Okay, okay - war wohl nicht eine meiner Sternstunden gestern Abend.
Ich wollte Dir als Einsteiger das Interrupt-Programmieren ersparen,
aber der Schuß ging wohl nach hinten los - sorry.
Zum reinen Prüfen, ob's der Servo überhaupt tut, sollte das folgende
genügen:
Kommentiere einfach in Deinem Originalprogramm
(Beitrag "Re: Servos "zittern"") die folgende Zeile
aus:
1
// adc_wert += 1000;
Dann drehe nochmals an Deinem Poti (=ADC-Eingangsspannung verändern). Da
sollten dann hinten an PB1 Pulswerte zwischen fast 0 und vielleicht 6ms
rumkommen. Bei Pulswerten zwischen 1-2ms sollte Dein Servo hübsch das
Ärmchen drehen.
Viele Grüße
Igel1
PS: ... ich sehe gerade: während ich meinen Beitrag verfaßt habe, hast
Du es mit dem Timer geschafft - das ist natürlich viel perfekter -
Glückwunsch!
>Ansonsten habe ich mein Programm jetzt mal mit nem Timer aufgebaut,>funktioniert 1A. Danke an alle für die Hilfe!
Was hällst du davon deinen Code hier anstandshalber zu veröffentlichen?
Ich brauche ihn nicht, evtl jemand anders?
Mr. Kaktus schrieb:>>Ansonsten habe ich mein Programm jetzt mal mit nem Timer> aufgebaut,>>funktioniert 1A. Danke an alle für die Hilfe!>> Was hällst du davon deinen Code hier anstandshalber zu veröffentlichen?>> Ich brauche ihn nicht, evtl jemand anders?
Tut mir Leid, klar kann ich gerne machen:
Das Programm gibt gleich noch den den Wert von 1000 bis 2023 ms auf dem
LCD aus, das entspricht dann ca. Links- und Rechtsanschlag.
Ist es etwas durcheinander programmiert und kann man sicherlich deutlich
effektiver machen, aber wie gesagt gings mir hier nur darum, das mal zu
testen.
Die Dateien lcd_routines.c und lcd_routines.h findet man hier:
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/LCD-Ansteuerung
Muss natürlich auf den µC, den Takt und das LCD angepasst werden.
Andreas S. schrieb:> Ich wollte Dir als Einsteiger das Interrupt-Programmieren ersparen
Das ist nicht nötig. Ist doch eine gute Sache so ein Interrupt und
schwierig ist es auch nicht. Habe schon etwas Erfahrung im AVR
programmieren mit Assembler, ist zwar schon ein Jahr her, aber da
erinnert man sich an einiges wieder. Und C konnte ich auch schon, von
daher ist das für mich nicht all zu schwer, aber danke trotzdem!
Robotico schrieb:
da ...
>> uint8_t high;
muss noch ein volatile hin
1
volatileuint8_thigh;
Gut, das wird jetzt in deinem Fall sich nicht auswirken, weil der
COmpiler mit den ganzen LCD Funktionsaufrufen sowieso keine Chance haben
wird, den Wert in der Hauptschleife in einem Register vorzuhalten, aber
wenn du das Programm dann mal in einer abgespeckten Version benutzt
könnte es dazu kommen. Und dann funktioniert es aus unerklärlichen
Gründen nicht mehr.
Karl Heinz schrieb:> Robotico schrieb:>> da ...>>>> uint8_t high;>> muss noch ein volatile hin> volatile uint8_t high;>> Gut, das wird jetzt in deinem Fall sich nicht auswirken, weil der> COmpiler mit den ganzen LCD Funktionsaufrufen sowieso keine Chance haben> wird, den Wert in der Hauptschleife in einem Register vorzuhalten, aber> wenn du das Programm dann mal in einer abgespeckten Version benutzt> könnte es dazu kommen. Und dann funktioniert es aus unerklärlichen> Gründen nicht mehr.
Was macht denn das volatile? Das kenne ich noch nicht.
Karl Heinz schrieb:> KLeiner Trick>> Das hier if (high==0)> {> high = 1;> }> else> {> high = 0;> }>> kann man auch so schreiben high = 1 - high;>> ist kürzer und solange high nur die Werte 1 und 0 haben kann, läuft es> aufs gleiche raus. Genauso wie high ^= 0x01;
Stimmt, danke für den Tipp.
Ah okay vielen Dank,
man lernt eben immer zu. Werde ich natürlich nächstes Mal beachten.
Ist das nur bei Interrupts so oder allgemein bei Variablen, die in
mehreren Funktionen verwendet werden?
Robotico schrieb:> Ist das nur bei Interrupts so oder allgemein bei Variablen, die in> mehreren Funktionen verwendet werden?
C kann von haus aus nicht mit Interrupts anfangen, d.h. der Compiler
weiß nicht, dass es eine Beziehung bezüglich der Verwendung zwischen den
Variablen gibt. Wenn die Optimierung eingeschaltet ist, baut sie
unkontrolliert Mist, wenn der Compiler nicht mit "volatile" zu einer
bestimmten Vorgehensweise gezwungen ist. IMHO dürfte das Problem also
nur zusammen mit Variablenverwendung in Interrupts auftreten.
Robotico schrieb:> Ah okay vielen Dank,>> man lernt eben immer zu. Werde ich natürlich nächstes Mal beachten.> Ist das nur bei Interrupts so oder allgemein bei Variablen, die in> mehreren Funktionen verwendet werden?
Was im Link nicht steht:
Wenn in einem Code eine Funktion aufgerufen wird
1
inti;
2
3
intmain()
4
{
5
...
6
7
while(1)
8
{
9
if(i==5)
10
irgendwas;
11
12
foo();
13
}
14
}
dann muss der Compiler sowieso davon ausgehen, dass sich alle globalen
Variablen verändert haben. D.h. hier ist implizit sowieso enthalten,
dass i sich zwischendurch durch den Funktionsaufruf verändert haben
könnte.
Der 'Optimierungsfall' schlägt nur dann zu, wenn es keine für den
COMpiler ersichtliche Möglichkeit gibt, wie i seinen Wert verändern
könnte.
So wie hier
1
inti;
2
3
intmain()
4
{
5
intj;
6
...
7
8
while(1)
9
{
10
if(i==5)
11
j=8;
12
}
13
}
aufgrund des Programmverlaufs in der Schleife existiert keine
theoretische Möglichkeit, wie i seinen Wert verändern könnte. Und dann
darf der Compiler die Variablenzugriffe wegoptimieren.
Sobald es aber auch nur den Hauch einer Chance gibt, wie zb einen
Funktionsaufruf, ist dem Compiler diese Möglichkeit sowieso bereits
verbaut.
Interrupt Routinen sind eben insofern ein Sonderfall, als es sich um
Funktionen handelt, die zu jedem Zeitpunkt erfolgen können, ohne dass
davon im Source Code etwas zu erkennen wäre.
Ein Grenzfall sind Aliasing-Konstrukte, wie sich durch Pointer möglich
sind. Das war lange Zeit eine Grauzone, bis das Konzept des
'Strict-Aliasing' in die Sprache mit aufgenommen wurde.
Ah okay ja logisch macht Sinn. Aber warum hat man das nicht beim
Compilter so mit eingebaut, dass wenn der Interrupt schon in der
gleichen C-Datei steht, dass der das dann auch rausfindet.
Wie im Beitrag steht, könnte sich der Interrupt ja auch in einer anderen
Datei befinden, da kann ich das nachvollziehen.
Aber vielen Dank für die ausführlichen Erklärungen!