Forum: Compiler & IDEs Atmega32 kleines Problem mit Modellservoansteuerung


von Dennis R. (dennis_r)


Lesenswert?

Moin moin,
ich weiß, dass es ein oft Diskutiertes Thema ist, aber ich hab trotzdem 
ein kleines Problem mit meinem Code.
An sich denke ich, dass ich es verstanden habe wie man den Servo 
ansteuert:
- 20ms Signal generieren
- die ersten 2ms sind das Servosignal und der Rest ist für nix quasi.


Das hab ich mal probiert mit nem Atmega32 mit dem internen 8MHz Quarz.
Währe cool wenn jemand vlt. Mal drüber gucken könnte und mir nen kleinen 
Denkanstoß geben könnte?

Danke im Voraus.
Gruß Dennis


hier mein C Code:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define servo_anzahl 1
5
uint16_t isvalue=1000;
6
uint16_t value[1]={1500};
7
uint16_t a=0,b=0;
8
9
10
ISR (TIMER2_COMP_vect) // µs Takt
11
{
12
uint8_t i;
13
14
isvalue++;
15
16
if (isvalue>=20000) isvalue=0; //gucken ob die 20ms um sind
17
18
if(isvalue==0){//wenn sie um sind die Servoports auf High schalten
19
  for(i=0;i<servo_anzahl;i++) PORTA|=(1<<i);
20
}
21
22
for(i=0;i<servo_anzahl;i++){//wenn gewünschte zeit rum ist servosignal auf low schalten
23
  if(value[i]==isvalue) PORTA &=~(1<<i);
24
}// end for(i=0
25
26
if(isvalue>=2000) for(i=0;i<servo_anzahl;i++) PORTA &=~(1<<i); // wenn man über die 2ms kommt, alle servos auf null schalten
27
28
} // end isr timer2comp
29
30
31
32
  
33
34
int main(void){
35
sei();
36
DDRA=0xFF;
37
DDRB=0xFF;
38
PORTB=0x00;
39
// timer2 im µsekundentakt output compare betrieb
40
TCCR2 =(1<<WGM21) |(1<<CS20);        //Prescale von 1 und CTC mode
41
OCR2=8;                  //Vergleich bei 4 --> (8MHz/1)/8= Interruptabstand von einer µs
42
TIMSK|=(1<<OCIE2);
43
while(1);
44
}

von Karl H. (kbuchegg)


Lesenswert?

Dennis R. schrieb:

> - 20ms Signal generieren
> - die ersten 2ms sind das Servosignal und der Rest ist für nix quasi.

die 2ms sind wichtig.
die 20ms sind nicht so wichtig.


> Das hab ich mal probiert mit nem Atmega32 mit dem internen 8MHz Quarz.

Es gibt keinen 8Mhz internen Quarz.
Es gibt einen RC-Schwingkreis, der in etwa mit 8Mhz schwingt.

> Währe cool wenn jemand vlt. Mal drüber gucken könnte und mir nen kleinen
> Denkanstoß geben könnte?

In welche Richtung sollen wir suchen?

von Karl H. (kbuchegg)


Lesenswert?

Du denkst also, du kannst alle 8 Taktzyklen einen Interrupt auslösen 
lassen, und dann in der Interrupt Routine massig Taktzyklen für 
Berechnungen zur Verfügung zu haben.

Das wird nicht gehen.
Wenn du für eine Tätigkeit 5 Minuten brauchst, dann hat es keinen Sinn 
wenn dein Chef alle 10 Sekunden da steht und dir noch einen Auftrag gibt 
und noch einen und noch einen ad infinitum.
Du wirst mit der Arbeit nicht hinterherkommen.

von Hc Z. (mizch)


Lesenswert?

Wie kommst Du auf die Idee, Deine Interruptroutine könne in weniger als 
1 us abgearbeitet sein?  Das müsste sie nämlich, wenn Obiges 
funktionieren soll.  Dein Programm kommt aus dem Interrupt nicht raus 
und hängt trotzdem weit hinterher.

Edit: 1 Bemerkung gestrichen, weil sie nicht zutraf.

von Karl H. (kbuchegg)


Lesenswert?

8 Taktzyklen sind zu wenig Zeit, die du der ISR lässt.
80 Taktzyklen könnte sich so lala ausgehen. Müsste man im Simulator 
durchspielen.
Also: schraub deine Erwartungen runter. Kein Servo muss man auf 1000 
unterschiedliche Positionen fahren können. Das gibt die Mechanik sowieso 
nicht her. 100 reichen auch.

