Le protocole DShot décortiqué : trames, CRC et DShot bidirectionnel

Trame DShot 0x82C6 : bits de gaz, bit de télémétrie et CRC

Écrit par

dans

Sur un multirotor, le contrôleur de vol recalcule la consigne de chaque moteur plusieurs milliers de fois par seconde. Le lien entre le contrôleur de vol et les contrôleurs de moteur (ESC) est donc devenu un point critique : il doit être rapide, insensible aux parasites et, idéalement, capable de remonter des informations. Le DShot répond à ces trois besoins. Cet article décortique le protocole au niveau du bit, puis sa variante bidirectionnelle qui renvoie la vitesse de rotation des moteurs sur le même fil.

Un calculateur en fin d’article encode et décode les trames décrites ici, commande comme réponse bidirectionnelle. Le côté pratique (réglages Betaflight, câblage, choix de la variante) est traité sur La fabrique du drone.

Du PWM au DShot

Historiquement, une consigne d’actionneur RC est une impulsion dont la largeur code la valeur : 1 ms pour 0 %, 2 ms pour 100 %, répétée environ toutes les 20 ms (50 Hz). Ce codage analogique convient parfaitement à un servo ou au variateur d’un avion, dont la bande passante utile se limite à celle des doigts du pilote. Il pose en revanche trois problèmes sur un drone :

  • Fréquence de rafraîchissement : même poussé à 400–490 Hz, le PWM reste très en dessous des boucles de régulation modernes (4 à 8 kHz) ;
  • Précision : la valeur dépend de la mesure d’une durée par l’ESC, donc de la gigue des horloges et du bruit sur la ligne ; il faut en plus calibrer les bornes de gaz de chaque ESC ;
  • Immunité au bruit : une impulsion déformée par un parasite donne une consigne fausse, sans aucun moyen de le détecter.

Les premières évolutions ont simplement raccourci les impulsions pour augmenter la cadence, sans changer de principe :

ProtocoleCodagePlage d’impulsionCadence maximale typiqueCalibration
PWManalogique1000–2000 µs≈ 490 Hzoui
OneShot125analogique125–250 µs≈ 4 kHzoui
OneShot42analogique42–84 µs≈ 11 kHzoui
Multishotanalogique5–25 µs≈ 32 kHzoui
DShot300numériquetrame de 53 µs≈ 16 kHz (théorique)non
DShot600numériquetrame de 27 µs≈ 32 kHz (théorique)non
Évolution des protocoles contrôleur de vol → ESC

OneShot et Multishot repoussent la limite de cadence mais gardent les défauts d’un signal analogique : plus l’impulsion est courte, plus quelques dizaines de nanosecondes d’erreur pèsent lourd. Le DShot (Digital Shot) transmet au contraire une valeur numérique protégée par une somme de contrôle : l’ESC reçoit exactement la valeur émise, ou rejette la trame.

Le DShot côté physique

Le DShot utilise le même fil que le PWM, avec les mêmes niveaux logiques (3,3 V ou 5 V selon le contrôleur). Le nombre qui suit le nom est le débit en kbit/s : le DShot600 transmet 600 kbit/s. Il n’y a pas d’horloge séparée : chaque bit commence par un front montant, et c’est la durée de l’état haut qui donne sa valeur.

bit 0T0H = 0,625 µs (37,5 %)bit 1T1H = 1,25 µs (75 %)période d’un bit = 1,67 µs (DShot600)HL
Un bit DShot : la ligne monte en début de période ; un état haut court code un 0, un état haut long code un 1.

Le rapport est de 1 à 2 entre les deux durées : environ 37,5 % de la période pour un 0, 75 % pour un 1. Les implémentations varient un peu autour de ces valeurs (Betaflight génère par exemple ses bits avec un timer et les ESC tolèrent un écart confortable), car le récepteur se contente de comparer la durée de l’état haut à un seuil. Ce principe est le même que celui des LED adressables WS2812, avec d’autres durées.

VarianteDébitT1H (µs)T0H (µs)Bit (µs)Trame de 16 bits (µs)
DShot150150 kbit/s5,002,506,67106,7
DShot300300 kbit/s2,501,253,3353,3
DShot600600 kbit/s1,250,6251,6726,7
DShot12001200 kbit/s0,6250,3130,8313,3
Durées des variantes DShot (source : brushlesswhoop.com)

Entre deux trames, la ligne reste à l’état bas ; c’est ce silence qui permet à l’ESC de se resynchroniser sur le début de la trame suivante. La cadence d’envoi n’est pas imposée par le protocole : elle suit en général la boucle de régulation du contrôleur de vol.

