Forum: Mikrocontroller und Digitale Elektronik Servo-Interrupt Problem


von Jan M. (pietus3)


Lesenswert?

Guten Tag,

ich möchte euch bitte mein Problem anzuschauen und mir vielleicht 
zusagen was ich ändern muss.

Ich habe ein Programm geschrieben welches einen Servo ansteuern soll per 
CTC Interrupt. Das ist dabei raus gekommen :
1
#define F_CPU 8000000UL
2
 
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <util/delay.h>
6
7
volatile boolean StromAn;
8
volatile int ZaehlerMillisekunden;
9
10
ISR( TIMER1_COMPA_vect)                // Interruptbehandlungsroutine
11
{
12
  if(ZaehlerMillisekunden<1000){ 
13
   StromAn= false;
14
   ZaehlerMillisekunden++;
15
  }
16
  else if(ZaehlerMillisekunden<=1500){    //1500 gibt Winkel an/ sollte Mittelposition sein
17
    StromAn= true;
18
    ZaehlerMillisekunden++;
19
  }
20
  else if(ZaehlerMillisekunden<2000){ 
21
   StromAn= false;
22
   ZaehlerMillisekunden++;
23
  }
24
  else{
25
    
26
    ZaehlerMillisekunden = 0;
27
  }
28
      
29
}
30
31
void setup() {
32
  DDRB = 0b11111111;               //PortB Ausgang
33
  PORTB &= ~(1<<PORTB5);           // Pullup für PB0 und PB1
34
 
35
  TCCR1A = (1<<COM1A0);                 // Togglen bei Compare Match
36
  TCCR1B = (1<<WGM12) | (1<<CS10);      // CTC-Mode; Prescaler 1
37
  TIMSK1  = (1<<OCIE1A);                 // Timer-Compare Interrupt an
38
  StromAn = false;
39
 
40
  OCR1A = 40;                         // 1us
41
 
42
  sei();                                // Interrupts global an
43
44
}
45
46
void loop() {
47
  if (StromAn) {       // Signal an
48
      PORTB |= (1<<PORTB5); 
49
    } 
50
    else{       // Signal aus
51
      PORTB &= ~(1<<PORTB5); 
52
    } 
53
}

Mein Problem ist das Ich den Servo nur gezielt auf 0 einstellen kann und 
sonst die Anderen Positionen nicht. Außerdem zittert der Servo die ganze 
Zeit. Ich arbeite unter der Arduino IDE, deshalb Loop und setup statt 
main. Mein Mikroprozessor ist ein ATmega 328.

Ich danke jetzt für eure Tipps und wenn ich einfach zu dumm rügt mich 
einfach.

MFG Jan

von Uwe (Gast)


Lesenswert?

Geh noch mal Schritt für Schritt durch was dieses Konstrukt hier macht !
  if(ZaehlerMillisekunden<1000){
   StromAn= false;
   ZaehlerMillisekunden++;
  }
  else if(ZaehlerMillisekunden<=1500){    //1500 gibt Winkel an/ sollte 
Mittelposition sein
    StromAn= true;
    ZaehlerMillisekunden++;
  }
  else if(ZaehlerMillisekunden<2000){
   StromAn= false;
   ZaehlerMillisekunden++;
  }
  else{

    ZaehlerMillisekunden = 0;
  }

von Irgendwer (Gast)


Lesenswert?

1s aus -> 0.5s an -> 0.5s aus -> 1s aus -> 0.5s an -> 0.5s aus -> 1s aus 
-> 0.5s an -> 0.5s aus ->

Gibt 0,5Hz mit einem Puls-Pausen-Verhältnis von 3:1
Ist glaube ich nicht gerade das was ein Modellbauservo haben will

von Jan M. (pietus3)


Lesenswert?

if(ZaehlerMillisekunden<=500){    //500 gibt Winkel an/ sollte
Mittelposition sein
    StromAn= true;
    ZaehlerMillisekunden++;
  }
  else if(ZaehlerMillisekunden<20000){
   StromAn= false;
   ZaehlerMillisekunden++;
  }
  else{

    ZaehlerMillisekunden = 0;
  }

Ich hab den einen Tippfehler korrigiert danke :) statt 2000 20000 für 
den 20 ms Intervall. Außerdem hab ich die erste Pause entfernt.

Trotzdem funktioniert das einstellen immer noch leider nicht.

von Karl H. (kbuchegg)


Lesenswert?

Die ganze ISR ist nicht logisch.

Ich geh mal davon aus, dass das hier stimmt (ich habs nicht 
nachgerechnet)
1
  OCR1A = 40;                         // 1us