von Karl H. (kbuchegg)


Lesenswert?

So kannst du die ISR noch ein wenig kürzer gestalten
1
ISR (TIMER2_COMP_vect) // µs Takt
2
{
3
  uint8_t i, servoMask;
4
5
  isvalue++;
6
  if (isvalue >= 20000)
7
    isvalue=0; //gucken ob die 20ms um sind
8
9
  servoMask = 0x01;
10
  for( i = 0; i < servo_anzahl; i++) {
11
    if( isvalue < value[i] )
12
      PORTA |= servoMask;
13
    else
14
      PORTA &= ~servoMask;
15
16
    servoMask <<= 1;
17
  }
18
}

Das ändert aber nichts daran, dass du die ISR Aufruffrequenz absenken 
musst.

von Mark .. (mork)


Lesenswert?

Wenn Du mehrere Servos am gleichen Port hast, kannst Du das ganze auch 
anders lösen. Die Servos werden dabei nicht gleichzeitig, sondern 
nacheinander angesteuert.

Es gibt dann eine Variable 'servonummer'. Zu beginn wird die Nummer auf 
0 gesetzt. Der Timer wird mit einem Prescaler von 64 im CTC Modus 
betrieben. Der Servoport.0 wird auf 1 gesetzt und OCR des Timers mit dem 
Wert des ersten Servowertes geladen:
1
// Pseudocode
2
3
void InitServo()
4
{
5
    servonummer = 0;
6
    SERVOPORT = 1;
7
    Timer = PRESCALE 64;
8
    OCR = value[0];
9
}

In der Compare Match ISR wird dann der nächste Pin des Ports auf 1 
gesetzt und der aktuelle gelöscht. Die Servonummer wird hochgezählt und 
OCR mit dem Wert des nächsten Servos geladen. Sobald alle Servos durch 
sind, wird wieder von vorne angefangen:
1
// Pseudocode
2
3
ISR(TIMER_COMP_VECT)
4
{
5
    if(++servonummer == ANZAHL_DER_SERVOS) // alle Servos durch
6
    {
7
        servonummer = 0; // -> vorne anfangen
8
        SERVOPORT = 1;
9
    }
10
    else
11
        SERVOPORT <<= 1; // ansonsten nächstes Servo ansteuern
12
    
13
    OCR = value[servonummer]; // Timer mit nächstem Servowert laden
14
}

Bei einem OCR-Wert von 128 ergibt das etwa 1ms, bei 255 etwa 2ms 
Impulslänge.

Die Zeit zwischen 2 Impulsen, die ein Servo kriegt, ist dabei zwar nicht 
konstant 20ms, was in der Praxis jedoch nichts ausmacht. Ein großer 
Vorteil ist die sehr geringe Prozessorauslastung, da die ISR höchstens 
1x pro Millisekunde ausgeführt wird.

Ich hatte auf diese Weise 8 Servos an einem Controller hängen, hat immer 
wunderbar funktioniert. Hab nur leider den Originalcode nicht mehr.

MfG Mark

von Dennis R. (dennis_r)


Lesenswert?

Ui danke für die vielen und vorallem schnellen antworten.
Hatte es ebend mal versucht die ISR frequenz herab zu senken um den 
faktor 10, dann ging es. aber ich konnte den servo nicht bewegen.
D.h. wenn ich value[0] verändert habe dann hat sich am servo aber nix 
verändert. nur wenn ich den vorgeladenen wert verändert habe dann hat er 
sich bewegt.
also nur ein kleienr erfolg.

Zu dem code von mark ..
hab ich so umgesetzt
der code fährt zwar den server an die richtige position, aber hält ihn 
da dann nicht fest!
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define SERVO_ANZAHL 8
5
#define SERVOPORT PORTA
6
7
uint8_t value[8]={250};
8
uint8_t servonummer;
9
10
11
ISR (TIMER2_COMP_vect) 
12
{
13
if(++servonummer == SERVO_ANZAHL) // alle Servos durch
14
    {
15
        servonummer = 0; // -> vorne anfangen
16
        SERVOPORT = 1;
17
    }
18
    else
19
        SERVOPORT <<= 1; // ansonsten nächstes Servo ansteuern
20
    
21
    OCR2 = value[servonummer]; // Timer mit nächstem Servowert laden
22
23
24
} // end isr timer2comp
25
26
27
void InitServo()
28
{
29
    servonummer = 0;
30
    SERVOPORT = 1;
31
32
    // timer2 im µsekundentakt output compare betrieb
33
TCCR2 =(1<<WGM21) |(1<<CS22);        //Prescale von 8 und CTC mode
34
OCR2=value[0];                  //Vergleich bei 4 --> (8MHz/8)/1= Interruptabstand von einer µs
35
TIMSK|=(1<<OCIE2);
36
}
37
38
39
  
