Forum: Mikrocontroller und Digitale Elektronik Hum. Roboterhand Part II: codeeffektive kontinuierliche Servoansteuerung


von Alex (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,
bereits der zweite Post heute zur selben hardware, die ich mir 
überwiegend gebaut habe, um daran weiterzulernen (um Fragen schon einmal 
vorzubeugen ;))

Die Humanoide Roboterhand (im Bild) bekommt gerade eine kleine 
Kalibrierung.
Ich benutze den ATMega16 und habe nun folgende zwei Fragen:

Ich möchte die Servos der Hand "kontinuierlich" ansteuern, das heißt die 
bewegung soll halbwegs flüssig sein und nicht ruckhaft.

Wenn ich nun (auf meine anfängerweise) die pulsweite des servosignals 
vom ist zum sollzustand in einer forschleife in kleinen (µs-) schritten 
durch addition zum istzustandspuls verändere, (momentan mache ich die 
pulsweite über delay routinen weil ich mit dem internen timer noch nicht 
vertraut bin), frisst der Rechenakt mir 20% vom Speicher des µC weg. 
Diese Methode ist also offensichtlich äußerst ineffizient - damit die 
erste Frage:

Wie kann ich servos codeeffizient "kontinuierlich" ansteuern?

Dann, was eventuell mit an der ersten frage hängt: Wie sorgt ihr für 
eine langsamere bewegung eurer servos? Wenn ich eine position vorgebe, 
fährt der servo ja mit maximaler geschwindigkeit an seine position - wie 
(geht das?) kann ich die geschwindigkeit verringern?

Beste Grüße

Alex

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Alex schrieb:
> Wenn ich eine position vorgebe,
> fährt der servo ja mit maximaler geschwindigkeit an seine position - wie
> (geht das?) kann ich die geschwindigkeit verringern?

Ich habe zwar keine große Erfahrung bei der Ansteuerung von Servos.
Die Geschwindigkeit selber kann man jedenfalls nicht verringern.
Jedoch musst Du ja nicht die entgültige Endposition angeben, sondern 
Dich in kleinen Schritten dieser annähern. Aber das hast Du ja bereits 
so gemacht.

Du solltest Dich aber dringend über Timer schlau machen. Vor allem das 
Stichwort PWM ist hier wichtig. Denn damit macht der Prozessor das ganze 
Timing ohne große Programmierung selber.

Mit PWM musst Du nur noch eine Variable von der Istposition zur 
Sollposition durchzählen (eventuell mit einer gewissen Zeit zwischen den 
Schritten).

von Michael W. (mictronics) Benutzerseite


Lesenswert?

Geschwindigkeit von Modellbauservos ändern geht, wie Christian auch 
schon angemerkt hat, eigendlich nicht.

Was mich aber interessieren würde, wie steuerst du die Servos überhaupt 
an?

Ich würde einen internen 16bit Timer mit Output Capture verwenden um die 
PWM zu erzeugen.
Diese auf eine beliebige Anzahl kaskatierte 4017 Counter geben zum 
demultiplexen. Jeder Counter kann dabei 10 Servos bedienen.
Dazu reichen 2 Pins am AVR.

von Rolf Magnus (Gast)


Lesenswert?

Alex schrieb:
> Wenn ich nun (auf meine anfängerweise) die pulsweite des servosignals
> vom ist zum sollzustand in einer forschleife in kleinen (µs-) schritten
> durch addition zum istzustandspuls verändere,

Wozu in µs-Schritten? Bei der üblichen Ansteuerung bekommt ein Servo 
doch sowieso nur alle 20 ms eine neue Position.

> (momentan mache ich die pulsweite über delay routinen weil ich mit dem
> internen timer noch nicht vertraut bin), frisst der Rechenakt mir 20%
> vom Speicher des µC weg.
> Diese Methode ist also offensichtlich äußerst ineffizient

Warum das so ist, kann man ohne Code kaum sagen. Ins Blaue getippt: Hast 
du vielleicht die delay-Funktionen der avr-libc verwendet, ohne die Doku 
zu lesen? Da kann man schnell was falsch machen, was die Codegröße 
explodieren läßt.

> Wie kann ich servos codeeffizient "kontinuierlich" ansteuern?

Die angegebene Methode sollte sich eigentlich bezüglich Codegröße recht 
effizient implementieren lassen. Bezüglich Prozessorauslastung sieht es 
aber wegen der Delays ganz anders aus. Da helfen dann Timer.

> Dann, was eventuell mit an der ersten frage hängt: Wie sorgt ihr für
> eine langsamere bewegung eurer servos? Wenn ich eine position vorgebe,
> fährt der servo ja mit maximaler geschwindigkeit an seine position - wie
> (geht das?) kann ich die geschwindigkeit verringern?

