PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : DIY Servo



Der Einsteiger
22.11.2014, 11:24
Hallo, ich bin es mal wieder...
Nachdem ich hier im Wiki auf der Seite Sensorarten auf den AS5040 Winkelsensor gestoßen bin, wollte ich mir damit ein großes Servo selber bauen.
Also habe ich mir einen RB-35 Getriebemotor geschnappt und diesen über zwei Zahnräder mit der Welle (mit Magnet) des AS5040 verbunden.
Nun kann ich schon Werte des Sensors auf dem PC ausgeben. :) (Das war ja mal ne Arbeit den So16 per hand zu löten :D )

Jetzt folgt jedoch erst der schwierige Teil mit der Regelung !

Dazu habe ich mich im Wiki auf der Seite Regelungstechnik belesen und habe danach erstmal mit einem PD-Regler angefangen. Dieser funktioniert jetzt auch schon ganz ok (ohne Schwingung um die Nulllage) doch überschreitet mein Motor die Nulllage dank Massenträgheit (glaube ich) noch um etwa 10° und bleibt dann da wegen nur geringer PWM stehen.

Dann habe ich den PID-Regler ausprobiert und musste feststellen, dass dieser zwar versucht, die Nullposition anzusteuern, jedoch endlos um diese schwingt.

Meine Frage ist nun, wie ich es schaffen kann, dass mein PID-Regler doch funktioniert (also die Massenträgheit berücksichtigt) ?
Außerdem : wie macht ihr das mit der Regelung, wenn ihr große Motoren benutzt ?

Ich hoffe ihr Profis könnt mir helfen :)

Hier ist mein Code (Arduino) (nur der Teil mit dem Regler):

int pwm_pin = 3; //Motor pwm
int in_1 = 4; //Motor Richtung
int in_2 = 5; //Motor Richtung
String msg;
char a;
int soll_wert;
int ist_wert;
int e;
float esum;
int ealt;
float y;
float r;
float s;
int yaus;
int y_max = 255;
int y_min = 0;
int s_max = 50;
float Kp = 1.0; //P
float Ki = 0.5; //I0,001
float Kd = 1.05; //D0,01
float Ta = 1.0; //Abtast
int esum_max = 150;


void regler() {

if(soll_wert >= 10) {
if(soll_wert <= 350) {
e = soll_wert - ist_wert;
e = abs(e);
esum = esum + e;
s = float(esum) * Ta;
s = s * Ki;
if(int(s) > s_max) {
s = float(s_max);
}
r = float(e) - float(ealt);
r = r / Ta;
r = r * Kd;
if(esum > esum_max) {
esum = esum_max;
}

y = Kp * float(e);
y = y + s;
y = y + r;

yaus = int(y);
Serial.println(y);
ealt = e;

if(yaus > y_max) {
yaus = y_max;
}

if(yaus < y_min) {
yaus = y_min;
}

if(ist_wert > soll_wert) {
digitalWrite(in_1, HIGH);
digitalWrite(in_2, LOW);
analogWrite(pwm_pin, yaus);
}

if(ist_wert < soll_wert) {
digitalWrite(in_1, LOW);
digitalWrite(in_2, HIGH);
analogWrite(pwm_pin, yaus);
}

if(ist_wert == soll_wert) {
digitalWrite(in_1, LOW);
digitalWrite(in_2, LOW);
analogWrite(pwm_pin, 0);
esum = 0;
}

}
}



}

Geistesblitz
22.11.2014, 12:13
Hmm, also es gibt zwei nichtlineare Effekte, die bei DC-Getriebemotoren zum Schwingen führen können. Das ist einmal die Anlaufspannung, die der Motor braucht, um sich zu bewegen und sonst das Getriebespiel. Beides lässt sich nur schwierig durch die Regelung ausgleichen. Bei der Anlaufspannung geht es eigentlch noch, ich mach das so, dass die PWM für den Motor nicht bei 0 losgeht, sondern schon bei einem gewissen Wert. Wenn du ein Labornetzteil hast, kannst du die Anlaufspannung ganz gut ermitteln, ansonsten musst du eben ein wenig herumprobieren. Das Getriebespiel ist allerdings kaum zu bändigen, daher ist in Präzisionsanwendungen auch eine Spielfreiheit auch so wichtig, selbst wenn direkt gemessen wird.

Ansonsten, nach welchem Verfahren hast du den PID-Regler ausgelegt?

Der Einsteiger
22.11.2014, 12:25
daher ist in Präzisionsanwendungen auch eine Spielfreiheit auch so wichtig, selbst wenn direkt gemessen wird.
Und wie wird das dann in Modellbauservos gelöst ? Die haben ja auch ein Spiel...


Ansonsten, nach welchem Verfahren hast du den PID-Regler ausgelegt?
probieren :D Also ich habe alles auf Null und dann Kp und Kd probiert bis die ganz gut gingen. Dann habe ich Ki hinzugefügt.
Bevor ich Ki eingestellt hatte ging es ja auch ganz gut. Der Motor kam nur eben nicht auf die Position.

Wie muss man denn Ta ca. dimensionieren ?

oberallgeier
22.11.2014, 13:02
Das Schwingen um den Sollwert kann durch ein Band mit wenig Abstand um die Sollposition vermieden werden, innerhalb dem der I-Anteil nicht aktiviert wird.


