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


von Matthias (Gast)


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:
1
#include <avr/io.h>
2
#include <util/delay.h>
3
4
#define F_CPU 1000000UL  // 1 MHz
5
6
7
int main(void){
8
9
    float z = 2;
10
    DDRB |= _BV(PB1); 
11
  
12
    DDRB &= ~_BV(PB2); 
13
    PORTB |= _BV(PB2);
14
15
    DDRB &= ~_BV(PB3); 
16
    PORTB |= _BV(PB3); 
17
     
18
    while (1) {
19
       PORTB |= _BV(PB1); 
20
       _delay_ms(z);
21
       PORTB &= ~_BV(PB1); 
22
       _delay_ms(20-z);
23
24
       if (bit_is_clear(PINB,2)) {
25
         if(z<2) {
26
           z=2;
27
         }
28
       }
29
30
       if (bit_is_clear(PINB,3)) {
31
         if(z>1) {
32
           z=1;
33
         }
34
       }
35
    }
36
37
return 0;
38
}

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

von Stefan E. (sternst)


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__util__delay.html
Dort insbesondere das zweite "Note:".

von Matthias (Gast)


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.
1
#include <avr/io.h>
2
3
#define F_CPU 1000000UL 
4
#include <util/delay.h>
5
6
7
int main(void){
8
9
    DDRB |= _BV(PB1); 
10
  
11
  DDRB &= ~_BV(PB2); // PB2 ist nun ein Eingang
12
  PORTB |= _BV(PB2); // Pull Up an der Stelle PB2 setzen
13
  
14
15
        
16
    while (1) {
17
18
       if (bit_is_clear(PINB,2)) {
19
               PORTB |= _BV(PB1); _delay_ms(1);
20
               PORTB &= ~_BV(PB1); _delay_ms(19);
21
       }
22
       else {
23
            PORTB |= _BV(PB1); _delay_ms(2);
24
               PORTB &= ~_BV(PB1); _delay_ms(18);       
25
       }
26
  }
27
 
28
return 0;
29
}

Hat irgendwer eine Idee?

von Helfer (Gast)


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/8051projects/servo-motor-project-circuit

von Helfer (Gast)


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.

von Helfer (Gast)


Lesenswert?

20ms Frequenz^H^H^HPeriodenlänge bzw. 1/20ms = 50Hz Frequenz

von Matthias (Gast)


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°.

von Karl H. (kbuchegg)


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.

von Karl H. (kbuchegg)


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

von Matthias (Gast)


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!

von Stefan W. (swessels)


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.
1
/*
2
V-Kabel v0.2
3
V-Kabel für max. 4 Servos
4
MCU: Attiny24 @ 8 MHz
5
Reiner Pollingbetrieb
6
Erste Version
7
Servos: PA0..4
8
PPM: PA7
9
*/
10
11
#ifndef F_CPU
12
    #define F_CPU 8000000UL
