🤖 Cinématique robotique · sur automate (PLC)

Annexe
Optimiser les matrices 4×4 en SCL (Siemens)

Objectifs de l'annexe

Cette annexe suppose acquis les chapitres 05 (cinématique directe) et 06 (le Jacobien). On note T = [R | p ; 0 0 0 1] une matrice homogène 4×4 (rotation R 3×3 orthonormée, translation p). Le code est du SCL (Structured Control Language), l'implémentation Siemens du ST (IEC 61131-3), pour TIA Portal / S7-1200 & S7-1500.

0. Règle d'or : mesurer d'abord

Sur automate, la contrainte n'est pas « aller vite » mais « tenir le temps de cycle » de façon déterministe. Avant d'optimiser une fonction, mesurez-la : sur une matrice 4×4, une multiplication naïve coûte quelques microsecondes sur un S7-1500 — souvent déjà négligeable. On optimise ce qui pèse réellement.

// Mesure du temps d'exécution avec l'instruction RUNTIME (renvoie des secondes, LREAL)
#t0    := RUNTIME(#memRuntime);        // amorce le chronomètre
"MatMul4x4"(A := #A, B := #B, C := #C); // le code à mesurer
#duree := RUNTIME(#memRuntime);        // durée écoulée, en secondes (ex : 3.1E-6 = 3,1 µs)
Optimisez un algorithme avant de micro-optimiser le code : un Jacobien qu'on résout (au lieu de l'inverser) économise plus qu'un déroulage de boucle. Hiérarchie : bon algorithme → bonne structure de données → micro-optimisation.

1. Représenter les matrices en SCL

On utilise des tableaux à deux dimensions. Deux choix structurent toute la suite :

Activez l'« accès optimisé au bloc » (Optimized block access) sur vos FC/FB et DB : l'adressage symbolique du S7-1500 est plus rapide et laisse le compilateur ranger les données au mieux. Gardez les résultats intermédiaires dans des variables TEMP (pile locale, accès rapide) plutôt que dans des DB globales.

2. Multiplication 4×4 : du naïf à l'optimisé

2.1 Version naïve (triple boucle)

Correcte et lisible — le point de départ. Notez le passage par référence et l'accumulateur en TEMP.

FUNCTION "MatMul4x4" : Void
VAR_IN_OUT
    A : ARRAY[0..3, 0..3] OF LREAL;   // par référence : pas de copie
    B : ARRAY[0..3, 0..3] OF LREAL;
    C : ARRAY[0..3, 0..3] OF LREAL;   // résultat (⚠ doit être distinct de A et B)
END_VAR
VAR_TEMP
    i, j, k : INT;
    s : LREAL;
END_VAR
BEGIN
    FOR i := 0 TO 3 DO
        FOR j := 0 TO 3 DO
            s := 0.0;                          // accumulateur en pile locale
            FOR k := 0 TO 3 DO
                s := s + A[i, k] * B[k, j];
            END_FOR;
            C[i, j] := s;                      // une seule écriture dans C par case
        END_FOR;
    END_FOR;
