Forum: Mikrocontroller und Digitale Elektronik Servocontroller mit Mega16


von David Reis (Gast)


Lesenswert?

Salut,

momentan entwickle ich einen Servocontroller, der auf einem Hexapod zum
Einsatz kommen soll. Leider zeigt dieser seltsame Fehler, bei denen mir
auch im Roboternetz niemand helfen konnte, deshalb frage ich hier
einmal nach.

Problem ist, dass sich die Servos nicht separat regeln lassen;
stattdessen wird der (gemeinsame) Puls einfach fuer alle Werte, die im
SRAM hintereinander liegend == 255 sind, etwas laenger, in einer Art
exponentiellen Kurve. Der Code an sich ist simpel, weshalb ich mir den
Fehler nicht erklaeren kann; hier der ASM-Code zur Erzeugung der
variablen Komponente des Signals:

  clr lowmask
  clr highmask
  clr ptr

  ldi counter,127    ; count down from 127 to -128
  sec      ; c is to be rotated into ptr
  ldi ZH,4     ; RAM pointer is at 1024, address of first servo value

  ; repeat this once every ( 1.35 / 255 ) ms
  cycle:

    clr ZL     ; point Z to first servo

    compare_next_motor:

      rol ptr        ; rotate servo ptr to next value
      brcs write      ; if all values compared: break
      ldd servo,z+8      ; load servo in higher byte
      cp servo,counter    ; and compare w/ counter
      brne second      ; if not equal: leave high mask blank at position
      or highmask,ptr      ; else start pulse at end of cycle
          second: ld servo,z+     ; compare lower servo respectively
      cp servo,counter
      brne compare_next_motor
      or lowmask,ptr

    rjmp compare_next_motor      ; and loop

    write:
    out _SFR_IO_ADDR(PORTA),lowmask
    out _SFR_IO_ADDR(PORTD),highmask

  dec counter       ; count comparision value for servos down
  brvc cycle       ; if not transition -128 -> 127: repeat

Der Code liegt in einem .S-File, umgeben von den noetigen Pushs und
Pops, und wird als Funktion aus C in einer ISR alle 20ms aufgerufen.
Grundidee, falls nicht ersichtlich, ist ein von 127 abwaerts zaehlender
int8_t-Wert, der laufend mit saemtlichen Servowerten, die sequentiell ab
1024 im SRAM liegen, abgeglichen wird; bei gleichem Wert wird der Servo
aktiviert, je hoeher der Wert, je frueher findet das statt. Soweit ganz
einfach - gerade deshalb komme ich nicht weiter... Kann vielleicht
jemand helfen?

Gruss,
David

PS: Fuer Optimierungsvorschlaege, was die Geschwindigkeit angeht, waere
ich ebenfalls sehr dankbar; bin zwar momentan auf 0-1.34 ms
Variationsbreite runter, das reicht aber nur fuer die mir vorliegenden
Conrad-Servos und entspricht nicht der Norm (zu lang - die liegt bei
1-2 ms AFAIK?).

von Hannes L. (hannes)


Lesenswert?

Also so richtig verstehe ich nicht, was du da vor hast.

Unter Servocontroller stelle ich mir das Teil vor, was die
Nachlaufsteuerung im Servo realisiert, also die Servo-Elektronik.

Solltest du aber einen Controller meinen, der Servoimpulse generiert,
dann schau mal hier:
http://www.hanneslux.de/avr/mobau/7ksend/7ksend02.html

Du kannst ja die ADC-Abfrage rauswerfen und dafür eine alternative
Bereitstellung der Servosollwerte einbauen.

...

von David Reis (Gast)


Lesenswert?

Hallo,

an dieser Begriffsverwirrung mag auch teilweise die geringe Resonanz
bei einem so einfachen Problem liegen ;)
Jedenfalls plane ich zweiteres, naemlich eine Ansteuerung von 16 Servos
aus einem uC. Bei obigem Code beeinflussen sich die Werte jedoch
gegenseitig, und ich weiss nicht, warum...

Nun brauche ich den uC allerdings noch fuer andere Aufgaben als die
Servoansteuerung, sonst waere die Sache kein Problem - haette es dann
genauso implementiert wie Du bei der RC-Sache, naemlich mit (wenn ich
es ohne RC-Wissen richtig verstanden habe) sequentieller Abfrage der
Werte und Erzeugung der Impulse fuer jeden Kanal. Dabei ist der
Controller leider voll ausgelastet, das faellt also weg; gibt auch
Probleme mit der Kommunikation ueber z. B. UART, wenn wg. Interrupt
ploetzlich der Puls ausbleibt...