... Wie muss man denn Ta ca. dimensionieren ?Kurz. Für 8bittige Controller sind 10 ms ein guter Wert, aber mit Deinem
... float Kp = 1.0; //P ... float Ki = 0.5; //I0,001 ... float Kd = 1.05; //D0,01 ...Floatingpointtiming wird das eher nix. Wie lange dauert denn bei Dir ein Durchgang zum Rechnen eines Regelungswertes?

Ich betreibe meine Regelung(en), meist zwei getrennte Rechnungen für zwei getrennte Motoren, mit 100 Hz, rechne aber nie FP. Mit meiner Integerrechnung bekomme ich dabei recht gute Resultate.

Im Übrigen sind die Ratschläge von waste z.B. hier (http://rn-wissen.de/wiki/index.php/Regelungstechnik#Dimensionierung_nach_Einstellrege ln) ziemlich gut; danach hatte ich fast alles ausgelegt.

Viel Erfolg.

Der Einsteiger
22.11.2014, 13:51
Das Schwingen um den Sollwert kann durch ein Band mit wenig Abstand um die Sollposition vermieden werden, innerhalb dem der I-Anteil nicht aktiviert wird.Dann würde doch meine Ungenauigkeit bleiben, oder ?


Wie lange dauert denn bei Dir ein Durchgang zum Rechnen eines Regelungswertes?
Wie soll ich das denn bestimmen ?


Im Übrigen sind die Ratschläge von waste z.B. hier ziemlich gut; danach hatte ich fast alles ausgelegt.
Wie soll man denn die Sprungantwort aufzeichnen ?

oberallgeier
22.11.2014, 14:26
Dann würde doch meine Ungenauigkeit bleiben, oder ...Was ist Dir denn lieber? Dauerndes Schwingen um den Sollwert oder eine minimale, bleibende Abweichung? Die Regelungs tech nik überlässt DIR die Wahl.


... Wie soll ich das denn bestimmen ? ...Ein 1000er-Loop der nur die Regelschleife enthält (evtl. gaaanz knappe Variationen der Werte), eine Stoppuhr . . . das ist die Warmduschervariante. In der Hoffnung, dass die arduinoumgebung neben dem *hex-File auch eine *.lls erzeugt: die enthält den kompletten Maschinencode. Da braucht man nur die Maschinenzyklen der einzelnen Befehle zusammenzuzählen . . .


... Wie soll man denn die Sprungantwort aufzeichnen ?Starte Aufzeichnung der aktuellen Motorposition in schnellem Takt (ich hatte das im Takt von 1 bis 2,irgendwas Millisekunden gemacht - jeweils gleiche Abstände). Starte Motor mit einem mittleren oder hohen PWM-Wert. Beende Aufzeichnung - der Motor sollte dann annähernd oder vollständig seine Enddrehzahl erreicht haben. Daten ausgeben - z.B. über Terminal und auswerten. Auswertung hatte ich üblicherweise mit einer Tabellenkalkulation gemacht. Resultat sind dann Motorzeitkonstante und Anhaltswerte zur Reglereinstellung. Sinn macht das, auch bei einem Servo, aber nicht nur im Leerlauf, bei meinen Antrieben hatte ich die Sprungantwort für die endgültige Reglerauslegung immer im Endausbau bestimmt.

Geistesblitz
22.11.2014, 14:37
Wegen der Rechenzeit: ich hatte mir da mal geholfen, indem ich ganz zu Anfang der Schleife einen Timer auf 0 gesetzt habe und am Ende der Schleife dann den Timerwert auf einem Display ausgeben lassen. Dabei muss man darauf achten, dass der Timer so eingestellt ist, dass er nicht während der Schleife schon überläuft, sonst bekommt man unbrauchbare Werte. Über Taktfrequenz und Prescaler kann man dann auf die tatsächliche Zeit schließen. Natürlich kannst du auch etwas reinbauen, was dir gleich den Timerwert in die Rechenzeit umrechnet, zwecks Anschaulichkeit. Ich würd dir aber auch empfehlen, auf Integer-Rechnung umzusteigen, da das sehr viel schneller ist. Dafür brauchst nur die Zahlen, mit denen gerechnet wird, in einen brauchbaren Bereich transformieren. Der gemessene Winkel und die PWM sind ja meist eh Integer-Werte. Musst nur aufpassen bei zB. Multiplikation, da 8bit*8bit einen 16bit-Wert ergibt.

Rabenauge
22.11.2014, 15:58
Beim Arduino ist die Zeitmessung ganz einfach:
http://arduino.cc/en/pmwiki.php?n=Reference/Micros

Damit kann man sich den, von Geistesblitz beschriebenen Zeit-Zähler ganz leicht basteln, indem man
-vor dem interessanten Teil die aktuellen Microsekunden speichert
-direkt nach dem interessierenden Codeteil die Microsekunden ausliest
und beides voneinander abzieht.


unsigned long startmicros=micros(); // StartZeit
//hier Dinge tun, deren Dauer wir wissen möchten


unsigned long endmicros=micros(); // EndZeit
//und dann bei passender Gelegenheit das Ergebnis ausspucken:

Serial.println(endmicros-startmicros,DEC); // Zeitspanne ausgeben



Was den PID angeht: richtig. Ohne I-anteil wirst du nie wirklich auf Null landen.
Der Trick dürfte es sein, den Bereich, indem kein I mehr verwendet wird, so knapp zu bemessen, dass das Ergebnis noch zufriedenstellend ist (also: so knapp wie möglich, aber eben so dass es läuft).
Auch Modellbauservos fahren nicht _auf den Punkt_, die Abweichung ist lediglich so klein, dass man sie nicht bemerkt (bei den besseren jedenfalls, die Billigdinger machen wegen ein, zwei Grad keinen Wirbel).

Wenn du das Problem hast, dauernd über den Sollbereich drüber zu rauschen, dann benutz doch mal in der Gegend um den Sollbereich zahmere Regelparameter.
So wie das hier empfohlen wird: http://playground.arduino.cc/Code/PIDLibraryAdaptiveTuningsExample
Das lässt sich auch mit beliebig vielen Stufen aufbauen, und es können jeweils alle drei Grössen einzeln angepasst werden.

Auch empirisch geht sowas: ich hab mir seinerzeit kurzerhand ein paar Potis temporär hinzugefügt, um P,I und D "live" verstellen zu können, so erreicht man schnell erst mal "brauchbare" Wertebereiche.

Der Einsteiger
22.11.2014, 16:23
ok, danke für die Antworten :)
Rabenauge, das mit den Micros war ein guter Vorschlag.
Also, ich habe jetzt mal gemessen: Für einen Reglerdurchlauf brauche ich rund 60micros und für einen kompletten Loop (mit Sensorauswertung) rund 11064micros (nein, ich habe mich nicht verschrieben ! )......
Dazu muss ich sagen, dass ich die Berechnung wieder auf integer zurückgestellt habe.