Wie du es getan hast, die Sollposition langsam verstellen. So macht das 
ja auch der Modellpilot, wenn er langsam am Steuerknüppel rührt.

von Markus (Gast)


Lesenswert?

>Ich möchte die Servos der Hand "kontinuierlich" ansteuern, das heißt die
>bewegung soll halbwegs flüssig sein und nicht ruckhaft.

Dazu müsste man alle 20ms einen neuen Servowert berechnen, da ja die 
Impulse alle 20ms ausgegeben werden. Für eine flüssige Bewegung musst Du 
interpolieren ( Stichwort Bahninterpolation )

>, frisst der Rechenakt mir 20% vom Speicher des µC weg.

Der Rechentakt frisst Speicher?

von Karl H. (kbuchegg)


Lesenswert?

Alex schrieb:

> durch addition zum istzustandspuls verändere, (momentan mache ich die
> pulsweite über delay routinen weil ich mit dem internen timer noch nicht
> vertraut bin)

Das muss sich ändern.
Die Pulsausgabe an die Servos muss losgelöst von allem anderen 
funktionieren.

Hier mal Code, mit dem ich auf einem Mega128 mit 16Mhz 12 Servos 
ansteuere.
Dass die Servos auf PortF und PortC verteilt sind, hat keinen tieferen 
Grund, ausser dass auf meiner Platine die Anschlüsse günstig lagen.
Jeweils 6 Servos sind auf F und 6 Servos sind auf C.
1
#define F_CPU  16000000UL
2
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <util/delay.h>
6
7
#define SERVO_ANZAHL 12
8
9
#define CENTER 30
10
11
volatile uint8_t value[SERVO_ANZAHL] =
12
   { CENTER, CENTER, CENTER,
13
     CENTER, CENTER, CENTER,
14
15
     CENTER, CENTER, CENTER,
16
     CENTER, CENTER, CENTER
17
   };