Format de la trame

Une trame DShot fait toujours 16 bits, transmis poids fort en premier :

  • 11 bits de valeur (0 à 2047) ;
  • 1 bit de demande de télémétrie, qui demande à l’ESC de renvoyer ses mesures sur le fil de télémétrie série, s’il existe ;
  • 4 bits de CRC, calculés sur les 12 bits précédents.
11501401301201101019081716050403121100valeur de gaz : 1046 (11 bits)TCRC : 6Trame 0x82C6 sur la ligne : impulsions longues = 1, courtes = 0, poids fort en premier — 26,7 µs en DShot600
Exemple : valeur 1046 sans demande de télémétrie, soit la trame 0x82C6.

Valeurs de gaz et commandes

Les 11 bits de valeur ne codent pas que les gaz :

  • 0 : moteur désarmé (arrêt) ;
  • 1 à 47 : commandes spéciales, exécutées uniquement moteur arrêté ;
  • 48 à 2047 : gaz, soit 2000 pas de 0 à 100 %. En mode 3D (moteur réversible), la plage est coupée en deux : 48–1047 dans un sens, 1048–2047 dans l’autre.
CommandeRôleRemarque
1 à 5bips de l’ESCattendre au moins 260 ms avant la commande suivante
6demande d’informations ESCréponse sur la télémétrie
7, 8sens de rotation 1 / 2à envoyer 6 fois
9, 10mode 3D désactivé / activéà envoyer 6 fois
12sauvegarde des réglagesà envoyer 6 fois, puis attendre 35 ms
13, 14télémétrie DShot étendue (EDT) activée / désactivéeà envoyer 6 fois
20, 21sens de rotation normal / inverséà envoyer 6 fois
22 à 29LED 0 à 3 allumées puis éteintessurtout BLHeli_32
Principales commandes DShot

Les commandes sensibles sont répétées pour qu’une trame isolée, même avec un CRC valide par hasard, ne puisse pas inverser un moteur. Betaflight les envoie avec le bit de télémétrie à 1.

Le CRC

Le « CRC » du DShot est en réalité un simple XOR des trois quartets de la valeur de 12 bits (11 bits de gaz + bit de télémétrie) :

crc = (v ^ (v >> 4) ^ (v >> 8)) & 0x0F

Sur l’exemple : 1046 décalé d’un bit avec le bit de télémétrie à 0 donne v = 0x82C. Les trois quartets 8, 2 et C donnent 8 ⊕ 2 ⊕ C = 6, d’où la trame 0x82C6. Ce contrôle détecte toute erreur sur un bit isolé, mais pas deux erreurs à la même position dans deux quartets différents : c’est rudimentaire, et suffisant pour une liaison courte répétée des milliers de fois par seconde. Une trame rejetée est simplement remplacée par la suivante.

/* Construit une trame DShot de 16 bits.
   valeur    : 0 = arrêt, 1..47 = commande, 48..2047 = gaz
   telemetrie: bit de demande de télémétrie
   bidir     : true en DShot bidirectionnel (CRC inversé) */
uint16_t dshot_trame(uint16_t valeur, bool telemetrie, bool bidir)
{
    uint16_t v = (uint16_t)((valeur << 1) | (telemetrie ? 1u : 0u));
    uint16_t crc = (v ^ (v >> 4) ^ (v >> 8)) & 0x0F;
    if (bidir)
        crc = (~crc) & 0x0F;
    return (uint16_t)((v << 4) | crc);
}

Le DShot bidirectionnel

À quoi sert la vitesse des moteurs ?

Le contrôleur de vol stabilise le drone à partir d’un gyroscope MEMS, très sensible aux vibrations. La principale source de vibrations est le balourd des hélices et des moteurs, dont la fréquence est exactement la vitesse de rotation de chaque moteur (et ses harmoniques, notamment celle du passage des pales). Si le contrôleur de vol connaît ces vitesses en temps réel, il peut placer des filtres réjecteurs (notch) qui suivent précisément chaque moteur : c’est le filtre RPM de Betaflight. Le filtrage est plus efficace et introduit moins de retard qu’un filtre passe-bas fixe ; un drone qui vibre reste pilotable.

Les ESC savaient déjà renvoyer leur vitesse par un fil de télémétrie série séparé (protocole KISS, à environ 115 kbit/s), mais trop lentement et sur un fil supplémentaire. Le DShot bidirectionnel renvoie la vitesse sur le fil de commande, après chaque trame.

