Wie kompiliert man MicroPython vom Quellcode selbst?

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Hi und guten Abend,

heute habe ich versucht die Theorie und die Praxis wieder etwas zu vereinen. Eigentlich hatte ich vor, mit Werten aus der Praxis den PID-Regler zu simulieren. Soweit bin ich allerdings noch nicht gekommen bzw. das ist mir so nicht möglich (erkläre ich weiter unten).

Da ich jetzt schon Live-Werte des Getriebemotors vorliegen habe möchte ich als erstes mal das tatsächliche Verhalten des Getriebemotors ohne Last zeigen. Ich habe ein PWM.duty(80) gewählt und den Motor stoppen lassen, wenn die aktuelle Positionsmessung >= 1111 Schritte ist.
Aufgezeichnet habe ich alle 0,02s die Schritte und die verstrichene Zeit. Dafür habe ich ticks_us() und 'ticks_diff()' verwendet. Aus den Werten lies sich die Winkelgeschwindigkeit berechnen und die habe ich abhängig von der zurückgelegten Gradzahl aufgezeichnet:
https://www.dropbox.com/s/o5ymquns1dn4aol/s_v.png?dl=0
Man erkennt, das sich *keine* konstante Geschwindigkeit einstellt. Das freut mich aus dem Grund schon, weil es einfach verdeutlicht wie wichtig Erfahrung ist und vorallem bin ich froh, das ich darauf gehört habe.

Falls dich die Werte interessieren: Die verstrichene Zeit und die Positionen bzw. Messpunkte des Encoders

Mal weiter zum Regler:
Die Steuergröße lässt sich wie folgt berechnen (Die Konstante der einzelnen Regler werden mit "K" und "p", "i" oder "d" abgekürzt)

Code: Alles auswählen

