🤖 Cinématique robotique · sur automate (PLC)

Chapitre 03
Paramétrisation Denavit-Hartenberg

Objectifs du chapitre

1. Pourquoi Denavit-Hartenberg ?

Au chapitre 2, nous avons appris à composer des repères par produit de transformations homogènes : 0Tn = 0T1 · 1T2 · … · n-1Tn. La cinématique directe se ramène donc à écrire, liaison par liaison, chaque i-1Ti. Le problème : écrire à la main une matrice 4×4 par liaison est fastidieux et fragile. Une transformation rigide quelconque possède 6 degrés de liberté (3 pour la rotation, 3 pour la translation) ; remplir 6 nombres — plus les fonctions trigonométriques — pour chacune des liaisons, sans se tromper de signe ni d'axe, c'est le genre d'exercice où les erreurs se glissent silencieusement.

En 1955, Jacques Denavit et Richard Hartenberg ont proposé une convention de placement des repères qui impose deux contraintes géométriques sur la façon d'attacher chaque repère à son segment. Ces deux contraintes suppriment 2 des 6 degrés de liberté : il ne reste alors que 4 paramètres pour décrire la transformation entre deux repères consécutifs. On passe de 6 nombres arbitraires par liaison à 4 nombres normalisés, ce qui donne une table compacte — une ligne, quatre colonnes, par liaison — universellement lue et documentée.

L'idée en une phrase. DH ne change pas la géométrie du robot ; il choisit et comment poser les repères de manière à ce que chaque passage de Ri-1 à Ri ne coûte que quatre nombres au lieu de six. C'est une discipline de placement, pas une formule magique.

2. Les quatre paramètres DH

Il existe deux conventions de Denavit-Hartenberg (voir la mise en garde plus bas et le chapitre 4). Ce chapitre présente la convention distale, dite aussi « standard » ou classique : c'est la plus répandue dans la littérature académique et dans de nombreuses documentations constructeur. Les quatre paramètres de la liaison i sont :

ParamètreNomNature du mouvementVariable si…
θiangle articulairerotation autour de zi-1liaison rotoïde
didécalage (offset)translation le long de zi-1liaison prismatique
ailongueur du brastranslation le long de xitoujours constant
αitorsion (twist)rotation autour de xitoujours constant

Deux paramètres se rapportent à l'axe z (celui de la liaison précédente) et deux à l'axe x (la normale commune). Pour chaque liaison, un seul des quatre est variable — celui qui correspond au type de la liaison — et les trois autres sont des constantes géométriques figées par la construction mécanique :

Offset constant vs variable articulaire. Dans notre type T_DH (chapitre 1), le champ theta stocke l'offset constant (souvent 0) et le champ d l'offset prismatique constant. Lors de la cinématique directe, on ajoute la variable articulaire qi au paramètre variable : θi := theta + qi pour une rotoïde, di := d + qi pour une prismatique. On garde ainsi la géométrie constante séparée de l'état, comme au chapitre 1.

3. Placer les repères : l'algorithme DH

Les quatre paramètres n'ont de sens que parce que les repères sont posés selon des règles précises. Voici l'algorithme, à appliquer du repère de base R0 vers l'effecteur Rn.

3.1 L'axe z : porté par l'axe de la liaison

On place l'axe zi le long de l'axe de la liaison i+1 — c'est-à-dire l'axe autour duquel (rotoïde) ou le long duquel (prismatique) se fait le mouvement de la liaison suivante. C'est la règle la plus importante et la source de la première contrainte : le repère Ri « regarde vers » l'articulation qui vient. Le sens positif de zi fixe le sens positif de rotation ou de translation de cette liaison.

3.2 L'axe x : la normale commune

