Un test de moteur brushless qui saute d'un niveau de gaz au suivant produit des pics de courant, une surtension du bus à la décélération et des lectures qui dépendent du timing plutôt que du moteur. Apprenez à piloter les gaz en rampes depuis un microcontrôleur en DShot, à enregistrer gaz, courant et vitesse dans une seule courbe, à interrompre le test sur des limites, et à garantir l'arrêt des moteurs sur chaque chemin de sortie d'une procédure TofuPilot.
Pourquoi des rampes et pas des marches
Une marche de gaz demande au moteur de changer de vitesse instantanément. L'ESC répond avec le courant dont le moteur a besoin pour accélérer son rotor et son hélice, et à la descente il freine le moteur et renvoie l'énergie récupérée dans le bus.
Valeurs d'exemple mesurées sur un moteur de taille 3115, 1250 KV, avec un ESC 4-en-1 BLHeli_32 sur un bus 6S, sans hélice :
| Profil | Effet observé |
|---|---|
| 70 % de gaz constant | 2,65 A, 22 600 tr/min |
| Marches de gaz | Pics de courant jusqu'à environ 10 A par moteur |
| Marche descendante depuis un gaz élevé | Bus porté à 36,6 V sur un bus 6S nominal (22,2 V), alimenté par une alimentation de banc à découpage |
Les pics valent presque quatre fois le courant établi à 70 %, sans hélice. La surtension dépasse les 25,2 V d'une batterie 6S pleine, et une alimentation de banc à découpage ne peut pas absorber le courant renvoyé, donc rien ne retient le bus.
Les commandes de test moteur des contrôleurs de vol aggravent le problème. La commande MAV_CMD_DO_MOTOR_TEST d'ArduPilot fixe un gaz et une durée, et le gaz ne peut pas être modifié pendant un test. Un profil en marches devient donc une suite d'arrêts et de redémarrages, chacun étant une accélération complète depuis l'arrêt. Générer le DShot directement depuis un microcontrôleur retire le contrôleur de vol de la boucle et permet une vraie rampe.
Architecture du banc
Répartissez le travail pour que chaque élément fasse ce qu'il sait faire :
| Élément | Responsabilité |
|---|---|
| PC du poste avec le CLI TofuPilot | Séquence le test, envoie les consignes de rampe, enregistre les mesures, applique les limites |
| Microcontrôleur en série USB | Génère le DShot300 en continu, exécute les rampes avec un timing déterministe, gère un watchdog, renvoie le gaz appliqué et l'eRPM |
| ESC 4-en-1 | Pilote les moteurs, renvoie l'eRPM (DShot bidirectionnel) ou des trames de télémétrie |
| INA228 sur l'entrée DC de l'ESC | Mesure le courant |
| Arrêt d'urgence câblé | Coupe l'alimentation de l'ESC, indépendamment de tout logiciel |
La rampe elle-même s'exécute sur le microcontrôleur. Une boucle Python sur un système d'exploitation de bureau ne peut pas garantir une trame chaque milliseconde ; une interruption de timer, si. L'hôte n'envoie que des consignes et des pentes.
Le DShot en bref
Une trame DShot fait 16 bits : une valeur sur 11 bits, un bit de demande de télémétrie et une somme de contrôle sur 4 bits. Les valeurs de 0 à 2047 se répartissent en 0 (moteur arrêté), 1 à 47 (commandes) et 48 à 2047 (gaz). Le DShot300 transmet à 300 kbit/s, donc une trame dure environ 53 µs. L'ESC attend des trames en continu et s'arme après avoir reçu la valeur 0 pendant un court instant ; le firmware envoie donc 0 dès qu'aucun moteur n'est commandé.
Le gaz en pourcentage se convertit en valeur DShot par 48 + round(pct / 100 x 1999).
Étape 1 : définir le contrat du firmware
Le firmware du microcontrôleur est à écrire par vos soins. Ce qui compte pour le test, c'est un petit protocole texte explicite sur la liaison série USB. Ce guide utilise celui-ci comme exemple :
| Commande | Réponse | Effet |
|---|---|---|
RAMP <motor> <target_pct> <rate_pct_s> | OK | Monte ou descend un moteur depuis son gaz actuel jusqu'à la consigne, à la pente donnée |
STOP <rate_pct_s> | OK | Ramène tous les moteurs à 0 à la pente donnée |
STATUS <motor> | OK <throttle_pct> <erpm> | Gaz actuellement appliqué et dernier eRPM reçu |
Deux comportements sont obligatoires dans le firmware, pas optionnels :
- Watchdog. Si aucune commande n'arrive pendant 500 ms, le firmware ramène tous les moteurs à 0. Un hôte planté, un CLI tué ou un câble USB débranché se terminent alors par un arrêt, pas par un moteur qui reste à 70 %.
- Limite de pente. Le firmware refuse une pente au-dessus d'un maximum fixe, pour qu'une faute de frappe dans une procédure ne transforme pas une rampe en marche.
Étape 2 : écrire le plug moteur
Le plug encapsule le protocole série, convertit l'eRPM en tr/min mécaniques et arrête les moteurs à sa destruction :
pyserial>=3.5smbus2>=0.4plugs/motor.py39 lines
import timeimport serialclass MotorDriver: def __init__(self, port, pole_pairs, baudrate=115200): self.pole_pairs = pole_pairs self.ser = serial.Serial(port, baudrate, timeout=0.2) time.sleep(2.0) # beaucoup de cartes redémarrent à l'ouverture du port self.ser.reset_input_buffer() self._cmd("STOP 50") def __del__(self): if hasattr(self, "ser") and self.ser.is_open: try: self._cmd("STOP 50") finally: self.ser.close() def _cmd(self, line): self.ser.write((line + "").encode()) reply = self.ser.readline().decode().strip() if not reply.startswith("OK"): raise RuntimeError(f"{line!r}: unexpected reply {reply!r}") return reply[2:].strip() def ramp_to(self, motor, target_pct, rate_pct_s): self._cmd(f"RAMP {motor} {target_pct} {rate_pct_s}") def stop(self, rate_pct_s): self._cmd(f"STOP {rate_pct_s}") def status(self, motor): """Renvoie [throttle_pct, rpm] tels qu'appliqués par le firmware.""" throttle, erpm = self._cmd(f"STATUS {motor}").split() # L'eRPM compte des tours électriques : diviser par le nombre de paires de pôles return [float(throttle), int(erpm) / self.pole_pairs]Un moteur 12N14P a 14 aimants, soit 7 paires de pôles : 158 200 eRPM correspondent à 22 600 tr/min. Chaque interrogation STATUS compte aussi comme une commande pour le watchdog, donc une phase qui enregistre des données maintient le firmware actif.
Étape 3 : enregistrer la rampe dans une seule courbe
Gaz, courant et vitesse partagent un axe de temps. Enregistrez-les comme une seule mesure multidimensionnelle à trois axes y, et posez les limites de validation sur des agrégations :
procedure.yaml72 lines
name: Motor Ramp Testversion: 0.1.0execution: workers: 1plugs: - name: Motor Driver key: motor python: plugs.motor:MotorDriver scope: station with: port: /dev/ttyACM0 pole_pairs: 7 - name: Current Monitor key: current_monitor python: plugs.ina228:INA228 scope: station with: bus: 1 address: 0x40 shunt_ohm: 0.0005setup: - name: Motors Stopped python: phases.ramp:stop_allmain: - name: Ramp Motor 1 to 70 % key: ramp_m1 python: phases.ramp:ramp_profile timeout: 60s with: motor_index: 1 target_pct: 70 rate_pct_s: 20 hold_s: 3.0 abort_current_a: 6.0 min_rpm_per_pct: 200 measurements: - name: Ramp Motor 1 key: ramp description: 0 to 70 % at 20 %/s, 3 s hold, ramp down at 20 %/s. No propeller. x_axis: legend: Time unit: s y_axis: - legend: Throttle key: throttle unit: "%" - legend: Current key: current unit: A aggregations: - type: max validators: - operator: "<=" expected_value: 4.0 - legend: Speed key: rpm unit: rpm aggregations: - type: max validators: - operator: ">=" expected_value: 21000 - operator: "<=" expected_value: 24500teardown: - name: Stop All Motors python: phases.ramp:stop_allworkers: 1 garde les phases séquentielles. TofuPilot exécute par défaut les phases indépendantes en parallèle, ce qui, sur un banc moteur, revient à deux phases qui commandent le même ESC.
La phase distingue deux types de limites. Les limites d'arrêt protègent le banc et sont vérifiées dans la boucle : la phase arrête le moteur et échoue immédiatement. Les limites de validation jugent le moteur et sont évaluées sur la courbe enregistrée par les validateurs.
phases/ramp.py53 lines
import timedef ramp_profile(motor, current_monitor, measurements, phase, log, motor_index, target_pct, rate_pct_s, hold_s, abort_current_a, min_rpm_per_pct): times, throttle, current, rpm = [], [], [], [] t0 = time.monotonic() def save(): m = measurements.ramp m.x_axis = times m.y_axis.throttle = throttle m.y_axis.current = current m.y_axis.rpm = rpm m.y_axis.current.aggregations.max = max(current, default=0.0) m.y_axis.rpm.aggregations.max = max(rpm, default=0.0) def abort(reason): motor.stop(rate_pct_s) save() phase.fail(reason) motor.ramp_to(motor_index, target_pct, rate_pct_s) ramp_down_at = target_pct / rate_pct_s + hold_s ramping_down = False while True: elapsed = time.monotonic() - t0 if not ramping_down and elapsed >= ramp_down_at: motor.ramp_to(motor_index, 0, rate_pct_s) ramping_down = True thr, speed = motor.status(motor_index) amps = current_monitor.read_current() times.append(round(elapsed, 3)) throttle.append(thr) current.append(amps) rpm.append(speed) if amps > abort_current_a: abort(f"{amps:.2f} A above abort limit {abort_current_a} A at {thr:.0f} %") if not ramping_down and thr > 10 and speed < thr * min_rpm_per_pct: abort(f"{speed:.0f} rpm at {thr:.0f} % throttle: stalled or desynchronized") if ramping_down and thr == 0: break time.sleep(0.02) save() log.info(f"Peak {max(current):.2f} A, top speed {max(rpm):.0f} rpm")def stop_all(motor): motor.stop(50)Le contrôle de calage utilise le rapport observé sur le moteur d'exemple : 22 600 tr/min à 70 % font 323 tr/min par pourcent, donc 200 tr/min par pourcent laisse de la marge au retard de montée en vitesse tout en détectant un moteur qui a perdu la synchronisation. save() s'exécute avant phase.fail() parce que phase.fail() sort immédiatement de la fonction ; sans cela, la courbe qui explique l'arrêt serait perdue.
Sur le dashboard, la courbe montre où le courant a monté par rapport au gaz, et la carte de contrôle de l'agrégation de courant max montre la dérive d'une unité à l'autre.
Étape 4 : arrêter les moteurs sur chaque chemin de sortie
Listez toutes les façons dont un test peut se terminer et vérifiez ce qui arrête le moteur dans chaque cas :
| Chemin de sortie | Ce qui arrête le moteur |
|---|---|
| La phase passe | La phase redescend elle-même |
Limite d'arrêt ou phase.fail() | La rampe d'arrêt, puis la phase de teardown |
| Timeout de la phase | La phase de teardown (le teardown s'exécute toujours) |
| L'opérateur arrête le run | La phase de teardown |
| Exception Python dans la phase | La phase de teardown |
| Plug détruit (arrêt du poste, nouveau déploiement) | MotorDriver.__del__ |
| CLI tué, plantage de l'hôte, USB débranché | Watchdog du firmware après 500 ms |
| Emballement, incendie, défaillance mécanique | Arrêt d'urgence câblé |
La dernière ligne est la seule qui soit une fonction de sécurité. Les phases de teardown, les destructeurs de plug et les watchdogs de firmware sont du logiciel et tombent avec le logiciel. Un banc avec des pièces en rotation a besoin d'un arrêt d'urgence câblé qui coupe la puissance (ISO 13850, catégorie d'arrêt 0 ou 1), d'un protecteur autour des pièces en rotation (ISO 14120) avec un verrouillage s'il peut être ouvert (ISO 14119), et d'une appréciation du risque selon l'ISO 12100 au titre de la Directive Machines 2006/42/CE, remplacée par le Règlement Machines (UE) 2023/1230 à partir du 20 janvier 2027. Travailler sous 60 V DC limite le risque de choc électrique, pas les risques liés à l'énergie, à l'incendie ou aux projections.
Redescendez en rampe dans le teardown, à une pente bornée. Un teardown qui écrit un gaz de 0 instantanément fait de l'arrêt la plus grande marche descendante du test, avec la surtension de freinage régénératif qui l'accompagne.
Sans hélice et avec hélice
La même structure de procédure sert les deux cas. Les valeurs, le matériel autour et les risques changent :
| Sans hélice | Avec hélice | |
|---|---|---|
| Courant par moteur | 0,1 A au ralenti à 3,5 à 4,5 A à 100 % (mesuré) | Dizaines d'ampères, 60 à 80 A plein gaz en 6S (estimation pour une hélice typique) |
| Puissance pour quatre moteurs plein gaz | Moins de 500 W | 22,2 V x 80 A x 4 = 7,1 kW (estimation) |
| Source d'énergie | Alimentation de banc, avec batterie en parallèle ou écrêteur pour la régénération | Batterie, avec précharge ou connecteur anti-étincelle |
| Pente de rampe | 20 à 50 %/s | Plus lente, dimensionnée d'après le dépassement de courant mesuré |
| Mécanique | Support moteur seul | Banc de poussée dimensionné pour la poussée, protecteur contre l'éjection de pale |
| Ce que le test prouve | Santé électrique et roulements, voie d'ESC | Poussée, rendement, comportement thermique en charge |
Gardez deux procédures distinctes, ou des phases distinctes avec leurs propres valeurs with: et leurs limites. Une limite réglée sans hélice fait échouer toutes les unités avec hélice, et un courant d'arrêt dimensionné pour une hélice ne protège pas un moteur nu.
Pièges courants
Faire tourner la boucle de rampe en Python. La gigue du système d'exploitation rend la rampe irrégulière et le timing non reproductible. Faites la rampe sur le microcontrôleur ; envoyez les consignes depuis l'hôte.
Utiliser le test moteur du contrôleur de vol pour des profils. MAV_CMD_DO_MOTOR_TEST ne peut pas changer le gaz en cours de test, donc les profils deviennent des suites d'arrêts et de redémarrages avec un pic à chaque redémarrage.
Pas de watchdog dans le firmware. Un CLI tué laisse le dernier gaz appliqué. Le watchdog est l'arrêt pour toutes les défaillances que l'hôte ne peut pas gérer.
Écrire un gaz de 0 dans le teardown. Un arrêt instantané est une marche descendante, avec la surtension du freinage régénératif. Redescendez en rampe.
Appeler phase.fail() avant d'enregistrer la mesure. La fonction sort et la courbe est perdue. Enregistrez d'abord.
Prendre le teardown pour l'arrêt d'urgence. C'est du logiciel. L'arrêt d'urgence est un circuit câblé qui coupe la puissance.
Laisser les phases en parallèle. Réglez workers: 1 sur un banc moteur.
Points clés
- Les marches de gaz produisent des pics de courant et une surtension régénérative ; les rampes évitent les deux et rendent les lectures reproductibles.
- Générez le DShot et exécutez les rampes sur un microcontrôleur ; le plug TofuPilot envoie les consignes et relit le gaz et l'eRPM.
- Divisez l'eRPM par le nombre de paires de pôles (7 pour un moteur 12N14P) pour obtenir les tr/min mécaniques.
- Enregistrez gaz, courant et tr/min dans une seule mesure multidimensionnelle ; vérifiez les limites d'arrêt dans la boucle et les limites de validation sur les agrégations.
- Le teardown, le destructeur du plug et le watchdog du firmware arrêtent le moteur par logiciel ; seul un arrêt d'urgence câblé est une fonction de sécurité.
Pour une vue d'ensemble des bancs de test de moteurs et d'actionneurs, consultez Motor and Actuator Testing with TofuPilot.
