www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Atmega8L - Einfache Servoansteuerung in C : Timingprobleme


Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Zusammen,

ich verwende einen Atmega8L der in AVR Studio / GCC programmiert wird.

Mithilfe folgenden Codes möchte ich einen Servo mit zwei Tastern in 
jeweils eine Richtung drehen lassen:
#include <avr/io.h>
#include <util/delay.h>

#define F_CPU 1000000UL  // 1 MHz


int main(void){

    float z = 2;
    DDRB |= _BV(PB1); 
  
    DDRB &= ~_BV(PB2); 
    PORTB |= _BV(PB2);

    DDRB &= ~_BV(PB3); 
    PORTB |= _BV(PB3); 
     
    while (1) {
       PORTB |= _BV(PB1); 
       _delay_ms(z);
       PORTB &= ~_BV(PB1); 
       _delay_ms(20-z);

       if (bit_is_clear(PINB,2)) {
         if(z<2) {
           z=2;
         }
       }

       if (bit_is_clear(PINB,3)) {
         if(z>1) {
           z=1;
         }
       }
    }

return 0;
}

Das Programm ist recht einfach gehalten:
Mithilfe der Variablen z definiere ich die Stellzeit. Diese kann hier im 
Programm entweder 1ms oder 2ms betragen und ändert sich je nach 
Tastendruck. In der Main-Schleife wird dann immer der Port für die Zeit 
z auf high gesetzt und für 20ms-z auf low. Somit habe ich immer eine 
Periodendauer von 20ms.

Kurz zum Aufbau:
Mein Atmega8 wird auf einer fertig geätzten Testplatine (mit Tastern und 
Potis und Piezo und LEDs...) über USB betrieben. Der Servo wird extern 
durch ein kleines Netzteil mit 4,8V versorgt. Die Masse vom Servo ist 
mit der Masse der Testplatine kurzgeschlossen.

Nun zu meinem Problem:
Trotz passender Fuses (Int. Clock 1MHz) fährt der Servo nach dem 
einschalten in eine Position und reagiert nicht auf Tastendruck.

Der Witz an der Sache ist:
Erhöhe ich den internen Clock auf 8Mhz (der Quellcode wird für F_CPU 
natürlich entsprechend angepasst!) reagiert der Servo auf Tastendruck, 
fährt aber nicht in die Endpositionen sondern bewegt sich ca. in einem 
90° Winkel bei abwechselndem Tastendruck.

Ich habe zum weiteren Testen einen externen Quarz angeschlossen und den 
Clock wieder entsprechend angepasst (Fuses natürlich auch). Dieser 
schwingt bei einer Frequenz von 3,6864 MHz. Jetzt dreht sich der Servo 
nur noch in einem ca. 45° Winkel bei abwechselndem Tastendruck.

In der Arbeit konnte ich mit dem besagten externen angeschlossenen Quarz 
den Ausgangsport mit dem Oszi messen. Und siehe da: meine Periodendauer 
beträgt ca. 23ms und mein Impuls liegt bei ca. 3ms!?

Wo geht hier die Zeit verloren? Habe ich etwas übersehen?
Vielleicht weiß einer von euch Profis rat.

Grüße
Matthias

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias schrieb:
> Wo geht hier die Zeit verloren? Habe ich etwas übersehen?

Ja, hast du, die Dokumentation zu der Funktion _delay_ms.
http://www.nongnu.org/avr-libc/user-manual/group__...
Dort insbesondere das zweite "Note:".

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke Stefan für den Hinweis.

Ich hab jetzt meinen Quellcode überarbeitet, so dass die Delay-Zeiten 
vor der Kompilierung fest sind.

Die Compileroptimierung ist ebenfalls aktiviert (-Os).

Jedoch habe ich nach wie vor dasselbe Ergebnis: Mit einer 1MHz 
Taktfrequenz passiert nichts. Bei 8MHz dreht sich der Servo jedoch in 
einem 90° Winkel.
#include <avr/io.h>

#define F_CPU 1000000UL 
#include <util/delay.h>


int main(void){

    DDRB |= _BV(PB1); 
  
  DDRB &= ~_BV(PB2); // PB2 ist nun ein Eingang
  PORTB |= _BV(PB2); // Pull Up an der Stelle PB2 setzen
  

        
    while (1) {

       if (bit_is_clear(PINB,2)) {
               PORTB |= _BV(PB1); _delay_ms(1);
               PORTB &= ~_BV(PB1); _delay_ms(19);
       }
       else {
            PORTB |= _BV(PB1); _delay_ms(2);
               PORTB &= ~_BV(PB1); _delay_ms(18);       
       }
  }
 
return 0;
}