END_FUNCTION
Aliasing. Si C pointe sur A ou B (ex : MatMul4x4(A:=#T, B:=#T, C:=#T)), le résultat est faux : on écrit dans C[i,j] des cases encore nécessaires au calcul. Une multiplication de matrices ne peut pas se faire en place : le résultat doit être une matrice tierce.

2.2 Optimisation : cacher la ligne et dérouler la boucle interne

Deux idées, à effort minime : (1) copier la ligne i de A dans quatre scalaires TEMP pour éviter de recalculer l'adresse A[i,k] à chaque itération ; (2) dérouler la boucle sur k (taille fixe = 4) pour supprimer le compteur, le test de fin et le contrôle de bornes.

VAR_TEMP
    i, j : INT;
    a0, a1, a2, a3 : LREAL;
END_VAR
BEGIN
    FOR i := 0 TO 3 DO
        a0 := A[i, 0];  a1 := A[i, 1];  a2 := A[i, 2];  a3 := A[i, 3];   // ligne cachée
        FOR j := 0 TO 3 DO
            C[i, j] := a0 * B[0, j] + a1 * B[1, j]
                     + a2 * B[2, j] + a3 * B[3, j];                      // boucle k déroulée
        END_FOR;
    END_FOR;
END_FUNCTION

On peut aller jusqu'au déroulage total (16 lignes C[0,0] := A[0,0]*B[0,0] + …) : gain maximal mais code verbeux et difficile à maintenir. La version ci-dessus est le bon compromis lisibilité / performance pour du 4×4.

TechniqueGainEffort / risque
VAR_IN_OUT au lieu de VAR_INPUTÉlevé (supprime 3 copies de 128 o)Minime
Accumulateur + résultat dans une matrice tierceCorrection & clartéMinime
Cache de ligne (4 scalaires TEMP)MoyenMinime
Déroulage de la boucle interneMoyenFaible
Déroulage total (16 lignes)ÉlevéFort (maintenabilité)

3. Inverser une matrice 4×4 : exploiter la structure

3.1 Le piège : ne pas dégainer Gauss par réflexe

Inverser une 4×4 quelconque par élimination de Gauss-Jordan demande des dizaines d'opérations, des divisions, une recherche de pivot et des branchements — coûteux et sensible au bruit numérique. Or, en cinématique directe, on n'inverse jamais une matrice quelconque : toutes les transformations de repère sont homogènes.

3.2 Inverse d'une matrice homogène : formule analytique

Pour T = [R | p ; 0\,0\,0\,1] avec R orthonormée, l'inverse est exact et immédiat :

T-1 = [ RT | −RT·p ;  0  0  0  1 ]

La rotation s'inverse par simple transposition, la translation par un petit produit. Aucune division, aucun pivot, aucun branchement.

FUNCTION "InvHomogene" : Void
VAR_IN_OUT
    T    : ARRAY[0..3, 0..3] OF LREAL;   // entrée : [R | p ; 0 0 0 1]
    Tinv : ARRAY[0..3, 0..3] OF LREAL;   // sortie
END_VAR
VAR_TEMP
    i, j : INT;
END_VAR
BEGIN
    // 1) Rotation : R_inv = R^T  (transposition du bloc 3×3)
    FOR i := 0 TO 2 DO
        FOR j := 0 TO 2 DO
            Tinv[i, j] := T[j, i];
        END_FOR;
    END_FOR;

    // 2) Translation : p_inv = -R^T * p   (R^T[i,:] = colonne i de R = T[.,i])
    FOR i := 0 TO 2 DO
        Tinv[i, 3] := -( T[0, i] * T[0, 3]
                       + T[1, i] * T[1, 3]
                       + T[2, i] * T[2, 3] );
    END_FOR;

    // 3) Dernière ligne (0 0 0 1)
    Tinv[3, 0] := 0.0;  Tinv[3, 1] := 0.0;  Tinv[3, 2] := 0.0;  Tinv[3, 3] := 1.0;
END_FUNCTION

Bilan : ≈ 9 multiplications et 6 additions, zéro division, temps d'exécution constant (déterministe). À comparer aux dizaines d'opérations et divisions d'un Gauss général.

Cette formule est déterministe (pas de branchement de pivot) : son temps d'exécution ne dépend pas des données. C'est exactement ce qu'on veut dans une tâche cyclique. Utilisez-la systématiquement pour inverser une pose, une transformation outil, un changement de repère base↔monde.
La formule n'est valable que si R est bien une rotation orthonormée. Après beaucoup de calculs en REAL, R peut « dériver » (déterminant ≠ 1). Si besoin, ré-orthonormalisez R (Gram-Schmidt) — ou travaillez en LREAL pour limiter la dérive.

4. Inverser un Jacobien : résoudre, ne pas inverser

Le Jacobien J relie vitesses articulaires et vitesse de l'effecteur : v = J·q̇. Pour la commande en vitesse (cinématique inverse instantanée) on veut q̇ = J-1·v. Deux réflexes d'automaticien :

4.1 Résoudre le système au lieu de former l'inverse

Calculer explicitement J-1 puis multiplier est plus coûteux et moins stable que résoudre directement J·q̇ = v par élimination de Gauss avec pivot partiel. La fonction ci-dessous résout un système A·x = b (ici A = J, b = v, x = q̇) et renvoie FALSE si la matrice est (quasi) singulière.

FUNCTION "ResoudreLin4" : BOOL   // renvoie FALSE si (quasi-)singulier
VAR_IN_OUT
    A : ARRAY[0..3, 0..3] OF LREAL;   // ⚠ modifiée : passez-en une COPIE de travail
    b : ARRAY[0..3] OF LREAL;         // ⚠ modifié également
    x : ARRAY[0..3] OF LREAL;         // solution
END_VAR
VAR_TEMP
    i, j, k, p : INT;
    maxv, coef, s, tmp : LREAL;
END_VAR
CONSTANT
    EPS : LREAL := 1.0E-9;            // seuil de singularité
END_CONSTANT
BEGIN
    FOR k := 0 TO 3 DO
        // --- recherche du pivot (plus grand |A[i,k]|) pour la stabilité ---
        p := k;  maxv := ABS(A[k, k]);
        FOR i := k + 1 TO 3 DO
            IF ABS(A[i, k]) > maxv THEN  maxv := ABS(A[i, k]);  p := i;  END_IF;
        END_FOR;
        IF maxv < EPS THEN
            "ResoudreLin4" := FALSE;     // singulier → on renonce proprement
            RETURN;
        END_IF;
        // --- échange des lignes k et p ---
        IF p <> k THEN
            FOR j := 0 TO 3 DO  tmp := A[k, j];  A[k, j] := A[p, j];  A[p, j] := tmp;  END_FOR;
            tmp := b[k];  b[k] := b[p];  b[p] := tmp;
        END_IF;
        // --- élimination sous le pivot ---
        FOR i := k + 1 TO 3 DO
            coef := A[i, k] / A[k, k];
            FOR j := k TO 3 DO  A[i, j] := A[i, j] - coef * A[k, j];  END_FOR;
            b[i] := b[i] - coef * b[k];
        END_FOR;
    END_FOR;
    // --- remontée (substitution arrière) ---
    FOR i := 3 TO 0 BY -1 DO
        s := b[i];
        FOR j := i + 1 TO 3 DO  s := s - A[i, j] * x[j];  END_FOR;
        x[i] := s / A[i, i];
    END_FOR;
    "ResoudreLin4" := TRUE;
END_FUNCTION
L'exemple est en 4×4 pour rester lisible. Un Jacobien spatial complet est 6×6 (ou non carré pour un robot redondant) : la même routine se généralise en remplaçant 3 par n-1. Le pivot partiel est indispensable : sans lui, une case pivot proche de zéro ruine la précision.

4.2 Près d'une singularité : amortir (Damped Least Squares)

Au voisinage d'une singularité, J devient mal conditionné : J-1 « explose » et commande des vitesses articulaires énormes. La parade classique est l'inverse amorti (Damped Least Squares, Levenberg-Marquardt) :

q̇ = JT · ( J·JT + λ²·I )-1 · v

Le terme λ²·I borne la solution : on troque un peu de précision de suivi contre des vitesses lisses et sûres. En pratique sur PLC : surveiller la manipulabilité w = √(det(J·JT)), augmenter λ quand w chute, et toujours saturer la sortie avant de l'envoyer aux axes.

La résolution par Gauss contient des branchements (pivot, singularité) : son temps varie légèrement selon les données. Bornez tout : nombre d'axes fixe, sortie saturée, et prévoyez le pire cas dans votre budget de temps de cycle. En cas de retour FALSE (singulier), tenez la dernière consigne valide plutôt que d'envoyer NaN/Inf aux variateurs.

Exercices

Exercice 1 — Cache de ligne

À partir de la multiplication naïve, réécris uniquement la double boucle en cachant la ligne i de A dans des scalaires, sans dérouler la boucle sur j. Combien de lectures de A économises-tu par ligne ?

Voir la solution
FOR i := 0 TO 3 DO
    a0 := A[i,0]; a1 := A[i,1]; a2 := A[i,2]; a3 := A[i,3];   // 4 lectures
    FOR j := 0 TO 3 DO
        C[i,j] := a0*B[0,j] + a1*B[1,j] + a2*B[2,j] + a3*B[3,j];
    END_FOR;
END_FOR;

Sans cache, chaque case C[i,j] relit 4 fois la ligne de A → 16 lectures de A par ligne i. Avec le cache : 4 lectures par ligne. On divise les accès à A par 4.

Exercice 2 — Compter les opérations de l'inverse homogène

Sans regarder, dénombre les multiplications et les divisions de InvHomogene, puis compare qualitativement à un Gauss-Jordan 4×4 général.

Voir la solution

La transposition ne coûte que des copies (0 multiplication). La translation −RTp fait 3 lignes × 3 produits = 9 multiplications et 6 additions, 0 division, sans aucun branchement.

Un Gauss-Jordan 4×4 général demande de l'ordre de plusieurs dizaines de multiplications et divisions, plus la recherche de pivot (comparaisons + échanges de lignes). Exploiter la structure homogène est donc à la fois plus rapide, plus stable et déterministe.

Exercice 3 — Pourquoi amortir ?

Explique en une phrase pourquoi q̇ = J-1v devient dangereux près d'une singularité, et ce que change le terme λ²I.

Voir la solution

Près d'une singularité, det(J) → 0 : de petites vitesses cartésiennes v exigent des vitesses articulaires arbitrairement grandes (l'inverse « explose »). Le terme λ²I rend la matrice (J·JT + λ²I) toujours inversible et borne : le robot suit la consigne un peu moins fidèlement, mais avec des vitesses finies et lisses.

Récapitulatif