Tutoriel · Intermédiaire · 35 min de lecture

Cinématique des roues mecanum : quatre roues, trois axes

Dérivez la cinématique inverse d'une base mecanum depuis un galet à 45°, inversez-la pour l'odométrie, et la quatrième équation détecte le patinage.

Un galet décide de tout

Démontez une roue mecanum du robot et tenez-la. Elle tourne sur son essieu comme toute roue. Autour de sa jante se tiennent huit ou neuf galets en tonneau, chacun sur son petit essieu, chacun libre de tourner, et chacun à 45° de l’essieu de la roue.

Demandez maintenant quel effort cette roue peut mettre dans le sol.

Elle ne peut pas pousser le long de la direction de roulement du galet — le galet tourne et rien n’est transmis. Elle ne peut que pousser le long de l’essieu du galet, la seule direction où le galet refuse de rouler. Donc l’effort au sol de la roue est verrouillé sur une seule ligne au sol, à 45° du châssis, et aucun couple moteur ne change laquelle.

Voilà tout le mécanisme. Tout ce qui suit est de la comptabilité.

Une roue mecanum seule dessinée de côté au-dessus d'une ligne de sol. Le pneu est hachuré de barres de galets à 45 degrés, légendées galets libres de tourner, essieux à 45 degrés. Depuis la zone de contact au sol, une flèche rouge pointe vers le bas et la droite à 45 degrés légendée effort au sol F racine de deux, plafonné par mu N. Une flèche cyan pointe horizontalement le long du sol légendée F utile, dessinée comme la composante horizontale de la flèche rouge, avec une ligne en pointillés fermant le triangle rectangle et un arc marquant l'angle de 45 degrés. Une légende note que le frottement plafonne tout le vecteur à 45 degrés mais que seule sa partie horizontale entraîne le robot, donc l'effort utile par roue vaut mu N sur racine de deux, environ 71 pour cent d'une roue simple.
Toute la pénalité de traction en un triangle. Le galet ne transmet d'effort que le long de son propre essieu, donc l'effort au sol de la roue est coincé à 45° — le frottement plafonne tout ce vecteur tandis que seule sa composante horizontale déplace le robot. Télécharger le SVG

La contrainte, roue par roue

Posez le repère robot usuel : x vers l’avant, y vers la gauche du robot, ω antihoraire. C’est la convention ROS, et en utiliser une autre signifie se battre contre chaque bibliothèque jamais touchée.

Posez la roue i en position (xᵢ, yᵢ) dans ce repère, et donnez-lui une main de galet sᵢ = ±1 disant laquelle des deux lignes à 45° ses galets suivent. Alors la vitesse de jante vᵢ de la roue (en mètres par seconde au pneu, pas en radians) se relie à la torsion du châssis par :

vᵢ = vx + sᵢ·vy + (sᵢ·xᵢ − yᵢ)·ω

Trois termes, et chacun vaut une phrase :

  • vx — chaque roue contribue pleinement au mouvement avant, quelle que soit sa main. C’est pourquoi l’avant sur une base mecanum semble parfaitement normal.
  • sᵢ·vy — le mouvement latéral entraîne la moitié des roues dans un sens et l’autre moitié dans l’autre. La main décide quelle moitié.
  • (sᵢ·xᵢ − yᵢ)·ω — le bras de levier. Notez que ce n’est pas juste la voie ; la main du galet est dedans, et c’est pourquoi une roue dans le mauvais coin ruine la rotation autant que le ripage.

Pour passer de la vitesse de jante à ce que vous commandez vraiment à un moteur, divisez par le rayon de roue : ωroue = vᵢ / r, en rad/s.

Quatre roues : la matrice

Empilez ces quatre lignes et vous avez une matrice 4 × 3 A, avec la torsion du châssis ξ = (vx, vy, ω) :

v = A · ξ

Pour la disposition standard — avant-gauche et arrière-droite d’une main, avant-droite et arrière-gauche de l’autre — écrivez k = lx + ly où lx est la moitié de l’empattement (avant à arrière) et ly la moitié de la voie (gauche à droite). Alors :

Roue vx vy ω
Avant gauche 1 −1 −k
Avant droite 1 +1 +k
Arrière gauche 1 +1 −k
Arrière droite 1 −1 +k