Zu dem Motor muss ich sagen, dass dieser, wenn ich ihn direkt mit Spannung versorge und dann abtrenne, noch mindestens 1,5 sec ohne Strom weiterdreht. Auch läuft er überhaupt erst bei 1/4 PWM an :D

Rabenauge
23.11.2014, 10:25
Sind doch gar keine so üblen Werte. Dann dauert eine Schleife 11 ms- das finde ich durchaus ok.
Bedenke auch, dass der Motor ja erst mal reagieren muss, rein mechanisch. In 11 Millisekunden wird da so viel nicht passieren.
Die Arduino-PID-Lib arbeitet standardmässig, so glaub ich, mit 200ms Zykluszeiten....

Der Einsteiger
27.11.2014, 19:43
So, nach ein paar Stunden frustrierender Tests, bin ich nun zu dem Entschluss gekommen, dass ich einfach mit dem RB-35 nicht weiterkommen werde, da dieser eine sehr große Massenträgheit besitzt und deshalb nicht genau anzusteuern ist (jedenfalls für mich)...

Da ich jedoch dieses Thema sehr spannend finde gebe ich so schnell nicht auf.
Deshalb habe ich mir jetzt ein billiges Servo aus der Bastelkiste geschnappt und das Poti entfernt. Dann habe ich mir eine Adapterwelle gedreht, welche auf der einen Seite eine Abflachung besitzt, sodass die Welle genau in das Abtriebszahnrad passt, in dem vorher das Poti steckte. Auf der anderen Seite ragt nun ein Wellenstummel mit einem Magneten an der Spitze hinaus ( Dieser Aufbau soll nur zu Testzwecken dienen...).
Nun konnte ich bereits mit einem abgewandelten Programm akzeptable Ergebnisse erzielen.

Doch stellt sich für mich jetzt eine andere Frage. Wie kann ich verhindern, dass mein Servo spinnt, sobald es über Null fährt (denn es bekommt ja dann einen ungewollten Sprung...) ?

Hier ist noch mal mein neuer Code (wieder mit float, da es für mich keinen Unterschied gemacht hat)

int pwm_pin = 3; //Motor pwm
int in_1 = 4; //Motor Richtung
int in_2 = 5; //Motor Richtung
String msg;
char a;
int soll_wert;
int ist_wert;
int e;
int esum;
int ealt;
int yaus;
int y_max = 45;
int y_min = 0;
int esum_max = 700;

float y;
float i;
float d;
float Kp = 2.0; //P
float Ki = 3.0; //I
float Kd = 0.5; //D
float Ta = 0.02; //Abtast

void regler() {

if(soll_wert >= 10) {
if(soll_wert <= 350) {
e = soll_wert - ist_wert;
e = abs(e);
esum = esum + e;
if(esum > esum_max) {
esum = esum_max;
}

i = float(esum) * Ta;
i = i * Ki;

d = float(e) - float(ealt);
d = d / Ta;
d = d * Kd;

y = Kp * e;
y = y + i;
y = y + d;

yaus = int(y);
//Serial.println(y);
ealt = e;

if(yaus > y_max) {
yaus = y_max;
}

if(yaus < y_min) {
yaus = y_min;
}

if(ist_wert > soll_wert) {
digitalWrite(in_1, HIGH);
digitalWrite(in_2, LOW);
analogWrite(pwm_pin, yaus);
}

if(ist_wert < soll_wert) {
digitalWrite(in_1, LOW);
digitalWrite(in_2, HIGH);
analogWrite(pwm_pin, yaus);
}

if(ist_wert == soll_wert) {
digitalWrite(in_1, LOW);
digitalWrite(in_2, LOW);
analogWrite(pwm_pin, 0);
esum = 0;
ealt = 0;
}

}
}



}