Le principe de l’échange

Contrôleur de vol → ESCESC → contrôleur de volretournementcommande 16 bits : 26,7 µs≈ 30 µsréponse 21 bits à 750 kbit/s : 28 µssignal inversé, CRC inversécodage GCR + transitionsla broche passe en entréeHL
Un cycle DShot600 bidirectionnel complet dure environ 85 µs : commande, retournement de la ligne, réponse de l’ESC.

Deux modifications de la trame de commande signalent à l’ESC qu’une réponse est attendue :

  • le signal est inversé : la ligne est haute au repos et chaque bit commence par un front descendant ; c’est la durée de l’état bas qui code la valeur ;
  • le CRC est inversé : crc = ~(v ^ (v >> 4) ^ (v >> 8)) & 0x0F. La trame 0x82C6 devient ainsi 0x82C9.

Après sa trame, le contrôleur de vol passe la broche en entrée (avec résistance de tirage vers le haut). L’ESC attend environ 30 µs, puis répond avec une trame de 21 bits émise à 5/4 du débit de la commande : 750 kbit/s en DShot600. Le bidirectionnel n’existe qu’à partir du DShot300.

Contenu de la réponse

L’ESC ne transmet pas une vitesse mais la période électrique du moteur en microsecondes, sous une forme flottante très compacte :

  • 3 bits d’exposant e et 9 bits de mantisse m : période = m × 2e µs ;
  • 4 bits de CRC, inversé comme dans la commande : le XOR des quatre quartets de la trame vaut 0xF ;
  • la valeur maximale (période de 65 408 µs) signifie « moteur arrêté ».

Le contrôleur de vol en déduit la vitesse électrique : eRPM = 60 000 000 / période. La vitesse mécanique s’obtient en divisant par le nombre de paires de pôles du moteur (7 pour un moteur à 14 aimants, cas le plus courant sur un 5 pouces) : c’est pour cela que Betaflight demande le paramètre motor_poles.

Le codage GCR

La réponse n’est pas transmise comme la commande. L’ESC émet alors que le contrôleur de vol n’a aucune horloge commune avec lui ; le décodage se fait par suréchantillonnage (Betaflight échantillonne la ligne à environ trois fois le débit). Pour que le récepteur puisse se recaler, il faut garantir des fronts fréquents. Deux étapes s’en chargent :

  • codage GCR 4b/5b : chaque quartet est remplacé par un code de 5 bits choisi pour ne jamais contenir plus de deux 0 consécutifs, même en enchaînant les codes ;
  • codage en transitions (type NRZI) : un bit GCR à 1 provoque un changement de niveau de la ligne, un bit à 0 le laisse inchangé. Une trame commence par un bit de départ à 0, ce qui donne 1 + 4 × 5 = 21 bits.
Quartet01234567
GCR1100111011100101001111101101011011010111
Quartet89ABCDEF
GCR1101001001010100101111110011010111001111
Table de codage GCR 4b/5b

Exemple complet de réponse

Un moteur à 7 paires de pôles tourne à 18 000 tr/min, soit 126 000 eRPM et une période électrique d’environ 476 µs :

ÉtapeRésultat
Période476 µs : tient sur 9 bits, donc e = 0, m = 476
Valeur 12 bits0x1DC
CRC1 ⊕ D ⊕ C = 0, inversé : F → trame 0x1DCF
GCR des quartets 1, D, C, F11011 01101 11110 01111
Transitions, bit de départ en tête0 10010 01001 01011 10101
Décodage côté contrôleur de vol60 000 000 / 476 = 126 050 eRPM, soit 18 007 tr/min
quartet 1GCR 11011quartet DGCR 01101quartet CGCR 11110quartet F (CRC)GCR 01111départ010010010010101110101Chaque point orange est une transition = un bit GCR à 1 ; pas de transition = bit GCR à 0
La réponse 0x1DCF sur la ligne : 21 bits, un niveau par bit, les bits GCR à 1 sont portés par les transitions.

Le décodage suit le chemin inverse : XOR de la trame brute avec elle-même décalée d’un bit pour retrouver les bits GCR, table inverse pour les quartets, vérification du CRC, puis calcul de la période.

/* Table GCR inverse : code 5 bits -> quartet, -1 si code invalide */
static const int8_t gcr_inv[32] = {
    -1, -1, -1, -1, -1, -1, -1, -1, -1,  9, 10, 11, -1, 13, 14, 15,
    -1, -1,  2,  3, -1,  5,  6,  7, -1,  0,  8,  1, -1,  4, 12, -1
};