18
19
ISR (TIMER2_COMP_vect) 
20
{
21
  static uint8_t ServoId = SERVO_ANZAHL;
22
23
  if( ++ServoId >= SERVO_ANZAHL )
24
    ServoId = 0;
25
26
  if( ServoId == 0 ) {
27
    PORTF = 0x01;
28
    PORTC = 0x00;
29
  }
30
  else if( ServoId < 6 )
31
    PORTF <<= 1;
32
  
33
  else if( ServoId == 6 ) {
34
    PORTF = 0x00;
35
    PORTC = 0x01;
36
  }
37
  else
38
    PORTC <<= 1;
39
    
40
  OCR2 = 57 + value[ServoId];  // die 57 sind der 'Zeitoffset' für ca. 1ms
41
}
42
43
44
void InitServo()
45
{
46
  TCCR2 = (1<<WGM21) | (1<<CS22);  //Prescale von 256 und CTC mode
47
  OCR2 = value[0];
48
  TIMSK |= (1<<OCIE2);
49
}
50
51
int main(void)
52
{
53
  uint8_t i;
54
55
  DDRF = 0xFF;
56
  DDRC = 0xFF;
57
58
  PORTF = 0x00;
59
60
//  TCCR0 =(1<<WGM01) |(1<<CS00)|(1<<CS01);
61
//  OCR0=125;
62
//  TIMSK|=(1<<OCIE0);
63
64
  InitServo();
65
66
  sei();
67
68
  ....
69
70
  // ab hier reicht es an value[x] einen neuen Wert zuzuweisen und
71
  // das jeweilige Servo Nr x. fährt in die Richtige Position
72
73
  ...

Die 12 Servos steuern die Beine eines Hexapods (daher auch 6+6 für die 
Servos der Ober- und der Unterarme).

Um zb einen Schritt auszuführen benutze ich zb
1
    for( i = 0; i < 10; ++i ) {
2
      // angehobene Beine nach vorne
3
4
      value[ 0] = CENTER + i;
5
      value[ 1] = CENTER - i;
6
      value[ 2] = CENTER + i;
7
      value[ 3] = CENTER + i;
8
      value[ 4] = CENTER - i;
9
      value[ 5] = CENTER + i;
10
      _delay_ms( 100 );
11
    }

mit dem delay stellt man dann die Geschwindigkeit ein. Die 
Pulsgenerierung selbst im Interrupt braucht so gut wie keine Zeit.

von Michael L. (Gast)


Lesenswert?

@Karl Heinz:

Merkt man in der Praxis den Zeitversatz zwischen den einzelnen Servos? 
Die bekommen ihr Steuersignal ja quasi nicht 'synchron', oder ist das 
kein Problem? Bzw. würdest du ab einer bestimmten Anzahl Servos da etwas 
in der Richtung vermuten?

@Alex und alle. Das ist ein interessanter Spielplatz! Gibt es 
Bezugsquellen für derartige Hände? Denn sowohl eine humanoide Hand, als 
auch ein vergleichsweise schnöder Hexabod wären rein mechanisch für mich 
unerreichbar.

von Karl H. (kbuchegg)


Lesenswert?

Michael L. schrieb:
> @Karl Heinz:
>
> Merkt man in der Praxis den Zeitversatz zwischen den einzelnen Servos?

Bei mir spielt das keine Rolle.

Im schlimmsten Fall ist die Pause zwischen den Updates eines Servos ja 
maximal
12 * 2ms = 24ms
gross. Also sogar relativ nahe an den 20ms die sonst so genommen werden.
Im besten Fall kriegt jedes Servo so alle 10ms einen neuen 
Positionspuls.

> Die bekommen ihr Steuersignal ja quasi nicht 'synchron', oder ist das
> kein Problem?

Macht ja nichts. In einer Fernsteuerung bekommen sie ja auch nicht alle 
gleichzeitig ihren jeweils nächsten Puls sondern reihum eines nach dem 
anderen. Genau daraus (und aus ein wenig Spielraum um den Anfang einer 
Sequenz bestimmen zu können) resultieren ja die 20ms in der 
Wiederholrate.

> Bzw. würdest du ab einer bestimmten Anzahl Servos da etwas
> in der Richtung vermuten?

Da musst du schon sehr viele Servos haben

von Michael H. (michael_h45)


Lesenswert?

Dass dein Code riesig wird, dürfte daran liegen, dass du eine 
_delay_us-() "Funktion" mit Nichtkonstanten aufrufst - hab ich Recht?

Das sind nämlich keine Funktionen, sondern Makros, die _always_inline_ 
in deinen Code eingebettet werden. Sprich: bei jeder Ausführung liegt 
dieses Stückchen Code wieder im Programmspeicher.
Das bläst natürlich deine Schleife mächtig auf.

Umgehen kannst du das Ganze, indem du z.B. folgende Funktion baust:
1
void warte_10us(unsigned char wartezeit)
2
{
3
   while(wartezeit)
4
   {
5
       wartezeit--;
6
       _delay_us(10);
7
   }
8
}
Davon darfst du jetzt aber keine präzise 10us Zeitbasis erwarten, weil 
neben der eigentlichen Wartezeit z.B. noch Sprünge zur 
Schleifenabarbeitung "Rechenzeit" brauchen.

von Alex (Gast)


Lesenswert?

Hallo und schon einmal herzlichen Dank für die rege Beteiligung!

@Michael Wolf: Im Moment steuere ich die Servos über einfache schleifen 
an, die ungefähr so aussehen:
1
for (int i=0; i<50; i++)
2
  {
3
    PORTA &= ~(1<<T_1);
4
    _delay_us(19400);
5
  
6
    PORTA |= (1<<T_1);
7
    _delay_us(600);
8
  }
wobei T_1 das erste Glied des Daumens darstellt.

@Rolf Magnus: in 100er µs schritten, damit die servobewegung 
"kontinuierlich" wird, und in der delay-schleife überhaupt kleinere 
schritte als ms möglich sind. Womit (auch s.o.) deine Frage nach der 
unrühmlichen Verwendung der delay funktion klar mit einem JA beantwortet 
wäre ;-)

@Markus: Mit Rechenakt war die Rechenoperation innerhalb der 
delay-funktion (also zB _delay_ms(10+i) gemeint. Dann ist der code 
plötzlich nicht mehr 5 sonder 25% des vorhandenen flashes groß

@ Karl Heinz: Ich sehe es ein, ich komme wohl um den Timer nicht mehr 
herum. Vielen Dank für dein Codebeispiel, mal wieder genau das, was ich 
brauchte um mich reinzufriemeln! Kommt meinem Problem doch sehr nache 
(2*5 Servos).

@Michael H.: Danke für die Erklärung, ich tus nie wieder! ;)

mal sehen, wann ich wieder mehr zeit habe zum fummeln.
Viele Grüße

Alex

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Alex schrieb:
> (also zB _delay_ms(10+i) gemeint. Dann ist der code
> plötzlich nicht mehr 5 sonder 25% des vorhandenen flashes groß

Das ist genau das, was Michael H. schreibt:

Michael H. schrieb:
> Dass dein Code riesig wird, dürfte daran liegen, dass du eine
> _delay_us-() "Funktion" mit Nichtkonstanten aufrufst - hab ich Recht?

von Alex (Gast)


Lesenswert?

achso und @Michael L: Ich weiß nicht, obs da Bausätze gibt, die Hand 
habe ich in stundenlanger arbeit am pc gemesht und dann aus plaast 
nachgebaut. ist also ein unikat ;-)

von funky (Gast)


Lesenswert?

Respekt!!!

Wie hast du die Plastikteile denn geformt? Hab gelesen das man das 
Plaast bei 60° verformen kann. Hast du das alles Freihand gemacht? Oder 
hattest du irgendeine Form?