Ich hoffe ihr versteht meine Frage und kennt eine Lösung :)
Der Einsteiger

Rabenauge
27.11.2014, 20:15
Weiss ehrlich gesagt nicht, wer da auf das Servo springt. Hat es was mit deinem Magneten zu tun?
Mit den Dingern hab ich noch nix gemacht (auch sowas, dass ich schon lange vor hab).
Du zählst doch da Impulse, oder?
Dann musst du doch wissen, wenn ne volle Umdrehung um ist, und fängst dann halt einfach von vorn an zu zählen (ggf. kannst du ja _dieses_ Ereignis auch noch speichern, dann weisst du später auch, wieviele Umdrehungen es waren).

Der Einsteiger
27.11.2014, 20:49
Weiss ehrlich gesagt nicht, wer da auf das Servo springt. Hat es was mit deinem Magneten zu tun?
Mit den Dingern hab ich noch nix gemacht (auch sowas, dass ich schon lange vor hab).
Du zählst doch da Impulse, oder?
Dann musst du doch wissen, wenn ne volle Umdrehung um ist, und fängst dann halt einfach von vorn an zu zählen (ggf. kannst du ja _dieses_ Ereignis auch noch speichern, dann weisst du später auch, wieviele Umdrehungen es waren).

Ne, es ist ein bisschen anders... Also der AS5040 wird über den ssi Bus (glaube, der heißt so) ausgewertet. Diesen Programmteil habe ich nur von Google übernommen...:)
Also am Ende werden mir auf jeden Fall 0-360° ausgegeben (das funktioniert ja auch prima). Wenn ich also weiter drehe fängt er wieder mit Null an und so weiter...

Und genau das ist das spinnen, was ich meinte. Nehmen wir als Beispiel an ich möchte von 150° auf 1° positionieren. Dann kann es durch Schwingung und so weiter dazu kommen, dass meine Achse nun ein bisschen weiter dreht und dann 355° anzeigt wird. In diesem Fall dreht dann nämlich mein Servo nicht ein kleines Stück zurück, sondern macht fast eine ganze Umdrehung...
Wie kann ich das nun beheben (im Programmcode) ??

Geistesblitz
27.11.2014, 21:12
Hehe, damit plag ich mich auch gerade herum. Ich versuch das so zu lösen, dass ich nebenher in einer weiteren Variable die Umdrehungen zähle. Dazu berechne ich die Differenz zwischen dem aktuellen und dem vorherigen Winkel und wenn dieser über 180° ist, wird eine Umdrehung raufgezählt und bei kleiner -180° einen runter. Dann kommt alles in eine neue Variable, meinetwegen
Winkel=360*Umdrehungen+gemessener Winkel
Du dürftest allerdings nicht wirklich ° herausbekommen, sondern Werte von 0 bis 4096, oder? Weiß gerade nicht, ob der AS5040 12-Bit Auflösung hatte. Ich hab jedenfalls bei mir einen AS5048A drin, der hat 14 Bit, also muss ich da mit 16384 pro Umdrehung rechnen. Allerdings haut das bei mir mit den ganzen Datentypen noch nicht so ganz hin.

Und dein Getriebemotor hat ein zu hohes Trägheitsmoment? Ich hab bei mir auch einen Motor im RB-35-Format drin (angegeben mit 76 U/min im Leerlauf bei 12V) und jetzt, wo ich den PID-Regler ganz gut abgestimmt bekommen hab, ist zumindest mit dem Auge kaum noch irgendwelches Schwingen zu erkennen und die Bewegung ist auch ziemlich fix. Beim PID-Regler muss man übrigens auch teilweise ganz schön auf die Wertebereiche der Variablen achten, gerade der Integratoranteil macht gerne mal Mist.

Der Einsteiger
27.11.2014, 21:44
Hehe, damit plag ich mich auch gerade herum. Ich versuch das so zu lösen, dass ich nebenher in einer weiteren Variable die Umdrehungen zähle. Dazu berechne ich die Differenz zwischen dem aktuellen und dem vorherigen Winkel und wenn dieser über 180° ist, wird eine Umdrehung raufgezählt und bei kleiner -180° einen runter. Dann kommt alles in eine neue Variable, meinetwegen
Na das ist doch schön zu hören :)
Werde ich mal Morgen testen.

Nein, ich bekomme natürlich eigentlich 0 bis 1023 raus. Aber das Arduino Beispielprogramm macht halt daraus gleich ganze Grad.

Was haut denn bei dir mit den Datetypen nicht so hin ?

Naja, ob das nun wirklich am Motor lag, kann ich nicht genau sagen. Es kann natürlich auch an meinen mangelnden Programmierkünsten liegen. :)
Dürfte ich vielleicht dein Programm sehen? Das würde mich sehr interessieren...
Hast du auch eine Seite, wo mehr über dein Projekt steht, oder youtube ?

Geistesblitz
27.11.2014, 23:17
Leider hab ich keine Seite und bei Youtube hab ich schon ewig nix hochgeladen. An sich wär das aber was, was ich mal gebrauchen könnte. Aber erstmal egal.