/* Décode une réponse eRPM de 21 bits (bit de départ en tête, poids fort d'abord).
   Renvoie la vitesse en eRPM, 0 moteur arrêté, -1 si la trame est invalide. */
int32_t dshot_reponse_erpm(uint32_t brut21)
{
    uint32_t gcr = (brut21 ^ (brut21 >> 1)) & 0xFFFFF;  /* transitions -> 20 bits GCR */
    uint32_t trame = 0;
    for (int q = 3; q >= 0; q--) {
        int8_t n = gcr_inv[(gcr >> (q * 5)) & 0x1F];
        if (n < 0)
            return -1;
        trame = (trame << 4) | (uint32_t)n;
    }
    uint32_t x = trame ^ (trame >> 8);
    x ^= x >> 4;
    if ((x & 0x0F) != 0x0F)          /* le XOR des 4 quartets doit valoir 0xF */
        return -1;
    uint32_t v = trame >> 4;         /* 12 bits : eee mmmmmmmmm */
    uint32_t periode_us = (v & 0x1FF) << (v >> 9);
    if (periode_us == 0 || periode_us >= 65408u)
        return 0;                    /* période maximale : moteur arrêté */
    return (int32_t)(60000000u / periode_us);
}

Budget de temps

VarianteCommandeRetournementRéponseCycle completBoucle compatible
DShot30053,3 µs≈ 30 µs56 µs≈ 140 µs4 kHz
DShot60026,7 µs≈ 30 µs28 µs≈ 85 µs8 kHz
DShot120013,3 µs≈ 30 µs14 µs≈ 57 µs8 kHz et plus
Durée d’un échange bidirectionnel

C’est pour cela que Betaflight associe généralement le DShot300 bidirectionnel à une boucle de 4 kHz et le DShot600 à une boucle de 8 kHz : un cycle de 140 µs ne tient pas dans les 125 µs d’une boucle à 8 kHz.

Télémétrie DShot étendue (EDT)

Une fois activée par la commande 13, la télémétrie étendue intercale d’autres mesures entre les trames de vitesse. Elle exploite un motif que les trames de période n’utilisent jamais : une période normalisée a toujours le bit de poids fort de la mantisse à 1 dès que l’exposant est non nul. Une trame dont les 4 premiers bits valent 0x2, 0x4, 0x6… porte donc une donnée de 8 bits d’un autre type :

TypeDonnéeUnité
0x02température°C
0x04tension0,25 V par pas
0x06courantA
0x08, 0x0A, 0x0Cvaleurs de débogageselon le firmware
0x0Eétat / événementsselon le firmware
Types de trames EDT

Le support dépend du firmware de l’ESC : la tension et le courant sont surtout disponibles sur BLHeli_32 et AM32 ; Bluejay (firmware ouvert pour les ESC BLHeli_S) transmet notamment la température.

Calculateur de trames DShot

Le premier onglet construit une trame de commande et sa forme d’onde. Le second suit le chemin de la réponse bidirectionnelle : d’une vitesse de rotation jusqu’aux 21 bits émis par l’ESC, ou l’inverse à partir d’une trame relevée à l’analyseur logique.

Trame (16 bits)

–

CRC

–

Signification

–

Durée

–

Mise en œuvre sur microcontrôleur

Générer des fronts à 1,67 µs près sur quatre moteurs ou plus ne se fait pas en logiciel pur. Côté émission, les contrôleurs de vol STM32 utilisent un timer en mode PWM dont le registre de comparaison est rechargé par DMA à chaque bit : le cœur prépare un tableau de 16 durées et le matériel fait le reste. Côté réception, deux approches coexistent :

  • capture d’entrée par timer : le timer horodate chaque front et le DMA stocke les dates ;
  • mode « bitbang » de Betaflight : le DMA recopie l’état d’un port GPIO complet à intervalle fixe, ce qui permet de lire plusieurs moteurs sur le même port avec un seul flux DMA. Ce mode s’accommode de presque toutes les affectations de broches, ce qui explique qu’il soit souvent choisi automatiquement.

Côté ESC, le protocole bidirectionnel demande un firmware compatible : BLHeli_32, AM32, et pour les ESC BLHeli_S les firmwares Bluejay ou JESC (le BLHeli_S d’origine ne le gère pas). Dans Betaflight, il s’active avec set dshot_bidir = ON ; l’onglet Moteurs affiche alors la vitesse de chaque moteur et le taux d’erreurs de réception, à vérifier avant de voler.