40
41
int main(void){
42
sei();
43
DDRA=0xFF;
44
DDRB=0xFF;
45
PORTB=0x00;
46
47
InitServo();
48
49
50
while(1){
51
52
}
53
}

von Karl H. (kbuchegg)


Lesenswert?

Dennis R. schrieb:

> D.h. wenn ich value[0] verändert habe dann hat sich am servo aber nix
> verändert. nur wenn ich den vorgeladenen wert verändert habe dann hat er
> sich bewegt.

könnte ein volatile Problem sein.
1
volatile uint16_t value[1]={1500};


> der code fährt zwar den server an die richtige position, aber hält ihn
> da dann nicht fest!

Was heißt 'hält ihn nicht fest'?

> uint8_t value[8]={250};

gib den anderen Servos wenigstens eine kleine Zeit aber nicht 0, damit 
das Servo nicht Puls an Puls bekommt. Auch wenn die 20ms nicht kritisch 
sind, würde ich meine Hand da nicht dafür ins Feuer legen, was wohl 
passiert, wenn die 2ms Stellimpulse da dicht an dicht kommen. Ein paar 
Millisekunden dürfen schon Pause dazwischen sein. Und auch hier wieder: 
volatile!
1
volatile uint8_t value[8] = { 250, 200, 200, 200, 200, 200, 200, 200 };

Die Pause die jedes Servo zwischen den Pulsen zugestanden bekommt, ist 
ja die Summe der Pulslängen aller anderen Servos.


Und bitte:
Bemüh dich wenigstens ein bischen, ein optisch einigermassen akzeptables 
Programm zu produzieren. Auch jetzt in der Entwicklungsphase!
Alles einfach nur an den linken Rand zu quetschen ist ein ziemlich 
sicherer Weg irgendwann den Überblick zu verlieren, welche Schleife in 
welches if geschachtelt wurde und welcher Block von welcher Bedingung 
abhängt.

Das bischen korrekte Einrückung kostet keine Zeit während man tippt, 
kann aber oft den Unterschied zwischen 'Ich sehs' oder 'Ich sehs nicht' 
ausmachen.

von Dennis R. (dennis_r)


Lesenswert?

na mit nicht festhalten mein ich das man, den servo nach dem stellen mit 
der hand bewegen kann.
Normal ist ja eignetlich(so kenne ich es aus dem modellbau bereich) das 
er an der Position feststehen tut?

so mit dem code haste ja recht
hab ihn nochmal etwas verschönert ;)
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
#define SERVO_ANZAHL 8
5
#define SERVOPORT PORTA
6
7
volatile uint8_t value[8]={128};
8
volatile uint8_t servonummer;
9
10
11
ISR (TIMER2_COMP_vect) 
12
{
13
  if(++servonummer == SERVO_ANZAHL)   // alle Servos durch
14
      {
15
          servonummer = 0;         // -> vorne anfangen
16
          SERVOPORT = 1;
17
       }
18
    else SERVOPORT <<= 1;         // ansonsten nächstes Servo ansteuern
19
    
20
    OCR2 = value[servonummer];       // Timer mit nächstem Servowert laden
21
} // end isr timer2comp
22
23
24
void InitServo()
25
{
26
  servonummer = 0;
27
  SERVOPORT = 1;
28
  TCCR2 =(1<<WGM21) |(1<<CS22);  //Prescale von 64 und CTC mode
29
  OCR2=value[0];
30
  TIMSK|=(1<<OCIE2);
31
}
32
33
int main(void){
34
35
  sei();
36
  DDRA=0xFF;
37
  DDRB=0xFF;
38
  PORTB=0x00;
39
40
  InitServo();
41
42
  while(1);
43
}// end main

von Dennis R. (dennis_r)


Lesenswert?

aso zu der pause zwischen den pulsen für den servo.
wenn ich jetzt zum Beispiel 4 Servos ansteuern will, hab ich ja 
mindestens 4 ms pause, wenn ich sage das Minimum für ein servo ist 1ms 
und das max. ist 2ms?
hmm aber leider funktioniert der code leider doch nett.
ich komme irgedwie nicht mehr aus der ISR raus. das hab ich mit dem 
simulator auch nachstellen können.
Aber warum?