Hmm, wenn du mit dem Code was anfangen kannst ... ist in Bascom geschrieben :D

$regfile = "m32def.dat"
$framesize = 32
$swstack = 32
$hwstack = 32
$crystal = 16000000
$baud = 9600

Declare Sub Serial0charmatch()

Config Spi = Hard , Interrupt = Off , Data Order = Msb , Master = Yes , Polarity = Low , Phase = 1 , Clockrate = 128

Config Adc = Single , Prescaler = Auto , Reference = Avcc

Config Timer1 = Pwm , Pwm = 10 , Compare A Pwm = Clear Up , Compare B Pwm = Clear Up , Prescale = 1
Config Timer0 = Timer , Prescale = 1024

On Timer0 Isr0

Config Lcdpin = Pin , Db4 = Portc.3 , Db5 = Portc.2 , Db6 = Portc.1 , _
Db7 = Portc.0 , E = Portc.4 , Rs = Portc.5
Config Lcd = 20 * 4
Cursor Off
Cls
Config Porta.0 = Input
Config Porta.1 = Output
Config Porta.2 = Output
Config Portb.3 = Output
Portb.3 = 1

Spiinit

Config Serialin = Buffered , Size = 30 , Bytematch = 13


Dim Incoming_str As String * 4
Dim Angle As Integer
Dim Angle_vor As Integer
Dim Angle_low As Byte At Angle Overlay
Dim Angle_high As Byte At Angle + 1 Overlay
Dim Anglereg(2) As Byte
Dim Errorreg(2) As Byte
Dim Adcwert As Long
Dim Tast As Integer
Dim Temp As Long
Dim E As Long
Dim E_vor As Long
Dim E_int As Long
Dim De As Long
Dim Count As Byte
Dim Pos As Long
Dim Turns As Integer
Turns = 0
E_vor = 0
E_int = 0
De = 0
Count = 0

Anglereg(1) = &HFF
Anglereg(2) = &HFF
Errorreg(1) = &H40
Errorreg(2) = &H01

Wait 1

Porta.1 = 1
Porta.2 = 1

Portb.3 = 0
Waitus 10
'Angle_high = Spimove(errorreg(1) , 1)
'Angle_low = Spimove(errorreg(2) , 1)
'Waitus 10
Angle_high = Spimove(anglereg(1) , 1)
Angle_low = Spimove(anglereg(2) , 1)
Waitus 10
Portb.3 = 1
Angle_high = Angle_high And &B00111111
Adcwert = Angle

Enable Interrupts
Enable Timer0

Waitms 500

Do
Count = Count + 1
If Count = 20 Then
Count = 0
If Adcwert = 1000 Then
Adcwert = 17384
Else
Adcwert = 1000
End If
End If
Cls
Locate 1 , 1
Lcd Angle 'anzeigen Istwinkel
Locate 2 , 1
Lcd Adcwert 'anzeigen Sollwinkel
Locate 2 , 10
Lcd E
Locate 1 , 10
Lcd E_int
Locate 3 , 1
Lcd Tast 'anzeigen PWM-Wert
Locate 4 , 10
Lcd Pos
Locate 4 , 1
Lcd Turns
Waitms 100
Loop

Sub Serial0charmatch()
Input Incoming_str Noecho
Print Angle
End Sub

Isr0:
Load Timer0 , 30
Portb.3 = 0
Waitus 5
Angle_high = Spimove(anglereg(1) , 1)
Angle_low = Spimove(anglereg(2) , 1)
Waitus 5
Portb.3 = 1
Angle_high = Angle_high And &B00111111

Temp = Angle - Angle_vor
If Temp < -8192 Then
Turns = Turns + 1
Elseif Temp > 8192 Then
Turns = Turns - 1
End If
Pos = 16384 * Turns
Pos = Pos + Angle


Angle_vor = Angle

' Start Adc
' Adcwert = Getadc(0)
' Adcwert = Adcwert * 16

E = Adcwert - Pos


De = E - E_vor
If De > 400 Then
De = 400
Elseif De < -400 Then
De = -400
End If
E_vor = E
Tast = E * 2
Temp = E_int / 128
Tast = Tast + Temp
Temp = De * 40
Tast = Tast + Temp
If Tast > 0 Then
Porta.1 = 1
Porta.2 = 0
Else
Porta.1 = 0
Porta.2 = 1
End If
Tast = Abs(tast)
Tast = Tast + 400
If Tast > 1023 Then
Tast = 1023
Else
E_int = E_int + E
End If
Compare1a = Tast
Return