und du tatsächlich alle 1µs einen Interrupt bekommst. (was mir ehrlich 
gesagt ein wenig komisch vorkommt. Bei einem mit 16Mhz getaktetem AVR, 
wäre das dann in jedem 16-ten Takt. Das wird sich nie und nimmer 
vernünftig ausgehen. Ich würde da auf jeden Fall mal eine Zehnerpotenz 
höher gehen)

D.h alle 1µs wird das hier
1
ISR( TIMER1_COMPA_vect)                // Interruptbehandlungsroutine
2
{
3
  ...
4
}

aufgerufen.

Jetzt benutzt du einen Zähler, mit dem du die µs in die Zeiten bzw. 
Zeitpunkte verwandelst, die du für das Servo Signal benötigst.
Du möchtest eine 20ms Wiederholrate des Servosignals haben, also lässt 
du deinen Zähler nur bis 20000 laufen, denn das wären 20ms
1
ISR( TIMER1_COMPA_vect)                // Interruptbehandlungsroutine
2
{
3
  timeCount++;
4
  if( timeCount == 20000 )
5
    timeCount = 0;
6
7
  ...
8
}
Damit hast du eine Variable, die Werte von 0 bis 19999 annimmt, wobei 
jeder Zahlenwert für eine weitere 1µs Periode steht.

Das Wesen eines Servosignals besteht darin, dass es am Begin der 
kompletten 20ms Periode auf 1 geht und dann je nach gewünschtem 
Drehwinkel eine gewisse Zeit später wieder auf 0.
Diese gewisse Zeit hat Einschränkungen. Sie ist minimal 1ms und maximal 
2ms. Das braucht dich aber momentan nicht wirklich kümmern, denn das ist 
ja nur ein Zahlenwert (wenn man die Zeiten in Einheiten dieses timCount 
ausdrückt). Ich nenne die Variable, die diese 'Abschaltzeit' beinhaltet 
einfach mal servoPos. Wichtig ist, das der Ausgangspin bei Beginn der 
20ms Periode auf 1 geht, und nach servoPos Zeiteinheiten wieder auf 0. 
Diese Zeitpunkte sind aber leicht zu finden, indem man einfach nur den 
entsprechenden Zahlenwert mit dem µs-Zähler vergleicht, der ja alle 20ms 
erneut wieder bei 0 startet und jede 1µs um 1 höher wird)

Also:
1
ISR( TIMER1_COMPA_vect)                // Interruptbehandlungsroutine
2
{
3
  timeCount++;
4
  if( timeCount == 20000 )
5
    timeCount = 0;
6
7
  if( timCount == 0 )
8
    PORTB |= ( 1 << PORTB5 );
9
  else if( timeCount == servoPos )
10
    PORTB &= ~( 1 << PORTB5 );
11
}

Fertig. Das wars im Grunde schon.
Wo das Servo stehen soll, wird durch den WErt in servoPos bestimmt. 
Weist du dem 1000 zu, dann fährt das Servo nach links. weist du der 
Variablen 1500 zu, dann steht das Servo in der Mitte und weist du der 
Variablen den Wert 2000 zu, dann fährt das Servo auf Rechtsanschlag.
1
void setup()
2
{
3
   ....
4
5
  servoPos = 1500;
6
}
7
8
void loop()
9
{
10
  servoPos = 1500;
11
  delay( 10000 );
12
13
  servoPos = 1000;
14
  delay( 10000 );
15
16
  servoPos = 2000:
17
  delay( 10000 );
18
}

Aber: Am Arduino gibts doch fix&fertige Servo Klassen. Warum benutzt du 
die nicht?

von Jan M. (pietus3)


Lesenswert?

Karl Heinz schrieb im Beitrag #3551787:
> Aber: Am Arduino gibts doch fix&fertige Servo Klassen. Warum benutzt du
> die nicht?

Ich bin in einem Mikroprozessorprojektkurs. Und wir wollten ein R/C Auto 
per Funk ansteuern. Leider überschneiden sich die Klassen VirtualWire 
und Servo. Da Ich allgemein Programmierkenntnisse besitze in C++ soll 
ich von meinem Lehrer aus versuchen selbst das Problem zu lösen, indem 
ich eine Ebene tiefer geh. Und das Versuch ich jetzt zu erlernen. MAcht 
auch Spaß ist trotzdem für den Einstieg extrem hart.

von Irgendwer (Gast)


Lesenswert?