On place ensuite xi le long de la normale commune aux axes zi-1 et zi : c'est le segment le plus court reliant ces deux droites (la perpendiculaire commune). Le sens de xi pointe de zi-1 vers zi. C'est cette normale commune qui matérialise géométriquement la longueur ai (sa longueur) et la torsion αi (l'angle entre les deux z, mesuré autour de x).

3.3 L'origine : à l'intersection

L'origine Oi se trouve à l'intersection de la normale commune avec l'axe zi. Enfin, yi complète le repère orthonormé direct (yi = zi × xi).

Une fois les repères posés, les quatre paramètres se lisent directement sur le dessin :

Ordre de lecture : d'abord z, ensuite x. On fixe tous les axes z le long des axes de liaison, puis on trace les normales communes pour les x. En procédant dans cet ordre, les quatre paramètres tombent naturellement. Tenter de placer un repère complet d'un seul coup mène presque toujours à une erreur de torsion.

4. La matrice DH standard

La transformation i-1Ti résulte de quatre mouvements élémentaires appliqués dans l'ordre : deux le long / autour de z, puis deux le long / autour de x. C'est précisément parce que ces quatre opérations suffisent (grâce au placement des repères) que DH n'a besoin que de quatre paramètres :

i-1Ti = Rotzi) · Transz(di) · Transx(ai) · Rotxi)

En développant le produit (avec les raccourcis cθ = cos θ, sθ = sin θ, cα = cos α, sα = sin α), on obtient la matrice DH standard 4×4 :

i-1Ti = [ cθ   −sθ cα   sθ sα   a cθ ; sθ   cθ cα   −cθ sα   a sθ ; 0   sα   cα   d ; 0   0   0   1 ]