Die Probleme, die ich da derzeit habe, hat mit der Größe der Zahlen zu tun. Der Sensor gibt wie gesagt 14 Bit aus, mit Integer kann ich also gerade einmal 4 Umdrehungen abdecken (wobei das je nach Anwendung auch schon reichen kann). Nagut, wöllte ich die Auflösung nicht nutzen, könnte ich ganz leicht mittels Bitshift auf weniger Bit runterskalieren, aber ich wills ja gerne so präzise wie möglich. Jedenfalls hab ich damit erst recht Probleme beim Integrator, da dieser ja in jedem Reglertakt die Abweichung aufaddiert. Bei einem relativ großen Sprung kommt da wirklich viel zusammen und wenn man dann versucht, mit diesem riesigen Wert weiterzurechnen, bekommt man erst recht Probleme. Da verschwinden dann gerne mal Bits und der Regler dreht durch. Hatte zwischendurch schon paar Mal Versionen gehabt, da hat der Motor nicht aufgehört zu drehen. Jedenfalls ist mein nächster Schritt, mal eine vernünftige Kommunikation mit dem Rechner dazu zu basteln, um dann mal den Motor ordentlich modellieren und simulieren zu können. Ich arbeite bei Regelungstechnik doch lieber mit Übertragungsfunktionen, als nur herum zu probieren. Bei meinem letzten Experiment hatte die Verwendung einer Vorsteuerung schon eine enorme Verbesserung gebracht. Die sorgt nämlich dafür, dass der Motor hauptsächlich den Sollwerten entsprechend so angesteuert wird, dass er schon ohne Regelung etwa die Sollbahn abfahren würde. Dann braucht der Regler nur noch die dabei auftretende Abweichung ausregeln.

Der Einsteiger
02.12.2014, 19:54
So, ich bin es mal wieder. Hatte leider die Tage doch keine Zeit. Also jetzt:

Erstmal vielen Dank an dich Geistesblitz :) Jetzt kann mein Servo endlich 360° ohne Probleme drehen ....
Nur die Regelung muss ich noch ein bisschen verbessern, aber erstmal egal.


Hmm, wenn du mit dem Code was anfangen kannst ... ist in Bascom geschrieben
Also Bascom benutze ich normalerweise auch, doch steige ich durch dein Programm trotzdem nicht durch (im Thema Software bin ich eben noch ein Einsteiger :D ).
Ist das Programm komplett mit Regelung, oder fehlt da noch was ?


Der Sensor gibt wie gesagt 14 Bit aus, mit Integer kann ich also gerade einmal 4 Umdrehungen abdecken (wobei das je nach Anwendung auch schon reichen kann).
Also möchtest du eher ein endlos Servo bauen (vgl. mit Schrittmotor...) ?

Mein Servo soll übrigens eher eine Art Eigenbau eines Dynamixels sein. Aber das nimmt sich ja nicht so viel.


Bei meinem letzten Experiment hatte die Verwendung einer Vorsteuerung schon eine enorme Verbesserung gebracht. Die sorgt nämlich dafür, dass der Motor hauptsächlich den Sollwerten entsprechend so angesteuert wird, dass er schon ohne Regelung etwa die Sollbahn abfahren würde. Dann braucht der Regler nur noch die dabei auftretende Abweichung ausregeln.
Wie genau soll das mit einer Vorsteuerung funktionieren ?

Geistesblitz
03.12.2014, 18:04
Naja, man kann die Dynamik eines Elektromotors mittels Differentialgleichungen beschreiben, und wenn man die passend umstellt, kann man aus dem Zeitverlauf von Sollposition, -geschwindigkeit und -beschleunigung den Spannungsverlauf berechnen, der genau zur gewünschten Motorbewegung führt. Die Regelung ist dann nur noch zum Ausgleichen von Modellabweichungen und Störungen da. Bringt allerdings nur etwas, wenn sich die Sollposition nicht nur sprunghaft ändert, sondern einen halbwegs glatten Verlauf zeigt. Wenn man nämlich einen Sprung versucht zu differenzieren, bekommt man nur einen kurzen Puls. Da geht der Motor durch die Regelung eh in die Begrenzung. Der Sollwertverlauf sollte also so geschaffen werden, dass der Motor da auch hinterherkommt, ich möchte eben eine gute Folgeregelung erzielen. Für das Anfahren von diskreten Positionen reicht der Regler an sich aus.

Ja, der Regler befindet sich in der ISR. Da wird im Prinzip nur der Winkel (Angle) mittels SPI vom Sensor eingelesen, die Position (Pos) für eben mehrere Umdrehungen berechnet und mit dem Sollwert (Adcwert) verglichen. Aus dem Vergleich resultiert die Abweichung (E), von der dann Differenz (De) und Integral (E_int) gebildet werden, jeweils mit einer Verstärkung multipliziert und in der Stellgröße (Tast) gespeichert. Anschließend noch ein wenig Anpassung wegen der Begrenzung, da Tast eben größer werden kann als die maximalen 1023 für die PWM.

Das Programm ist zugegebenermaßen nicht wirklich übersichtlich und es hat sich seit ich es hier reingestellt hab auch schon wieder an vielen Stellen verändert. Wahrscheinlich schreib ich es nochmal neu, wenn alles weitestgehend passt. Die Vorsteuerung hab ich zwar schon versucht zu implementieren, allerdings bekomm ich das Modell des Motors nicht zufriedenstellend aufgestellt.

Der Einsteiger
01.01.2015, 18:46
Hi
Pünktlich zum Jahresbeginn gibt es von mir ein Update :D
Ich habe es in den Ferien endlich mal wieder geschafft, etwas für mein Hobby zu tun....
Dabei herausgekommen ist ein neues und auch viel stärkeres Servo. :


https://www.youtube.com/watch?v=VZ11tBXkl0s

Wer sich für den Code interessiert, der jetzt auch schon ganz gut funktioniert (für Arduino):