Ich kenn das ganze eher so:
Der eine Anschlag -> 0,5ms an -> 19,5ms aus -> 0,5ms an -> 19,5ms usw.
Mitte -> 1ms an -> 19ms aus -> 1ms an -> 19ms usw.
Der andere Anschlag -> 2ms an -> 18ms aus -> 2ms an -> 18ms usw.
Die Gesamtdauer zwischen dem Impulsbeginn ist immer 20ms

Und benenne "ZaehlerMillisekunden" mal in "ZaelherMikroSekunden" um, das 
verwirrt doch etwas

von Karl H. (kbuchegg)


Lesenswert?

Irgendwer schrieb:
> Ich kenn das ganze eher so:
> Der eine Anschlag -> 0,5ms an -> 19,5ms aus -> 0,5ms an -> 19,5ms usw.
> Mitte -> 1ms an -> 19ms aus -> 1ms an -> 19ms usw.
> Der andere Anschlag -> 2ms an -> 18ms aus -> 2ms an -> 18ms usw.
> Die Gesamtdauer zwischen dem Impulsbeginn ist immer 20ms

Die 20ms sind beim Servoansteuern das unwichtigste überhaupt.
Wichtig ist die Impulsdauer.

Leider gibt es da 2 Definitionen. Die meisten Firmen definieren den Puls 
als 1ms - 2ms (mit einer Mitte von 1.5ms). Wobei natürlich nicht 
ausgeschlossen ist, dass ein Servo auch noch bei 0.9ms noch sauber 
positioniert, oder bei 0.8ms. Ein bischen Reserve ist da immer mit 
drinnen, ehe man dann am mechanischen Endanschlag angelangt ist.

Eine einzige Firma, deren Name mit 'Multi' beginnt und mit 'plex' endet, 
hat auch noch aus historischen Zeiten andere Pulsdauern im Gebrauch. 
Aber auch die sind nicht die von dir angegebenen Zeiten. Wenn mich nicht 
alles täuscht, dann sind die M...x Pulsdauern ein bischen kürzer und 
laufen von 0.8ms bis ca 1.8ms, mit einer Mittelstellung von 1.3ms. Also 
einfach nur die Zeiten ein klein wenig kürzer.

Aber 0.5ms für links, 1ms für mitte und 2ms fpr rechts ist mir noch nie 
unter gekommen. Das wäre auch höchst kontraproduktiv und nicht sehr 
logisch.

von Jan M. (pietus3)


Lesenswert?

Bei mir will der Servo bei einem Wert von 2000 noch weiter nach links 
fahren und ich hab jetzt mal deine Variante übernommen Karl Heinz.

von Karl H. (kbuchegg)


Lesenswert?

Ach, ich hab ja komplett überlesen

> #define F_CPU 8000000UL

Du fährst mit 8 Mhz.

Wie kommst du dann auf

>   OCR1A = 40;                         // 1us

für 1µs?

Du hast hier nicht 1µs eingestellt, sondern 5µs!

Demenstsprechend stimmen natürlich die Zeiten, respektive die 
Zahlenwerte in der ISR bzw. bei den Zuweisungen an servoPos überhaupt 
nicht. Alles um den Faktor 5 zu groß

Eigentlich sollte ich es schon wissen, das man Kommentaren nicht trauen 
soll. Lieber alles nachrechnen.

: Bearbeitet durch User
von Jan M. (pietus3)


Lesenswert?

Ich hab damit den CTC Wert ausgerechnet 
http://www.bunbury.de/Technik/timerberechnung.htm

von Karl H. (kbuchegg)


Lesenswert?

Jan Meyer schrieb:
> Ich hab damit den CTC Wert ausgerechnet
> http://www.bunbury.de/Technik/timerberechnung.htm

Und?
Welche Werte hast du eingesetzt?
Egal was ich dort einsetze, 40 kriege ich nicht als Ergebnis bei 1µs


Lies dir das durch
FAQ: Timer
und rechne dir das selber aus, anstatt irgendwo Zahlen einzusetzen.


Was ist jetzt mit den 8Mhz. Stimmen die?
Ich dachte immer Arduinos laufen mit 16Mhz, was mir auch eine kurze 
Web-Recherche bestätigt.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Und nein. Nur um das klar zu stellen.
Du willst keinen CTC MOdus mit einem Compare Wert von 4. Alles unter 100 
ist nicht wirklich akzeptabel. Selbst 40 (die eigentlich als 39 im 
Programm geschrieben werden müssten), sind schon grenzwertig. Alle 40 
Prozessortakte ein Interrupt müllt dir die Maschine zu. Die macht ausser 
Interrupts nur noch Interrupts.

