Instrument Control

Lire la télémétrie d'un ESC BLHeli_32 en test

Apprenez à lire la télémétrie d'un ESC BLHeli_32 dans un test TofuPilot : décoder la trame KISS, convertir l'eRPM et valider chaque moteur.

JJulien Buteau
advanced10 min de lecture27 octobre 2026

Un ESC BLHeli_32 mesure déjà la vitesse, la température, la tension et le courant de chaque moteur qu'il pilote. Lire cette télémétrie dans un test donne une vitesse par moteur sans tachymètre et une température par voie sans thermocouple. Apprenez comment fonctionnent la trame de télémétrie KISS et l'eRPM du DShot bidirectionnel, comment convertir l'eRPM en tr/min mécaniques, et comment décoder les trames dans un plug TofuPilot et les enregistrer comme mesures par moteur avec des limites.

Deux canaux de télémétrie

Les ESC BLHeli_32 peuvent renvoyer des données sur deux canaux distincts :

Télémétrie série KISSeRPM en DShot bidirectionnel
FilPastille de télémétrie dédiée (TX de l'ESC), un seul fil partagé par les quatre voies d'un ESC 4-en-1Le fil de signal DShot lui-même
FormatTrame de 10 octets, 115200 bauds, 8N1Courte réponse après chaque trame DShot, décodée par le contrôleur
ContenuTempérature, tension, courant, consommation, eRPMeRPM uniquement
CadenceUne trame par demande, un moteur à la foisÀ chaque trame DShot, pour chaque moteur
PrérequisBit de demande de télémétrie positionné dans la trame DShotFirmware d'ESC compatible DShot bidirectionnel, contrôleur qui émet un DShot inversé et décode la réponse

Utilisez la télémétrie KISS pour la température, la tension et un courant grossier, et le DShot bidirectionnel quand il faut la vitesse à haute cadence, par exemple pendant une rampe. Les deux renvoient une vitesse électrique, que vous convertissez vous-même en tr/min mécaniques.

La trame de télémétrie KISS

La structure ci-dessous est celle que Betaflight décode dans src/main/sensors/esc_sensor.c. Une trame fait 10 octets, envoyés à 115200 bauds, 8N1, les champs sur plusieurs octets en big-endian :

OctetChampUnité
0Température°C
1-2Tension0,01 V
3-4Courant0,01 A
5-6ConsommationmAh
7-8Vitesse électrique100 eRPM
9CRC-8 sur les octets 0 à 8polynôme 0x07, valeur initiale 0

L'ESC n'envoie une trame que sur demande. Le contrôleur positionne le bit de demande de télémétrie dans une trame DShot adressée à un moteur, et cette voie de l'ESC répond sur le fil de télémétrie partagé. Sur un ESC 4-en-1, le contrôleur interroge donc les moteurs à tour de rôle : demande au moteur 1, attente de sa trame, demande au moteur 2, et ainsi de suite. Une trame dont le CRC est faux est rejetée, pas corrigée.

La mise à l'échelle des champs n'est pas identique pour tous les firmwares d'ESC ni pour tous les modes de télémétrie. Vérifiez une fois le champ de vitesse avec un tachymètre optique et le champ de tension avec un multimètre avant de faire confiance à la mise à l'échelle sur un nouveau modèle d'ESC.

L'eRPM en DShot bidirectionnel

En DShot bidirectionnel, le contrôleur émet un signal DShot inversé et l'ESC répond sur le même fil peu après chaque trame. La réponse porte la période d'un tour électrique en microsecondes, dans un format compact exposant et mantisse avec sa propre somme de contrôle sur 4 bits, en codage GCR. La conversion est :

bidir-dshot.txt
eRPM = 60,000,000 / period_us

Le décodage de la réponse est critique en temps : il doit se faire dans le microcontrôleur qui génère le DShot, pas sur le PC du poste. Le microcontrôleur transmet ensuite les eRPM décodés à l'hôte via la liaison série USB. C'est la bonne source pour une courbe de vitesse pendant une rampe, parce qu'elle se met à jour à chaque trame DShot au lieu d'une fois par interrogation de télémétrie.

Convertir l'eRPM en tr/min mécaniques

Les deux canaux renvoient des tours électriques. Un moteur brushless effectue un tour électrique par paire de pôles, donc :

erpm-to-rpm.txt
pole_pairs = magnets / 2rpm        = eRPM / pole_pairs12N14P motor: 14 magnets, 7 pole pairs31,000 rpm  ->  217,000 eRPM  ->  KISS speed field = 2170

Le nombre « N » de 12N14P compte les encoches du stator et n'entre pas dans la formule ; le nombre « P » compte les aimants. Une erreur sur les paires de pôles multiplie chaque lecture de vitesse par un facteur constant, et toutes les unités passent ou échouent ensemble, donc l'erreur passe facilement inaperçue. Contrôle de cohérence : la vitesse à vide se situe un peu sous KV x tension de bus. Un moteur de 1250 KV sur un bus de 25,2 V donne au plus 31 500 tr/min, et le moteur d'exemple a mesuré 31 000 tr/min à 100 % de gaz sans hélice.

Choisir où la trame est décodée

Deux câblages fonctionnent avec un poste TofuPilot :

OptionCâblageAvantagesInconvénients
A : l'hôte lit le fil de télémétriePastille de télémétrie vers le RX d'un adaptateur USB-UART 3,3 V, masse commune ; le microcontrôleur positionne le bit de demande sur commandeDécodage visible en Python, trames brutes disponibles pour le débogageDeux ports série, demande et lecture à coordonner
B : le microcontrôleur transmet les valeurs décodéesPastille de télémétrie vers une UART du microcontrôleurUn seul port série, télémétrie interrogée en continuLe décodage vit dans le firmware

Ce guide utilise l'option A. Le microcontrôleur qui génère le DShot accepte une commande TLM <motor> sur sa propre liaison série USB, positionne le bit de télémétrie dans la trame suivante de ce moteur, et répond OK.

Étape 1 : décoder la trame

Gardez le décodeur sans entrée/sortie, pour pouvoir le tester unitairement avec des trames enregistrées :

plugs/kiss.py
25 lines
FRAME_SIZE = 10def crc8(data):    crc = 0    for byte in data:        crc ^= byte        for _ in range(8):            crc = ((crc << 1) ^ 0x07) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF    return crcdef parse_frame(frame, pole_pairs):    """Renvoie un dict pour une trame KISS valide de 10 octets, None si le CRC échoue."""    if len(frame) != FRAME_SIZE or crc8(frame[:9]) != frame[9]:        return None    erpm = ((frame[7] << 8) | frame[8]) * 100    return {        "temperature_c": frame[0],        "voltage_v": ((frame[1] << 8) | frame[2]) / 100,        "current_a": ((frame[3] << 8) | frame[4]) / 100,        "consumption_mah": (frame[5] << 8) | frame[6],        "erpm": erpm,        "rpm": erpm / pole_pairs,    }

La boucle de CRC est une transposition directe de la fonction updateCrc8 de Betaflight : XOR de l'octet, puis huit décalages avec le polynôme 0x07.

Étape 2 : écrire le plug de télémétrie

Le plug possède le port série de télémétrie et renvoie des dicts simples, qui traversent sans difficulté la frontière du processus de plug :

requirements.txt
pyserial>=3.5
plugs/esc_telemetry.py
21 lines
import serialfrom plugs.kiss import FRAME_SIZE, parse_frameclass EscTelemetry:    def __init__(self, port, pole_pairs):        self.pole_pairs = pole_pairs        self.ser = serial.Serial(port, 115200, timeout=0.05)    def __del__(self):        if hasattr(self, "ser"):            self.ser.close()    def flush(self):        self.ser.reset_input_buffer()    def read_frame(self):        """Renvoie la trame décodée, ou None sur timeout ou CRC faux."""        raw = self.ser.read(FRAME_SIZE)        return parse_frame(raw, self.pole_pairs)

Les plugs ne peuvent pas s'appeler entre eux, donc la phase coordonne les deux : elle vide le port de télémétrie, demande au plug de pilotage moteur de réclamer une trame, puis lit. Le système d'exploitation met en tampon les octets reçus entre-temps, donc la trame n'est pas perdue si elle arrive avant l'appel à read_frame().

Étape 3 : enregistrer des mesures par moteur avec des limites

Faites tourner chaque moteur à un gaz fixe, collectez une série de trames et enregistrez un jeu de mesures par moteur. Une seule fonction sert les quatre phases grâce à with: :

procedure.yaml
81 lines
name: ESC Telemetry Checkversion: 0.1.0execution:  workers: 1plugs:  - name: Motor Driver    key: motor    python: plugs.motor:MotorDriver    scope: station    with:      port: /dev/ttyACM0      pole_pairs: 7  - name: ESC Telemetry    key: esc_telemetry    python: plugs.esc_telemetry:EscTelemetry    scope: station    with:      port: /dev/ttyUSB0      pole_pairs: 7main:  - name: Telemetry Motor 1    key: telemetry_m1    python: phases.telemetry:telemetry_check    timeout: 45s    with:      motor_index: 1      throttle_pct: 70      frames: 50    measurements: &telemetry_measurements      - name: Motor Speed        key: motor_speed        unit: rpm        description: Mean of valid KISS frames at 70 % throttle, no propeller, eRPM / 7        validators:          - operator: ">="            expected_value: 21000          - operator: "<="            expected_value: 24500      - name: ESC Temperature        key: esc_temperature        unit: °C        validators:          - operator: "<="            expected_value: 60      - name: ESC Voltage        key: esc_voltage        unit: V        validators:          - operator: ">="            expected_value: 21.0          - operator: "<="            expected_value: 25.5      - name: ESC Current        key: esc_current        unit: A        description: ESC-reported, uncalibrated. Recorded for cross-check, no limits.      - name: Frame Error Rate        key: frame_error_rate        unit: "%"        validators:          - operator: "<="            expected_value: 5  - name: Telemetry Motor 2    key: telemetry_m2    python: phases.telemetry:telemetry_check    timeout: 45s    with:      motor_index: 2      throttle_pct: 70      frames: 50    measurements: *telemetry_measurements  # Les moteurs 3 et 4 suivent le même modèleteardown:  - name: Stop All Motors    python: phases.telemetry:stop_all

L'ancre YAML (&telemetry_measurements) et l'alias (*telemetry_measurements) relèvent du YAML standard et gardent les quatre phases sur un même jeu de limites. La fenêtre de vitesse encadre les 22 600 tr/min mesurés à 70 % sur le moteur d'exemple ; la limite de température est une valeur d'exemple. Fixez les vôtres à partir d'unités connues bonnes.

phases/telemetry.py
37 lines
import timeRAMP_RATE_PCT_S = 20def telemetry_check(motor, esc_telemetry, measurements, phase, log, motor_index, throttle_pct, frames):    motor.ramp_to(motor_index, throttle_pct, RAMP_RATE_PCT_S)    time.sleep(throttle_pct / RAMP_RATE_PCT_S + 2.0)  # rampe, puis stabilisation    valid, errors = [], 0    try:        for _ in range(frames):            esc_telemetry.flush()            motor.request_telemetry(motor_index)            frame = esc_telemetry.read_frame()            if frame is None:                errors += 1            else:                valid.append(frame)            time.sleep(0.02)    finally:        motor.ramp_to(motor_index, 0, RAMP_RATE_PCT_S)    measurements.frame_error_rate = 100 * errors / frames    if not valid:        phase.fail(f"No valid telemetry frame from motor {motor_index} in {frames} requests")    n = len(valid)    measurements.motor_speed = sum(f["rpm"] for f in valid) / n    measurements.esc_temperature = max(f["temperature_c"] for f in valid)    measurements.esc_voltage = sum(f["voltage_v"] for f in valid) / n    measurements.esc_current = sum(f["current_a"] for f in valid) / n    log.info(f"Motor {motor_index}: {n} valid frames, {errors} errors")def stop_all(motor):    motor.stop(50)

Le plug de pilotage moteur est celui qui génère le DShot sur le banc ; cette phase suppose qu'il expose ramp_to, stop et request_telemetry, cette dernière envoyant TLM <motor> au firmware. Le bloc finally ramène le moteur en rampe même quand une lecture lève une exception, et la phase de teardown arrête tous les moteurs sur toute autre sortie.

Chaque run porte alors vitesse, température, tension, courant et taux d'erreur de trame par moteur, liés au numéro de série de l'unité. Sur le dashboard, une voie dont la température monte d'une unité à l'autre, ou un taux d'erreur de trame qui grimpe sur un poste, apparaît dans la carte de contrôle de la mesure avant que les unités ne commencent à échouer.

À quoi sert chaque mesure

MesureFiable pourÀ ne pas utiliser pour
Vitesse moteur (depuis l'eRPM)Vitesse à un gaz donné, désynchronisation, mauvais nombre de pôlesPrécision absolue avant une vérification au tachymètre
Température de l'ESCComparaison entre voies, dérive thermique pendant un runLa température du bobinage moteur
Tension de l'ESCChute du bus au niveau de l'ESC, recoupement avec un multimètreTension de bus étalonnée
Courant de l'ESCDéfauts grossiers, recoupement avec un shuntLimites pass/fail, surtout à faible courant
Taux d'erreur de trameProblèmes de câblage, de masse et de débitQualité du moteur

Le courant de l'ESC est renvoyé avec un champ de 0,01 A, mais la mesure dans l'ESC n'est pas étalonnée unité par unité et l'offset domine aux quelques ampères qu'un moteur consomme sans hélice. Mesurez le courant avec un shunt quand il compte.

Pièges courants

Utiliser le mauvais nombre de pôles. Un 12N14P a 7 paires de pôles, ni 6 ni 12. Une valeur fausse multiplie chaque tr/min par une constante et passe inaperçue.

Demander la télémétrie à plusieurs moteurs à la fois. Un ESC 4-en-1 a un seul fil de télémétrie. Deux réponses simultanées entrent en collision et échouent au CRC. Interrogez un moteur à la fois.

Lire la trame sans vider le tampon d'abord. Des octets anciens dans le tampon décalent la trame, et chaque trame échoue au CRC. Videz, demandez, lisez.

Oublier la masse commune. Sans elle, l'adaptateur USB-UART lit du bruit et le taux d'erreur de trame grimpe.

Attaquer la pastille de télémétrie avec un adaptateur 5 V. La pastille est une sortie logique basse tension. Utilisez un adaptateur 3,3 V.

Poser des limites sur le courant de l'ESC. Enregistrez-le et comparez, mais jugez le courant avec un shunt.

Laisser les phases en parallèle. Réglez workers: 1 : deux phases qui interrogent le même fil de télémétrie corrompent mutuellement leurs trames.

Points clés

  • La télémétrie KISS est une trame de 10 octets à 115200 bauds : température, tension (0,01 V), courant (0,01 A), consommation (mAh), vitesse (100 eRPM) et un CRC-8 de polynôme 0x07.
  • L'ESC ne répond que si le bit de télémétrie est positionné pour son moteur ; sur un ESC 4-en-1, interrogez les moteurs un à la fois.
  • Le DShot bidirectionnel donne l'eRPM à chaque trame et se décode dans le microcontrôleur ; eRPM = 60 000 000 / période en µs.
  • Tr/min mécaniques = eRPM / paires de pôles, soit 7 pour un moteur 12N14P.
  • Enregistrez vitesse, température, tension et taux d'erreur de trame par moteur avec des limites ; enregistrez le courant de l'ESC sans limites et mesurez le courant avec un shunt.

Pour une vue d'ensemble des bancs de test de moteurs et d'actionneurs, consultez Motor and Actuator Testing with TofuPilot.

Plus de guides

Mettez ce guide en pratique