Soit les quatre lignes que tout le monde écrit :

float fl = vx - vy - K * omega;
float fr = vx + vy + K * omega;
float rl = vx + vy - K * omega;
float rr = vx - vy + K * omega;

Vérifiez-la contre les quatre choses que vous savez déjà que fait un robot mecanum :

Commande Vitesses des roues (AVG, AVD, ARG, ARD) Ce que vous voyez
Avant, vx = 1 1, 1, 1, 1 Les quatre pareilles. Les parties latérales s’annulent.
Ripage gauche, vy = 1 −1, 1, 1, −1 Les paires diagonales s’opposent. C’est le tour de magie.
Pivot antihoraire, ω = 1 −k, k, −k, k Côté gauche en arrière, côté droit en avant — un virage char ordinaire.
Diagonale, vx = vy = 1 0, 2, 2, 0 Deux roues s’arrêtent complètement. Retenez cela.
Quatre panneaux de dessus du même robot mecanum nez en haut, chacun montrant dans quel sens tournent les quatre roues pour une commande, avec les vitesses de roues listées comme avant gauche, avant droite, arrière gauche, arrière droite, normalisées à la roue la plus rapide. Avant : plus un sur les quatre, toutes flèches pareilles, noté comme parties latérales s'annulant. Ripage gauche : moins un, plus un, plus un, moins un, avec les paires diagonales opposées et une flèche ambre vers la gauche. Pivot antihoraire : moins un, plus un, moins un, plus un, côté gauche en arrière et côté droit en avant, avec un arc ambre. Diagonale : zéro, plus un, plus un, zéro, avec une flèche ambre vers avant-gauche et une note que deux roues font tout pendant que deux ne font rien.
Les quatre commandes, droit sorties de la matrice. Notez la dernière : une diagonale demande tout à deux roues et rien à deux autres, et c'est d'où vient le plafond diagonal à 71 %. Télécharger le SVG

L’exemple calculé

Une base avec lx = 75 mm, ly = 85 mm, roues de 97 mm (r = 48,5 mm), à qui on demande vx = 0,30 m/s, vy = 0,20 m/s, ω = 0,50 rad/s. Donc k = 0,160 m.

Roue Calcul Vitesse de jante Vitesse moteur
Avant gauche 0.30 − 0.20 − 0.160 × 0.50 0.020 m/s 0.41 rad/s (3.9 rpm)
Avant droite 0.30 + 0.20 + 0.160 × 0.50 0.580 m/s 11.96 rad/s (114 rpm)
Arrière gauche 0.30 + 0.20 − 0.160 × 0.50 0.420 m/s 8.66 rad/s (83 rpm)
Arrière droite 0.30 − 0.20 + 0.160 × 0.50 0.180 m/s 3.71 rad/s (35 rpm)

Vingt-neuf fois la vitesse sur une roue contre une autre, pour une commande qui n’a pas l’air extrême. Une transmission mecanum passe l’essentiel de sa vie avec ses quatre moteurs faisant des choses follement différentes, et c’est pourquoi « elles reçoivent toutes la même PWM et ça roule droit » n’est pas un test qui signifie quoi que ce soit ici.

Saturation : mise à l’échelle, jamais d’écrêtage

Demandez plus que n’ont les moteurs et il faut abandonner quelque chose. Il y a exactement deux choix et ils ne sont pas équivalents.

float peak = max(max(fabs(fl), fabs(fr)), max(fabs(rl), fabs(rr)));
if (peak > V_MAX) {            // SCALE: keep the direction, lose the speed
  float s = V_MAX / peak;
  fl *= s; fr *= s; rl *= s; rr *= s;
}

contre la chose qui arrive si vous écrivez juste constrain(fl, -255, 255) sur chaque moteur : l’écrêtage. L’écrêtage garde la vitesse et jette la direction. Demandez 0,60 m/s avant, 0,15 latéral et 0,8 rad/s de pivot sur un robot dont les roues plafonnent à 0,50 m/s :

Cap Avant Pivot Verdict
Commandé 14.0° 0.60 m/s 0.80 rad/s —
Échelle 14.0° 0.34 m/s 0.46 rad/s Plus lent sur chaque axe, exactement où vous pointiez
Écrêtage 5.6° 0.46 m/s 0.28 rad/s Plus vite avant, 8,4° hors cap, et 40 % de pivot en moins