von Karl H. (kbuchegg)


Lesenswert?

Dennis R. schrieb:

> ich komme irgedwie nicht mehr aus der ISR raus. das hab ich mit dem
> simulator auch nachstellen können.
> Aber warum?

Das ist an dieser Stelle schon korrekt.
Da in deiner Hauptschleife nichts mehr passiert, macht der Simulator 
dieses 'da passiert nichts weiter' im schnellen Vorlauf, bis eben der 
nächste Compare Match eintritt.

von Karl H. (kbuchegg)


Lesenswert?

Dennis R. schrieb:
> aso zu der pause zwischen den pulsen für den servo.
> wenn ich jetzt zum Beispiel 4 Servos ansteuern will, hab ich ja
> mindestens 4 ms pause, wenn ich sage das Minimum für ein servo ist 1ms
> und das max. ist 2ms?

Ja.
Bedenke: Normalerweise ist diese Pause ca. 19ms lang

PS: Bei dir ist sie jetzt 0 (ok, nahezu 0) ms lang.

von Dennis R. (dennis_r)


Lesenswert?

Aber is schon komisch hab jetzt den Code mal um das in einen Sekenduen 
takt invertieren des PortB ergänzed.
Damit müsste die LED an meinen Controller, ja im 1 Sec. Takt blinken.
Was sie ja mit anderen Programmen tut, aber in diesem fall nicht!

Und nochmal zur pause, ok vlt. versteh ich den code falsch:
Wenn ich nur einen Servo hätte, dann leuchtet es mir ein das ich eine 
Pause von fast 0 habe. aber wenn ich jetzt hier in diesem Bsp. 8 servos 
habe dann müsste ich doch eine Pause von ca. 8 bis 16ms haben oder?
Ich verschiebe doch mein bit an PortA solange bios ich wieder beim 
ersten anfange. Somit ist ja der PortA 0 z.B. solange bis er weider dran 
ist?
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#ifndef F_CPU
4
#define F_CPU 8000000ul
5
#endif
6
#include <util/delay.h>
7
#define SERVO_ANZAHL 8
8
#define SERVOPORT PORTA
9
10
volatile uint8_t value[8]={128};
11
volatile uint8_t servonummer;
12
13
14
ISR (TIMER2_COMP_vect) 
15
{
16
  if(++servonummer == SERVO_ANZAHL)   // alle Servos durch
17
      {
18
          servonummer = 0;         // -> vorne anfangen
19
          SERVOPORT = 1;
20
       }
21
    else SERVOPORT <<= 1;         // ansonsten nächstes Servo ansteuern
22
    
23
    OCR2 = value[servonummer];       // Timer mit nächstem Servowert laden
24
} // end isr timer2comp
25
26
27
void InitServo()
28
{
29
    servonummer = 0;
30
    SERVOPORT = 1;
31
  TCCR2 =(1<<WGM21) |(1<<CS22);  //Prescale von 64 und CTC mode
32
  OCR2=value[0];
33
  TIMSK|=(1<<OCIE2);
34
}
35
36
int main(void){
37
38
  sei();
39
  DDRA=0xFF;
40
  DDRB=0xFF;
41
  PORTB=0x00;
42
43
  InitServo();
44
45
  while(1){
46
  _delay_ms(1000);
47
  PORTB=~PORTB;
48
    
49
50
  }
51
}// end main

von Karl H. (kbuchegg)


Lesenswert?

Dennis R. schrieb:
> Aber is schon komisch hab jetzt den Code mal um das in einen Sekenduen
> takt invertieren des PortB ergänzed.
> Damit müsste die LED an meinen Controller, ja im 1 Sec. Takt blinken.
> Was sie ja mit anderen Programmen tut, aber in diesem fall nicht!
>
> Und nochmal zur pause, ok vlt. versteh ich den code falsch:
> Wenn ich nur einen Servo hätte, dann leuchtet es mir ein das ich eine
> Pause von fast 0 habe. aber wenn ich jetzt hier in diesem Bsp. 8 servos
> habe dann müsste ich doch eine Pause von ca. 8 bis 16ms haben oder?
> Ich verschiebe doch mein bit an PortA solange bios ich wieder beim
> ersten anfange.

Ja.
Aber du lädst einen OCR Wert von 0, wodurch beim Verlassen der ISR 
gleich wieder der nächste Compare Match vorliegt