Der ganze Ansatz mit einem µs Interrupt ist nicht besonders schlau. Aber 
das kann man immer noch ändern. Jetzt ist mir erst mal wichtig, dass du 
verstehst was du da tust. Denn dafür, dass du allgemeine 
Programmierkenntnisse besitzt, schwächelst du da ganz schön.

: Bearbeitet durch User
von Jan M. (pietus3)


Lesenswert?

Nur ob ich es richtig verstanden habe.

Mein Mikrocontroller hat 16mhz;

und ich muss auf 1 mHz kommen um pro us einen Interrupt zu haben.

Also lass ich den immer bis 16 hochzählen um 1mHz zu bekommen.Was 
1000000 Interrupts pro Sekunde, also 1 pro us entspricht

stimmt doch oder?

MFG Pietus3

: Bearbeitet durch User
von ?? (Gast)


Lesenswert?

Jan Meyer schrieb:
> und ich muss auf 1 mHz kommen um pro us einen Interrupt zu haben.

Nee, wenn du 1 mHz hast, dann hast du pro 1000s einen Interrupt.
Wenn du wirklich pro µs einen willst, brauchst du 1 MHz.

von Jan M. (pietus3)


Lesenswert?

?? schrieb:
> Nee, wenn du 1 mHz hast, dann hast du pro 1000s einen Interrupt.
> Wenn du wirklich pro µs einen willst, brauchst du 1 MHz.

Du schreibst gleichzeitig das 1 mHz richtig und falsch ist.

von ?? (Gast)


Lesenswert?

Jan Meyer schrieb:
> ?? schrieb:
>> Nee, wenn du 1 mHz hast, dann hast du pro 1000s einen Interrupt.
>> Wenn du wirklich pro µs einen willst, brauchst du 1 MHz.
>
> Du schreibst gleichzeitig das 1 mHz richtig und falsch ist.

m = Einheitenvorsatz für "Milli", z.B mg -> Milligramm (1/1000g)
M = Vorsatz für "Mega", z.B. MJ -> Megajoule (1 Mio Joule)

Ok? :-))

von Jan Peter Meyer (Gast)


Lesenswert?

Oh entschuldige mein Fehler.  Ich meinte aufjedenfall Mega die ganze 
Zeit.

von ?? (Gast)


Lesenswert?

Jan Meyer schrieb:
> Nur ob ich es richtig verstanden habe.
>
> Mein Mikrocontroller hat 16mhz;
>
> und ich muss auf 1 mHz kommen um pro us einen Interrupt zu haben.
>
> Also lass ich den immer bis 16 hochzählen um 1mHz zu bekommen.Was
> 1000000 Interrupts pro Sekunde, also 1 pro us entspricht
>
> stimmt doch oder?
>
> MFG Pietus3

Aber ich will dir auch mal auf deine eigentliche Frage eine Antwort 
geben.
Wenn du aller 16 Takte einen Interrupt auslöst, dann mußt du natürlich 
mit der Abarbeitung dieses Interrupts komplett fertig sein, wenn der 
nächste kommt. Und das wird dir mit 16 Takten schwer fallen.
Das hat Karl Heinz schon geschrieben, lies dir seinen Beitrag nochmal 
durch, ok?

von Jan Peter Meyer (Gast)


Lesenswert?

Das hab ich auch vor zu ändern dabei ging es nur um die Rechnung.

von ?? (Gast)


Lesenswert?

Jan Peter Meyer schrieb:
> Das hab ich auch vor zu ändern dabei ging es nur um die Rechnung.

Die Rechnung selbst stimmt schon, aber in der Praxis ist das so nicht 
durchführbar, sonst ist der µC nur noch mit Interrupts beschäftigt, und 
nicht mal die schafft er abzuarbeiten. Du solltest also das Raster 
größer machen. Beim Faktor 10 hast du schon 160 Takte für einen 
Interrupt. Überlege, wie "fein" du die Auflösung brauchst und lege dann 
die Interrupt-Rate fest.

von Jan M. (pietus3)


Lesenswert?

Danke Jungs. Mein Servo läuft nun. Ich hoffe ich hab euch nicht zu sehr 
genervt.

Noch einen schönen Abend

MFG Jan

von Mike (Gast)


Lesenswert?

Jan Peter Meyer schrieb:
> Ich meinte aufjedenfall Mega die ganze Zeit.

Dann schreib etweder Mega bzw. Milli aus oder verwende die richtigen 
Präfixe. Beim Programmieren funktioniert es auch nicht, wenn du dem 
Computer was anderes sagst, als was du von ihm möchtest.
http://de.wikipedia.org/wiki/Vors%C3%A4tze_f%C3%BCr_Ma%C3%9Feinheiten#SI-Pr.C3.A4fixe

von Quack (Gast)