const int ledPin = 13; //LED connected to digital pin 13
const int clockPin = 7; //output to clock
const int CSnPin = 6; //output to chip select
const int inputPin = 2; //read AS5040

int inputstream = 0; //one bit read from pin
long packeddata = 0; //two bytes concatenated from inputstream
long angle = 0; //holds processed angle value
long anglemask = 65472; //0x1111111111000000: mask to obtain first 10 digits with position info
long statusmask = 63; //0x000000000111111; mask to obtain last 6 digits containing status info
long statusbits; //holds status/error information
int DECn; //bit holding decreasing magnet field error data
int INCn; //bit holding increasing magnet field error data
int OCF; //bit holding startup-valid bit
int COF; //bit holding cordic DSP processing error data
int LIN; //bit holding magnet field displacement error data
int debug = 1; //SET THIS TO 0 TO DISABLE PRINTING OF ERROR CODES
int shortdelay = 80; // this is the microseconds of delay in the data clock
int longdelay = 8; // this is the milliseconds between readings


int pwm_pin = 3; //Motor pwm
int in_1 = 4; //Motor Richtung
int in_2 = 5; //Motor Richtung
String msg;
char a;
int soll_wert;
int ist_wert;
int e;
int Winkel;
int Winkel_alt;
int Winkel_abweichung;
int Um;
int esum;
int ealt;
int yaus;
int y_max = 128;
int y_min = 0;
int esum_max = 700;

float y;
float i;
float d;
float Kp = 2.0; //P
float Ki = 3.0; //I
float Kd = 0.5; //D
float Ta = 0.02; //Abtast

void setup()
{
Serial.begin(9600);
pinMode(ledPin, OUTPUT); // visual signal of I/O to chip
pinMode(clockPin, OUTPUT); // SCK
pinMode(CSnPin, OUTPUT); // CSn -- has to toggle high and low to signal chip to start data transfer
pinMode(inputPin, INPUT); // SDA

pinMode(in_1, OUTPUT);
pinMode(in_2, OUTPUT);
pinMode(pwm_pin, OUTPUT);

digitalWrite(in_1, HIGH);
digitalWrite(in_2, LOW);
analogWrite(pwm_pin, 0);

// CSn needs to cycle from high to low to initiate transfer. Then clock cycles. As it goes high
// again, data will appear on sda
digitalWrite(CSnPin, HIGH); // CSn high
digitalWrite(clockPin, HIGH); // CLK high
delay(longdelay);
digitalWrite(ledPin, HIGH); // signal start of transfer with LED
digitalWrite(CSnPin, LOW); // CSn low: start of transfer
delayMicroseconds(shortdelay); // delay for chip -- 1000x as long as it needs to be
digitalWrite(clockPin, LOW); // CLK goes low: start clocking
delayMicroseconds(shortdelay); // hold low for 10 ms
for (int x=0; x <16; x++) // clock signal, 16 transitions, output to clock pin
{
digitalWrite(clockPin, HIGH); //clock goes high
delayMicroseconds(shortdelay); // wait 10ms
inputstream =digitalRead(inputPin); // read one bit of data from pin
//Serial.print(inputstream, DEC);
packeddata = ((packeddata << 1) + inputstream);// left-shift summing variable, add pin value
digitalWrite(clockPin, LOW);
delayMicroseconds(shortdelay); // end of one clock cycle
}
// end of entire clock cycle
//Serial.println(" ");
digitalWrite(ledPin, LOW); // signal end of transmission
// lots of diagnostics for verifying bitwise operations
//Serial.print("packed:");
//Serial.println(packeddata,DEC);
//Serial.print("pack bin: ");
// Serial.println(packeddata,BIN);
angle = packeddata & anglemask; // mask rightmost 6 digits of packeddata to zero, into angle.
//Serial.print("mask: ");
//Serial.println(anglemask, BIN);
//Serial.print("bin angle:");
//Serial.println(angle, BIN);
//Serial.print("angle: ");
//Serial.println(angle, DEC);
angle = (angle >> 6); // shift 16-digit angle right 6 digits to form 10-digit value
//Serial.print("angleshft:");
//Serial.println(angle, BIN);
//Serial.print("angledec: ");
//Serial.println(angle, DEC);
angle = angle * 0.3515; // angle * (360/1024) == actual degrees
//Serial.print("angle: "); // and, finally, print it.


Winkel_alt = int(angle);

//Serial.println("--------------------");
//Serial.print("raw: "); // this was the prefix for the bit-by-bit diag output inside the loop.
if (debug)
{
statusbits = packeddata & statusmask;
DECn = statusbits & 2; // goes high if magnet moved away from IC
INCn = statusbits & 4; // goes high if magnet moved towards IC
LIN = statusbits & 8; // goes high for linearity alarm
COF = statusbits & 16; // goes high for cordic overflow: data invalid
OCF = statusbits & 32; // this is 1 when the chip startup is finished.
if (DECn && INCn) { Serial.println("magnet moved out of range"); }
else
{
if (DECn) { Serial.println("magnet moved away from chip"); }
if (INCn) { Serial.println("magnet moved towards chip"); }
}
if (LIN) { Serial.println("linearity alarm: magnet misaligned? Data questionable."); }
if (COF) { Serial.println("cordic overflow: magnet misaligned? Data invalid."); }
}

packeddata = 0; // reset both variables to zero so they don't just accumulate
angle = 0;
}