13
#endif
14
15
#define VARIANT 0
16
17
//Portdefinitionen, ggf. anpassen!
18
#define SERVOPORT PORTA
19
#define SERVODDR DDRA
20
#define SERVO0 PA0
21
#define SERVO1 PA1
22
#define SERVO2 PA2
23
#define SERVO3 PA3
24
#define PPMPORT SERVOPORT
25
#define PPMDDR SERVODDR
26
#define PPM_INPUTPIN PA7
27
#define PPMPIN PINA
28
#define PPMIN PINA7
29
30
#include <avr/io.h>
31
32
int main(void) {
33
    
34
    uint16_t pulse_in;   //Eingangsimpuls
35
    uint16_t pulse_out_0; //Ausgangsimpuls Servo 0
36
    uint16_t pulse_out_1; //Ausgangsimpuls Servo 1
37
    uint16_t pulse_out_2; //Ausgangsimpuls Servo 2
38
    uint16_t pulse_out_3; //Ausgangsimpuls Servo 3
39
40
    uint8_t temp;
41
    
42
    //PORTB (ungenutzt) auf Eingang, Pullups an
43
    DDRB = 0x0F;
44
    PORTB = 0x0F;
45
    
46
    //Servoport konfigurieren
47
    SERVOPORT &= ~(1 << SERVO0 | 1 << SERVO1 | 1 << SERVO2 | 1 << SERVO3);
48
    SERVODDR |= 1 << SERVO0 | 1 << SERVO1 | 1 << SERVO2 | 1 << SERVO3;
49
    
50
    //PPM Eingang und ungenutzte Pins des PORTS konfigurieren
51
    PPMDDR &= ~(1 << PA4 | 1 << PA5 | 1 << PA6 | 1 << PPM_INPUTPIN);
52
    PPMPORT |= 1 << PA4 | 1 << PA5 | 1 << PA6;
53
    PPMPORT &= ~(1 << PPM_INPUTPIN);
54
    
55
    //Timer 1 starten, prescaler 8
56
    TCCR1B |= 1 << CS11;
57
    
58
    //ersten Impuls abwarten und verwerfen. Es kann nicht sichergestellt
59
    //werden das ein vollständiger Impuls detektiert wird, da keine
60
    //Flankenerkennung stattfindet
61
    while(PPMPIN &(1 << PPMIN)) {};
62
        
63
    while(1) {
64
        //Eingansipmuls ermitteln
65
        while(!(PPMPIN &(1 << PPMIN))) {};  //warten auf Eingangsimpuls
66
        TCNT1 = 0;                          //Timer1 0
67
        while(PPMPIN &(1 << PPMIN)) {};     //warten auf Eingangsimpulsende
68
        pulse_in = TCNT1;                   //Zählerwert holen -> Impulslänge in µs
69
        
70
        //Ausgangsimpulse berechnen
71
        pulse_out_0 = pulse_in+10;
72
        pulse_out_1 = pulse_in-10;
73
        pulse_out_2 = pulse_in;
74
        pulse_out_3 = pulse_in;
75
        
76
        #ifdef VARIANT
77
        //Variante 1 -> alle Servos gleichzeitig starten
78
        SERVOPORT |= (1 << SERVO0 | 1 << SERVO1 | 1 << SERVO2 | 1 << SERVO3);
79
        TCNT1 = 0;  //Timer 1 0
80
        //warten auf erreichen der Impulslänge
81
        temp = 15;
82
        while((temp <<= 4) != 0) {
83
            if(TCNT1 >= pulse_out_0) {SERVOPORT &= ~(1 << SERVO0);}
84
            if(TCNT1 >= pulse_out_1) {SERVOPORT &= ~(1 << SERVO1);}
85
            if(TCNT1 >= pulse_out_2) {SERVOPORT &= ~(1 << SERVO2);}
86
            if(TCNT1 >= pulse_out_3) {SERVOPORT &= ~(1 << SERVO3);}
87
            temp = SERVOPORT;
88
        }
89
        #endif
90
        #ifndef VARIANT
91
        //Variante 2 -> Servos in Reihe
92
        //Servo0
93
        SERVOPORT |= 1 << SERVO0;
94
        TCNT1 = 0;
95
        while(TCNT1 <= pulse_out_0) {};
96
        SERVOPORT &= ~(1 << SERVO0);
97
        //Servo1
98
        SERVOPORT |= 1 << SERVO1;
99
        TCNT1 = 0;
100
        while(TCNT1 <= pulse_out_1) {};
101
        SERVOPORT &= ~(1 << SERVO1);
102
        //Servo2
103
        SERVOPORT |= 1 << SERVO2;
104
        TCNT1 = 0;
105
        while(TCNT1 <= pulse_out_2) {};
106
        SERVOPORT &= ~(1 << SERVO2);
107
        //Servo3
108
        SERVOPORT |= 1 << SERVO3;
109
        TCNT1 = 0;
110
        while(TCNT1 <= pulse_out_3) {};
111
        SERVOPORT &= ~(1 << SERVO3);
112
        #endif
113
    }
114
    return(1);
115
}

Vielleicht kannst Du damit ja was anfangen.

Gruß,
Stefan

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.