Le plus vicieux est que l’écrêtage semble bien sur les commandes testées. Une diagonale pure demande 2 à deux roues et 0 aux deux autres ; les zéros sont déjà dans la limite, donc écrêtage et échelle produisent des nombres identiques et le bogue se cache. Il apparaît plus tard, sous un suiveur de trajectoire, comme un robot qui s’écarte des virages sans raison trouvable. Regardez les deux limiteurs sur la même course et la différence est une courbe visible.

En arrière : l’odométrie

La cinématique directe est la même matrice lue dans l’autre sens. Mais A fait 4 × 3 — quatre mesures, trois inconnues — donc il n’y a pas d’inverse. Il y a une solution aux moindres carrés :

ξ = (Aᵀ A)⁻¹ Aᵀ v

Pour la disposition standard, AᵀA sort diagonale et le tout s’effondre en trois lignes mettables sur un Arduino :

vx    = (fl + fr + rl + rr) / 4.0;
vy    = (-fl + fr + rl - rr) / 4.0;
omega = (-fl + fr - rl + rr) / (4.0 * K);

Intégrez cela via le cap du robot à chaque tic et vous avez de l’estime :

float dt = (now - last) * 1e-6f;
theta += omega * dt;
x += (vx * cosf(theta) - vy * sinf(theta)) * dt;
y += (vx * sinf(theta) + vy * cosf(theta)) * dt;

Ce qui marche exactement aussi bien que l’hypothèse dessous — que les roues ne patinent pas. Sur une base mecanum, le patinage est le principe de la roue, donc traitez l’odométrie latérale comme le nombre le moins fiable du robot.

La quatrième équation que personne n’utilise

Quatre équations, trois inconnues. Cette équation en trop n’est pas du gaspillage — c’est le seul diagnostic gratuit de toute la transmission.

Si le robot est rigide et que chaque roue accroche, les quatre vitesses de jantes ne peuvent pas être arbitraires. Additionnez les deux premières lignes et les deux dernières de la matrice et les deux font 2·vx, donc :

FL + FR = RL + RR

toujours, exactement, pour tout mouvement qu’un châssis mecanum rigide peut exécuter. Calculez donc le résidu à chaque boucle :

float residual = (fl + fr) - (rl + rr);   // should be ~0 forever

Non nul signifie que les quatre encodeurs décrivent un mouvement qu’aucun objet solide ne peut faire. Causes, par ordre approximatif de probabilité : une roue en l’air, une roue sur une flaque, un galet grippé, un accouplement qui patine, une voie d’encodeur morte — ou une roue montée dans le mauvais coin, ce qui rend le résidu grand et constant plutôt que par à-coups.

Soyez honnête sur ce qu’il ne peut pas voir : si les quatre roues patinent pareil en même temps, le résidu reste à zéro et le robot glisse latéralement hors cap dans un silence total. Cette cécité est exactement pourquoi l’odométrie mecanum échoue discrètement au lieu de bruyamment, et pourquoi les montages sérieux ajoutent une IMU ou des roues folles instrumentées plutôt que de croire les encodeurs de traction.

Les deux conséquences du 45°

Les deux tombent de la matrice, et aucune ne s’achète.

Le mouvement diagonal est limité à 1/√2. Une commande à 45° met vx + vy sur deux roues et 0 sur les deux autres, donc la paire occupée atteint la limite de vitesse quand le robot ne roule qu’à V_MAX / √2 ≈ 71 % de sa vitesse de pointe en ligne droite. Des moteurs plus rapides déplacent le plafond ; ils ne le suppriment pas.

La traction est limitée à 1/√2 aussi, pour une autre raison. L’effort moteur utile F se tient le long de la direction de roulement, mais l’effort réel au sol vaut F·√2 le long de l’essieu du galet, et le frottement plafonne cela. Donc par roue l’effort utile est plafonné à μN/√2, et le plafond d’accélération du robot est :

a_max = μ·g / √2