void loop()
{

//unsigned long startmicros=micros();

if(Serial.available() > 0)
{
msg = "";
while(Serial.available() > 0){
a = Serial.read();
msg = msg + String(a);
delay(250);
}
soll_wert = msg.toInt();
Serial.println(soll_wert);
}


// CSn needs to cycle from high to low to initiate transfer. Then clock cycles. As it goes high
// again, data will appear on sda
digitalWrite(CSnPin, HIGH); // CSn high
digitalWrite(clockPin, HIGH); // CLK high
delay(longdelay);
digitalWrite(ledPin, HIGH); // signal start of transfer with LED
digitalWrite(CSnPin, LOW); // CSn low: start of transfer
delayMicroseconds(shortdelay); // delay for chip -- 1000x as long as it needs to be
digitalWrite(clockPin, LOW); // CLK goes low: start clocking
delayMicroseconds(shortdelay); // hold low for 10 ms
for (int x=0; x <16; x++) // clock signal, 16 transitions, output to clock pin
{
digitalWrite(clockPin, HIGH); //clock goes high
delayMicroseconds(shortdelay); // wait 10ms
inputstream =digitalRead(inputPin); // read one bit of data from pin
//Serial.print(inputstream, DEC);
packeddata = ((packeddata << 1) + inputstream);// left-shift summing variable, add pin value
digitalWrite(clockPin, LOW);
delayMicroseconds(shortdelay); // end of one clock cycle
}
// end of entire clock cycle
//Serial.println(" ");
digitalWrite(ledPin, LOW); // signal end of transmission
// lots of diagnostics for verifying bitwise operations
//Serial.print("packed:");
//Serial.println(packeddata,DEC);
//Serial.print("pack bin: ");
// Serial.println(packeddata,BIN);
angle = packeddata & anglemask; // mask rightmost 6 digits of packeddata to zero, into angle.
//Serial.print("mask: ");
//Serial.println(anglemask, BIN);
//Serial.print("bin angle:");
//Serial.println(angle, BIN);
//Serial.print("angle: ");
//Serial.println(angle, DEC);
angle = (angle >> 6); // shift 16-digit angle right 6 digits to form 10-digit value
//Serial.print("angleshft:");
//Serial.println(angle, BIN);
//Serial.print("angledec: ");
//Serial.println(angle, DEC);
angle = angle * 0.3515; // angle * (360/1024) == actual degrees
//Serial.print("angle: "); // and, finally, print it.


//Serial.println(angle, DEC);
Winkel = int(angle);

//Serial.println("--------------------");
//Serial.print("raw: "); // this was the prefix for the bit-by-bit diag output inside the loop.
if (debug)
{
statusbits = packeddata & statusmask;
DECn = statusbits & 2; // goes high if magnet moved away from IC
INCn = statusbits & 4; // goes high if magnet moved towards IC
LIN = statusbits & 8; // goes high for linearity alarm
COF = statusbits & 16; // goes high for cordic overflow: data invalid
OCF = statusbits & 32; // this is 1 when the chip startup is finished.
if (DECn && INCn) { Serial.println("magnet moved out of range"); }
else
{
if (DECn) { Serial.println("magnet moved away from chip"); }
if (INCn) { Serial.println("magnet moved towards chip"); }
}
if (LIN) { Serial.println("linearity alarm: magnet misaligned? Data questionable."); }
if (COF) { Serial.println("cordic overflow: magnet misaligned? Data invalid."); }
}

packeddata = 0; // reset both variables to zero so they don't just accumulate
angle = 0;

regler();

//unsigned long endmicros=micros();

//Serial.println(endmicros-startmicros,DEC);

}


void regler() {

Winkel_abweichung = Winkel_alt - Winkel;
if(Winkel_abweichung > 180) {
Um = Um + 1;
}

if(Winkel_abweichung < -180) {
Um = Um - 1;
}

ist_wert = 360 * Um;
ist_wert = Winkel + ist_wert;

Winkel_alt = Winkel;
Serial.println(ist_wert);

e = soll_wert - ist_wert;
e = abs(e);
esum = esum + e;
if(esum > esum_max) {
esum = esum_max;
}

i = float(esum) * Ta;
i = i * Ki;

d = float(e) - float(ealt);
d = d / Ta;
d = d * Kd;

y = Kp * e;
y = y + i;
y = y + d;

yaus = int(y);
//Serial.println(y);
ealt = e;

if(yaus > y_max) {
yaus = y_max;
}

if(yaus < y_min) {
yaus = y_min;
}

if(ist_wert > soll_wert) {
digitalWrite(in_1, HIGH);
digitalWrite(in_2, LOW);
analogWrite(pwm_pin, yaus);
}

if(ist_wert < soll_wert) {
digitalWrite(in_1, LOW);
digitalWrite(in_2, HIGH);
analogWrite(pwm_pin, yaus);
}

if(ist_wert == soll_wert) {
digitalWrite(in_1, LOW);
digitalWrite(in_2, LOW);
analogWrite(pwm_pin, 0);
esum = 0;
ealt = 0;
}

}