Darum ist mein Ansatz eben, alle 16 Servos zur gleichen Zeit
anzusteuern - dazu muss ich allerdings in der variablem ms des Pulses
alle 255 moeglichen Positionen fuer alle Motoren abfragen und regeln,
was zwar auf nur etwa 5% Auslastung insgesamt hinauslaeuft, aber nicht
leicht zu implementieren ist. Graphisch:

statt:     will ich:
|"|______  ___|"|___ Servo1
__|"|____  ___|"|___ Servo2
____|"|__  ___|"|___ Servo3
...

Zwischen den Pulsen ist also voellig ungenutzte Zeit, sollte also das
Restprogramm nicht stoeren.

Ich hoffe, die Sache ist jetzt etwas verstaendlicher - waere nicht
schlecht, ich steig naemlich echt nicht dahinter, was das Problem sein
sollte. Leider hab ich kaum ASM-Erfahrung, bin GCC-Fan und versuche
mich das erste mal an geschwindigkeitsoptimiertem Code.

Gruss und Danke,
David

von Hannes L. (hannes)


Lesenswert?

> Dabei ist der
> Controller leider voll ausgelastet,

Das ist aber nicht der Fall. Der Controller verbringt die meiste Zeit
im Sleep-Mode Idle. Es sind also noch enorme Reserven an Rechenzeit
vorhanden, die Mainloop darf also noch drastisch wachsen.

Zeitkritische Sachen synchronisiert man vorteilhaft mittels
Timer-Interrupt. Das vermeidet rechenzeitfressende Warteschleifen.

Mein Vorschlag:
Nimm den Interrupt von Timer0, um alle 20ms eine Sequenz anzustoßen und
eventuelle mechanische Kontakte (z.B. Taster zu entprellen). Dann hast
du beide Compare-Interrupts von Timer1 frei. Nun teilst du deine 16
Impulse in zwei Gruppen auf, von denen die eine von Compare1A und die
andere von Compare1B sequentiell abgearbeitet wird. Die Sollwerte für
die Impulsbreite entnimmst du dabei dem SRAM. Somit läuft die gesamte
Impulserzeugung im Interrupt und dein Hauptprogramm kann sich um die
Generierung der Sollwerte kümmern. Ich denke mal, dass der Mega16 das
mit 1MHz Takt locker schafft und mehr als die Hälfte seiner Zeit im
Sleepmode vertrödelt.

Wenn du C wirklich beherrscht (ich kann es nicht), dann kannst du das
in C genauso effizient programmieren wie ich in ASM.

...

von H.joachim S. (crazy_horse)


Lesenswert?

// Timer 1 output compare interrupt service routine
interrupt [TIM1_COMP] void timer1_comp_isr(void)
{static unsigned char servo_nr;
PORTB=output_mask[servo_nr];   //alten Impulsausgang löschen, neuen
setzen
OCR1=servo_zeit[servo_nr];   //neue Impulszeit
if (servo_nr<9) servo_nr++;
   else servo_nr=0;
}

Das ist im Prinzip schon alles und läuft im Hintergrund, du merkst kaum
was davon. Ist für 8 Servos am PortB. Die Zeiten stehen in
servo_zeit[0..7], die Restzeit zu 20ms in servo_zeit[8].

Und noch Timer1_init:
// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 1000,000 kHz
// Mode: Output Compare
// OC1 output: Discon.
// Timer 1 is cleared on compare match
// Noise Canceler: Off
// Input Capture on Falling Edge
TCCR1A=0x00;
TCCR1B=0x0A;
TCNT1H=0x00;
TCNT1L=0x00;
OCR1H=0xf0;
OCR1L=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0xC0;

von David Reis (Gast)


Lesenswert?

Ham' wir wieder was gelernt, diesen Betriebsmodus kannte ich noch gar
nicht :) Kenne leider niemanden, der Ahnung von uCs hat, und bin noch
nicht selbst drueber gestolpert (sollte mich wohl mehr hier aufhalten
statt im RN). Dann hat das Projekt schonmal seinen Zweck erfuellt.

Danke,
David

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.