Bir ESP32 step motor MQTT kontrolcüsü; bir kamera kaydırıcısını, bir dozaj pompasını, bir panjuru veya küçük bir lineer ekseni ağın herhangi bir yerinden konumlandırmanızı sağlar. Bu eğitimde AccelStepper kütüphanesiyle bir A4988 veya DRV8825 sürücü üzerinden bir NEMA 17 motoru sürecek, MQTT üzerinden mutlak ve göreli hareketleri kabul edecek ve motor çalışırken canlı ilerleme yayınlayacaksınız. Zor kısım MQTT değil, ağ işini yaparken adım darbelerini akıcı tutmaktır; sketch bunu motoru kendi çekirdeğinde çalıştırarak çözer.
Komutlar, Topic'ler ve İlerleme Raporları
Step motorlar açık döngülüdür: ESP32 gönderdiği adımları sayar ve motor kilitlenmediği sürece bu sayı konumdur. Bu, cihazın enkoder olmadan konumu doğru şekilde bildirebilmesi anlamına geldiği için onları MQTT kontrolü için ideal kılar.
| Topic | Yön | Retained | Payload |
|---|---|---|---|
connectmq/demo/<id>/stepper/cmd |
Uygulamadan ESP32'ye | Hayır | JSON komut, aşağıya bakın |
connectmq/demo/<id>/stepper/progress |
ESP32'den uygulamalara | Evet | {"pos":1200,"target":3200,"pct":37,"moving":true} |
connectmq/demo/<id>/status |
ESP32'den uygulamalara | Evet | online / offline |
| Komut | Örnek | Etki |
|---|---|---|
| Mutlak hareket | {"cmd":"moveTo","pos":3200} |
3200. adıma git |
| Göreli hareket | {"cmd":"move","steps":-400} |
400 adım geri git |
| Hız | {"cmd":"speed","max":1500,"accel":800} |
Maks. adım/s ve adım/s² |
| Durdur | {"cmd":"stop"} |
Kontrollü bir duruşa yavaşla |
| Sıfırla | {"cmd":"zero"} |
Mevcut konumu 0 olarak tanımla |
| Serbest bırak | {"cmd":"disable"} |
Bobin akımını kapat |
Tipik 1,8 derecelik bir motor ve 1/16 mikro adımla bir tur 3200 adımdır. Tur başına 8 mm ilerleyen bir trapez vidada bu, milimetre başına 400 adım eder; bu da uygulamanıza koymak için kullanışlı bir dönüşümdür.
Donanım
- ESP32 geliştirme kartı.
- NEMA 17 step motor (bipolar, dört kablolu).
- İdeal olarak vidalı klemensli bir taşıyıcı kart üzerinde A4988 veya DRV8825 sürücü modülü.
- Motora uygun 12 V ile 24 V arası bir kaynak ve sürücünün motor besleme girişine paralel 100 µF bir kondansatör.
| Sürücü pini | Bağlantı |
|---|---|
| STEP | ESP32 GPIO 25 |
| DIR | ESP32 GPIO 26 |
| EN | ESP32 GPIO 27 (low = etkin) |
| MS1, MS2, MS3 (A4988) / M0, M1, M2 (DRV8825) | 1/16 (A4988) veya 1/32 (DRV8825) mikro adım için high'a bağlayın |
| VDD / GND (lojik) | ESP32 3V3 / GND |
| VMOT / GND | Motor kaynağı |
| 1A, 1B, 2A, 2B | Motor bobinleri |
| RESET + SLEEP | Birbirine köprüleyin |
Motoru uzun süreli çalıştırmak üzere bağlamadan önce akım limitini sürücünün trimpotuyla ayarlayın. Fazla akım sürücüyü ve motoru aşırı ısıtır; az akım adım kaçırır. Sürücü enerjiliyken motoru asla takıp çıkarmayın, çünkü endüktif sıçrama sürücüyü anında yok edebilir.
Kütüphaneler
- Nick O'Leary'nin PubSubClient kütüphanesi.
- İvme profilleri ve engellemeyen adım darbeleri üreten Mike McCauley'in AccelStepper kütüphanesi.
- ArduinoJson sürüm 7.
Motor Neden Kendi Görevinde Çalışır?
AccelStepper donanımsal bir timer kullanmaz. stepper.run() fonksiyonunu olabildiğince sık çağırmanız gerekir ve her çağrı, zamanı gelmişse en fazla bir adım üretir. Saniyede 2000 adımda her yarım milisaniyede bir adımın zamanı gelir. Wi-Fi meşgulken mqtt.loop() birkaç milisaniye engellerse veya bir yeniden bağlanma saniyelerce engellerse motor takılır ya da hareketin ortasında durur.
ESP32'nin iki çekirdeği vardır. Arduino'nun loop() fonksiyonu çekirdek 1'de, Wi-Fi yığını ise esas olarak çekirdek 0'da çalışır. Sketch, düşük öncelikle çekirdek 0'a sabitlenmiş ve run() çağırmaktan başka bir şey yapmayan bir FreeRTOS görevi oluşturur; loop() ise MQTT'yi yönetir. Mutex kullanmayan bir tasarım işi basit tutar: MQTT tarafı "bekleyen" istekleri volatile değişkenlere yazar ve motor görevi bunları alır. Bu desene daha derin bir bakış için MQTT ile ESP32 FreeRTOS görevleri yazısına bakın.
Eksiksiz ESP32 Step Motor MQTT Sketch'i
#include <WiFi.h>
#include <PubSubClient.h>
#include <AccelStepper.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 = "stepper-a19c44";
const String BASE = String("connectmq/demo/") + DEVICE_ID;
const int STEP_PIN = 25, DIR_PIN = 26, EN_PIN = 27;
const long SOFT_MIN = 0, SOFT_MAX = 32000; // adım cinsinden hareket limitleri
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
WiFiClient net;
PubSubClient mqtt(net);
// MQTT tarafından motor görevine istekler
volatile bool reqMove = false, reqStop = false, reqZero = false;
volatile bool reqEnable = false, reqDisable = false;
volatile long reqTarget = 0;
volatile float reqMaxSpeed = 1000, reqAccel = 500;
volatile bool reqSpeed = true;
// Raporlama için motor görevinden anlık görüntü
volatile long curPos = 0, curTarget = 0;
volatile bool moving = false;
long moveStart = 0;
unsigned long lastReport = 0, lastAttempt = 0;
bool wasMoving = false;
void motorTask(void*) {
for (;;) {
if (reqSpeed) { stepper.setMaxSpeed(reqMaxSpeed); stepper.setAcceleration(reqAccel); reqSpeed = false; }
if (reqEnable) { stepper.enableOutputs(); reqEnable = false; }
if (reqMove) { stepper.moveTo(reqTarget); reqMove = false; }
if (reqStop) { stepper.stop(); reqStop = false; } // yavaşlar
if (reqZero) { stepper.setCurrentPosition(0); reqZero = false; }
if (reqDisable) { stepper.stop(); stepper.setCurrentPosition(stepper.currentPosition()); stepper.disableOutputs(); reqDisable = false; }
stepper.run();
curPos = stepper.currentPosition();
curTarget = stepper.targetPosition();
moving = stepper.distanceToGo() != 0;
if (!moving) vTaskDelay(1); // boştayken işlemciyi bırak
}
}
void publishProgress(bool force) {
if (!force && millis() - lastReport < 250) return;
lastReport = millis();
long total = labs(curTarget - moveStart);
int pct = total == 0 ? 100 : (int)(100 * labs(curPos - moveStart) / total);
char buf[128];
snprintf(buf, sizeof(buf), "{\"pos\":%ld,\"target\":%ld,\"pct\":%d,\"moving\":%s}",
curPos, curTarget, constrain(pct, 0, 100), moving ? "true" : "false");
mqtt.publish((BASE + "/stepper/progress").c_str(), buf, true);
}
void requestMove(long target) {
target = constrain(target, SOFT_MIN, SOFT_MAX);
moveStart = curPos;
reqTarget = target;
reqEnable = true;
reqMove = true;
}
void onMessage(char* topic, byte* payload, unsigned int length) {
JsonDocument doc;
if (deserializeJson(doc, payload, length)) return;
const char* cmd = doc["cmd"] | "";
if (!strcmp(cmd, "moveTo") && doc["pos"].is<long>()) requestMove(doc["pos"].as<long>());
else if (!strcmp(cmd, "move") && doc["steps"].is<long>()) requestMove(curPos + doc["steps"].as<long>());
else if (!strcmp(cmd, "stop")) reqStop = true;
else if (!strcmp(cmd, "zero") && !moving) reqZero = true;
else if (!strcmp(cmd, "disable")) reqDisable = true;
else if (!strcmp(cmd, "speed")) {
reqMaxSpeed = constrain(doc["max"] | 1000.0f, 50.0f, 4000.0f);
reqAccel = constrain(doc["accel"] | 500.0f, 50.0f, 10000.0f);
reqSpeed = true;
} else return;
Serial.printf("cmd=%s\n", cmd);
}
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 + "/stepper/cmd").c_str(), 1);
publishProgress(true);
return true;
}
void setup() {
Serial.begin(115200);
stepper.setEnablePin(EN_PIN);
stepper.setPinsInverted(false, false, true); // EN active-low
stepper.disableOutputs();
disableCore0WDT(); // motor görevi hareketler sırasında çekirdek 0'ı meşgul tutar
xTaskCreatePinnedToCore(motorTask, "motor", 4096, nullptr, 1, nullptr, 0);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setSocketTimeout(5);
}
void loop() {
if (WiFi.status() == WL_CONNECTED) {
if (!mqtt.connected()) {
if (millis() - lastAttempt > 5000) { lastAttempt = millis(); connectMqtt(); }
} else {
mqtt.loop();
if (moving) { publishProgress(false); wasMoving = true; }
else if (wasMoving) { publishProgress(true); wasMoving = false; } // son rapor
}
}
delay(2);
}
Dikkat edilmeye değer birkaç ayrıntı var. Motor görevi yalnızca boştayken vTaskDelay(1) ile işlemciyi bırakır; bu yüzden bir hareket sırasında çekirdek 0'da tam hızda çalışır; öncelik 1'de olduğu için daha yüksek öncelikte çalışan Wi-Fi görevlerinin işlerini yapmasına yine izin verir. Meşgul görev uzun hareketler sırasında çekirdek 0'ın idle görevini aç bıraktığı için disableCore0WDT() o çekirdek için idle görev watchdog kontrolünü kapatır; bu olmadan birkaç saniye süren hareketlerde görev watchdog uyarıları görürsünüz. Yazılımsal limitler her hedefi sınırlar; böylece yanlış yazılmış bir "pos":320000 bir taşıyıcıyı şaseye çarptıramaz. Ve "moving":false içeren son ilerleme raporu her zaman gönderilir; bu da uygulamalara temiz bir "hareket tamamlandı" sinyali verir.
Bir Hareket Çalıştırın ve İlerlemeyi İzleyin
mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-a19c44 -v -t "connectmq/demo/stepper-a19c44/#"
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-a19c44 -t "connectmq/demo/stepper-a19c44/stepper/cmd" -m '{"cmd":"speed","max":1600,"accel":1200}'
mosquitto_pub -h broker.connectmq.com -p 1883 -i cmd-a19c44 -t "connectmq/demo/stepper-a19c44/stepper/cmd" -m '{"cmd":"moveTo","pos":6400}'
Abone, hareket tamamlanana kadar saniyede dört kez ilerleme yazdırır:
connectmq/demo/stepper-a19c44/stepper/progress {"pos":312,"target":6400,"pct":4,"moving":true}
connectmq/demo/stepper-a19c44/stepper/progress {"pos":2101,"target":6400,"pct":32,"moving":true}
connectmq/demo/stepper-a19c44/stepper/progress {"pos":6400,"target":6400,"pct":100,"moving":false}
Hareketin ortasında {"cmd":"stop"} gönderin; motor aniden durmak yerine yapılandırılmış ivmeyi kullanarak yumuşakça yavaşlar, bu da kayışları ve kaplinleri korur. Saniyede dört raporluk hız, sizi ücretsiz herkese açık MQTT broker'da client başına izin verilen saniyede 10 mesajın rahatça altında tutar.
Referanslama (Homing) ve Güvenlik Kontrol Listesi
Açık döngülü konum, referans noktası kadar iyidir. Açılıştan sonra ESP32 taşıyıcının nerede olduğunu bilmez; bu yüzden bildirilen konum, motor nerede bulunuyorsa orada 0'dan başlar.
- Bir limit anahtarı ekleyin ve bir referanslama rutini uygulayın: anahtara doğru yavaşça ilerleyin, tetiklendiğinde durun, ardından
zerogönderin. - Production firmware'inde referanslanana kadar mutlak hareketleri reddedin ve ilerleme JSON'unda bir
homedbayrağı yayınlayın. - Referanslamadan sonra bile yazılımsal limitleri mekanik hareket aralığının içinde tutun.
- Eksenin tutma torkuna ihtiyacı yoksa boştayken çıkışları devre dışı bırakın; güç tasarrufu sağlar ve sürücü ile motoru serin tutar.
- Birine zarar verebilecek her şey için kopan bağlantıyı bir durma koşulu olarak ele alın. Last Will'i, komutların gelmesi durursa hareketi durduran yerel mantıkla birleştirin.
- Acil durdurmalar için asla yalnızca MQTT'ye güvenmeyin. Motor gücünü kesen donanımsal bir acil stop, kabul edilebilir tek güvenlik cihazıdır.
Sorun Giderme
- Motor uğulduyor ama dönmüyor. Bobin çiftleri yanlış bağlanmış veya ivme yük için çok yüksek. Çiftleri bir multimetreyle kontrol edin ve
acceldeğerini yarıya indirin. - Motor dönüyor ama yüksek hızlarda adım kaçırıyor. Besleme gerilimini sürücü limitleri içinde artırın, akım limitini ayarlayın veya
maxdeğerini düşürün. - MQTT mesajları geldiğinde takılma. Motor kendi görevi yerine
loop()içinde çalışıyor ya da görev ağır işlerle aynı çekirdeğe sabitlenmiş. - Sürücü çok ısınıyor. Akım limiti çok yüksek; bir soğutucu ekleyin ve akımı düşürün.
- Birçok hareketten sonra konum kayıyor. Kilitlenmelerden kaynaklanan adım kayıpları; geri bildirim olmadığı için periyodik olarak referanslayın.
Production: Kimlik Bilgileri, TLS ve İzleme
Hareket cihazları özel kimlik bilgilerini ve şifrelemeyi hak eder. ConnectMQ portalında bir broker oluşturun, üretilen kullanıcı adını, parolayı ve client ID'yi kullanın ve 8883 portunda WiFiClientSecure'a geçin; SSL/TLS üzerinden ESP32 MQTT rehberi sertifika kurulumunu kapsar. ConnectMQ gibi barındırılan bir MQTT broker, hızları ayarlarken stepper/progress topic'ini izleyebileceğiniz canlı bir topic gezgini ve sürücü sıcaklığı gibi sayısal telemetriyi kendi topic'inde yayınlarsanız ona yönlendirebileceğiniz eşik uyarı kuralları da sunar. Daha basit aktüatörler için aynı komut ve durum deseni MQTT üzerinden ESP32 servo kontrolünde de yer alır; bu ilk ESP32 projenizse başlangıç noktası ESP32 IoT eğitimidir.
ConnectMQ ile Başlayın
app.connectmq.com adresinde ücretsiz bir Serverless broker oluşturun, kredi kartı gerekmez. Bir dakikada TLS kimlik bilgilerini, hareket komutları göndermek için tarayıcı içi bir client'ı ve motorunuzun ilerlemesinin canlı görünümünü elde edersiniz.
Sık Sorulan Sorular
ESP32 MQTT mesajları aldığında step motorum neden takılıyor?
AccelStepper, run() fonksiyonunun çok sık çağrılmasını gerektirir. loop() fonksiyonunu ağ işlemleriyle paylaştığında her gecikme adım kaçırmaya neden olur. Motoru yukarıda gösterildiği gibi ayrı bir FreeRTOS görevinde çalıştırın veya timer tabanlı bir step motor kütüphanesi kullanın.
Bu sketch ile bir 28BYJ-48 motoru kontrol edebilir miyim?
Evet, bir ULN2003 kartıyla. AccelStepper nesnesini DRIVER yerine AccelStepper::HALF4WIRE ve dört pinle (IN1, IN3, IN2, IN4 sırasıyla) oluşturun; MQTT kısmı aynı kalır.
Bildirilen konum ne kadar doğru?
ESP32'nin gönderdiği tam adım sayısıdır. Motor hiç kilitlenmediği sürece gerçek konumla eşleşir; bir anahtara karşı referanslama birikmiş her hatayı ortadan kaldırır.
Motor komutları için hangi QoS'u kullanmalıyım?
Komutlar için QoS 1 kullanın. Mutlak hareketlerin (moveTo) iki kez alınması güvenlidir; yinelenen göreli bir hareket ise iki kez hareket ettirir. Bu yüzden teslimatın tekrarlanabileceği durumlarda mutlak konumları tercih edin.



