Tutoriel · Avancé · 22 min de lecture
Cinématique inverse d'un bras robotique à deux segments
Comprenez la cinématique inverse d'un bras plan à deux segments : coude haut et coude bas, espace de travail atteignable, singularités et cibles inatteignables.
La cinématique directe est facile : étant donné les angles articulaires, multipliez les transformées des segments et vous savez où est l’outil. La cinématique inverse va dans l’autre sens — étant donné une position cible, trouvez des angles articulaires qui l’atteignent — et c’est dans ce sens que vivent les problèmes intéressants. Un bras plan à deux segments est le plus petit exemple qui les montre tous. Explorez-le dans le simulateur de bras robotique.
L’espace de travail atteignable
Avec des longueurs de segments L₁ et L₂, l’outil ne peut atteindre que des points dans un anneau. Le rayon extérieur est L₁ + L₂ (bras pleinement étendu) et le rayon intérieur est |L₁ − L₂| (bras pleinement replié). Tout ce qui est hors de l’anneau est trop loin ; tout ce qui est dans le trou est trop près. Aucun angle articulaire ne peut atteindre ces points, donc la bonne réponse est simplement « inatteignable ».
Deux solutions : coude haut et coude bas
Pour la plupart des cibles dans l’anneau, le coude peut plier des deux façons tout en posant l’outil sur le même point. Ce sont les configurations coude haut et coude bas. Elles viennent d’un ± dans l’angle en loi des cosinus pour la seconde articulation :
cos(θ₂) = (x² + y² − L₁² − L₂²) / (2·L₁·L₂)
θ₂ = ± acos(cos(θ₂)) # + is one elbow, − is the other
θ₁ = atan2(y, x) − atan2(L₂·sin θ₂, L₁ + L₂·cos θ₂)
Parce qu’il y a deux réponses valides, la cinématique inverse n’est pas une fonction unique — c’est un choix. Les robots choisissent la configuration qui tient les articulations loin de leurs limites, minimise le mouvement depuis la pose courante, ou évite un obstacle.
Singularités
Aux bords de l’espace de travail — bras pleinement étendu ou pleinement replié — les deux solutions fusionnent en une. Ce sont des singularités, et près d’elles un petit mouvement de l’outil peut exiger un énorme mouvement des articulations. Les planificateurs ralentissent ou contournent les poses singulières parce que le bras perd brièvement la capacité de déplacer l’outil dans une direction.
Quand n’y a-t-il pas de solution ?
Une cible n’a pas de solution quand elle tombe hors de l’anneau atteignable, quand elle exigerait un angle articulaire que le matériel interdit, ou quand elle demande une orientation que les articulations disponibles ne peuvent produire. Un bon simulateur distingue ces cas honnêtement au lieu de s’aimanter à la pose la plus proche en prétendant avoir réussi.
Solveurs analytiques contre numériques
Pour deux ou trois segments, la géométrie a une solution en forme close (analytique), donc vous calculez les angles articulaires directement, exactement et vite. Cela rend la CI analytique idéale pour enseigner et tester. Les bras redondants ou pleinement spatiaux n’ont généralement pas de forme close et exigent des solveurs numériques qui itèrent vers une solution — plus généraux, mais plus lents et plus durs à raisonner parce qu’ils peuvent rater des solutions ou caler près des singularités.
Le solveur, écrit en sécurité
Les équations ci-dessus sont correctes et produiront quand même NaN sur du vrai matériel. Voici les mêmes maths avec chaque panne traitée explicitement :
enum IkStatus { IK_OK, IK_TOO_FAR, IK_TOO_CLOSE, IK_JOINT_LIMIT, IK_SINGULAR };
struct IkResult {
IkStatus status;
float theta1, theta2; // radians
};
IkResult solveIk(float x, float y, float L1, float L2, bool elbowUp) {
IkResult r{};
const float r2 = x * x + y * y;
const float reach = sqrtf(r2);
// 1. Reachability FIRST, before any trigonometry.
// A small epsilon keeps a target exactly on the boundary from
// producing an acos argument of 1.0000001 through rounding.
const float eps = 1e-4f;
if (reach > L1 + L2 - eps) { r.status = IK_TOO_FAR; return r; }
if (reach < fabsf(L1 - L2) + eps) { r.status = IK_TOO_CLOSE; return r; }
// 2. Now acos is guaranteed to be in range.
float c2 = (r2 - L1 * L1 - L2 * L2) / (2.0f * L1 * L2);
c2 = constrain(c2, -1.0f, 1.0f); // belt and braces
const float s2 = sqrtf(1.0f - c2 * c2);
r.theta2 = elbowUp ? atan2f(s2, c2) : atan2f(-s2, c2);
// 3. atan2, never atan — this is what gets all four quadrants right.
r.theta1 = atan2f(y, x) - atan2f(L2 * sinf(r.theta2), L1 + L2 * cosf(r.theta2));
// 4. Warn near the boundary, where the arm is ill-conditioned.
if (reach > (L1 + L2) * 0.95f) { r.status = IK_SINGULAR; return r; }
// 5. Mechanical limits are part of the answer, not an afterthought.
if (r.theta1 < T1_MIN || r.theta1 > T1_MAX ||
r.theta2 < T2_MIN || r.theta2 > T2_MAX) { r.status = IK_JOINT_LIMIT; return r; }
r.status = IK_OK;
return r;
}
Cinq détails séparent ceci de la version des manuels, et chacun correspond à une vraie panne :
L’atteignabilité avant la trigonométrie. Si la cible est au-delà de L1 + L2, la règle du cosinus produit un argument hors de −1 à 1 et acos renvoie NaN. Ce NaN coule ensuite dans la commande des servos comme un angle poubelle — et c’est pourquoi une cible inatteignable produit si souvent un mouvement violent plutôt qu’un refus de bouger.
L’epsilon. Une cible calculée pile sur la frontière peut, après arrondi en flottants, donner c2 = 1,0000001. Tester l’atteignabilité avec une petite marge l’attrape avant acos.
atan2f, pas atan. atan simple prend un seul quotient et ne peut distinguer une cible devant de sa miroir derrière, donc le bras marche dans deux quadrants et se reflète dans les deux autres. C’est le bogue le plus courant d’une première implémentation de CI.
Un avertissement de singularité plutôt qu’un refus. Près de la pleine extension, la solution reste valide mais mal conditionnée — un petit mouvement d’outil exige un énorme mouvement articulaire. La signaler laisse l’appelant ralentir ou contourner.
Les limites articulaires comme statut. Un bras dont l’épaule ne peut pas descendre sous −10° a des cibles géométriquement atteignables et mécaniquement non. Borner silencieusement produit un bras qui va quelque part près de la cible en annonçant succès.
Choisir entre les deux solutions
Les maths donnent les deux branches et ne peuvent pas dire laquelle utiliser. C’est une décision de robotique, et il y a quatre règles courantes :
| Règle | Choisissez la branche qui… | Bonne pour |
|---|---|---|
| Mouvement minimal | Est la plus proche des angles articulaires courants | Trajets continus lisses — le défaut usuel |
| Branche fixe | Est toujours coude haut (ou toujours bas) | Prévisibilité ; éviter une table sous le bras |
| Marge aux limites articulaires | Tient les deux articulations le plus loin de leurs limites | Bras au débattement restreint |
| Évitement d’obstacles | Ne heurte pas un obstacle connu | Espaces de travail encombrés |
Le mouvement minimal est le bon défaut, et la raison est ce qui arrive sans lui : si le solveur change silencieusement de branche en cours de trajet, le bras bascule par un mouvement ample, rapide et totalement non planifié. Sur un vrai bras c’est alarmant et peut être destructeur.
// Pick the branch closest to where the arm already is.
IkResult up = solveIk(x, y, L1, L2, true);
IkResult down = solveIk(x, y, L1, L2, false);
if (up.status == IK_OK && down.status == IK_OK) {
const float costUp = fabsf(up.theta1 - cur1) + fabsf(up.theta2 - cur2);
const float costDown = fabsf(down.theta1 - cur1) + fabsf(down.theta2 - cur2);
return (costUp <= costDown) ? up : down;
}
return (up.status == IK_OK) ? up : down;
Les lignes droites exigent la CI à chaque pas
Interpoler entre deux poses dans l’espace articulaire — déplacer chaque articulation linéairement de son angle de départ à son angle d’arrivée — est simple et fait suivre à l’outil une courbe. Pour beaucoup de tâches cela suffit. Pour tout ce qui doit voyager en ligne droite, on interpole dans l’espace cartésien et on résout la CI à chaque pas :
void moveStraight(float x0, float y0, float x1, float y1, int steps) {
for (int i = 1; i <= steps; i++) {
const float t = (float)i / steps;
const float x = x0 + (x1 - x0) * t;
const float y = y0 + (y1 - y0) * t;
IkResult r = solveClosest(x, y);
if (r.status != IK_OK) { abortMove(r.status); return; } // do not skip the point
commandJoints(r.theta1, r.theta2);
}
}
Deux choses deviennent visibles la première fois qu’on le fait. La vitesse articulaire n’est pas constante alors même que la vitesse d’outil l’est — près du bord de l’espace de travail, les articulations doivent bouger bien plus vite pour le même mouvement d’outil. Et une ligne droite peut traverser une région que le bras ne peut atteindre alors même que les deux extrémités vont bien, et c’est pourquoi le contrôle de statut dans la boucle compte plutôt que juste aux bouts.
Des angles articulaires aux comptes de servos
Le solveur renvoie des angles articulaires dans un repère mathématique. Un servo ne connaît rien de ce repère : son zéro est là où le palonnier a été monté, et il peut tourner dans le sens opposé.
struct Joint {
uint8_t channel;
float offsetDeg; // servo angle when the joint is at mathematical zero
int8_t sign; // +1 or -1
float minDeg, maxDeg;
};
int servoAngle(const Joint &j, float jointRad) {
const float deg = jointRad * 57.2957795f;
const float clamped = constrain(deg, j.minDeg, j.maxDeg);
return (int)(j.offsetDeg + j.sign * clamped);
}
Cette structure est tout le pont entre la cinématique et le matériel, et la garder explicite permet de distinguer un bogue de solveur d’un bogue d’étalonnage. Un bras qui bouge en douceur et atterrit nulle part près de la cible est presque toujours ceci, pas les maths.
Dépannage
| Symptôme | Cause probable | Correctif |
|---|---|---|
| Atteint correctement devant, reflète derrière | atan au lieu de atan2 |
atan2 résout les quatre quadrants |
| Renvoie NaN, le bras bondit | Argument de acos hors plage |
Testez l’atteignabilité avant la trigonométrie |
| Les cibles près de la base échouent | Dans le trou |L1 − L2| |
Correct — l’espace de travail est un anneau, pas un disque |
| Le bras bascule violemment en cours de trajet | Le solveur a changé de branche | Choisissez la branche la plus proche de la pose courante |
| La vitesse articulaire pique près de la pleine extension | Singularité | Gardez les trajets planifiés dans ~95 % de la portée max |
| Boucle bien, atterrit au mauvais endroit | Décalage ou signe de servo | Étalonnez le décalage et le sens de chaque articulation |
| Systématiquement court à pleine portée | Affaissement sous gravité, pas maths | Vérifiez le budget de couple de l’épaule ; contrepoids |
| Atterrit différemment selon l’approche | Jeu du réducteur | Approchez toujours une cible du même côté |
| Le déplacement en ligne droite échoue en cours | Le trajet sort de l’espace de travail entre les extrémités | Vérifiez le statut à chaque pas d’interpolation |
Essayez vous-même
Dans le simulateur de bras robotique, glissez la cible et regardez le bras résoudre pour elle en temps réel. Basculez la branche du coude pour voir la solution alternative, poussez la cible hors de l’anneau pour voir un honnête « inatteignable », et avancez vers le bord de l’espace de travail pour sentir une singularité.
Explorer le graphe
Fait partie de ces réalisations
Les projets et parcours d'apprentissage qui incluent ce tutoriel.
Pour aller plus loin