Filament runout sensor
An optical slot sensor on the filament path and an A4988 extruder: a debounced runout, 10 mm more printed from the filament still in the path, then a beeping pause that waits for you. The whole build, an Arduino Uno and 7 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.
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Filament runout sensor: an optical slot sensor, an extruder stepper and a pause that waits for you.
//
// pin 4 the sensor's signal (a slot sensor: filament in the slot blocks the beam)
// pin 2 extruder STEP pin 5 extruder DIR pin 8 EN (LOW enables the A4988)
// pin 12 resume button (to GND)
// pin 13 amber "filament" lamp pin 11 the beeper, a piezo
//
// A runout sensor sits on the filament path before the extruder. The filament
// passes through it; when the spool runs dry the tail end goes by and the
// sensor changes state. Three details turn that into a good feature:
//
// 1. Debounce. The reading must stay "empty" for RUNOUT_CONFIRM_MS before the
// sketch believes it, so a wobble of the filament is not a runout.
// 2. Runout distance. The sensor is upstream of the extruder, so there is still
// filament between them. The sketch keeps printing for RUNOUT_DISTANCE_MM
// before pausing, the idea behind Marlin's FILAMENT_RUNOUT_DISTANCE_MM.
// 3. A real pause. The extruder stops, the beeper calls, and nothing resumes
// until filament is back in the sensor AND you press resume. Marlin runs
// M600, a filament change, at this point.
//
// This is a sketch, not Marlin. Mokxi has no spool: click the card into the
// sensor's slot to load filament and out again to run out. The extruder turns at
// 2 mm of filament a second, 93 steps a millimeter at 1/16 step.
const int SENSOR_PIN = 4;
const int STEP_PIN = 2;
const int DIR_PIN = 5;
const int ENABLE_PIN = 8;
const int RESUME_PIN = 12;
const int LAMP_PIN = 13;
const int BEEPER_PIN = 11;
const long STEPS_PER_MM = 93;
const long FEED_MM_S = 2;
const unsigned long RUNOUT_CONFIRM_MS = 300;
const long RUNOUT_DISTANCE_MM = 10;
enum State { WAITING, PRINTING, RUNNING_OUT, PAUSED };
State state = WAITING;
long extrudedSteps = 0; // since printing started
long runoutAtSteps = 0; // where the runout was confirmed
unsigned long emptySince = 0;
unsigned long lastStep = 0, lastBeep = 0, lastReport = 0;
// The slot sensor's output is HIGH while the beam is blocked: filament present.
bool filamentPresent() { return digitalRead(SENSOR_PIN) == HIGH; }
void say(const char *text) { Serial.println(text); }
void setup() {
Serial.begin(115200);
pinMode(SENSOR_PIN, INPUT);
pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);
pinMode(ENABLE_PIN, OUTPUT);
pinMode(RESUME_PIN, INPUT_PULLUP);
pinMode(LAMP_PIN, OUTPUT);
digitalWrite(DIR_PIN, HIGH);
digitalWrite(ENABLE_PIN, HIGH);
say("Runout demo: load filament (put the card in the slot) to start printing");
}
void loop() {
unsigned long now = millis();
bool present = filamentPresent();
digitalWrite(LAMP_PIN, present ? HIGH : LOW);
// Debounce: "empty" has to last before it counts.
if (present) emptySince = 0;
else if (emptySince == 0) emptySince = now;
bool confirmedEmpty = !present && emptySince != 0 && now - emptySince >= RUNOUT_CONFIRM_MS;
switch (state) {
case WAITING:
if (present) {
state = PRINTING;
digitalWrite(ENABLE_PIN, LOW);
say("filament loaded: printing");
}
break;
case PRINTING:
if (confirmedEmpty) {
state = RUNNING_OUT;
runoutAtSteps = extrudedSteps;
say("runout detected: using the filament still in the path");
}
break;
case RUNNING_OUT:
if (extrudedSteps - runoutAtSteps >= RUNOUT_DISTANCE_MM * STEPS_PER_MM) {
state = PAUSED;
digitalWrite(ENABLE_PIN, HIGH);
say("PAUSED: load new filament, then press resume");
}
break;
case PAUSED:
if (now - lastBeep >= 2000) {
lastBeep = now;
tone(BEEPER_PIN, 2000, 150);
}
if (digitalRead(RESUME_PIN) == LOW) {
if (present) {
state = PRINTING;
digitalWrite(ENABLE_PIN, LOW);
say("resumed: printing");
} else if (now - lastReport >= 1000) {
lastReport = now;
say("still no filament in the sensor: load it first");
}
}
break;
}
// The extruder: one step every 1 / (2 mm/s x 93 steps/mm), about 5.4 ms, while printing.
bool extruding = state == PRINTING || state == RUNNING_OUT;
if (extruding && micros() - lastStep >= 1000000UL / (FEED_MM_S * STEPS_PER_MM)) {
lastStep = micros();
digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(4);
digitalWrite(STEP_PIN, LOW);
extrudedSteps++;
if (extrudedSteps % (10 * STEPS_PER_MM) == 0) {
Serial.print("extruded ");
Serial.print(extrudedSteps / STEPS_PER_MM);
Serial.println(" mm");
}
}
}
Parts list
14 parts, plus the jumper wires. Every one is in the editor's parts bin.
- 1 × Arduino Uno R3
- 1 × Full-size breadboard
- 1 × Stepper driver, A4988
- 1 × Stepper motor, NEMA 17
- 1 × Power, 12 V
- 4 × Power
- 1 × Photo interrupter, slot
- 1 × Pushbutton
- 1 × LED, yellow
- 1 × Resistor, 220 Ω
- 1 × Piezo speaker
How it is wired
21 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.
- Arduino Uno R3 pin 13; LED, yellow pin A
- Arduino Uno R3 pin 12; Pushbutton pin 1a; Pushbutton pin 1b
- Arduino Uno R3 pin 11; Piezo speaker pin 1
- Arduino Uno R3 pin 8; Stepper driver, A4988 pin EN
- Arduino Uno R3 pin 5; Stepper driver, A4988 pin DIR
- Arduino Uno R3 pin 4; Photo interrupter, slot pin SIG
- Arduino Uno R3 pin 2; Stepper driver, A4988 pin STEP
- Arduino Uno R3 pin 5V; Photo interrupter, slot pin VCC
- Ground: Arduino Uno R3 pin GND; Photo interrupter, slot pin GND; Pushbutton pin 2a; Pushbutton pin 2b; Resistor, 220 Ω pin 2; Piezo speaker pin 2
- 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
- LED, yellow pin C; Resistor, 220 Ω pin 1
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.