Observez la structure : la première colonne ne contient que θ (c'est l'image de l'axe x tourné autour de z) ; la dernière colonne est la translation (a cθ, a sθ, d) ; le bloc central couple θ et α. Le facteur cα / sα qui apparaît dans les colonnes 2 et 3 est la torsion : c'est lui qui fait sortir le mécanisme du plan.

L'ordre des quatre facteurs n'est pas commutatif. La séquence est bien z d'abord (rotation θ puis translation d), x ensuite (translation a puis rotation α). Intervertir Transx et Rotx, ou faire les x avant les z, produit une matrice différente qui ressemble pourtant à s'y méprendre à la bonne. C'est aussi ce qui distingue la convention distale (ce chapitre) de la convention modifiée (chapitre 4), où l'ordre et l'indexation changent.

4.1 Le cas dégénéré : retrouver Rz

Avec d = a = α = 0, la matrice se réduit à une simple rotation homogène autour de z :

i-1Ti = [ cθ   −sθ   0   0 ; sθ   cθ   0   0 ; 0   0   1   0 ; 0   0   0   1 ] = Rz(θ)

C'est un excellent test de non-régression pour valider une implémentation : si DhTransform avec d=a=α=0 ne redonne pas exactement Rz(θ), c'est qu'un signe ou une position est erroné.

5. La table DH

Décrire un robot revient alors à remplir un tableau à quatre colonnes (θ, d, a, α), une ligne par liaison. C'est la « fiche géométrique » du robot : compacte, portable, universelle.

5.1 Exemple (a) : le bras planaire 2R

Deux liaisons rotoïdes dans le plan, de longueurs L1 et L2. Les axes des deux liaisons sont parallèles (tous deux perpendiculaires au plan), donc la torsion est nulle ; le mécanisme reste plan, donc les offsets d sont nuls ; la longueur de chaque segment se lit sur ai :

iθidiaiαi
1θ1 (var.)0L10
2θ2 (var.)0L20

On retrouve bien la position d'effecteur du chapitre précédent : x = L1c1 + L2c12, y = L1s1 + L2s12, en multipliant les deux matrices DH.

5.2 Exemple (b) : un bras anthropomorphe 6 axes

Voici une table DH générique pour un bras anthropomorphe à 6 axes rotoïdes avec poignet sphérique (les trois derniers axes se coupent en un point). Les valeurs sont plausibles ; les torsions α = ±90° (soit ±π/2 rad) sont caractéristiques du changement d'orientation des axes entre l'épaule, le coude et le poignet. Rappel : toutes les valeurs sont en radians et en mètres.

iθidi (m)ai (m)αi (rad)rôle
1θ1 (var.)0,3400−π/2base / rotation
2θ2 (var.)00,3500épaule
3θ3 (var.)00,040−π/2coude
4θ4 (var.)0,3100+π/2poignet 1
5θ5 (var.)00−π/2poignet 2
6θ6 (var.)0,08000bride outil

Comment la lire. Chaque ligne i donne la transformation i-1Ti à injecter dans la matrice DH standard. Les six θi sont variables (six liaisons rotoïdes) : ce sont les consignes moteur. Le reste est constant :

Pour obtenir la pose de l'outil, on remplit six matrices DH et on les chaîne : 0T6 = 0T1 · 1T2 · … · 5T6 (code au §8). La même fonction générique traite les 2R, les 6 axes, ou n'importe quel bras série.

6. Les avantages de DH

Pourquoi cette convention s'est-elle imposée pendant plus d'un demi-siècle ?

Sur automate, la table DH est parfaite. 24 LREAL constants tiennent dans un ARRAY[0..5] OF T_DH figé à la compilation, sans allocation. Les cos α / sin α ne dépendent pas des angles articulaires : on peut les précalculer une fois au démarrage et les garder au chaud, ce qui allège la boucle cyclique (voir chapitre 5).

7. Les limites de DH classique

DH standard n'est pas sans défauts. Ces limites motivent directement le chapitre 4 (convention modifiée MDH, paramétrisation de Hayati…).

7.1 Placement non unique

L'algorithme laisse des libertés : sens de l'axe z, choix de la normale commune quand plusieurs conviennent, position du premier et du dernier repère. Résultat : deux ingénieurs peuvent produire deux tables DH différentes pour le même robot, toutes deux correctes. C'est pourquoi les tables d'un même bras diffèrent parfois d'un fournisseur ou d'un article à l'autre.

7.2 Conventions multiples

Il existe la convention distale (standard, celle de ce chapitre) et la convention proximale / modifiée (dite « MDH » de Khalil-Kleinfinger, chapitre 4). Elles diffèrent par l'ordre des facteurs et l'indexation des repères : une même table de nombres ne donne pas la même cinématique selon la convention.

Vérifiez toujours la convention d'une table fournisseur. Reprendre une table DH d'une documentation sans savoir si elle est standard ou modifiée est une erreur classique et coûteuse : la pose calculée sera fausse d'une liaison de décalage, souvent de façon subtile. Le symptôme typique : le robot est correct « à un maillon près ».

7.3 Le problème des axes parallèles

C'est la limite la plus sérieuse. Quand deux axes consécutifs zi-1 et zi sont parallèles, la normale commune n'est pas unique : une infinité de perpendiculaires conviennent. Pire, pour des axes quasi parallèles — le cas réel, car aucun robot n'est parfait — la normale commune est mal conditionnée : un minuscule défaut de parallélisme (quelques centièmes de degré) fait sauter brutalement la valeur de a et de d.

zi-1 ∥ zi → normale commune indéterminée → ai, di discontinus

Concrètement, la paramétrisation DH devient discontinue et instable précisément dans une configuration très courante (deux axes d'épaule/coude nominalement parallèles). C'est catastrophique pour la calibration (chapitre 7), qui cherche à identifier les écarts géométriques réels : l'algorithme d'identification voit un paramètre qui varie énormément pour un défaut minuscule, donc mal conditionné numériquement. La solution — la paramétrisation de Hayati et la convention MDH — fait l'objet du chapitre 4.

7.4 Pas d'arborescence

DH décrit une chaîne ouverte simple (base → … → effecteur). Il ne gère pas nativement les structures arborescentes (un tronc portant plusieurs branches : deux bras, plusieurs doigts d'une pince, robot humanoïde). Il faut alors plusieurs chaînes DH ou une autre formulation (arbre cinématique, twists / exponentielles de screw).

8. Implémentation en Structured Text

On code la fonction DhTransform qui construit la matrice DH standard à partir des quatre paramètres, en réutilisant le type T_MAT4 du chapitre 1. Le résultat est passé en VAR_IN_OUT pour éviter toute copie — réflexe temps réel.

// Construit la matrice DH standard (convention distale) dans T :
//   T := Rot_z(theta) * Trans_z(d) * Trans_x(a) * Rot_x(alpha)
// theta, alpha en radians ; d, a en mètres. LREAL partout.
FUNCTION DhTransform : BOOL
VAR_INPUT
    theta : LREAL;   // rad — inclut déjà la variable articulaire si rotoïde
    d     : LREAL;   // m   — inclut déjà la variable articulaire si prismatique
    a     : LREAL;   // m
    alpha : LREAL;   // rad
END_VAR
VAR_IN_OUT
    T : T_MAT4;      // matrice résultat 4×4
END_VAR
VAR
    ct, st : LREAL;  // cos/sin de theta
    ca, sa : LREAL;  // cos/sin de alpha
END_VAR
    // Précalcul des fonctions trigonométriques (une seule fois chacune).
    ct := COS(theta);
    st := SIN(theta);
    ca := COS(alpha);
    sa := SIN(alpha);

    // Ligne 0
    T[0,0] := ct;   T[0,1] := -st * ca;   T[0,2] :=  st * sa;   T[0,3] := a * ct;
    // Ligne 1
    T[1,0] := st;   T[1,1] :=  ct * ca;   T[1,2] := -ct * sa;   T[1,3] := a * st;
    // Ligne 2
    T[2,0] := 0.0;  T[2,1] :=  sa;        T[2,2] :=  ca;        T[2,3] := d;
    // Ligne 3 (homogène)
    T[3,0] := 0.0;  T[3,1] := 0.0;        T[3,2] := 0.0;        T[3,3] := 1.0;

    DhTransform := TRUE;
END_FUNCTION

Enchaîner les liaisons donne la cinématique directe. On part de l'identité et on post-multiplie chaque matrice de liaison, en ajoutant la variable articulaire q au paramètre variable selon le type de liaison.

// Cinématique directe d'un bras série à N liaisons décrites par une table DH.
// dh[i] : géométrie constante ; q[i] : variable articulaire (rad ou m).
// Résultat T0n := 0_T_N (base -> effecteur).
FUNCTION FkDh : BOOL
VAR_IN_OUT
    dh  : ARRAY[0..5] OF T_DH;    // table DH (6 liaisons ici)
    q   : ARRAY[0..5] OF LREAL;   // variables articulaires
    T0n : T_MAT4;                 // résultat
END_VAR
VAR
    i        : INT;
    theta, d : LREAL;
    Ti       : T_MAT4;   // 0_T_i courant (accumulateur)
    Tliaison : T_MAT4;   // i-1_T_i de la liaison
    Ttmp     : T_MAT4;   // résultat temporaire (jamais aliasé)
END_VAR
    Mat4Identity(M := Ti);           // 0_T_0 = identité

    FOR i := 0 TO 5 DO
        // Ajout de la variable articulaire au bon paramètre.
        IF dh[i].isPrismatic THEN
            theta := dh[i].theta;          // constant
            d     := dh[i].d + q[i];       // prismatique : d varie
        ELSE
            theta := dh[i].theta + q[i];   // rotoïde : theta varie
            d     := dh[i].d;              // constant
        END_IF

        DhTransform(theta := theta, d := d, a := dh[i].a, alpha := dh[i].alpha,
                    T := Tliaison);

        // Ttmp := Ti * Tliaison   (C distinct de A et B — cf. Mat4Mul)
        Mat4Mul(A := Ti, B := Tliaison, C := Ttmp);
        Ti := Ttmp;                        // on avance l'accumulateur
    END_FOR

    T0n := Ti;
    FkDh := TRUE;
END_FUNCTION

Coût borné. Chaque liaison = 4 appels trigonométriques + un produit 4×4 (64 multiplications). Six liaisons ≈ quelques microsecondes, parfaitement déterministe dans une tâche cyclique. Aucune boucle de longueur inconnue, aucune allocation : tout tient dans des tableaux fixes, conformément au modèle temps réel du chapitre 1.

Ne jamais aliaser le résultat de Mat4Mul. On multiplie dans Ttmp (distinct de Ti et Tliaison) puis on recopie dans Ti. Écrire Mat4Mul(A := Ti, B := Tliaison, C := Ti) écraserait des coefficients de Ti encore nécessaires au calcul → pose fausse. C'est le même piège d'aliasing qu'au chapitre 1.

Exercices

Exercice 1 — Coder DhTransform et vérifier le cas dégénéré

Réimplémentez DhTransform, puis vérifiez que pour d = a = α = 0 elle redonne exactement Rz(θ) homogène. Testez avec θ = 0,7 rad.

Voir la solution
PROGRAM P_TestDhDegenere
VAR
    T       : T_MAT4;
    theta   : LREAL := 0.7;   // rad
    ct, st  : LREAL;
    ok      : BOOL := TRUE;
END_VAR

// Cas dégénéré : d = a = alpha = 0  ->  T doit valoir Rz(theta).
DhTransform(theta := theta, d := 0.0, a := 0.0, alpha := 0.0, T := T);

ct := COS(theta);
st := SIN(theta);

// Coefficients attendus de Rz(theta) homogène.
IF ABS(T[0,0] - ct)  > 1E-12 THEN ok := FALSE; END_IF
IF ABS(T[0,1] + st)  > 1E-12 THEN ok := FALSE; END_IF   // -sin
IF ABS(T[1,0] - st)  > 1E-12 THEN ok := FALSE; END_IF
IF ABS(T[1,1] - ct)  > 1E-12 THEN ok := FALSE; END_IF
IF ABS(T[2,2] - 1.0) > 1E-12 THEN ok := FALSE; END_IF
IF ABS(T[0,3])       > 1E-12 THEN ok := FALSE; END_IF   // translation nulle
IF ABS(T[2,3])       > 1E-12 THEN ok := FALSE; END_IF
// ok = TRUE si la matrice DH dégénérée est bien Rz(theta).

Avec α = 0 on a cα = 1 et sα = 0 : le couplage colonnes 2/3 disparaît, et il ne reste que la rotation planaire autour de z. On compare toujours à une tolérance (1E-12), jamais avec = strict.

Exercice 2 — Paramètres variables : rotoïde vs prismatique

Pour chacun des quatre paramètres θ, d, a, α, indiquez lequel est variable et lesquels sont constants, d'abord pour une liaison rotoïde, puis pour une liaison prismatique. Écrivez ensuite le bout de code ST qui construit le θ et le d effectifs à partir d'un T_DH et de la variable articulaire q.

Voir la solution

Rotoïde : θ variable ; d, a, α constants.
Prismatique : d variable ; θ, a, α constants.
Dans les deux cas, a et α sont toujours constants (géométrie figée du segment).

// liaison : T_DH ; q : variable articulaire ; theta, d : sorties effectives.
IF liaison.isPrismatic THEN
    theta := liaison.theta;         // constant
    d     := liaison.d + q;         // prismatique : d = offset + course
ELSE
    theta := liaison.theta + q;     // rotoïde : theta = offset + angle
    d     := liaison.d;             // constant
END_IF
// a := liaison.a ; alpha := liaison.alpha  (toujours constants)

C'est exactement la logique utilisée dans FkDh : l'offset constant du champ theta (ou d) et la variable articulaire q s'additionnent.

Exercice 3 — Table DH d'un bras 2R et transformation résultante

Remplissez une table DH pour un bras planaire 2R avec L1 = 0,5 m et L2 = 0,3 m, puis calculez 0T2 pour q1 = q2 = 0 (bras tendu). Vérifiez que la position d'effecteur vaut bien (0,8 ; 0 ; 0).

Voir la solution
PROGRAM P_Test2R
VAR
    dh   : ARRAY[0..1] OF T_DH;
    q    : ARRAY[0..1] OF LREAL;
    Ti, Tliaison, Ttmp : T_MAT4;
    i        : INT;
    theta, d : LREAL;
    ok       : BOOL := TRUE;
END_VAR

// --- Table DH du bras 2R : theta variable, d = 0, a = Li, alpha = 0 ---
dh[0].theta := 0.0; dh[0].d := 0.0; dh[0].a := 0.5; dh[0].alpha := 0.0; dh[0].isPrismatic := FALSE;
dh[1].theta := 0.0; dh[1].d := 0.0; dh[1].a := 0.3; dh[1].alpha := 0.0; dh[1].isPrismatic := FALSE;

q[0] := 0.0;   // bras tendu
q[1] := 0.0;

// --- Chaînage 0_T_2 = 0_T_1 * 1_T_2 ---
Mat4Identity(M := Ti);
FOR i := 0 TO 1 DO
    theta := dh[i].theta + q[i];    // rotoïde
    d     := dh[i].d;
    DhTransform(theta := theta, d := d, a := dh[i].a, alpha := dh[i].alpha, T := Tliaison);
    Mat4Mul(A := Ti, B := Tliaison, C := Ttmp);
    Ti := Ttmp;
END_FOR

// --- Vérification : effecteur en (0.8, 0, 0) ---
IF ABS(Ti[0,3] - 0.8) > 1E-12 THEN ok := FALSE; END_IF   // x = L1 + L2
IF ABS(Ti[1,3])       > 1E-12 THEN ok := FALSE; END_IF   // y = 0
IF ABS(Ti[2,3])       > 1E-12 THEN ok := FALSE; END_IF   // z = 0

Bras tendu (q1=q2=0) : les deux segments s'alignent sur x, donc x = L1+L2 = 0,8 m. Essayez ensuite q2 = π/2 : vous devez trouver (0,5 ; 0,3 ; 0).

Exercice 4 — Précalcul des cos α / sin α constants

Les αi ne dépendent pas des angles articulaires : leurs cos et sin peuvent être calculés une seule fois au démarrage. Proposez un type T_DH_PRE stockant, en plus des paramètres, les i et i précalculés, et la fonction d'initialisation associée.

Voir la solution
// Version "précalculée" d'une liaison DH : on garde cos/sin de alpha au chaud.
TYPE T_DH_PRE :
STRUCT
    theta       : LREAL;
    d           : LREAL;
    a           : LREAL;
    alpha       : LREAL;
    isPrismatic : BOOL;
    cAlpha      : LREAL;   // cos(alpha) — précalculé
    sAlpha      : LREAL;   // sin(alpha) — précalculé
END_STRUCT
END_TYPE

// À appeler une seule fois (démarrage / après calibration).
FUNCTION DhPrepare : BOOL
VAR_IN_OUT
    dh : ARRAY[0..5] OF T_DH_PRE;
END_VAR
VAR
    i : INT;
END_VAR
    FOR i := 0 TO 5 DO
        dh[i].cAlpha := COS(dh[i].alpha);
        dh[i].sAlpha := SIN(dh[i].alpha);
    END_FOR
    DhPrepare := TRUE;
END_FUNCTION

Dans la boucle cyclique, on ne recalcule alors que cos θ / sin θ (qui dépendent de q) et on réutilise cAlpha / sAlpha. C'est le principe du chapitre 1 : on précalcule tout ce qui est constant hors de la boucle chaude.

Récapitulatif