/* * Standing desk leg controller - Wokwi simulation. * * Position simulation: measuredPos is updated internally by * counting STEP pulses. Each step changes the position by one * unit in the current direction. On real hardware, measuredPos * would come from the VL53L0X ToF sensor. * * Controls: * SIT -> target = 200 * STAND -> target = 700 * STOP -> halt motion, disable driver * ENDSTOP -> reset position to 0 (simulates homing) * POT -> fine manual adjust: if the potentiometer moves * more than POT_DEADBAND units from the last * accepted reading, it takes over as the target * (overrides any preset). This mimics the manual * height adjust knob on a real standing desk, * which needs a deadband to avoid ADC noise * constantly retargeting the motion. */ #include #include // ---------- Pin mapping ---------- const uint8_t PIN_STEP = 2; const uint8_t PIN_DIR = 3; const uint8_t PIN_ENABLE = 8; const uint8_t PIN_SDA = 4; // I2C0 SDA const uint8_t PIN_SCL = 5; // I2C0 SCL const uint8_t PIN_BTN_SIT = 10; const uint8_t PIN_BTN_STAND = 11; const uint8_t PIN_BTN_STOP = 12; const uint8_t PIN_ENDSTOP = 13; const uint8_t PIN_POT = 28; // ADC2 // ---------- Motion / control ---------- const int POS_MIN = 0; const int POS_MAX = 1000; const int PRESET_SIT = 200; const int PRESET_STAND = 700; const int TOLERANCE = 2; const unsigned long STEP_INTERVAL_US = 2000; // ~500 steps/s const uint8_t DIR_UP = HIGH; const uint8_t DIR_DOWN = LOW; // Manual override (potentiometer) — deadband avoids ADC noise // constantly retargeting the motion. The pot only takes control // when its reading differs from the last accepted value by more // than POT_DEADBAND. const int POT_DEADBAND = 15; // ---------- State ---------- int target = 0; int measuredPos = 0; bool moving = false; int lastDir = DIR_UP; int lastPotReading = -1000; // force first read to register bool manualMode = false; unsigned long lastStepMicros = 0; unsigned long lastLcdMillis = 0; unsigned long lastSerialMillis = 0; LiquidCrystal_I2C lcd(0x27, 16, 2); // ---------- Helpers ---------- int readPotScaled() { int raw = analogRead(PIN_POT); // Wokwi's potentiometer model returns 0-1023 at full range. return map(raw, 0, 1023, POS_MIN, POS_MAX); } void enableDriver(bool on) { digitalWrite(PIN_ENABLE, on ? LOW : HIGH); } void pulseStep() { digitalWrite(PIN_STEP, HIGH); delayMicroseconds(5); digitalWrite(PIN_STEP, LOW); if (lastDir == DIR_UP) measuredPos++; else measuredPos--; measuredPos = constrain(measuredPos, POS_MIN, POS_MAX); } void updateLcd() { if (millis() - lastLcdMillis < 100) return; lastLcdMillis = millis(); lcd.setCursor(0, 0); lcd.print("Target: "); lcd.print(target); if (manualMode) lcd.print(" M "); else lcd.print(" "); lcd.print(" "); lcd.setCursor(0, 1); lcd.print("Actual: "); lcd.print(measuredPos); lcd.print(" "); } void setTarget(int newTarget, bool manual) { target = constrain(newTarget, POS_MIN, POS_MAX); moving = true; manualMode = manual; enableDriver(true); Serial.print("New target: "); Serial.print(target); Serial.println(manual ? " (manual)" : " (preset)"); } void stopMotion() { moving = false; enableDriver(false); Serial.println("Stopped."); } void setup() { Serial.begin(115200); delay(500); Serial.println("=== Standing desk leg controller ==="); Serial.println("Booting..."); pinMode(PIN_STEP, OUTPUT); pinMode(PIN_DIR, OUTPUT); pinMode(PIN_ENABLE, OUTPUT); enableDriver(false); pinMode(PIN_BTN_SIT, INPUT_PULLUP); pinMode(PIN_BTN_STAND, INPUT_PULLUP); pinMode(PIN_BTN_STOP, INPUT_PULLUP); pinMode(PIN_ENDSTOP, INPUT_PULLUP); Serial.println("Setting up I2C on GP4/GP5..."); Wire.setSDA(PIN_SDA); Wire.setSCL(PIN_SCL); Wire.begin(); Serial.println("Initializing LCD..."); lcd.init(); lcd.backlight(); lcd.setCursor(0, 0); lcd.print("Ready."); lcd.setCursor(0, 1); lcd.print("SIT / STAND / POT"); delay(1200); lcd.clear(); // Seed the pot tracker so the startup position doesn't fire // the manual override immediately. lastPotReading = readPotScaled(); Serial.println("Ready. Press SIT, STAND, STOP, ENDSTOP or move the pot."); } void loop() { if (digitalRead(PIN_BTN_SIT) == LOW) setTarget(PRESET_SIT, false); if (digitalRead(PIN_BTN_STAND) == LOW) setTarget(PRESET_STAND, false); if (digitalRead(PIN_BTN_STOP) == LOW) stopMotion(); if (digitalRead(PIN_ENDSTOP) == LOW) { Serial.println("Endstop hit. Position reset to 0."); measuredPos = POS_MIN; target = POS_MIN; stopMotion(); } // Manual override: check pot movement against deadband. int potNow = readPotScaled(); if (abs(potNow - lastPotReading) > POT_DEADBAND) { lastPotReading = potNow; setTarget(potNow, true); } if (moving) { int error = target - measuredPos; if (abs(error) <= TOLERANCE) { stopMotion(); } else { lastDir = (error > 0) ? DIR_UP : DIR_DOWN; digitalWrite(PIN_DIR, lastDir); unsigned long now = micros(); if (now - lastStepMicros >= STEP_INTERVAL_US) { pulseStep(); lastStepMicros = now; } } } updateLcd(); if (millis() - lastSerialMillis > 500) { lastSerialMillis = millis(); Serial.print("target="); Serial.print(target); Serial.print(" measured="); Serial.print(measuredPos); Serial.print(" moving="); Serial.print(moving); Serial.print(" manual="); Serial.println(manualMode); } }