Das hier
1
volatile uint8_t value[8]={128};

ist gleichbedeutend mit
1
volatile uint8_t value[8]={128, 0, 0, 0, 0, 0, 0, 0};

d.h. deine restlichen Servos stehen alle auf 0!


Ich hab jetzt auch noch nicht das Timing nachgerechnet, was ein 
Prescaler von 64 mit 8Mhz und einem OCR Wert von 128 für eine Zeitdauer 
ergibt.

von Dennis R. (dennis_r)


Lesenswert?

aso hmm ok dachte bis ebend das
1
volatile uint8_t value[8]={128};

das gleiche ist wie
1
volatile uint8_t value[8]={128,128,128,128,128,128,128,128};

deswegen hatte das kbuchegg schon vorhin hervorgehoben.

also mit dem code geht es jetzt.

die ISR für den timer0 hab ich nur reingemacht zum testen. Damit der 
servo hin und her fährt.

und das mit der Zeit haut schon hin.

1/((8MHz / 64)/128)=1,024mS
1/((8MHz / 64)/250)=2mS

ok nach oben ist da nicht mehr viel spielraum aber nach unten, falls der 
servo mehr kann!

Danke nochmals für die nett hilfe hier, DANKE!

1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#ifndef F_CPU
4
#define F_CPU 8000000ul
5
#endif
6
#include <util/delay.h>
7
#define SERVO_ANZAHL 8
8
#define SERVOPORT PORTA
9
10
volatile uint8_t value[SERVO_ANZAHL]={128,128,128,128,128,128,128,128};
11
volatile uint8_t servonummer;
12
13
14
ISR (TIMER2_COMP_vect) 
15
{
16
  if(++servonummer == SERVO_ANZAHL)   // alle Servos durch
17
      {
18
          servonummer = 0;         // -> vorne anfangen
19
          SERVOPORT = 1;
20
       }
21
    else SERVOPORT <<= 1;         // ansonsten nächstes Servo ansteuern
22
    
23
    OCR2 = value[servonummer];       // Timer mit nächstem Servowert laden
24
} // end isr timer2comp
25
26
ISR (TIMER0_COMP_vect) {
27
  
28
  static uint16_t i=0;
29
  static uint8_t asd=0;
30
  i++;
31
  if(i>=50){
32
    if(!asd){
33
      i=0;
34
      value[0]+=1;
35
      if(value[0]>=250) asd=1;
36
    }else{
37
      i=0;
38
      value[0]-=1;
39
      if(value[0]<=128) asd=0;
40
    }
41
  }
42
}
43
44
void InitServo()
45
{
46
    servonummer = 0;
47
    SERVOPORT = 1;
48
  TCCR2 =(1<<WGM21) |(1<<CS22);  //Prescale von 64 und CTC mode
49
  OCR2=value[0];
50
  TIMSK|=(1<<OCIE2);
51
}
52
53
int main(void){
54
55
  sei();
56
  DDRA=0xFF;
57
  DDRB=0xFF;
58
  PORTB=0x00;
59
60
  TCCR0 =(1<<WGM01) |(1<<CS00)|(1<<CS01);
61
  OCR0=125;
62
  TIMSK|=(1<<OCIE0);
63
64
65
  InitServo();
66
67
  while(1);
68
}// end main

von Dennis R. (dennis_r)


Lesenswert?

Aso,
am besten wähere es wenn man natürlich eine pause nach dem servo anfügen 
würde oder?
damit man eine bestimmte minimum zeit hat bis der erste servo wieder 
dran ist oder?

greetz dennis

von Karl H. (kbuchegg)


Lesenswert?

Dennis R. schrieb:
> Aso,
> am besten wähere es wenn man natürlich eine pause nach dem servo anfügen
> würde oder?

Das musst du ausprobieren.
Stell deine Servos auf Minimalzeit ein (aber nicht 0)
und sieh nach ob sie mit dieser Pause noch zurecht kommen.

Ich denke mal sie werden es, aber in dem Fall macht Versuch kluch.

> damit man eine bestimmte minimum zeit hat bis der erste servo wieder
> dran ist oder?

Die 0 war deswegen ein Problem, weil dein µC dann nicht mehr hinterher 
gekommen ist. Der kann seine ISR nun mal nicht in 0-Zeit abarbeiten.

von Dennis R. (dennis_r)


Lesenswert?

ja alles kalro jetzt hab ich es gecheckt!


tausend Danke nochmal an alle die geholfen haben.!

gruss

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.