13 KiB
Koordinatensystem, Roboter-Aufstellung & Nullstellung
Diese Datei beschreibt
- das Koordinatensystem,
- wie der Roboter darin steht,
- die angestrebte ideale Nullstellung und
- die Schritte, um den Driver auf diese Konvention zu bringen (Weg 2: Modell auf −Y).
Status: Phase 1 (y-Flip) ist umgesetzt und am Roboter verifiziert. α/β werden jetzt von −y aus gemessen (α=0 → Arm zeigt nach −y), passend zu robot.json und appRobotHoming. Offen ist Phase 2 (Handgelenk-/Finger-Nullstellung, B/C).
1. Koordinatensystem
Aus robot.json (coordinateSystem): rechtshändig, Längen mm, Winkel Grad.
| Achse | Richtung | Bedeutung im Aufbau |
|---|---|---|
| x | rechts | entlang der Linearschiene |
| y | „backward" | Arm-Arbeitsrichtung: Arm streckt nach −y |
| z | oben | Höhe |
Seitenansicht (Blick entlang +x, also die −y/z-Ebene):
z
▲
| ■═══════════════●===========●---o Arm1 ═══ , Arm2 === und Finger --- waagerecht
| Schulter (Ursprung, z=0) ausgestreckt → Fingerspitze o
└───────────────────────────────► −y (Arbeitsrichtung)
In der −y/z-Seitenansicht liegen beide Handgelenk-Achsen end-on (als Punkt ●), weil sie entlang x verlaufen:
- linkes ● = a-Achse (Unterarm-Dreher, zwischen Ellbogen und Arm2)
- rechtes ● = b-Achse (Hand-Knick-Achse)
Draufsicht (Blick von oben auf −z; x nach oben, −y nach rechts):
x
▲ │ │
│ ■════╪═══ Arm1 ═════════╪══ Arm2 ══----o o = Fingerspitze (y ≈ −590)
│ Schulter
└────────────────────────────────────────────► −y
│ = a-Achse (Ellbogen↔Arm2) bzw. b-Achse (Hand-Knick).
Bei a=0 laufen BEIDE parallel zur x-Achse (hier als senkrechte Striche).
2. Wie der Roboter darin steht
Gelenk-Kette (robot.json links):
Board → Base (Schiene x) → Arm1 (Oberarm) → Ellbow → Arm2 (Unterarm) → Hand → Palm → Finger.
- Linearschiene entlang x; die Base fährt darauf (
xMotor, mm). - Schultergelenk (Base→Arm1,
variable y): Drehung in der y-z-Ebene → α. Bei Gelenkwinkel 0 zeigt Arm1 entlang −y (skeleton.to = [0,-250,0]). - Ellbogen (
variable z) → β; Unterarm-Dreher (variable a) → a; Handgelenk-Knick (variable b) → b; Hand-Roll (variable c) → c; Greifer (variable e) → e. - z = 0 im Driver-Modell = Schulterachse. (In der Welt liegt sie ~45 mm über dem Brett — robot.json Joint1-Origin z=45 —, der Driver rechnet schulter-relativ.)
- Armlängen (aus
linksabgeleitet): l1 = 250 (Oberarm), l2 = 250 (Unterarm), l3 ≈ 90 (Hand). Σ ≈ 590 mm.
3. Ideale Nullstellung (Grundstellung)
Definition (deine Vorgabe):
- Arm1 entlang −y (Schulter-/y-Gelenk α) → α = 0.
- Ellbogen so angewinkelt, dass auch Arm2 entlang −y (z-Gelenk β) → β = 0 (β ist absolut, also auch direkt entlang −y, nicht relativ zum Oberarm).
- a-Achse so gedreht, dass die Hand-Knick-Achse genau in x-Richtung läuft → a = 0.
Diese drei Bedingungen erfüllt der Driver nach Phase 1 bereits (verifiziert, s.u.):
| Achse | Nullwert | Bedeutung | Status |
|---|---|---|---|
| X | (frei) | Schienenposition xMotor in mm |
— |
| Y (α) | 0° | Oberarm waagerecht entlang −y | ✅ Phase 1 |
| Z (β) | 0° | Unterarm waagerecht entlang −y (gestreckt) | ✅ Phase 1 |
| A (a) | 0° | Hand-Knick-Achse ∥ x | ✅ (Code erfüllt es) |
| B (b) | 0° (Ziel) | Hand gerade — derzeit ist gerade = 180° | ⏳ Phase 2 |
| C (c) | 0° (Ziel) | kein Hand-Roll — derzeit neutral ≠ 0 | ⏳ Phase 2 |
| E (e) | 0 | Greifer geschlossen / Referenz | — |
→ Resultierende Fingerspitze (α=β=a=0, gerade Hand): (xMotor, −(l1+l2+l3), 0) ≈ (x, −590, 0). (Beobachtet ~−550; Differenz steckt in der l3-Ableitung / Resthandstellung.)
4. Ist-Zustand (nach Phase 1) vs. Ideal
| Aspekt | nach Phase 1 (jetzt) | Ideal (nach Phase 2) |
|---|---|---|
| α=0 zeigt nach | −y ✅ | −y |
| β=0 Unterarm | −y ✅ | −y |
| a=0 Hand-Knick-Achse | ∥ x ✅ | ∥ x |
| G92 der Grundstellung → y | ≈ −590 ✅ | ≈ −590 |
| gerade Hand | b = 180° | b = 0° |
| neutraler Roll | c: ψ = 90° − C (posenabh.) | c = 0° |
Verifiziert per FK: FK(α=0, β=0, gerade Hand) → (0, −590, 0); bei a=0 bleibt
Fingerspitze.x = xMotor konstant, während b variiert (Knick in der y-z-Ebene).
5. Schritte (Weg 2)
Reihenfolge nach Workflow: erst Tests (rot), dann Code, dann grün.
Phase 1 — y-Flip ✅ ERLEDIGT (am Roboter verifiziert)
Umgesetzt:
- Spiegelung an der x-z-Ebene in
Arm3SegmentLinearX(_mirrorWorkspaceY): die interne Mathematik (_ikPlusY/_fkPlusY) rechnet weiter in +y, die öffentlichen Methoden spiegeln die Workspace-Pose (y, pY, φ, ψ; θ bleibt) → α=0 zeigt nach −y. - G28 (Home) auf −y umgestellt und Singularität behandelt: die voll ausgestreckte
Stellung (
|y| = l1+l2+l3) ist eine Handgelenk-Singularität, in der die IKa/cnicht bestimmen kann (Müll wiea=135°, c=45°→ Finger schräg). G28 setzt dort die Motorwerte direkt (alpha=beta=a=c=0,b=π= gerade Hand) und füllt die Pose per FK. - Tests:
test/Robot.Kinematics.NegativeY.test.js(Grundstellung −590, Homing-Pose in −y, Round-Trip in −y, a=0 → Knick-Achse ∥ x). - Migration:
Robot.02_UpperArmund der G28-Test auf −y umgestellt. - Doku:
doc/Info_G92.mdY/Z (und C/A nach der Spiegelung) aktualisiert.
Phase 2 — Handgelenk-/Finger-Nullstellung (B, C, Greifer) — OFFEN
Voraussetzung (User): Erst die Finger visualisieren/prüfen — dort werden noch Fehler vermutet. a-Achse ist bereits korrekt (a=0 → Knick-Achse ∥ x). Phase 2 betrifft B (Knick), C (Roll) und die Greifer-Kopplung.
Ziel-Konvention: gerade Hand → B = 0° (statt 180°); neutraler Roll → C = 0°; Greifer-Kopplung konsistent und gegen die echte Mechanik kalibriert.
Vorab-Erkenntnis aus dem Code (wichtig!)
Die b/c/e-Konvention ist an mehreren Stellen kodiert, die gemeinsam geändert werden müssen. Invariante: solange die Hardware-Nullpunkte nicht neu kalibriert werden, müssen die an FluidNC gesendeten Port-Werte gleich bleiben — eine reine Modell-Umbenennung darf die Hardware-Bewegung nicht verändern.
Fundstellen:
| Datei / Stelle | aktuelle Kodierung |
|---|---|
Arm3SegmentLinearX._fkPlusY |
vHand = rotate(vecUnterarm, n, b); psi = c − acos(−n.z) → b=π = gerade |
Arm3SegmentLinearX._ikPlusY |
b = acos(cosB) (∈[0,π]); c = acos(cosC) + psi → c hat posenabh. Offset |
Arm3SegmentLinearX.gripperMotorFromOpening |
eMotor = e − b − c (b,c in rad) — Greifer-Kopplung #1 |
RobotController G92/M92 |
b = B/D, c = C/D, eMotor = gripperMotorFromOpening(e) |
RobotController M1 |
b += B, c += C (relativer Motor-Jog) |
RobotController G28 |
b = π, c = 0 (Phase 1) → nach B-Umstellung auf b = 0 ändern |
portInverse.js |
b = hand.z/D, c = hand.x/D + b (Port→Motor, Hardware-Sync) |
TelnetSenderGRBL.execCommand / portValue |
Hand-Ports: z = b·D, x = (c−b)·D; e-Port mit factorTurnLift=1.2 — Greifer-Kopplung #2 |
Aufgaben
-
Finger visualisieren (User) → Soll-Bild, gegen das kalibriert wird.
-
Greifer-Kopplung — aktuell aktiv (identifiziert): Bei der realen Verkabelung (
hand.axes = ['c','e','b']) liegt der Greifer auf dem y-Port. Gesendet wird dahermNew.e · D = eMotor · D = (e − b − c) · D— die Kopplung steckt ingripperMotorFromOpening(→eMotor), der Sender hängt nur noch·180/πdran.- Die x-Port-Variante (
e + 1.2·b·D − c·D, mitfactorTurnLift = 1.2) greift nur bei anderer Verkabelung → derzeit toter Pfad. (factorOpenTurn = 1.92ungenutzt.) - Folge / Slam: bei
b = π(Phase-1-„gerade Hand") wirdeMotor = e−b−c = −π → −180°an den Finger-Motor gesendet → er fährt an den Anschlag und verdreht über die Sehne die ganze Hand. Phase 2 (b = 0= gerade) behebt das automatisch (eMotor = 0). - Aufgabe: Kopplung gegen die echte Sehnenmechanik validieren, toten x-Port-Pfad +
factorOpenTurnaufräumen, Vorzeichen je nach Motor-Verkabelung prüfen.
- Die x-Port-Variante (
-
B-Konvention (gerade = 0°). Durchgängig:
- FK/IK in
Arm3SegmentLinearX(b-Definition / acos-Zweig), gripperMotorFromOpeningnachziehen,- G92-Eingabe (
b = B/D) + M1 + G28, portInverse.js(Umkehrung),- Sender-Formeln so kompensieren, dass die FluidNC-Ports unverändert bleiben — ODER bewusst die Hardware-Nullpunkte neu kalibrieren (Entscheidung dokumentieren).
- FK/IK in
-
C-Nullpunkt (neutral = 0°). Der
c↔ψ-Bezug ist posenabhängig (ψ = acos(cos β · sin a) − c). Ein konstantesc=0=neutralist nicht global möglich, ohne die Hand-Parametrierung zu ändern. Bewerten: c als reinen Gelenkwinkel führen (Offset herausrechnen) oder die ψ-Definition anpassen. -
l3-Ableitung korrigiert ✅ (
RobotConfig.js):l3kommt jetzt aus Hand + Finger (|Hand.to[1]| + |FingerA.to[1]|= 35 + 60 = 95) statt aus dem Ellbogen-Versatz (90). Zusätzlich sindkinematics.l1/l2/l3in robot.json explizit überschreibbar (Vorrang vor der Ableitung) — zum Kalibrieren auf die gemessene Reichweite. ⚠️ Geometrie liefert Reichweite 595, beobachtet wurden ~550 → l3 (oder l1/l2) sollte perkinematics.l3explizit kalibriert werden (deutet auf l3 ≈ 50, falls l1=l2=250 stimmen). -
Tests + Doku nachziehen: Round-Trip mit neuer Konvention, Greifer-Kopplung, G92-Referenztabellen in
Info_G92.md, sowie diese Datei.
Ansatz-Entscheidung (vor Umsetzung)
- Klein/lokal: nur die G92-Eingabe umrechnen (appRobotHoming sendet B=0/C=0 für gerade/neutral, Driver mappt intern auf die alte Konvention). Wenig Risiko, aber das interne Modell bleibt „unsauber".
- Groß/sauber: interne Konvention durchgängig umstellen (alle Fundstellen oben) mit Hardware-Port-Invariante. Sauberes All-Zero-Home, aber koordinierter Eingriff.
Verifikation
Jede B/C/Greifer-Änderung gegen Visualisierung UND einen Hardware-Test (eine Achse isoliert) prüfen — das Modell allein genügt hier nicht, weil es um die Hardware-Abbildung geht.
Verifikation (Definition of Done)
- Phase 1 (erfüllt): G92 der Grundstellung → Driver
y ≈ −590, z ≈ 0; appRobotHoming sendet die gemessenen α/β/a direkt (ohne Spiegelung); G28 fährt sauber gestreckt nach −y (a=0, kein Singularitäts-Müll); volle Suite grün. - Phase 2 (Ziel): Grundstellung mit allen Gelenkwinkeln 0 (inkl. B=C=0); Greifer- Kopplung vereinheitlicht; Finger visuell korrekt.
Anhang: Stand der Kinematik
- y-Flip (Phase 1): Spiegelung in
Arm3SegmentLinearX(_mirrorWorkspaceY, genutzt voncalculateAngles3DundcalculatePositionFromMotorAngles). Am Roboter bestätigt. - G28-Singularität (Phase 1): voll ausgestreckt setzt
RobotControllerdie Motorwerte direkt (statt der singulären IK) → Finger sauber entlang −y. - atan2-Fix in der IK (
gamma = Math.atan2(pZ, pY)): macht die interne IK für −y mathematisch korrekt — Voraussetzung des y-Flips. - Winkel-Konventionen (Y/Z/A/B/C/E) sind in doc/Info_G92.md dokumentiert und nach Phase 1 aktualisiert.