Sur μ = 0,85 cela fait 5,9 m/s², là où le même robot sur roues simples aurait 8,3. La masse du robot s’annule des deux côtés, donc le lest n’aide pas — une leçon exactement opposée à un robot sumo, où le poids est toute la stratégie.

Un graphe de la vitesse latérale contre la vitesse avant en mètres par seconde, montrant chaque vitesse qu'un robot mecanum avec des roues à 0,60 mètre par seconde peut atteindre. L'ensemble atteignable est un losange cyan plein avec des sommets à plus et moins 0,60 sur chaque axe. Un cercle gris en pointillés de rayon 0,60 est dessiné pour comparaison et légendé sans pénalité de direction. Des marqueurs ambre se tiennent à 0,60 mètre par seconde avant et 0,60 mètre par seconde latéral sur les sommets du losange, et un marqueur rouge sur l'arête diagonale est légendé 0,424 mètre par seconde sur la diagonale.
Chaque vitesse que le robot peut atteindre, résolue direction par direction. Tout droit avant et tout droit latéral sont gratuits ; l'arête diagonale du losange est là où la commande vous coûte 1/√2. Seule une transmission sans 45° dedans obtient le cercle. Télécharger le SVG

En micrologiciel

Mettez la géométrie à un endroit et ne la laissez jamais taper deux fois.

struct Mecanum {
  float lx, ly, r;                 // half wheelbase, half track, wheel radius (m)
  float k() const { return lx + ly; }

  // twist -> four wheel angular velocities, rad/s
  void inverse(float vx, float vy, float w, float out[4]) const {
    out[0] = (vx - vy - k() * w) / r;   // FL
    out[1] = (vx + vy + k() * w) / r;   // FR
    out[2] = (vx + vy - k() * w) / r;   // RL
    out[3] = (vx - vy + k() * w) / r;   // RR
  }

  // four measured wheel angular velocities -> twist, plus the residual
  float forward(const float in[4], float &vx, float &vy, float &w) const {
    float fl = in[0] * r, fr = in[1] * r, rl = in[2] * r, rr = in[3] * r;
    vx = (fl + fr + rl + rr) * 0.25f;
    vy = (-fl + fr + rl - rr) * 0.25f;
    w  = (-fl + fr - rl + rr) * 0.25f / k();
    return (fl + fr) - (rl + rr);       // the free diagnostic
  }
};

Mesurez lx et ly de centre de roue à centre de roue, puis divisez par deux — pas la plaque du châssis, généralement plus large. Se tromper de k n’empêche pas le robot de rouler ; cela fait saigner rotation et translation l’une dans l’autre, donc une commande de pivot pur rampe et un ripage pur courbe. Si votre robot rampe en pivotant sur place, suspectez k avant le code.

Dépannage

Symptôme Cause probable Quoi vérifier
Avant bien, ripage qui courbe et pivote Une roue dans le mauvais coin Le contrôle X ci-dessus ; le résidu sera grand et stable
Ripe dans le sens opposé à la commande Les quatre roues en miroir Permutez la paire gauche avec la paire droite
Pivoter sur place translate aussi k faux, ou lx/ly mesurés sur le châssis au lieu des roues Remesurez de centre de roue à centre de roue
Diagonale bien plus lente qu’avant Le plafond 1/√2 — pas une panne Rien à corriger ; concevez avec
Le robot s’écarte des virages sous suiveur de trajectoire Écrêtage par moteur au lieu de mise à l’échelle Mettez toute la commande à l’échelle par la pire roue
Odométrie qui dérive vite en latéral, bien en avant Patinage des galets, fonctionnement nominal IMU pour le cap, roues instrumentées pour la position
Résidu qui pique sur un seul sol Cette zone glisse, ou un galet est grippé Tournez chaque galet à la main

Suite

Faites tourner les nombres contre un robot qui peut patiner : le simulateur de traction mecanum enfonce ces équations exactes dans un modèle de frottement, pour voir l’odométrie et le sol en désaccord. Puis faites tenir aux roues les vitesses calculées — commande de vitesse des roues en boucle fermée — et faites signifier « avant » avant sur le sol plutôt que sur le robot avec la commande orientée terrain.

Explorer le graphe

Fait partie de ces réalisations

Les projets et parcours d'apprentissage qui incluent ce tutoriel.

Pour aller plus loin

Références