Un projet de servomoteur ESP32 MQTT transforme un servo de modélisme bon marché en actionneur pilotable à distance pour des stores, des vannes, des têtes panoramiques de caméra ou des distributeurs de croquettes. Dans ce tutoriel, vous allez déplacer un servo à l'aide de commandes d'angle MQTT, l'amener progressivement à sa position plutôt que brusquement, imposer des limites mécaniques dans le firmware et publier un retour de position lisible par n'importe quel tableau de bord. Vous pouvez tester chaque étape sur broker.connectmq.com sans créer de compte.
Ce que vous allez construire
L'ESP32 écoute les commandes sur un topic et rend compte de sa progression sur un autre. Les commandes peuvent être un simple angle ou un petit objet JSON avec une vitesse :
| Topic | Sens | Retained | Exemple de payload |
|---|---|---|---|
connectmq/demo/<id>/servo/set |
Application vers ESP32 | Non | 90 ou {"angle":45,"speed":60} |
connectmq/demo/<id>/servo/position |
ESP32 vers applications | Oui | {"angle":72,"target":45,"moving":true} |
connectmq/demo/<id>/status |
ESP32 vers applications | Oui | online / offline |
Une limite à annoncer d'emblée, en toute honnêteté : un servo de modélisme standard ne renvoie pas la position réelle de son axe. Le retour publié ici correspond à l'angle commandé par le firmware, mis à jour au fur et à mesure que le servo se déplace vers la cible. Cela suffit pour les tableaux de bord et pour confirmer qu'une commande a été acceptée. Si vous avez besoin de la position réellement mesurée, il vous faut un servo doté d'un fil de retour ou un codeur externe, ce qui est un autre projet.
Matériel et alimentation
- Une carte de développement ESP32.
- Un servo de modélisme comme un SG90 (micro, pignons en plastique) ou un MG996R (taille standard, pignons métalliques).
- Une alimentation 5 V séparée pour tout servo plus gros qu'un micro-servo, dimensionnée au moins pour le courant de blocage du servo.
- Un condensateur électrolytique de 470 µF à 1000 µF sur l'alimentation du servo, au plus près de celui-ci.
| Fil du servo | À relier à |
|---|---|
| Marron / noir (GND) | GND de l'alimentation et GND de l'ESP32 |
| Rouge (V+) | Alimentation 5 V |
| Orange / jaune (signal) | GPIO 18 de l'ESP32 |
La masse commune n'est pas facultative : sans elle, le signal n'a pas de référence et le servo tressaute de façon aléatoire. Alimenter un MG996R depuis la broche 5 V de l'ESP32 est la cause la plus fréquente de redémarrages par chute de tension (brownout) dans les projets de servos, car le servo tire un fort pic de courant à chaque démarrage de mouvement. Le signal 3,3 V de l'ESP32 est accepté par la quasi-totalité des servos de modélisme.
Bibliothèques
Installez le paquet de cartes esp32 et les bibliothèques suivantes depuis le gestionnaire de bibliothèques Arduino :
- PubSubClient de Nick O'Leary, pour MQTT.
- ESP32Servo de Kevin Harrington et John K. Bennett, qui pilote les servos via le matériel PWM LEDC de l'ESP32.
- ArduinoJson de Benoit Blanchon (version 7), pour analyser le format de commande JSON.
La bibliothèque Arduino Servo classique ne prend pas en charge l'ESP32 : ESP32Servo est donc le bon choix. Vous découvrez MQTT sur cette carte ? Commencez par le tutoriel IoT ESP32, qui couvre la configuration de la carte et une première publication.
Le sketch servomoteur ESP32 MQTT
#include <WiFi.h>
#include <PubSubClient.h>
#include <ESP32Servo.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* DEVICE_ID = "servo-4c81d2"; // unique pour chaque appareil
const String BASE = String("connectmq/demo/") + DEVICE_ID;
const int SERVO_PIN = 18;
const int MIN_ANGLE = 10; // limites mécaniques de VOTRE tringlerie
const int MAX_ANGLE = 170;
const int MIN_US = 500; // largeur d'impulsion pour 0 degré
const int MAX_US = 2400; // largeur d'impulsion pour 180 degrés
Servo servo;
WiFiClient net;
PubSubClient mqtt(net);
float currentAngle = 90;
int targetAngle = 90;
float speedDegPerSec = 90; // vitesse de rampe par défaut
unsigned long lastStep = 0, lastReport = 0, lastAttempt = 0;
bool wasMoving = false;
void publishPosition(bool force) {
bool moving = (int)round(currentAngle) != targetAngle;
if (!force && millis() - lastReport < 250) return;
lastReport = millis();
char buf[96];
snprintf(buf, sizeof(buf), "{\"angle\":%d,\"target\":%d,\"moving\":%s}",
(int)round(currentAngle), targetAngle, moving ? "true" : "false");
String topic = BASE + "/servo/position";
mqtt.publish(topic.c_str(), buf, true);
}
void onMessage(char* topic, byte* payload, unsigned int length) {
JsonDocument doc;
int angle;
if (deserializeJson(doc, payload, length) == DeserializationError::Ok && doc.is<JsonObject>()) {
if (!doc["angle"].is<int>()) return;
angle = doc["angle"];
if (doc["speed"].is<float>()) speedDegPerSec = constrain(doc["speed"].as<float>(), 5.0f, 600.0f);
} else if (doc.is<int>()) {
angle = doc.as<int>(); // nombre seul comme "90"
} else {
return;
}
targetAngle = constrain(angle, MIN_ANGLE, MAX_ANGLE);
if (!servo.attached()) servo.attach(SERVO_PIN, MIN_US, MAX_US);
Serial.printf("Target %d deg at %.0f deg/s\n", targetAngle, speedDegPerSec);
publishPosition(true);
}
bool connectMqtt() {
String status = BASE + "/status";
if (!mqtt.connect(DEVICE_ID, nullptr, nullptr, status.c_str(), 1, true, "offline")) {
Serial.printf("MQTT failed, state=%d\n", mqtt.state());
return false;
}
mqtt.publish(status.c_str(), "online", true);
mqtt.subscribe((BASE + "/servo/set").c_str(), 1);
publishPosition(true);
return true;
}
void setup() {
Serial.begin(115200);
ESP32PWM::allocateTimer(0);
servo.setPeriodHertz(50); // trame servo standard
servo.attach(SERVO_PIN, MIN_US, MAX_US);
servo.write((int)currentAngle);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setSocketTimeout(5);
}
void stepServo() {
unsigned long now = millis();
float dt = (now - lastStep) / 1000.0f;
lastStep = now;
float diff = targetAngle - currentAngle;
if (fabs(diff) < 0.5f) {
currentAngle = targetAngle;
if (wasMoving) {
servo.write(targetAngle);
publishPosition(true); // rapport de position final
wasMoving = false;
}
return;
}
float stepSize = speedDegPerSec * dt;
currentAngle += constrain(diff, -stepSize, stepSize);
servo.write((int)round(currentAngle));
wasMoving = true;
publishPosition(false); // progression limitée en fréquence
}
void loop() {
stepServo();
if (WiFi.status() != WL_CONNECTED) return;
if (!mqtt.connected()) {
if (millis() - lastAttempt > 5000) { lastAttempt = millis(); connectMqtt(); }
return;
}
mqtt.loop();
}
Pourquoi une rampe plutôt qu'un saut
Appeler directement servo.write(170) fait claquer le servo vers le nouvel angle à pleine vitesse. Sur une tringlerie qui actionne une vanne ou un couvercle, cela malmène les pignons en plastique et tire un gros pic de courant sur l'alimentation. La fonction stepServo() déplace un peu l'angle commandé à chaque boucle, en fonction du temps écoulé, si bien que la vitesse reste constante quelle que soit la cadence d'exécution de loop(). Une vitesse de 60 degrés par seconde paraît posée ; 300 donne une impression de vivacité.
Pourquoi les limites sont dans le firmware
MIN_ANGLE et MAX_ANGLE doivent correspondre à la course physique de ce que le servo entraîne. Si une application envoie 0 et que votre tringlerie bute à 8 degrés, le servo cale, chauffe et tire son courant de blocage jusqu'à ce que quelque chose cède. Borner les valeurs sur l'appareil garantit qu'une faute de frappe dans un tableau de bord ne pourra jamais endommager le matériel. Traitez chaque commande MQTT comme une entrée non fiable.
Envoyer des commandes et observer le retour
Abonnez-vous au préfixe de l'appareil :
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-4c81d2 -v -t "connectmq/demo/servo-4c81d2/#"
Puis déplacez le servo :
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-4c81d2 -t "connectmq/demo/servo-4c81d2/servo/set" -m 150
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-4c81d2 -t "connectmq/demo/servo-4c81d2/servo/set" -m '{"angle":30,"speed":40}'
Vous verrez défiler la position pendant le mouvement du servo, jusqu'à "moving":false :
connectmq/demo/servo-4c81d2/servo/position {"angle":90,"target":150,"moving":true}
connectmq/demo/servo-4c81d2/servo/position {"angle":113,"target":150,"moving":true}
connectmq/demo/servo-4c81d2/servo/position {"angle":150,"target":150,"moving":false}
Les rapports de progression sont limités à quatre par seconde. Cela vous maintient largement sous la limite du serveur MQTT public gratuit de 10 messages par seconde par client, et c'est amplement suffisant pour une jauge fluide sur un tableau de bord. N'oubliez pas que le serveur MQTT public gratuit est lisible par tous : c'est un banc d'essai, pas l'endroit pour un servo qui ouvre votre porte d'entrée.
Étalonner le servo avant de lui faire confiance
Les servos de fabricants différents, et même de lots différents d'un même modèle, font correspondre largeur d'impulsion et angle de façon légèrement différente. Consacrez cinq minutes à l'étalonnage avant de construire un support autour d'un servo :
- Retirez toute tringlerie pour que le palonnier tourne librement.
- Réglez temporairement
MIN_ANGLEà 0 etMAX_ANGLEà 180, puis envoyez0et écoutez. Si le servo bourdonne ou continue de forcer en butée, augmentezMIN_USde 50 et réessayez jusqu'à ce qu'il repose silencieusement. - Répétez à
180, en diminuantMAX_USjusqu'à ce que la position extrême soit silencieuse. - Envoyez
90et vérifiez que le palonnier pointe là où vous attendez le « centre ». Sinon, repositionnez le palonnier sur les cannelures plutôt que de compenser par logiciel. - Fixez la tringlerie, approchez lentement chaque extrémité avec une vitesse faible comme
{"angle":20,"speed":15}, et notez où elle commence à forcer. RéglezMIN_ANGLEetMAX_ANGLEquelques degrés en deçà de ces points.
Notez les valeurs finales dans un commentaire à côté des constantes. Quand vous remplacerez un servo cassé dans un an, vous saurez quels chiffres revérifier. L'étalonnage explique aussi pourquoi le topic de retour indique honnêtement l'angle commandé au lieu de prétendre le mesurer : ce nombre signifie « là où le firmware a dit au servo d'aller », et c'est l'étalonnage qui le fait correspondre à la réalité.
Dépannage
- Le servo tremble au repos. C'est généralement du bruit d'alimentation. Ajoutez le condensateur de découplage, raccourcissez les fils du servo et éloignez le fil de signal des câbles moteur. Si le servo ne supporte aucune charge, vous pouvez appeler
servo.detach()une fois la cible atteinte pour arrêter complètement les impulsions ; le sketch le rattache à la commande suivante. - L'ESP32 redémarre quand le servo bouge. Chute de tension. Utilisez une alimentation séparée pour le servo avec une masse commune.
- La course n'est que d'environ 90 degrés. Le servo attend une plage d'impulsions plus large. Ajustez
MIN_USetMAX_USpar petits pas ; beaucoup de servos utilisent environ 500 à 2500 microsecondes, mais dépasser les vraies butées du servo le fait bourdonner et forcer. - Le servo bouge mais le retour n'arrive jamais. Vérifiez que les publications ne dépassent pas le buffer par défaut de PubSubClient et que
mqtt.loop()s'exécute assez souvent. Undelay()bloquant n'importe où dansloop()casse à la fois la rampe et MQTT. - Commandes ignorées. Le JSON doit contenir un
angleentier. Les chaînes comme"angle":"90"sont volontairement rejetées. - Deux servos interfèrent. Chacun a besoin de sa propre broche ;
ESP32Servoattribue automatiquement les canaux PWM, mais appelezallocateTimerpour les timers que vous comptez utiliser.
De la démo à un vrai déploiement
Une fois le servo fonctionnel, sortez-le de la zone publique. Créez un broker dans le portail ConnectMQ, copiez le nom d'utilisateur, le mot de passe et le client ID générés, puis connectez-vous en TLS sur le port 8883 avec WiFiClientSecure. Le guide ESP32 MQTT SSL détaille pas à pas la configuration des certificats.
Pour tout ce qui bouge, la disponibilité compte. Un broker MQTT managé comme ConnectMQ donne à chaque appareil ses propres identifiants et vous permet de suivre servo/position dans l'explorateur de topics en direct du portail pendant que vous ajustez les vitesses. Le même schéma commande/état se transpose directement au pilotage de relais avec ESP32 et MQTT et aux moteurs pas à pas pilotés par MQTT, l'étape suivante lorsque vous avez besoin de plus de couple ou d'une rotation continue avec un positionnement précis.
Démarrer avec ConnectMQ
Inscrivez-vous sur app.connectmq.com et créez un broker Serverless gratuit, sans carte bancaire. Vous obtenez immédiatement des identifiants TLS, un client MQTT dans le navigateur pour envoyer des commandes au servo et un tableau de bord pour suivre le retour de position en direct.
Questions fréquentes
Un servo de modélisme renvoie-t-il sa position réelle via MQTT ?
Non. Un servo standard n'a pas de sortie de position : l'ESP32 renvoie donc l'angle qu'il a commandé. Pour une position mesurée, utilisez un servo à retour ou ajoutez un codeur ou un potentiomètre lu par l'ADC de l'ESP32.
Quelles broches de l'ESP32 peuvent piloter un servo ?
N'importe quel GPIO capable de fonctionner en sortie convient, car le PWM provient du périphérique LEDC. Évitez les broches 34 à 39, uniquement en entrée, et les broches de configuration au démarrage comme 0, 2, 12 et 15.
Combien de servos un ESP32 peut-il piloter via MQTT ?
L'ESP32 d'origine dispose de 16 canaux LEDC (les variantes plus récentes comme le S3 en ont moins) : de nombreux servos sont donc possibles en principe ; la vraie limite, ce sont l'alimentation et le câblage. Utilisez un topic par servo, par exemple servo/1/set et servo/2/set.
Les commandes de servo doivent-elles utiliser le QoS 1 ?
Oui, le QoS 1 est un choix par défaut judicieux. Une commande d'angle absolu est idempotente : une livraison en double redéfinit simplement la même cible.