Hat irgendwer eine Idee?

Autor: Helfer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Gibt es für den neu übersetzten Code ebenfalls Oszi-Messungen der 
tatsächlichen Delayzeiten?

Ich erwarte, dass die länger sind als im Code steht, weil der umgebende 
Code ebenfalls Rechenzeit braucht. Und zwar je mehr, desto langsamer der 
µC getaktet ist.

Was passiert, wenn du mit den gemessenen tatsächlichen Zeiten die Delays 
korrigierst?

http://www.engineersgarage.com/microcontroller/805...

Autor: Helfer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das Gefummel mit dem Timing über Delay kannst du übrigens einsparen, 
wenn du einen entsprechenden Timer betreibst, und dem sagst, wie lange 
ON und wie lange OFF sein sollen. Die Stufe weiter im Programmiererleben 
ist dann eine PWM mit 20ms Frequenz und Dutycycle entsprechend deiner ON 
Zeit.

Autor: Helfer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
20ms Frequenz^H^H^HPeriodenlänge bzw. 1/20ms = 50Hz Frequenz

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke für die schnelle Antwort.
Ich habe nochmals alle Fuses neu gesetzt und siehe da: es funktionert 
auch bei 1MHz. Sorry für meinen vorschnellen Post.

Aber ich kann allen den in meinem letzten Thread geschriebenen Code 
empfehlen, wenn man mal mit wenig Code einen Servotester bauen will.

Übrigens: Nach dem vergrößern der Zeit von 2ms auf 2.2ms und verringern 
von 1ms auf 0.8ms kann ich einen größeren Winkel ansteuern. Mein Servo 
kann maximal nur ca. 180°.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias schrieb:

> Jedoch habe ich nach wie vor dasselbe Ergebnis: Mit einer 1MHz
> Taktfrequenz passiert nichts. Bei 8MHz dreht sich der Servo jedoch in
> einem 90° Winkel.

Überprüf mal die Fusebit Einstellung deines µC

> Ich erwarte, dass die länger sind als im Code steht, weil der
> umgebende Code ebenfalls Rechenzeit braucht. Und zwar je mehr,
> desto langsamer der µC getaktet ist.

Im Prinzip richtig. Allerdings macht das das Kraut nicht fett. Wir reden 
davon, dass eine Zeit von 1 Millisekunde um ein paar µs nicht stimmt. 
Also ein paar Tausendstel der eingestellten Zeit. Das macht in der 
Servoposition praktisch keinen Unterschied.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias schrieb:
> Danke für die schnelle Antwort.
> Ich habe nochmals alle Fuses neu gesetzt und siehe da: es funktionert
> auch bei 1MHz. Sorry für meinen vorschnellen Post.

Gut.

> Aber ich kann allen den in meinem letzten Thread geschriebenen Code
> empfehlen, wenn man mal mit wenig Code einen Servotester bauen will.

Nicht wirklich.
Dein Code ist maximal ein erster Test. Für ein richtiges Gerät macht man 
das anders.

Modellbauservo Ansteuerung

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nunja, da hab ich wohl unabsichtlich etwas übertrieben... mit Servotest 
meinte ich nur das der Servo überhaupt dreht ^^

Von Professionalität ist der Code natürlich noch weit entfernt!

Autor: Stefan Weßels (swessels)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Moin,

Grundsätzlich würde ich einen Servotester

a) mit nem Quarz
b) mittels 16-Bit Timer im PWM Modus erstellen.

Es geht aber auch mit Polling, z.B wie in folgendem Code. Aber ohne 
Timer wird das nichts. Laufzeitkorrekturen und einige weitere Sachen 
sind noch nicht implementiert. Der Code enthält 2 Varianten der 
Servoansteuerung. Die erste bereitet mir noch ein paar Probleme.
/*
V-Kabel v0.2
V-Kabel für max. 4 Servos
MCU: Attiny24 @ 8 MHz
Reiner Pollingbetrieb
Erste Version
Servos: PA0..4
PPM: PA7
*/

#ifndef F_CPU
    #define F_CPU 8000000UL
#endif

#define VARIANT 0

