Built-in project · Arduino Uno R3

Stepper homing on an Arduino Uno

An A4988 and a NEMA 17 home against a microswitch endstop the way a 3D printer does, fast and then a slow second touch, then move to typed positions inside software limits. The whole build, an Arduino Uno and 3 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Intermediate Runs in your browser. Free, and no account needed.

Stepper homing · Arduino Uno R3live0.000 s 0.00x
Press Run, type H, then click the endstop when it says seeking
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// Stepper homing: an A4988, a NEMA 17 and an endstop, the way a 3D printer finds zero.
//
//   pin 2   STEP      pin 5   DIR      pin 8   EN (LOW enables the driver)
//   pin 9   the X endstop, a microswitch to GND (internal pull-up, LOW when pressed)
//
// These are the pins a GRBL CNC shield uses for X. This is not GRBL or Marlin;
// it is a small sketch that does one thing they both do, and does it the same way.
//
// A stepper knows how many steps it has taken, not where it is. At power-up the
// carriage could be anywhere, so the machine homes: it creeps toward the endstop
// until the switch closes, backs off a few millimeters, and comes in again
// slowly, because the slow touch is the repeatable one (Marlin calls it the
// bump). That switch position becomes 0, and every move after is counted from it.
//
// Mokxi has no carriage, so YOU are the carriage: when the Serial Monitor says
// "seeking", click and hold the endstop's lever, let go when it backs off, and
// press it again for the slow touch. Then type a position, such as 120, and the
// axis moves there. Positions outside 0 to 200 mm are refused, like a printer's
// software endstops.
//
// The axis is a GT2 belt on a 20 tooth pulley: 40 mm per turn. At 1/16 step a
// 200 step motor takes 3200 microsteps a turn, so 80 steps per millimeter.

const int STEP_PIN = 2;
const int DIR_PIN = 5;
const int ENABLE_PIN = 8;
const int ENDSTOP_PIN = 9;

const long STEPS_PER_MM = 80;
const long MAX_TRAVEL_MM = 200;
const long SEEK_MM_S = 20;    // the fast approach
const long BUMP_MM_S = 2;     // the slow, repeatable touch
const long BACKOFF_MM = 5;
const long MOVE_MM_S = 40;

bool homed = false;
long position = 0;  // in steps, from the endstop

bool endstopPressed() { return digitalRead(ENDSTOP_PIN) == LOW; }

// The wait between steps that gives a speed in millimeters a second.
unsigned int stepWait(long mmPerSecond) { return 1000000L / (mmPerSecond * STEPS_PER_MM) - 4; }

// One step in the direction already set, then the wait that sets the speed.
void stepOnce(unsigned int waitUs) __attribute__((noinline));
void stepOnce(unsigned int waitUs) {
  digitalWrite(STEP_PIN, HIGH);
  delayMicroseconds(4);
  digitalWrite(STEP_PIN, LOW);
  delayMicroseconds(waitUs);
}

// Move toward the endstop until it closes, giving up after `limitMm`.
bool seek(long mmPerSecond, long limitMm) {
  digitalWrite(DIR_PIN, LOW);  // LOW is toward the endstop on this axis
  unsigned int wait = stepWait(mmPerSecond);
  long left = limitMm * STEPS_PER_MM;
  while (!endstopPressed()) {
    if (left-- <= 0) return false;
    stepOnce(wait);
  }
  return true;
}

void fail(const char *why) {
  homed = false;
  digitalWrite(ENABLE_PIN, HIGH);  // let the motor go, as a firmware does on an error
  Serial.print("homing failed: ");
  Serial.println(why);
}

void home() {
  Serial.println("homing X: seeking the endstop");
  digitalWrite(ENABLE_PIN, LOW);
  if (!seek(SEEK_MM_S, MAX_TRAVEL_MM + 20)) {
    fail("no endstop within the axis length, check the switch and its wire");
    return;
  }
  Serial.println("endstop hit: backing off");
  digitalWrite(DIR_PIN, HIGH);
  for (long s = 0; s < BACKOFF_MM * STEPS_PER_MM; s++) stepOnce(stepWait(SEEK_MM_S));
  delay(300);  // give the switch time to open
  if (endstopPressed()) {
    fail("the endstop is still pressed after backing off");
    return;
  }
  Serial.println("bump: approaching slowly");
  if (!seek(BUMP_MM_S, BACKOFF_MM * 2)) {
    fail("the endstop did not close on the slow approach");
    return;
  }
  position = 0;
  homed = true;
  Serial.println("homed: X = 0.0");
}

void moveTo(long mm) {
  if (!homed) {
    Serial.println("refused: home the axis first (type H)");
    return;
  }
  if (mm < 0 || mm > MAX_TRAVEL_MM) {
    Serial.println("refused: outside 0 to 200 mm (software endstop)");
    return;
  }
  long target = mm * STEPS_PER_MM;
  digitalWrite(DIR_PIN, target > position ? HIGH : LOW);
  Serial.print("moving to X = ");
  Serial.println(mm);
  unsigned int wait = stepWait(MOVE_MM_S);
  while (position != target) {
    stepOnce(wait);
    position += target > position ? 1 : -1;
  }
  Serial.print("X = ");
  Serial.println(position / STEPS_PER_MM);
}

void setup() {
  Serial.begin(115200);
  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(ENABLE_PIN, OUTPUT);
  pinMode(ENDSTOP_PIN, INPUT_PULLUP);
  digitalWrite(ENABLE_PIN, HIGH);  // driver off until there is something to do
  Serial.println("X axis ready, position unknown: type H to home");
}

void loop() {
  static long typed = 0;
  static bool haveDigits = false;
  while (Serial.available() > 0) {
    char c = Serial.read();
    if (c == 'H' || c == 'h') {
      home();
    } else if (c >= '0' && c <= '9') {
      typed = typed * 10 + (c - '0');
      haveDigits = true;
    } else if (c == '\n' || c == '\r') {
      if (haveDigits) moveTo(typed);
      typed = 0;
      haveDigits = false;
    }
  }
}

Parts list

9 parts, plus the jumper wires. Every one is in the editor's parts bin.

How it is wired

17 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Ground: Arduino Uno R3 pin GND
  • Arduino Uno R3 pin 9; Limit switch, lever microswitch pin NO
  • Arduino Uno R3 pin 8; Stepper driver, A4988 pin EN
  • Arduino Uno R3 pin 5; Stepper driver, A4988 pin DIR
  • Arduino Uno R3 pin 2; Stepper driver, A4988 pin STEP
  • Supply: Stepper driver, A4988 pin MS1
  • Supply: Stepper driver, A4988 pin MS2
  • Supply: Stepper driver, A4988 pin MS3
  • Stepper driver, A4988 pin RST; Stepper driver, A4988 pin SLP
  • 12 V: Stepper driver, A4988 pin VMOT
  • Stepper driver, A4988 pin 2B; Stepper motor, NEMA 17 pin A1
  • Stepper driver, A4988 pin 2A; Stepper motor, NEMA 17 pin A2
  • Stepper driver, A4988 pin 1A; Stepper motor, NEMA 17 pin B1
  • Stepper driver, A4988 pin 1B; Stepper motor, NEMA 17 pin B2
  • Supply: Stepper driver, A4988 pin VDD
  • Ground: Stepper driver, A4988 pin GND
  • Ground: Limit switch, lever microswitch pin COM

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.