Lesenswert?

Karl Heinz schrieb:
> Die 20ms sind beim Servoansteuern das unwichtigste überhaupt.

Wenn, dann sowieso 22.5ms. Das ist der uebliche Wert, auch wenn allerlei 
Webseiten Anderes behaupten. Die haben aber nicht nachgemessen.

> Leider gibt es da 2 Definitionen. Die meisten Firmen definieren den Puls
> als 1ms - 2ms (mit einer Mitte von 1.5ms).

Auch das wird zwar gerne auf Webseiten angegeben, trifft aber in der 
Praxis praktisch nicht zu. Der kleinste Range, den ich bisher gemessen 
habe, ist 0.9ms bis 2.1ms. Teilweise mehr, aber nie weniger - hoechstens 
bei Servotestern.

> Eine einzige Firma, deren Name mit 'Multi' beginnt und mit 'plex' endet,
> hat auch noch aus historischen Zeiten andere Pulsdauern im Gebrauch.

Multiplex hatte frueher eine andere Mittelstellung, 1.6ms statt 1.5ms. 
Die Dauer lag auch da schon bei den ueblichen 1.2ms.

Achtung bei der Ansteuerung von HF-Modulen - Futaba moechte da zum 
Beispiel 1.4ms als Mitte sehen.

von Jan M. (pietus3)


Lesenswert?

Mit dem Timer 1 läuft der Servo jetzt perfekt. Hab auch die Interrupt 
Zeit angepasst, Damit er besser läuft. Nun wollte Ich das gleiche 
Programm über den 8-Bit Timer 0 laufen lassen. Also hab ich alle 
Register so geändert das es für den Timer 0 gilt. Nun läuft es nicht 
mehr. Gibt es dafür einen Grund oder hab ich doch aus versehen ein 
falsches-Register gesetzt.

von julius (Gast)


Lesenswert?

Jan Meyer schrieb:
> Mit dem Timer 1 läuft der Servo jetzt perfekt. Hab auch die Interrupt
> Zeit angepasst, Damit er besser läuft. Nun wollte Ich das gleiche
> Programm über den 8-Bit Timer 0 laufen lassen. Also hab ich alle
> Register so geändert das es für den Timer 0 gilt. Nun läuft es nicht
> mehr. Gibt es dafür einen Grund oder hab ich doch aus versehen ein
> falsches-Register gesetzt.

Ich tippe mal, die richtigen Register falsch gesetzt. Jedes gesetzte Bit 
in den Timer0 Registern anhand des Datenblattes auf ihre Funktion mit 
denen des Timer1 verglichen? Timer0 kann nur bis 255 laufen. Zahlenwerte 
im Programm daraufhin überprüft? Grundsätzlich würd ich sagen, daß es 
für das nicht mehr laufen einen Grund gibt.

von Jan M. (pietus3)


Lesenswert?

Ich habe jetzt 3 Tage damit verbracht das Datenblatt zu wälzen und den 
Fehler finde Ich nicht.
1
#define F_CPU 16000000UL
2
3
 
4
#include <avr/io.h>
5
#include <avr/interrupt.h>
6
#include <util/delay.h>
7
8
volatile int servoPos;
9
volatile int timeCount;
10
volatile int pos;
11
12
ISR( TIMER0_COMPA_vect)                // Interruptbehandlungsroutine
13
{
14
timeCount++;
15
  if( timeCount == 2000 ){
16
    timeCount = 0;
17
  }
18
  if( timeCount == 0 ){
19
    PORTB |= ( 1 << PORTB5 );
20
  }  
21
  
22
  else if( timeCount == 200 ){
23
    PORTB &= ~( 1 << PORTB5 );
24
  }    
25
}
26
27
void setup() {
28
  DDRB = 0b11111111;               //PortB Ausgang
29
 
30
  TCCR0A = (1<<WGM01) | (1<<COM0A1);      // CTC-Mode; Prescaler 1
31
  TIMSK0  = (1<<OCIE0A);                 // Timer-Compare Interrupt an
32
  servoPos = 1500;
33
 
34
  OCR0A = 16;                         // Neutralposition ((2500-2312)*0.008ms)=1,5ms)
35
 
36
  sei();                                // Interrupts global an
37
 
38
  timeCount = 0;
39
40
}
41
42
void loop() {
43
for(pos=0; pos<180; pos++) {
44
  servoPos = ((pos * 11) + 500)/10;
45
  delay(10);
46
  }
47
  
48
  for(pos=0; pos>0; pos--) {
49
  servoPos = ((pos * 11) + 500)/10;
50
  delay(10);
51
  }
52
}

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.