#include #include #include // ── Pins ────────────────────────────────────────────────────── #define M1_STEP D3 #define M1_DIR D4 #define M1_EN D5 #define M2_STEP D0 #define M2_DIR D1 #define M2_EN D2 #define REED1 D6 #define REED2 D7 // ── WiFi Access Point ───────────────────────────────────────── const char* AP_SSID = "Sandtable"; const char* AP_PASS = "12345678"; // ── Objects ─────────────────────────────────────────────────── WebServer server(80); AccelStepper motor1(AccelStepper::DRIVER, M1_STEP, M1_DIR); AccelStepper motor2(AccelStepper::DRIVER, M2_STEP, M2_DIR); // ── State ───────────────────────────────────────────────────── enum Mode { IDLE, HOMING, BUTTERFLY }; Mode mode = IDLE; bool homingOK = false; bool stopFlag = false; // ── After each full revolution of M1, M2 moves one step outward. // Adjust STEPS_PER_REV if the mechanical ratio differs. const float HOMING_SPEED = 400.0; const float M1_SPEED = 800.0; // Rotation arm const float M2_SPEED = 200.0; // Radius arm const long STEPS_PER_REV = 3200; // 200 steps × 1/16 const int RADIUS_PER_REV = 8; // M2 steps per M1 revolution const long MAX_RADIUS = 48000; // Maximum M2 travel in steps long m1StepCounter = 0; long m2Position = 0; // ── Webpage ─────────────────────────────────────────────────── const char WEBPAGE[] PROGMEM = R"rawliteral( Sandtable

✦ SANDTABLE

Control

STATUS
Ready


)rawliteral"; // ── Status text for webpage ─────────────────────────────────── String statusText() { switch (mode) { case HOMING: return "Homing running..."; case BUTTERFLY: return "Butterfly running..."; default: if (homingOK) return "Homing OK - Ready"; return "No Homing"; } } // ── Homing (blocking, but server.handleClient() is called inside) ── void doHoming() { mode = HOMING; homingOK = false; stopFlag = false; digitalWrite(M1_EN, LOW); digitalWrite(M2_EN, LOW); delay(100); motor1.setSpeed(-HOMING_SPEED); motor2.setSpeed(-HOMING_SPEED); bool m1 = false, m2 = false; while (!(m1 && m2)) { server.handleClient(); // Keep webpage accessible if (!m1) { if (digitalRead(REED1) == LOW) { motor1.setSpeed(0); motor1.setCurrentPosition(0); m1 = true; } else { motor1.runSpeed(); } } if (!m2) { if (digitalRead(REED2) == LOW) { motor2.setSpeed(0); motor2.setCurrentPosition(0); m2 = true; } else { motor2.runSpeed(); } } } delay(300); if (digitalRead(REED1) == LOW && digitalRead(REED2) == LOW) { homingOK = true; m1StepCounter = 0; m2Position = 0; Serial.println("[Homing] Done - both contacts OK"); } else { Serial.println("[Homing] Verification failed - restarting"); delay(1000); doHoming(); // Retry return; } mode = IDLE; } // ── Butterfly (one step per loop() call) ───────────────────── void butterflyStep() { if (stopFlag || m2Position >= MAX_RADIUS) { motor1.setSpeed(0); motor2.setSpeed(0); digitalWrite(M1_EN, HIGH); digitalWrite(M2_EN, HIGH); mode = IDLE; stopFlag = false; Serial.println("[Butterfly] Finished"); return; } // Motor 1 rotates continuously motor1.setSpeed(M1_SPEED); motor1.runSpeed(); m1StepCounter++; // Every full revolution, Motor 2 moves one step outward if (m1StepCounter % STEPS_PER_REV == 0) { motor2.move(RADIUS_PER_REV); m2Position += RADIUS_PER_REV; } motor2.setSpeed(M2_SPEED); motor2.run(); } // ── Server routes ───────────────────────────────────────────── void setupServer() { server.on("/", []() { server.send_P(200, "text/html", WEBPAGE); }); server.on("/status", []() { server.send(200, "text/plain", statusText()); }); server.on("/cmd", []() { String a = server.arg("a"); if (a == "homing") { server.send(200, "text/plain", "Homing running..."); doHoming(); } else if (a == "butterfly") { if (!homingOK) { server.send(200, "text/plain", "Error: please run homing first!"); return; } mode = BUTTERFLY; stopFlag = false; m1StepCounter = 0; m2Position = 0; digitalWrite(M1_EN, LOW); digitalWrite(M2_EN, LOW); server.send(200, "text/plain", "Butterfly running..."); } else if (a == "stop") { stopFlag = true; motor1.setSpeed(0); motor2.setSpeed(0); digitalWrite(M1_EN, HIGH); digitalWrite(M2_EN, HIGH); mode = IDLE; server.send(200, "text/plain", "Stopped"); } else { server.send(400, "text/plain", "Unknown command"); } }); } // ── Setup ───────────────────────────────────────────────────── void setup() { Serial.begin(115200); delay(500); pinMode(REED1, INPUT_PULLUP); pinMode(REED2, INPUT_PULLUP); pinMode(M1_EN, OUTPUT); pinMode(M2_EN, OUTPUT); digitalWrite(M1_EN, HIGH); digitalWrite(M2_EN, HIGH); motor1.setMaxSpeed(2000); motor2.setMaxSpeed(2000); // Start Access Point WiFi.softAP(AP_SSID, AP_PASS); Serial.print("IP: "); Serial.println(WiFi.softAPIP()); // always 192.168.4.1 setupServer(); server.begin(); Serial.println("Webserver started"); } // ── Loop ────────────────────────────────────────────────────── void loop() { server.handleClient(); if (mode == BUTTERFLY) { butterflyStep(); } }