Mise en œuvre en LabVIEW FPGA

Sur une cible FPGA, le DShot se prête bien à une implémentation matérielle : chaque bit est une simple durée à l’état haut, et la réponse bidirectionnelle se lit en échantillonnant la ligne. Les diagrammes ci-dessous viennent d’une implémentation en LabVIEW FPGA ; ils suivent pas à pas les calculs décrits plus haut.

Construire la trame

Construction de la trame : consigne, décalage d’un bit, bit de télémétrie, CRC, puis remise en ordre des bits.
Construction de la trame : consigne, décalage d’un bit, bit de télémétrie, CRC, puis remise en ordre des bits.

La consigne de gaz arrive sur 0–2000 ; le booléen Setting distingue une commande spéciale d’une consigne de gaz, qui est alors décalée au-delà des 47 commandes. La valeur est ensuite décalée d’un bit, le bit de télémétrie ajouté, puis le sous-VI de CRC complète les 16 bits. La trame est enfin convertie en tableau de booléens dans l’ordre d’émission, poids fort en premier.

Le CRC DShot en LabVIEW : XOR de la valeur avec elle-même décalée de 4 et 8 bits, masque 0xF, inversion en mode bidirectionnel.
Le CRC DShot en LabVIEW : XOR de la valeur avec elle-même décalée de 4 et 8 bits, masque 0xF, inversion en mode bidirectionnel.

Le sous-VI de CRC est la traduction directe de (v ^ (v >> 4) ^ (v >> 8)) & 0xF ; la structure Condition inverse le résultat en mode bidirectionnel. Il renvoie à la fois le CRC seul et la trame complète (v << 4) + crc.

Générer les bits

Table des durées par variante et envoi des 16 bits dans une FIFO vers la boucle de génération.
Table des durées par variante et envoi des 16 bits dans une FIFO vers la boucle de génération.

Plutôt que de calculer les durées à la volée, une constante indexée par la variante donne, pour un bit à 1 et pour un bit à 0, trois durées exprimées en périodes d’une horloge à 200 MHz (5 ns) : le temps à l’état haut, la période du bit, et la période d’un bit de la réponse (5/4 du débit). On retrouve les valeurs du tableau de début d’article : en DShot600, 250 périodes (1,25 µs) pour un 1, 125 (0,625 µs) pour un 0, 334 (1,67 µs) par bit et 267 (1,33 µs) par bit de réponse. Les 16 descripteurs de bits sont poussés dans une FIFO, lue par la boucle cadencée qui pilote la broche : la génération des fronts reste ainsi parfaitement régulière, quelle que soit la charge du reste du code.

Décoder la réponse

La reconstitution des 21 bits à partir de la ligne échantillonnée (suréchantillonnage, puis décodage des transitions) se fait dans la boucle d’acquisition. Les 20 bits GCR obtenus passent ensuite par le décodeur ci-dessous.

Décodage GCR : table inverse de 32 entrées, quatre groupes de 5 bits, puis contrôle du CRC.
Décodage GCR : table inverse de 32 entrées, quatre groupes de 5 bits, puis contrôle du CRC.

La table inverse compte 32 entrées : chaque code GCR valide donne son quartet, les 16 autres valent 65535 et signalent une trame corrompue. La boucle traite quatre groupes de 5 bits, du poids fort au poids faible, et reconstruit la trame de 16 bits quartet par quartet. Le sous-VI Check CRC vérifie ensuite que le XOR des quatre quartets vaut 0xF, et la sortie Frame OK combine les deux contrôles.

Période en microsecondes : mantisse sur 9 bits décalée de l’exposant.
Période en microsecondes : mantisse sur 9 bits décalée de l’exposant.
Conversion de la période en eRPM (par centaines) puis en tours par minute.
Conversion de la période en eRPM (par centaines) puis en tours par minute.

Les 12 bits utiles donnent la période : la mantisse (masque 0x1FF) décalée vers la gauche de l’exposant (les 3 bits de poids fort). La conversion suit la convention de Betaflight, qui compte les eRPM par centaines : (600000 + période/2) / période, l’ajout de la demi-période assurant l’arrondi. La vitesse mécanique s’en déduit en multipliant par 200 et en divisant par le nombre d’aimants, soit 100 × eRPM / nombre de paires de pôles.

Les images sont des VI snippets : dans leur version d’origine, un fichier PNG glissé dans un diagramme LabVIEW recrée le code. Les versions publiées ici sont optimisées pour le web et ne servent qu’à la lecture.

Références

Commentaires

Laisser un commentaire