wie müsste so ungefähr der code aussehn z.B. um beide
Servos ganz nach links/rechts zu schwenken?
Es gibt einige unterschiedliche Konzepte zur Servoansteuerung. Ich versuche mal die gebräuchlichsten zu erklären (soweit ich es selbst kapiert habe) Grundsätzlich muss man zwischen blockierend und nicht blockierend unterscheiden.
Bei blockierender Ansteuerung kann der Kontroller während der Impulserzeugung keine anderen Tätigkeiten ausführen, selbst Interrupts sollten gesperrt sein um ein Zittern der Servos zu unterdrücken. Diese Art der Ansteuerung ist einfach umzusetzen und hat wenig Seiteneffekte. Die Impuls-und Pausenlängen erzeugt man dabei z.B. mit Zählschleifen. Oder wie im folgenden Beispiel für den asuro (für die BackLEDs!) mit Sleep():
Code:
#include "asuro.h"
unsigned char i, servo_stellzeit;
void servo(unsigned char winkel){
unsigned int count=0;
do{
count++;
BackLED(ON,OFF);
FrontLED(ON);
Sleep(winkel);
BackLED(OFF,ON);
FrontLED(OFF);
Sleep(255); Sleep(255); Sleep(255);
}while (count<servo_stellzeit);
}
int main(void) {
Init();
StatusLED(OFF);
do{
servo_stellzeit=35;
servo(51);
servo(90);
servo(51);
servo(15);
servo_stellzeit=2;
for (i=15; i<88; i+=2) servo(i);
for (i=90; i>17; i-=2) servo(i);
}while (1);
return 0;
}
(Ich weiß, oben schreibe ich möglichst ohne Interrupts, aber weil's mit Sleep() so einfach und überschaubar funktioniert wollte ich das auch mal zeigen.)
Anstelle der BackLED()-Funktionen muss man die Ausgänge der gewählten Pins selbst ansteuern. Es müssen zuerst die Pins auf Ausgang geschaltet werden. Dazu schreibt man in das entsprechende Datenrichtungsregister eine 1, am Beispiel von PC2/3 könnte das so aussehen:
DDRC |= 0b00001100; // Die Zählung beginnt mit PC0 ganz rechts!
Das |= ändert nur die beiden in der Maske gesetzten Bits. Bei = würden zusätzlich alle anderen Pins auf Eingang geschaltet werden. Nun kann man mit
PORTC |= 0b00000100; // den Pin PC2 high (5V, 1, ein, aktiv...) schalten
oder mit
PORTC &= ~0b00000100; // wieder ausschalten.
Die Tilde ~ negiert die angegebene Maske (Einerkomplement), das &= löscht nur die Bits die in der negierten Maske den Wert 0 haben.
/*Nur für Interessierte:
Alternative mit shift-Operatoren
Wie man den Wert der Maske angibt ist übrigens völlig egal und jeder kann die Form wählen die ihm am besten zusagt. Bei den Portzugriffen sorgen shift-Operatoren für besseren Überblick:
PORTC |= (1<<PC3); // setzt z.B. den Ausgang PC3.
Dazu muss man wissen, dass PC3 ein #define ist und vom Präprozessor (vor dem Kopilieren) durch 3 ersetzt wird(->io.h). Und so funktionert es: Die 1 wird in der Maske ganz rechts eingetragen (0b00000001) und anschliessend um die Anzahl der Stellen in <<PC3 nach links verschoben. Das Ergebniss ist also 0b00001000. Wenn man nun noch mehrere Pins zusammenfasst wird es richtig übersichtlich:
PORTC &= ~((1<<PC3) | (1<<PC2));
*/
Bei der Ansteuerung der Servos muss man noch beachten, dass ein Servo nicht schlagartig auf einer neuen Postion steht wenn man die Impulslänge ändert. Vielmehr braucht es abhängig von der Größe der Positionsänderung eine gewisse Zeit während der man die Impulse wiederholen muss. Das erledigt im meinem Beispielprogramm oben die Variable stellzeit.
Noch kurz ein Ausblick auf nichtblockierende Ansteuerungen:
- Eine Interruptserviceroutine wird regelmässig über einen Timerinterrupt aufgerufen. Schöne Werte ergeben sich dabei wenn man die ISR alle 100µs aufrufen läßt, denn wenn man in dieser ISR eine Variable hochzählt vergeht dann genau 1 ms bis der Zähler 100 erreicht, 20ms erreicht man dann nach 2000 Aufrufen. Damit wäre dann auch eine Impuls/Pause-Periode beendet und man kann von vorne beginnen indem man den Zähler wieder mit 0 läd. Diese Methode ist auch im RN-Wiki (Servos) beschrieben. Mit der Anzahl der Servos steigt allerdings der Rechenaufwand beim Vergleich der einzelnen Servopositionen mit dem aktuellen Stand der Zählvariablen. Außerdem werden durch den gleichzeitigen Start der Impulse alle Servos gleichzeitig gestartet was hohe Einschaltsummenströme erzeugt. Dies kann man zwar durch eine pfiffige Programmierung der ISR umgehen, aber dadurch steigt der Rechenaufwand noch mehr.
- Eine weitere, deutlich resourcenschonendere Methode (die ich selbst noch nicht versucht habe): Die Signalleitung des Servos wird eingeschaltet und ein Timer wird so konfiguriert und gestartet dass er (einmalig!) nach der gewünschten Impulslänge eine ISR aufruft. In der ISR wird der Ausgang wieder ausgeschaltet und der Timer dann so umkonfiguriert dass nach ca. 20ms ein zweiter Interrupt ausgelöst wird. Dann wieder Ausgang setzen und Timer auf Impulslänge konfigurieren uswusw. Das funktioniert natürlich auch mit mehreren Servos die dann auch schön nacheinander starten weil die Impulse nacheinander erzeugt werden. Wenn bei mehreren Servos alle Impulse an die Servos gesendet wurden wird die zu 20ms fehlende Zeit mit einem Dummyservo vertrödelt. Ächz.
Gruß
mic
Lesezeichen