//Portdefinitionen, ggf. anpassen!
#define SERVOPORT PORTA
#define SERVODDR DDRA
#define SERVO0 PA0
#define SERVO1 PA1
#define SERVO2 PA2
#define SERVO3 PA3
#define PPMPORT SERVOPORT
#define PPMDDR SERVODDR
#define PPM_INPUTPIN PA7
#define PPMPIN PINA
#define PPMIN PINA7

#include <avr/io.h>

int main(void) {
    
    uint16_t pulse_in;   //Eingangsimpuls
    uint16_t pulse_out_0; //Ausgangsimpuls Servo 0
    uint16_t pulse_out_1; //Ausgangsimpuls Servo 1
    uint16_t pulse_out_2; //Ausgangsimpuls Servo 2
    uint16_t pulse_out_3; //Ausgangsimpuls Servo 3

    uint8_t temp;
    
    //PORTB (ungenutzt) auf Eingang, Pullups an
    DDRB = 0x0F;
    PORTB = 0x0F;
    
    //Servoport konfigurieren
    SERVOPORT &= ~(1 << SERVO0 | 1 << SERVO1 | 1 << SERVO2 | 1 << SERVO3);
    SERVODDR |= 1 << SERVO0 | 1 << SERVO1 | 1 << SERVO2 | 1 << SERVO3;
    
    //PPM Eingang und ungenutzte Pins des PORTS konfigurieren
    PPMDDR &= ~(1 << PA4 | 1 << PA5 | 1 << PA6 | 1 << PPM_INPUTPIN);
    PPMPORT |= 1 << PA4 | 1 << PA5 | 1 << PA6;
    PPMPORT &= ~(1 << PPM_INPUTPIN);
    
    //Timer 1 starten, prescaler 8
    TCCR1B |= 1 << CS11;
    
    //ersten Impuls abwarten und verwerfen. Es kann nicht sichergestellt
    //werden das ein vollständiger Impuls detektiert wird, da keine
    //Flankenerkennung stattfindet
    while(PPMPIN &(1 << PPMIN)) {};
        
    while(1) {
        //Eingansipmuls ermitteln
        while(!(PPMPIN &(1 << PPMIN))) {};  //warten auf Eingangsimpuls
        TCNT1 = 0;                          //Timer1 0
        while(PPMPIN &(1 << PPMIN)) {};     //warten auf Eingangsimpulsende
        pulse_in = TCNT1;                   //Zählerwert holen -> Impulslänge in µs
        
        //Ausgangsimpulse berechnen
        pulse_out_0 = pulse_in+10;
        pulse_out_1 = pulse_in-10;
        pulse_out_2 = pulse_in;
        pulse_out_3 = pulse_in;
        
        #ifdef VARIANT
        //Variante 1 -> alle Servos gleichzeitig starten
        SERVOPORT |= (1 << SERVO0 | 1 << SERVO1 | 1 << SERVO2 | 1 << SERVO3);
        TCNT1 = 0;  //Timer 1 0
        //warten auf erreichen der Impulslänge
        temp = 15;
        while((temp <<= 4) != 0) {
            if(TCNT1 >= pulse_out_0) {SERVOPORT &= ~(1 << SERVO0);}
            if(TCNT1 >= pulse_out_1) {SERVOPORT &= ~(1 << SERVO1);}
            if(TCNT1 >= pulse_out_2) {SERVOPORT &= ~(1 << SERVO2);}
            if(TCNT1 >= pulse_out_3) {SERVOPORT &= ~(1 << SERVO3);}
            temp = SERVOPORT;
        }
        #endif
        #ifndef VARIANT
        //Variante 2 -> Servos in Reihe
        //Servo0
        SERVOPORT |= 1 << SERVO0;
        TCNT1 = 0;
        while(TCNT1 <= pulse_out_0) {};
        SERVOPORT &= ~(1 << SERVO0);
        //Servo1
        SERVOPORT |= 1 << SERVO1;
        TCNT1 = 0;
        while(TCNT1 <= pulse_out_1) {};
        SERVOPORT &= ~(1 << SERVO1);
        //Servo2
        SERVOPORT |= 1 << SERVO2;
        TCNT1 = 0;
        while(TCNT1 <= pulse_out_2) {};
        SERVOPORT &= ~(1 << SERVO2);
        //Servo3
        SERVOPORT |= 1 << SERVO3;
        TCNT1 = 0;
        while(TCNT1 <= pulse_out_3) {};
        SERVOPORT &= ~(1 << SERVO3);
        #endif
    }
    return(1);
}

Vielleicht kannst Du damit ja was anfangen.

Gruß,
Stefan

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.