Um projeto ESP32 servo MQTT transforma um servo barato de hobby em um atuador controlado remotamente para persianas, válvulas, suportes pan de câmeras ou alimentadores de pets. Neste tutorial, você vai mover um servo a partir de comandos de ângulo via MQTT, fazê-lo se deslocar suavemente em vez de dar trancos, impor limites mecânicos no firmware e publicar um feedback de posição que qualquer dashboard consegue ler. Você pode testar cada etapa no broker.connectmq.com sem criar conta.
O Que Você Vai Construir
O ESP32 escuta comandos em um topic e informa o progresso em outro. Os comandos podem ser apenas um ângulo ou um pequeno objeto JSON com uma velocidade:
| Topic | Direção | Retido | Exemplo de payload |
|---|---|---|---|
connectmq/demo/<id>/servo/set |
App para o ESP32 | Não | 90 ou {"angle":45,"speed":60} |
connectmq/demo/<id>/servo/position |
ESP32 para os apps | Sim | {"angle":72,"target":45,"moving":true} |
connectmq/demo/<id>/status |
ESP32 para os apps | Sim | online / offline |
Uma limitação, dita com franqueza logo de início: um servo de hobby comum não informa a posição real do eixo. O feedback publicado aqui é o ângulo que o firmware comandou, atualizado à medida que o servo avança em direção ao destino. Isso basta para dashboards e para confirmar que um comando foi aceito. Se você precisa da posição realmente medida, vai precisar de um servo com fio de feedback ou de um encoder externo, o que é outro projeto.
Hardware e Alimentação
- Placa de desenvolvimento ESP32.
- Um servo de hobby, como um SG90 (micro, engrenagens plásticas) ou um MG996R (tamanho padrão, engrenagens metálicas).
- Uma fonte de 5 V separada para qualquer coisa maior que um micro servo, dimensionada para pelo menos a corrente de travamento (stall) do servo.
- Um capacitor eletrolítico de 470 µF a 1000 µF na alimentação do servo, perto dele.
| Fio do servo | Ligar em |
|---|---|
| Marrom / preto (GND) | GND da fonte e GND do ESP32 |
| Vermelho (V+) | Fonte de 5 V |
| Laranja / amarelo (sinal) | GPIO 18 do ESP32 |
O terra compartilhado não é opcional: sem ele, o sinal não tem referência e o servo se contorce aleatoriamente. Alimentar um MG996R pelo pino de 5 V do ESP32 é a causa mais comum de resets por brownout em projetos com servo, porque o servo puxa um grande pico de corrente toda vez que começa a se mover. O sinal de 3,3 V do ESP32 é aceito por quase todos os servos de hobby.
Bibliotecas
Instale o pacote de placas esp32 e estas bibliotecas pelo Gerenciador de Bibliotecas do Arduino:
- PubSubClient, de Nick O'Leary, para o MQTT.
- ESP32Servo, de Kevin Harrington e John K. Bennett, que controla servos por meio do hardware PWM LEDC do ESP32.
- ArduinoJson, de Benoit Blanchon (versão 7), para interpretar o formato de comando em JSON.
A biblioteca Servo comum do Arduino não suporta o ESP32, então a ESP32Servo é a escolha certa. É novo no MQTT nesta placa? Comece pelo tutorial IoT com ESP32, que cobre a configuração da placa e uma primeira publicação.
O Sketch ESP32 Servo 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"; // único por dispositivo
const String BASE = String("connectmq/demo/") + DEVICE_ID;
const int SERVO_PIN = 18;
const int MIN_ANGLE = 10; // limites mecânicos da SUA articulação
const int MAX_ANGLE = 170;
const int MIN_US = 500; // largura de pulso para 0 grau
const int MAX_US = 2400; // largura de pulso para 180 graus
Servo servo;
WiFiClient net;
PubSubClient mqtt(net);
float currentAngle = 90;
int targetAngle = 90;
float speedDegPerSec = 90; // velocidade padrão de deslocamento
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>(); // número puro, como "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); // quadro padrão de servo
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); // relatório da posição final
wasMoving = false;
}
return;
}
float stepSize = speedDegPerSec * dt;
currentAngle += constrain(diff, -stepSize, stepSize);
servo.write((int)round(currentAngle));
wasMoving = true;
publishPosition(false); // progresso com limite de taxa
}
void loop() {
stepServo();
if (WiFi.status() != WL_CONNECTED) return;
if (!mqtt.connected()) {
if (millis() - lastAttempt > 5000) { lastAttempt = millis(); connectMqtt(); }
return;
}
mqtt.loop();
}
Por que deslocar suavemente em vez de saltar
Chamar servo.write(170) diretamente faz o servo bater no novo ângulo em velocidade máxima. Em uma articulação que move uma válvula ou uma tampa, isso desgasta as engrenagens plásticas e puxa um grande pico de corrente da fonte. A função stepServo() avança um pouco o ângulo comandado a cada loop, com base no tempo decorrido, de modo que a velocidade fica constante independentemente de quão rápido loop() roda. Uma velocidade de 60 graus por segundo parece deliberada; 300 parece ágil.
Por que os limites ficam no firmware
MIN_ANGLE e MAX_ANGLE devem corresponder ao curso físico daquilo que o servo aciona. Se um app envia 0 e a sua articulação trava em 8 graus, o servo emperra, esquenta e puxa a corrente de travamento até algo ceder. Limitar no dispositivo significa que um erro de digitação em um dashboard nunca vai danificar o hardware. Trate todo comando MQTT como entrada não confiável.
Envie Comandos e Acompanhe o Feedback
Inscreva-se no prefixo do dispositivo:
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-4c81d2 -v -t "connectmq/demo/servo-4c81d2/#"
Depois mova o 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}'
Você verá o stream de posições enquanto o servo se move, terminando com "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}
Os relatórios de progresso são limitados a quatro por segundo. Isso mantém você bem dentro do limite de 10 mensagens por segundo por cliente do servidor MQTT público (gratuito) e é mais do que suficiente para um medidor suave no dashboard. Lembre-se de que ele pode ser lido por todos, então ele é uma bancada de testes, e não o lugar para um servo que abre a porta da sua casa.
Calibre o Servo Antes de Confiar Nele
Servos de fabricantes diferentes, e até de lotes diferentes do mesmo modelo, convertem a largura de pulso em ângulo de forma ligeiramente diferente. Dedique cinco minutos à calibração antes de construir um suporte em volta de um deles:
- Remova qualquer articulação para que o braço gire livremente.
- Defina temporariamente
MIN_ANGLEcomo 0 eMAX_ANGLEcomo 180, envie0e escute. Se o servo zumbir ou continuar forçando no fim do curso, aumenteMIN_USem 50 e tente de novo até ele parar em silêncio. - Repita com
180, reduzindoMAX_USaté a posição final ficar silenciosa. - Envie
90e verifique se o braço aponta para onde você espera que seja o "centro". Se não apontar, reposicione o braço no estriado em vez de compensar no software. - Instale a articulação, mova lentamente em direção a cada extremo com uma velocidade baixa, como
{"angle":20,"speed":15}, e anote onde ela começa a travar. DefinaMIN_ANGLEeMAX_ANGLEalguns graus antes desses pontos.
Anote os valores finais em um comentário ao lado das constantes. Quando você trocar um servo quebrado um ano depois, saberá quais números conferir de novo. A calibração também é o motivo de o topic de feedback informar com honestidade o ângulo comandado, em vez de fingir que o mede: o número significa "para onde o firmware mandou o servo ir", e é a calibração que faz esse número corresponder à realidade.
Solução de Problemas
- O servo treme em repouso. Normalmente é ruído na alimentação. Adicione o capacitor de filtragem, encurte os fios do servo e mantenha o fio de sinal longe dos cabos de motores. Se o servo não sustenta carga, você pode chamar
servo.detach()quando ele atingir o destino para interromper totalmente os pulsos; o sketch faz o attach de novo no próximo comando. - O ESP32 reinicia quando o servo se move. Brownout. Use uma fonte separada para o servo, com terra comum.
- O curso é de apenas uns 90 graus. O servo espera uma faixa de pulsos mais ampla. Ajuste
MIN_USeMAX_USem pequenos passos; muitos servos usam aproximadamente de 500 a 2500 microssegundos, mas ir além dos batentes reais do servo faz ele zumbir e forçar. - O servo se move, mas o feedback nunca chega. Verifique se as publicações não são maiores que o buffer padrão do PubSubClient e se
mqtt.loop()roda com frequência suficiente. Umdelay()bloqueante em qualquer ponto deloop()quebra tanto o deslocamento quanto o MQTT. - Comandos ignorados. O JSON precisa conter um
angleinteiro. Strings como"angle":"90"são rejeitadas de propósito. - Dois servos interferem um no outro. Cada um precisa do próprio pino; a
ESP32Servoaloca os canais PWM automaticamente, mas chameallocateTimerpara os timers que você pretende usar.
Da Demonstração a uma Implantação Real
Quando o servo estiver funcionando, tire-o do servidor MQTT público. Crie um broker no portal do ConnectMQ, copie o usuário, a senha e o client ID gerados e conecte-se via TLS na porta 8883 usando WiFiClientSecure. O guia ESP32 MQTT com SSL mostra a configuração do certificado passo a passo.
Para qualquer coisa que se move, a disponibilidade importa. Um broker MQTT gerenciado como o ConnectMQ dá a cada dispositivo credenciais próprias e permite acompanhar servo/position no explorador de topics ao vivo do portal enquanto você ajusta as velocidades. O mesmo padrão de comando e estado se aplica diretamente ao controle de relés com ESP32 e MQTT e a motores de passo controlados via MQTT, o próximo passo quando você precisa de mais torque ou de rotação contínua com posicionamento preciso.
Comece a Usar o ConnectMQ
Cadastre-se em app.connectmq.com e crie um broker Serverless gratuito, sem precisar de cartão de crédito. Você recebe credenciais TLS na hora, um cliente MQTT no navegador para enviar comandos ao servo e um dashboard para acompanhar o feedback de posição ao vivo.
Perguntas frequentes
Um servo de hobby informa sua posição real via MQTT?
Não. Um servo comum não tem saída de posição, então o ESP32 informa o ângulo que comandou. Para a posição medida, use um servo com feedback ou adicione um encoder ou potenciômetro lido pelo ADC do ESP32.
Quais pinos do ESP32 podem controlar um servo?
Qualquer GPIO com capacidade de saída funciona, porque o PWM vem do periférico LEDC. Evite os pinos somente entrada 34 a 39 e os pinos de strapping, como 0, 2, 12 e 15.
Quantos servos um ESP32 consegue controlar via MQTT?
O ESP32 original tem 16 canais LEDC (variantes mais novas, como o S3, têm menos), então muitos servos são possíveis em princípio; a fonte de alimentação e a fiação são o verdadeiro limite. Use um topic por servo, por exemplo servo/1/set e servo/2/set.
Os comandos do servo devem usar QoS 1?
Sim, QoS 1 é um padrão sensato. Um comando de ângulo absoluto é idempotente, então uma entrega duplicada simplesmente define o mesmo destino de novo.