von Philipp D. (philipp9494)


Lesenswert?

Hej! Cooles Projekt! Muss schon sagen!
Wie lange beschäftigst du dich schon mit der Hand?

@funky:
Kunststoff (nicht Plastik, denn Plastik ist PVC ;)) ist erst bei höheren 
Temperaturen verformbar.
PMMA (als Plexiglas/Acrylglas bekannt) ist ca ab 100-120°C verformbar.. 
also schon etwas "unangenehm" heißer :)

MfG
Philipp9494

von Alex (Gast)


Lesenswert?

hi, danke!
die teile habe ich in formen "gedrückt" (nicht gegossen, das zeug wird 
hat ja eine recht hohe viskosität über 60°). Die Formen habe ich nach 
meinem Computermesh als Vorlage aus Modellbauholz gebaut (beschichtet 
mit TESA, das ist wirklich die beste oberfläche für plaast :D )
Das schöne am plaast ist, dass man es, wenn was schiefgegangen ist, 
einfach wieder einschmelzen oder anschmelzen (und nur lokal verändern) 
kann.

grüße

Alex

von ServoInteressierter (Gast)


Lesenswert?

Michael Wolf schrieb:
> Ich würde einen internen 16bit Timer mit Output Capture verwenden um die
> PWM zu erzeugen.
> Diese auf eine beliebige Anzahl kaskatierte 4017 Counter geben zum
> demultiplexen. Jeder Counter kann dabei 10 Servos bedienen.
> Dazu reichen 2 Pins am AVR.

Hallo, das interessiert mich sehr!

Ich moechte zwar keine Roboterhand steuern, aber 16 Servos gleichzeitig 
und weich, also nicht ruckartig! Bislang dachte ich, dies sei ein Fall 
fuer CPLDs.

Ich bin leider ein ziemlicher Anfaenger, die Methode mit den 4017ern 
erscheint mir einfacher, vor allem, weil ich noch kein VHDL kann. Kannst 
du das bitte naeher erlaeutern?

Ich habe Zweifel, ob man mit dieser Methode viele Servos gleichzeitig 
"weich" steuern kann. Sieht fuer mich so aus, als bekaeme alle 
nacheinander ihren Wert. Ist das in der Praxis dann gleichzeitig genug 
oder eher recht abgehackt?

von Karl H. (kbuchegg)


Lesenswert?

ServoInteressierter schrieb:
^
> Ich habe Zweifel, ob man mit dieser Methode viele Servos gleichzeitig
> "weich" steuern kann. Sieht fuer mich so aus, als bekaeme alle
> nacheinander ihren Wert. Ist das in der Praxis dann gleichzeitig genug

Ich würde sagen: Wenn du einen Versatz von 20 bis 30 Millisekunden in 
den Sercobewegungen wahrnehmen kannst, dann bist du Superman.

von Clemens M. (panko)


Lesenswert?

Könnte das nicht doch auf Probleme rauslaufen bei z.B. einem recht 
komplexen Bein wo wirklich für flüssig aussehende Bewegung einige Servos 
drin sind.
Bei, ich sag mal ohne nachdenken 24, Servos addiert sich ein Versatz 
dann irgendwie doch schon und ich könnte mir bei der Modellierung einer 
Bewegung vorstellen, daß das mühsahm ausgeglichen werden muss?

von Karl H. (kbuchegg)


Lesenswert?

Clemens M. schrieb:
> Könnte das nicht doch auf Probleme rauslaufen bei z.B. einem recht
> komplexen Bein wo wirklich für flüssig aussehende Bewegung einige Servos
> drin sind.
> Bei, ich sag mal ohne nachdenken 24, Servos addiert sich ein Versatz
> dann irgendwie doch schon und ich könnte mir bei der Modellierung einer
> Bewegung vorstellen, daß das mühsahm ausgeglichen werden muss?

Irgendwann ist natürlich Schluss, dass ist schon klar.
24 * 2 Millisekunden sind 48 Millisekunden, die im Extremfall 
zusammenkommen. 48 Millisekunden sind aber immer noch erst 5 Hunderstel 
Sekunden und das ist dann auch noch 'im schlimmsten Fall'.

von Arno Lainer (Gast)


Lesenswert?

Guten Tag,

ich bin soeben auf den von Ihnen verfasst Eintrag gestoßen.
Ursprünglich war ich auf der Suche nach Info´s jeglicher Art bzgl. 
Roboterarme.

Mich würde sehr interessieren welches Ende dieses Projekt gefunden hab, 
alleine schon deshalb weil ich ein Student für Biomedical Engineering 
bin.

Würde mich über eine Rückmeldung freuen,

mfg
"Ein Interessent"

*arno.lainer@gmail.com

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.