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 KISS | eRPM en DShot bidirectionnel | |
|---|---|---|
| Fil | Pastille 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-1 | Le fil de signal DShot lui-même |
| Format | Trame de 10 octets, 115200 bauds, 8N1 | Courte réponse après chaque trame DShot, décodée par le contrôleur |
| Contenu | Température, tension, courant, consommation, eRPM | eRPM uniquement |
| Cadence | Une trame par demande, un moteur à la fois | À chaque trame DShot, pour chaque moteur |
| Prérequis | Bit de demande de télémétrie positionné dans la trame DShot | Firmware 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 :
| Octet | Champ | Unité |
|---|---|---|
| 0 | Température | °C |
| 1-2 | Tension | 0,01 V |
| 3-4 | Courant | 0,01 A |
| 5-6 | Consommation | mAh |
| 7-8 | Vitesse électrique | 100 eRPM |
| 9 | CRC-8 sur les octets 0 à 8 | polynô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 :
eRPM = 60,000,000 / period_usLe 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 :
pole_pairs = magnets / 2rpm = eRPM / pole_pairs12N14P motor: 14 magnets, 7 pole pairs31,000 rpm -> 217,000 eRPM -> KISS speed field = 2170Le 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 :
| Option | Câblage | Avantages | Inconvénients |
|---|---|---|---|
| A : l'hôte lit le fil de télémétrie | Pastille 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 commande | Décodage visible en Python, trames brutes disponibles pour le débogage | Deux ports série, demande et lecture à coordonner |
| B : le microcontrôleur transmet les valeurs décodées | Pastille de télémétrie vers une UART du microcontrôleur | Un seul port série, télémétrie interrogée en continu | Le 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.py25 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 :
pyserial>=3.5plugs/esc_telemetry.py21 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.yaml81 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_allL'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.py37 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
| Mesure | Fiable pour | À ne pas utiliser pour |
|---|---|---|
| Vitesse moteur (depuis l'eRPM) | Vitesse à un gaz donné, désynchronisation, mauvais nombre de pôles | Précision absolue avant une vérification au tachymètre |
| Température de l'ESC | Comparaison entre voies, dérive thermique pendant un run | La température du bobinage moteur |
| Tension de l'ESC | Chute du bus au niveau de l'ESC, recoupement avec un multimètre | Tension de bus étalonnée |
| Courant de l'ESC | Défauts grossiers, recoupement avec un shunt | Limites pass/fail, surtout à faible courant |
| Taux d'erreur de trame | Problèmes de câblage, de masse et de débit | Qualité 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.