Steuergroesse = (Kp * Regelabweichung) + (Ki * Summe_der_Regelabweichung * Abtastrate) + ((Kd * (Regelabweichung - vorherige-Regelabweichung) / Abtastzeit)
Das lässt sich grafisch auch schön darstellen, im aktuellen Fall erstmal mit erfunden Werten:
https://www.dropbox.com/s/io11yq3raf4hrd9/pid.png?dl=0

Die Kurve sieht aber meiner Meinung nach schon mal so ähnlich aus, wie sie aussehen soll.

Ich kann jetzt aber so den Regler nicht weiter simulieren, weil ich ja nicht weis, wie das System auf Regeleingriffe reagiert. Jetzt bin ich mir über den nächsten sinnvollen Schritt unschlüssig.
Soll ich die Formel, die ich für den Regler gepostet habe in Python übersetzen und dann die Werte für die Konstanten experimentel herausfinden?
Mein Ist-Wert würde ich aus meiner gewünschten Umfangsgeschwindigkeit berechnen. Also sagen wir mal ich will das sich mein Rohr mit dem Umfang von 100mm mit 10mm/s dreht und nach einer 400° Umdrehung anhält.
Das wäre ein zu fahrender Weg von 111,1mm und der Vorgang müsste dann in 11,1s fertig sein.
Bekannt ist auch das eine Umdrehung 1000 Messpunkte hat, bei 400° wären das 1111 Messpunkte. Pro Sekunde müssten 100,1 Messpunkte überfahren sein und pro Millisekunde 0,1 Messpunkte oder anders gesagt 1 Messpunkt muss innerhalb von 10ms erreicht werden.
Das wäre dann mein Ist-Wert. In diesem Fall würde ich vergleichen ob sich alle 10ms der Messpunkt um eins erhöht hat und zwar solange bis ich 1111 Messpunkte überfahren habe. Das ergibt eine konstante Geschwindigkeit und die hält auf der gewünschten Position an (die Toleranz dabei ist bei einem flüßigen Schmelzbad dehnend).

Habe ich es verstanden bzw. sieht es so aus als ob ich es verstanden habe oder war das eher Quatsch?

Wie würde denn der nächste sinnvolle Schritt deiner Meinung nach aussehen?

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hallo Dennis,

erstmal hast du einen Fehler beim Integral. Die Summer der Regelabweichung berechnet sich als Summe aus dem aktuellen Fehler, geteilt durch die Abtastzeit. Siehe den pseudo-code von hier:

https://en.wikipedia.org/wiki/PID_controller#Pseudocode

Was du da stehen hast, sieht nicht danach aus.

Und da sieht man etwas, das ich bei dir nicht sehe, aber auch auf der WP-Seite mal ja und mal nein: ich haette die Formel verkuerzt so geschrieben:

output += output + Kp * error + Ki * error_integral + Kd * error_derivative

Sprich: der aktuelle PWM-Wert wird durch den Regler nach oben oder unten angepasst. NICHT jedes mal komplett neu berechnet!

Und das mache ich so aus einer einfachen Ueberlegung: in einer idealen Welt haette ich eine bestimmte Kraft, mit der ich den Motor betreiben muss, um eine konstante Geschwindigkeit zu erreichen, um nichts als die inhaerente Reibung zu kompensieren. Und irgendwann habe ich diesen Punkt eingeregelt, und dann erwartet man, dass die PWM einfach bleibt. Wenn wir das jetzt aber gedanklich durchspielen, dann heisst das: der Fehler zwischen Soll + Ist ist 0. Setzten wir das aber in deine Formel ein, kommen wir auf einen output von 0! Und damit laeufter Motor zu langsam, und wir bekommen wieder einen Fehler, und regeln wieder hoch.

Warum das aber in den Formeln der Wikipedia, und teilweise dem Code, nicht so steht, kann ich noch nicht sagen. Muss ich nochmal genauer studieren.

Und dann zu deinen ganzen Ueberlegungen zu Soll- und Ist-Wert: das ist zu kurz, und von hinten gedacht. Ich hatte ja ein graphisches Beispiel verlinkt. Darin war die Position auf die Zeit aufgetragen, und durch drei Segmente - Beschleunigung, Konstante Fahrt, Abbremsen - gekennzeichnet.

Das ist also eine Funktion (im einfachsten, hart-kodierten Fall), die fuer einen Zeitpunkt t, der von 0 (Start der Fahrt) bis t_ende geht, einfach zurueckliefert, wo das Werkstueck sich jetzt befinden sollte. Wobei der Weg natuerlich in Grad ausgedrueckt wird. Und das ist dein Soll, das du jetzt also zu jedem Zeitpunkt (zb 100 mal pro Sekunde) abfragst, und mit dem im counter ermittelten Wert vergleichst. Und schon hast du deinen Fehler, und kannst delta-t berechnen.

Das unmittelbar naechste Ziel also ist, eine Funktion bzw. eher ein Objekt zu schreiben, dass dir dieses Soll ausrechnet. Eingabe dafuer sind die drei Groessen Anfangsbeschleunigung, Sattelgeschwindigkeit und Laenge in Sekunden, Bremsbeschleunigung. Und die musst du dann mal auftragen, und das muss so aussehen, wie in dem einen Post.

Das Objekt muss ausrechnen, ab welchem t_a die Regelung von Beschleunigung auf konstante Sattelgeschwindigkeit geht. Das ist natuerlich nicht schwer:

t_a = s_sattel / a_anfang

Dann laeuft das ganze fuer t_konstant mit gleicher Geschwindigkeit.

Gleiches gilt fuer den Moment, ab dem das Ding bremsen muss.

Und dann bekommt das Objekt einen Zeitpunkt t, bestimmt, an welcher Stelle der Kurve es sich befindet, und rechnet die Position aus.

Diese Werte werden dann natuerlich in Abhaengigkeit deiner eigentlichen Aufgabe bestimmt: die Geschwindigkeit ist ja immer in Grad/Zeit, also angular velocity. Da du aber eigentlich eine konstante Geschwindigkeit des Schweisskopfes ueber das Material haben willst, muss die Angabe in mm/s umgerechnet werden in die grad/s unter Beruecksichtigung des aktuellen Durchmessers.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

So, ich habe das mal kurz programmiert. Damit kannst du die zu fahrenden Positionen einfach berechnen. Die Angabe der Position kann in Grad, Schritten, radians erfolgen. Die Geschwindigkeit ist entsprechend in dieser Einheit / Sekunde.

Prologue & Epilogue sind ebenfalls in Position, die notwendige Beschleunigung wird ausgerechnet.

Code: Alles auswählen

import numpy as np
from matplotlib import pyplot as plt


class PathPlanner:
    
    def __init__(self, prologue, saddle, epilogue, speed):
        self.prologue = prologue
        self.epilogue = epilogue 
        self.saddle = saddle
        self.speed = speed
        # follows from the velocity leading ramp 
        # interpretation and its integral as position
        self.t_prologue = 2 * prologue / speed
        self.t_epilogue = 2 * epilogue / speed
        self.a_prologue = speed / self.t_prologue
        self.a_epilogue = speed / self.t_epilogue
        # the saddle is a simple rect, so duration is 
        # length / speed
        self.t_saddle = saddle / speed
        self.duration = self.t_prologue + self.t_saddle + self.t_epilogue
        self.length = prologue + saddle + epilogue
        
    def __call__(self, t):
        if t < self.t_prologue:
            return 0.5 * self.a_prologue * t**2
        position = self.prologue
        t -= self.t_prologue 
        if t < self.t_saddle:
            position += t * self.speed
            return position
        t -= self.t_saddle
        position += self.saddle
        if t < self.t_epilogue:
            # to understand this, think about the integral of the decelerating speed ramp
            # its a bit of a twister :) 
            position +=  self.epilogue - (0.5 * self.a_epilogue * (self.t_epilogue - t)**2)
            return position
        return self.length
        
        
def main():
    path = PathPlanner(15, 360, 10, 30)

    size = 500
    l = np.linspace(0, path.duration, size)
    positions = [path(t) for t in l]
    plt.plot(l, positions)
    plt.show()
    plt.close()
    speed = np.diff(positions) # simple derivative 
    plt.plot(l[:-1], speed)
    plt.show()
    plt.close()

main()
Ich habe auch noch eine Verbesserung für das integral: das sollte statt Fehler durch Abtastzeit besser (alter Fehler + aktueller Fehler) / 2 * abtastzeit sein. Das entspricht dann einer Rampe zwischen den beiden Abtastzeitpunkten, statt einem Balken.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend __deets__,

erst ein mal vielen lieben Dank für deine Arbeit und auch für die Erklärungen!

Gerade eben habe ich mir überlegt, ob ich noch einen Post absetze in dem ich dir mitteile, dass es noch ein zwei Tage gehen könnte bis ich wieder Ergebnisse liefern kann oder ob ich depremiert mit dröhnendem Kopf ins Bett gehen soll. Aus Gewohnheit habe ich die Seite aktuallisiert und die Lösung steht da. Ich bin echt sprachlos!

Ich habe zwar das Geschwindigkeitsprofil berechnet und korrekt in Trapezform dargestellt, aber seit 4 Stunden komme ich nicht darauf, wie ich die Positionsänderung beim abbremsen parabellförmig darstelle. Die Beschleunigung beim Start darzustellen war dagegen kein Problem. Das deprimierenste daran ist, dass das ja eigentlich logische Zustandsänderungen sind und auch mathematisch nicht all zu viel fordern. Ich hoffe mal, dass der Spruch mit dem Wald und den Bäumen zutrifft.
Morgen untersuche ich auf jeden Fall als erstes jede einzelne Code-Zeile von dir. Heute habe ich kein Kopf mehr und in 5 1/2 Stunden geht der Wecker schon wieder los.

Alles weitere was du über den Regler bzw. das Integral geschrieben hattest, habe ich zwar gelesen, aber ich bin noch nicht dazu gekommen daran weiter zu arbeiten. Deswegen kann ich darauf gerade noch nicht eingehen.

Vielen Dank noch einmal! 😊

Gute Nacht und viele Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich habe nochmal eine Vereinfachung der Formel vorgenommen, die sich an dem orientiert, was ich ja auch sage: einfach das Integral zu bilden.

In der velocity Form ist die Bremsrampe mit

speed = sattel_speed - a_epilogue * t

zu berechnen, wobei t hier normalisiert im Bereich [0..t_epilogue] liegt.

Wenn man das einfach mal integriert, kommt das hier bei raus:

position = sattel_speed * t - 0.5 * a_epilogue * t**2

Das ist dann auch tatsaechlich aequivalent, aber ein bisschen einfacher.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo __deets__,

ich habe heute unter anderem meine Pausen dazu genutzt, deinen Code zu analysieren. Um sicher zu gehen, dass ich alles verstanden habe, habe ich ich ihn handschriftlich mit Skizzen auf Papier übersetzt.
Die Rechnungen sind mir jetzt soweit klar 👍Da hab ich mich gestern ganz schön verhaspelt.
Ich bin auch vom Aufbau deines Codes total begeistert. So klar, deutlich und doch kompakt. Auf die Idee, das so zu gestalten wäre, ich nicht gekommen.

Zu deiner Vereinfachung, ich habe sie eingebaut und das funktioniert natürlich und liest sich auch schöner.


Jetzt muss ich mal noch auf deinen Text zu dem Regler eingehen:
Sprich: der aktuelle PWM-Wert wird durch den Regler nach oben oder unten angepasst. NICHT jedes mal komplett neu berechnet!
Ich habe meine Formel von dieser Seite.

Es hört sich aber logischer an, wenn man den PWM-Wert "nur" anpasst, anstatt neu zu berechnen.

Das wars an der Stelle aber auch schon, was ich bis jetzt dazu sagen kann. Muss mir erst mal den Wiki-Artikel durchlesen und überlegen, wie du das mit der Summe der Fehler gemeint hast. Da ich dank dir meine Soll-Werte ja berechnen lassen kann, wäre für mich der nächste logische Schritt den Regler zu verstehen und zu programmieren. Siehst du das auch so?


Den Code von dir müsste ich noch für MicroPython anpassen, aber ich denke der Regler ist erst mal wichtiger und viel ändern muss ich ja nicht, da 'numpy' nicht oft verwendet wird. Vielleicht kann ich da ulab verwenden. Muss ich aber erst noch genauers anschauen, was das für Funktionen hat.

Danke noch einmal für deine großartige Unterstützung. Ich habe gerade richtig Spass an dem Projekt.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du brauchst kein numpy. Das ist nur zur Visualisierung. Und der Pathplanner selbst sollte auf uPy laufen.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Oh sorry, da habe ich wieder zu schnell geschrieben und zu wenig nachgedacht. Ich brauche ja nur die Position bzw. habe die Geschwindigkeit schon dabei, wenn ich die Position zu einem Zeitpunkt habe. Das hatten wir ja schon.
Ich hatte das mit NumPy geschrieben, weil du damit so einfach die Position abgeleitet hast. Dabei brauche ich das ja gar nicht. Immer wieder passieren mir so "Schnellschüsse" 🙄

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend __deets__,

ich habe mal den PID-Regler nach deinem gezeigten Beispiel und deinem Vorschlag, den Ausgabewert du die Berechnung zu reichen umgesetzt.
Dabei ist folgender Code rausgekommen.
Der Counter ist eine Anlehnung an deinen Code, aber der hier zählt einfach nur in 1er Schritten hoch. Einfach, damit ich sehe ob der Code bzw. die PID-Berechnung läuft.

Code: Alles auswählen

from PathPlanner import PathPlanner
from LargeCounter import LargeCounter
from time import monotonic, sleep


class PID:
    def __init__(self, kp, ki, kd):
        self.kp = kp
        self.ki = ki
        self.kd = kd
        self.integral = 0
        self. previous_error = 0
        self.pwm = 0

    def __call__(self, error, t):
        print(t)
        self.integral += error * t
        self.pwm += (self.kp * error) + (self.ki * self.integral) + (self.kd * (error - self.previous_error) / t)
        self.previous_error = error
        return self.pwm


def main():
    lc = LargeCounter()
    path = PathPlanner(15, 360, 10, 30)
    pid = PID(1, 0, 0)
    start = monotonic()
    sleep(0.02)
    while True:
        _time = monotonic() - start
        should_be_position = path(_time)
        new_pwm_value = pid(should_be_position - lc.counter(), _time)
        print(new_pwm_value)
        sleep(0.2)


if __name__ == '__main__':
    main()
Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Sieht ok für den Anfang aus. Modul Namen LargeCounter und PathPlanner entsprechen natürlich nicht den Konventionen. Und für den echten Regler sind 0.2 zu langsam denke ich. 100 mal / Sekunde regeln sollte drin sein. Warum ist ein unterstrich vor _time? Das heißt eigentlich unused.

Was fehlt: clamping des PWM writes, mehr als 100% in jede Richtung geht ja nicht.

Aber dann kannst du mal ausprobieren, wie es läuft. Am besten loggst du die ist und sollwerte sowie das time Delta mal, allerdings nicht per print (das wird zu langsam), sondern wenn möglich erstmal im RAM, und dann streamen zb über WLAN. Dammit du ein Gefühl dafür bekommst, wie gut das System funktioniert.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Vielen Dank für deine schnelle Antwort.

Die Zeiten, so wie alle anderen Parameter muss ich auf dem ESP anpassen, hier wollte ich nur mal die Logik haben.

Ach, das war der nachfolgende Unterstrich, der signalisiert, dass ich nicht die Builtin-Funktion 'time' meine? Dafür wähle ich im richtigen Programm einen anderen Namen und und passe auch die Modulnamen an die Konventionen an.

Ich würde das mit einer if-Abfrage sicherstellen, das wenn der PWM-Wert über 100 ist, das er auf 100 gesetzt wird. Theoretisch kann ich auch negative PWM-Werte zulassen, zum bremsen. Aber es sollte nicht so sein dass der Motor zurück fährt wenn er die Position überfahren hat. Meinst du ich benötige negative Werte oder kann ich den unteren Wert auf 0 setzen?
Gefühlsmäßig kommt es mir zu "krass" vor, wenn mit einem negativen Wert gegen gesteuert wird.

Okay, du meinst ich ich soll die Werte in einer Liste sammeln? Wie ich die dann über W-lan streame muss ich mir noch anschauen. Ich habe einen Pi ohne Internetzugang in der Werkstatt stehen, der könnte eventuell die Daten empfangen.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du musst negative PWM Werte zulassen. Denn das ist einfach nur Gegenkraft. Wenn du mit deinem Motorrad anhalten willst, nimmst du ja auch nicht nur das Gas weg.

Und das clamping sollte im PID controller passieren. Sonst erzeugst du dort windup. Sprich der wird viel größer, und muss erst abgearbeitet werden. Clamping geht gut mit

pwm = min(max(pwm, -100), 100)
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Okay, alles klar. 👍

Ich muss mir immer wieder bewusst machen, dass sich das ja in Sekundenbruchteilen abspielt und da nicht eine Sekunde lang gebremst wird. Da fehlt mir noch das Gefühl dafür. Aber klar, mit ausrollen lassen, ist eine Regelung nicht wirkich genau, im Vergleich mit einer gezielten Gegenkraft.

Dann würde ich das clamping direkt nach dem berechnen des PWM-Werts setzen. Also so

Code: Alles auswählen

    def __call__(self, error, t):
        self.integral += error * t
        self.pwm += (self.kp * error) + (self.ki * self.integral) + (self.kd * (error - self.previous_error) / t)
        self.previous_error = error
        self.pwm = min(max(self.pwm, -100), 100)
        return self.pwm
Unter 'Windup'-Effekt habe ich gefunden, dass der I-Anteil größere Werte als die Begrenzung der Stellgrößen annehmen kann. So wie ich das jetzt geschrieben habe, kann der I-Anteil so groß werden wie er will, mein PWM-Wert wird nicht über 100 oder unter -100. Und erst wenn der Fehler geringer wird, nimmt der I-Anteil wieder ab.

Ich habe eben mit einem anderen ESP32 versucht, Daten über einen Accespoint zu teilen, das funktionierte gut. Also kann ich mein Versuch morgen so loggen.

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na windup ist ja genereller. Es beschreibt ein Integral, dass nachläuft. Auch deine PWM ist ein Integral. Schließlich addierst du pro Zeitschritt etwas. Das hat dann die gleichen negativen Folgen. Und ja, so sieht das clamping gut aus.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Hi __deets__,

danke für die Richtigstellung. Ich hatte Windup in Bezug auf den Regeler gegoogelt vielleicht deswegen die Erklärung.

Bin gerade dabei den Regler einzustellen und habe große Probleme dabei.
Ich kann die Daten leider nicht loggen, da ich ein "memory allocation failed" erhalte. Eine Liste mit Werten geht, aber zwei sind schon zu viel. Das ist nicht mein eigentliches Problem, obwohl die Fehlersuche mit Logwerten wohl wesentlich einfacher wäre. Macht es Sinn, denn Vorgang drei mal hinter einander zu starten und jeweils einen anderen Wert aufzuzeichnen oder ist das Ergebnis dann nicht aussagekräftig genug?

Mein Problem ist, das ich mir über den Wertebereich der drei Konstanten Kp, Ki und Kd gar nicht im klaren bin.
Wenn ich den I und den D Anteil auf 0 lasse und nur P mit 0,003 einstelle, dann regelt der Motor in der Nähe der Endposition herunter. Aber um ihn anhalten zu lassen brauche ich immer noch eine if-ABfrage, die PWM auf 0 setzt, wenn die End-Position erreicht wurde.
Wenn ich jetzt für I und D Werte eintrage, dann dreht der Motor wie verrückt vor und zurück und überschießt dabei die Endposition auch. Ich kann meine if-Abfrage, in der ich PWM auf 0 setze auf eine höhere Endposition setzen, als ich sie in 'PathPlanner' vorgebe und sie wird trotzdem erreicht.

Wie würdest du in diesem Fall ohne "richtiges" loggen vorgehen? Im Internet steht, das man ein Regelanteil nach dem anderen einstellen soll und dabei die Sprungantwort aufzeichnen. Hm das ist hier bei mir natürlich nicht so einfach, außer es macht Sinn, die Werte nacheinander in unterschiedlichen Durchläufen aufzuzeichnen.

Seit 2,5 Stunden versuche ich mich mit verschiedenen Wertebereichen und Kombinationen, aber ich denke ohne klare Struktur komme ich so zu keinem Ergebnis.

Der Code zum testen sieht gerade mal so aus:

Code: Alles auswählen

class WeldingControl:
    def __init__(self):
        encoder = PCNT(0, Pin(ENCODER_PINS[1]), Pin(ENCODER_PINS[0]))
        encoder.filter(100)
        self.count_angle = LargeCounter(encoder)
        self.welding_axis = MCPWM(0)
        self.welding_axis.bind(Pin(PWM_PINS[0][0]), Pin(PWM_PINS[0][1]))
        self.welding_axis.duty(0)
        self.path = PathPlanner(5, 270, 15, 0.1)
        self.pid = PID(0.003, 0.000005, 0.000001)
        self.actually_pos = []
        self.should_pos = []
        self.time_stamp = []
        self.test()

    def test(self):
        start = ticks_ms()
        sleep(0.001)
        while True:
            time_ = ticks_diff(ticks_ms(), start)
            should_be_position = self.path(time_)
            actually_position = self.translate_into_angle(self.count_angle.counter())
            new_pwm = self.pid(should_be_position - actually_position, time_)
            self.welding_axis.duty(new_pwm)
            if actually_position >= 300:
                self.welding_axis.duty(0)
                break
            self.should_pos.append(int(should_be_position))
            #self.actually_pos.append(actually_position)
            #self.time_stamp.append(time_)
            sleep(0.01)


    def translate_into_angle(self, value):
        return 360 * value // 1000
Das 'sleep' vor der Schleife brauche ich, da ich sonst durch 0 teilen würde.
PID, PathPlanner und LargeCounter sind alle so, wie du sie kennst.

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mal abgesehen davon, dass die Kx natuerlich Konstanten auf Modulebene sein sollten, damit du die nicht da unten tief im Code einstellen musst, sieht das an sich erstmal gut aus. Und ja, das Tuning ist nicht trivial. Da bin ich bisher auch immer eher experimentell rangegangen. Allerdings gab es in der Videoserie ja Hinweise, und da ist etwas von einer step-response geredet worden. Schaue ich mir im Zweifel auch mal gerne an, ich will das ja auch mal besser verstehen. Fuer den Anfang wuerde ich auch erstmal nur P nehmen, die beiden anderen auf 0 lassen.

Ein Ding, was du auf jeden Fall mal probieren solltest: die 5 Grad zur Beschleunigung sind schon sehr sportlich! Nimm erstmal ruhig eine viertel Umdrehung zum hochfahren, einen aehnlich langen Sattel, und dann wieder eine viertel Umdrehung um runter zu kommen. Auch das sollten Konstanten am Anfang sein.

Das Problem ist aber natuerlich, dass man ohne Logging wirklich schwer was sagen kann, und warum das mit einem Memory-Error rausknallt, ist auch erstmal komisch. Sooo viele Daten sind das ja nicht. Wieviel Speicher ist denn frei? Das ermittelt man mit

Code: Alles auswählen

>>> import gc
>>> gc.mem_free()
Wenn der Speicher wirklich dermassen knapp ist, gibt es zwei Auswege:

Statt Listen arrays nehmen, denn die sind kompakter in der Speicherung. Da reichen dann auch zB ein SHORT-Wert mit 2**16 Werten fuer die Soll/Ist, und auch eine timedelta zb in Millisekunden in dem Format.

Oder versuchen mal zu speichern in eine Datei. Auch da reichen diese Werte als 16-Bit-Wert.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ahhh, und etwas, ganz wichtig ist: D muss negativ sein! Es geht ja darum, dass die Ableitung des Fehlers bei zu starkem Anstieg eine Daempfung gegen ueberschiessen darstellt.

Und noch ein Nachtrag: ein Step-Response ist an sich natuerlich nicht besonders schwer, das ist einfach nur mal PWM auf 100 fuer eine Weile (bis das Ding nicht mehr schneller wird), dann wieder auf 0, und dabei natuerlich konstant die Position aufzeichnen. Damit man sich mal anschauen kann, was das System so macht.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und noch ein Nachtrag: wenn du einen sleep *vor* der Schleife brauchst, dann ist es besser, den einfach an den Anfang der Schleife zu setzen. Dann hast du nur einmal eine sleep-Instruktion. Und auch die Ermittlung des Time-Deltas ist nicht ideal. Du benutzt hier Millisekunden, willst aber deine Regelung bei 100 / Sekunde, also 10ms pro Periode, laufen lassen. Womit du bei einer zeitlichen Aufloesung von nur 1er Millisekunde natuerlich einen Jitter von 10% reinbekommst, je nachdem, wo genau der Zeitstempel hinfaellt! Da wuerde ich auf jeden Fall auf eine Mikrosekunde-genaue Messung zurueckgreifen.
Benutzeravatar
Dennis89
User
Beiträge: 1153
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für deine (zu) schnelle Antwort.

Ich habe nicht bedacht, dass die Schleife viel öfters pro Sekunde durchlief, als sie es jetzt tut. Daher waren die Listen vermutlich zu groß. Als ich den Fehler bemerkt hatte, das ich mich um eine Null vertippt hatte, habe ich das Logging-Problem schon verdrängt und war total auf die K-Werte fixiert.
Habe jetzt auf jeden Fall die Werte aus der drei Listen auf dem Laptop und kann die mal grafisch aufbereiten.

Da ich die Werte von jedem Versuch am besten grafisch darstellen sollte, um zu sehen was meine Änderung bewirkt hat, werde ich mir jetzt erst ein kleines Programm dafür schreiben.

Ich gehe gerade so vor, dass der ESP32 einen Access-Point und einen Webserver bereitstellt. Ich verbinde mich mit dem ESP über Wlan und über eine IP-Adresse öffnet sich eine Webseite auf der sich der Inhalt meiner drei Listen befindet.
Wenn ich die Werte jetzt jedes mal von Hand kopiere und Diagramme erstelle, werde ich nie fertig.

Ich setz mich jetzt erst mal da dran und hoffe das ich dir morgen dann Diagramme liefern kann.
Die Videos schaue ich mir dann auch gleich nochmals an.

Die K-Werte werden zu Konstanten auf Modulebene. Die Beschleunigungswerte vermutlich auch, der Sattel wird später eher als Variable übergeben. Der Wert ist ja von der Benutzereingabe abhängig. Je nach dem wie viel Grad geschweißt werden soll.
Aber eigentlich wäre es zum probieren schon angenehmer, da hast du recht. Ich ändere das noch ab.

Das Kd negativ sein muss, habe ich noch gar nicht bedacht, Danke!
Step-Response ist in dem Fall unabhängig vom Regler? Die Frage brauchst du nicht unbedingt gleich beantworten, muss dazu erst die Videos nochmal anschauen und gucken was ich dazu im Netz finde und was mir dass dann genau bringt.

Das mit 'sleep' vor der Schleife ist natürlich wieder ein super Hinweis. Dadrauf hätte man selbst kommen können. Dankeschön.

Schönen Rest-Samstag noch und hoffentlich bis morgen,
Dennis

Edit: Habe den Teil mit der Zeitmessung übersehen. Alles klar, dann gehe ich auf 'ticks_us()' zurück.
"When I got the music, I got a place to go" [Rancid, 1993]
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wir haben uns gerade ueberschnitte, schau nochmal auf meine Nachricht von vorher, zum Thema Zeit. Wenn du daran drehst, musst du aber natuerlich auch die K* veraendern, der Wertebereich ist ja Faktor 1000 groesser!

Die Daten via WLAN auszuliefern ist auf jeden Fall eine gute Idee.

Und ja, die Step-Response hat mit dem Regler nix zu tun. Die soll die Eigenschaft deines Systems beschreiben, wenn man Vollgas gibt. Man kann ja zB keine Beschleunigung erwarten, die groesser, als das was da erreicht werden kann, ist.

Ich bin gespannt, wie die Daten aussehen.
Antworten