From 7586f90565a28787f2c7f5fd589b6ad1d4609682 Mon Sep 17 00:00:00 2001 From: Cameron Date: Thu, 4 Dec 2025 16:43:26 -0500 Subject: [PATCH] Try to improve backlash handling --- blinds.ino | 90 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 69 insertions(+), 21 deletions(-) diff --git a/blinds.ino b/blinds.ino index 00e9545..9c8f71a 100644 --- a/blinds.ino +++ b/blinds.ino @@ -24,6 +24,16 @@ const bool CLEAR_EEPROM = false; unsigned long lastMillis = 0; +// Backlash compensation and hold configuration +const int BACKLASH_STEPS = 400; // Tune as needed for your linkage +const unsigned long HOLD_OUTPUTS_MS = 300; // Hold time after moves to prevent settling + +// Runtime variables for compensation and holding +int approachDir = 0; // Desired final approach direction for current command (-1 or 1) +long prepTarget = 0; // First phase target used to preload backlash +long finalMoveTarget = 0; // Second phase target: the actual requested position +unsigned long holdUntil = 0; // Timestamp when we should release outputs + struct Config { long openPosition; long closedPosition; @@ -39,6 +49,9 @@ const int OPENING_BLINDS = 2; const int CLOSING_BLINDS = 3; const int IDLE = 4; const int ADJUSTING = 5; +const int HOLDING = 6; +const int OPENING_PREP = 7; +const int CLOSING_PREP = 8; int state = SETUP; int previousState = SETUP; @@ -109,18 +122,37 @@ void updateState(int newState) { void openBlinds() { if (!config.blindsOpen && state == IDLE) { Serial.println("Opening Blinds"); - updateState(OPENING_BLINDS); + long baseTarget = config.openPosition; + long delta = config.openPosition - config.closedPosition; + int openDir = (delta > 0) ? 1 : ((delta < 0) ? -1 : -1); // default to -1 if equal (unlikely) + approachDir = openDir; + + // Phase 1: move to preload point on the "closing" side of the final target + prepTarget = baseTarget - (approachDir * BACKLASH_STEPS); + finalMoveTarget = baseTarget; + + updateState(OPENING_PREP); stepper.enableOutputs(); - stepper.moveTo(config.openPosition); + stepper.moveTo(prepTarget); } } void closeBlinds() { if (config.blindsOpen && state == IDLE) { Serial.println("Closing Blinds"); - updateState(CLOSING_BLINDS); + long baseTarget = config.closedPosition; + long delta = config.openPosition - config.closedPosition; + int openDir = (delta > 0) ? 1 : ((delta < 0) ? -1 : -1); + int closeDir = -openDir; + approachDir = closeDir; + + // Phase 1: move to preload point on the "opening" side of the final target (opposite of close direction) + prepTarget = baseTarget - (approachDir * BACKLASH_STEPS); + finalMoveTarget = baseTarget; + + updateState(CLOSING_PREP); stepper.enableOutputs(); - stepper.moveTo(config.closedPosition); + stepper.moveTo(prepTarget); } } @@ -213,20 +245,28 @@ void loop() { delay(10); // <- fixes some issues with WiFi stability break; case SLACK: - if (previousState == OPENING_BLINDS) { - stepper.move(3000); - } else if (previousState == CLOSING_BLINDS) { - stepper.move(-3000); - } else if (stepper.distanceToGo() == 0) { - updateState(IDLE); + // Deprecated slack handling; transition to idle once any pending motion completes + if (stepper.distanceToGo() == 0) { stepper.disableOutputs(); - return; + updateState(IDLE); + } else { + stepper.run(); + } + break; + case OPENING_PREP: + case CLOSING_PREP: + if (stepper.distanceToGo() != 0) { + stepper.run(); + } else { + Serial.println("Open/Close Preparation complete"); + // Phase 2: approach target from a consistent direction to remove backlash + if (state == OPENING_PREP) { + updateState(OPENING_BLINDS); + } else { + updateState(CLOSING_BLINDS); + } + stepper.moveTo(finalMoveTarget); } - - stepper.run(); - - // Update previous to SLACK until we take up the slack to prevent the stepper from moving forever - previousState = state; break; case OPENING_BLINDS: case CLOSING_BLINDS: @@ -234,17 +274,25 @@ void loop() { if (millis() - lastMillis > 2000) { Serial.print("Speed: "); Serial.print(stepper.speed()); - Serial.print(" Dist: "); + Serial.print(" Remaining Dist: "); Serial.println(stepper.distanceToGo()); lastMillis = millis(); } stepper.run(); } else { - stepper.disableOutputs(); bool open = state == OPENING_BLINDS; config.blindsOpen = open; sendBlindsUpdate(open); - updateState(SLACK); + // Hold outputs briefly to prevent settling drift, then transition to IDLE + holdUntil = millis() + HOLD_OUTPUTS_MS; + updateState(HOLDING); + } + break; + case HOLDING: + if (millis() >= holdUntil) { + Serial.println("Hold complete, transitioning to idle"); + stepper.disableOutputs(); + updateState(IDLE); } break; case ADJUSTING: @@ -259,8 +307,8 @@ void loop() { Serial.print("Finished adjusting to: "); Serial.println(stepper.currentPosition()); - updateState(IDLE); - stepper.disableOutputs(); + holdUntil = millis() + HOLD_OUTPUTS_MS; + updateState(HOLDING); } break;