Introduction
Build your own DIY automatic tubing measurement and cutter machine using Arduino!
This project is perfect for makers, hobbyists, or small businesses that work with flexible plastic, PVC, or vinyl tubing. No more measuring and cutting by hand—this machine does it for you accurately and efficiently.
Features:
- Automatic measurement based on your desired length
- Precise cutting with stepper motor control
- Works with soft tubing: PVC, vinyl, silicone, etc.
- Built with Arduino for easy customization
- Compact and DIY-friendly design
Parts Used:
- Arduino Uno/Nano
- Stepper motor + driver
- Rotary encoder
- Cutter blade + servo motor
- OLED display
- Power supply
Ideal for cutting:
Tubing for air, water, drip irrigation, medical, or lab use.
This project is perfect for:
- Hydroponics setups
- Aquarium tubing systems
- 3D printer filament cutting?? (possibly?)
- Industrial automation prototyping
With features such as EEPROM storage for settings, adjustable speed control, and emergency stop functionality, this device enables precise cutting. It ensures precision in every cut. It also offers ease of use and greater safety. It also provides repeatability.
Features
✅ OLED Menu Interface – Easy navigation using a rotary encoder.
✅ Stepper Motor Control – Precisely moves tubing to the desired length.
✅ Servo-Based Cutting Mechanism – Cleanly cuts the tubing.
✅ Multiple Measurement Units – Supports CM, MM, FT, and IN.
✅ Speed & Acceleration Control – Adjustable via a bar graph in the menu.
✅ EEPROM Storage – Saves settings between power cycles.
✅ Emergency Stop – Instantly halts the machine if needed.
Components Needed
- Arduino Uno/Nano (or any compatible board)
- OLED Display (128×64, I2C)
- Rotary Encoder (with switch)
- Stepper Motor + Driver (e.g., NEMA23, TB6600 or A4988)
- Servo Motor (e.g., DS5180SG )
- Power Supply (24V for the system and 7.4V for stepper motor)
- Limit Switch (optional for homing)
- Breadboard & Jumper Wires
How It Works
1. User Entry & Menu Navigation
The rotary encoder lets you:
- Rotate to navigate menu options.
- Short-press to select/change values.
- Long-press to return to the main menu.
The OLED display shows:
- Length (adjustable in Meter, CM, MM, FT, or IN).
- Quantity (number of cuts).
- Speed (1-10 scale).
- Progress bar during operation.
2. Stepper Motor Movement
The stepper motor moves the tubing based on:
- Steps per revolution (adjustable via microstepping).
- Gear circumference (calibrated for accuracy).
3. Servo Cutting Mechanism
Once the tubing reaches the desired length:
- The servo rotates 90° to cut.
- After a delay, it returns to 0°.
- The stepper moves again for the next cut.
4. EEPROM Storage
Your settings (length, quantity, speed, unit, gear diameter, and calibration) are saved in EEPROM, so they persist even after power is off.
5. Emergency Stop
A long press on the rotary encoder instantly:
- Stops the stepper motor.
- Resets the servo to 0°.
- Returns to the main menu.
Code Breakdown
Key Libraries Used
- U8g2 & MUI – For OLED menu handling.
- Versatile_RotaryEncoder – For encoder entry.
- AccelStepper – For stepper motor control.
- Servo – For servo motor control.
- EEPROM – For storing settings.
Pin Definitions
#define clk 3 // Rotary encoder CLK
#define dt 2 // Rotary encoder DT
#define sw 4 // Rotary encoder switch
#define STEP_PIN A1 // Stepper motor STEP
#define DIR_PIN A0 // Stepper motor DIR
#define ENABLE_PIN 12 // Stepper enable (optional)
#define SERVO_PIN 5 // Servo motor signal
EEPROM Addresses
#define ADDR_NUM_VALUE 0 // Stores cut quantity
#define ADDR_LEG_VALUE 4 // Stores cut length
#define ADDR_BAR_VALUE 8 // Stores speed setting
#define ADDR_UNIT_IDX_VALUE 12 // Stores unit (M/CM/MM/FT/IN)
#define ADDR_GEAR_DIAMETER_mm 16 // Stores gear diameter (in mm units)
#define ADDR_GEAR_CALIBRATION_100um 20 // Stores gear alibration (in 100µm units)
Stepper Motor Calibration
Adjust these based on your setup:
gear_diameter_mm = 28; // Default gear diameter in mm = 28
gear_calibration_100um = 1; //Default gear calibration is 1.0mm in 100um units(0.1mm)
float gear_circumference_mm = (gear_diameter_mm * 3.14 + gear_calibration_100um/10.0-0.1);
const int steps_per_revolution = 200 * 8; // Steps per rev (with microstepping)
Microstepping Settings (TB6600 Driver)
S1 | S2 | S3 | Microstep | Steps/Revolution |
---|---|---|---|---|
ON | ON | ON | Full Step | 200 |
ON | OFF | ON | 1/2 Step | 400 |
OFF | ON | ON | 1/2 Step | 400 |
ON | OFF | OFF | 1/4 Step | 800 |
OFF | ON | OFF | 1/8 Step | 1600 |
OFF | OFF | ON | 1/16 Step | 3200 |
OFF | OFF | OFF | 1/32 Step | 6400 |
How to Use
- Power on the device.
- Navigate the menu using the rotary encoder.
- Set length, quantity, speed, and unit.
- Press “Start” to start cutting.
- Long-press at any time for emergency stop.
Future Improvements
- Add a limit switch for homing.
- Implement wireless control via Bluetooth/Wi-Fi.
- Add a filament sensor for automatic feed detection.
- Integrate a battery for portability.
Conclusion
This DIY Tubing Cutter is a versatile, precise, and user-friendly solution for automating tubing cuts. Whether you’re working on hydroponics, aquariums, or industrial projects, this system saves time and ensures consistency.
Click here to order a hardware kit.
3D-printed Parts (10 items) Zip File Download.
Click here to download or copy and Paste The full code below:
/* Description: Arduino-based tubing cutter system with OLED menu, rotary encoder input, stepper motor, and servo motor. Features: - User-friendly menu interface using U8g2 and MUI libraries. - Rotary encoder for navigation and input. - Stepper motor for precise tubing movement. - Servo motor for cutting action. - EEPROM storage for saving and recalling user settings. - Emergency stop functionality. - Dynamic speed and acceleration control for the stepper motor. MUI Reference: https://github.com/olikraus/u8g2/wiki/muiref MUI: https://github.com/olikraus/u8g2/wiki/muimanual U8g2 Menu with Rotary Encoder (Versatile_RotaryEncoder library). Short Button Press: Switch between field movement and data increment/decrement Long Button Press: Return to main menu Universal 8bit Graphics Library (https://github.com/olikraus/u8g2/) Libraries Used: - U8g2: OLED display handling. - MUI: Menu user interface. - Versatile_RotaryEncoder: Rotary encoder input. - AccelStepper: Stepper motor control. - Servo: Servo motor control. - EEPROM: Persistent storage for user settings. Pin Definitions: - Rotary Encoder: clk (3), dt (2), sw (4). - Stepper Motor: STEP_PIN (A1), DIR_PIN (A0), ENABLE_PIN (12). - Servo Motor: SERVO_PIN (5). EEPROM Addresses: - ADDR_NUM_VALUE: Stores the quantity of cuts. - ADDR_LEG_VALUE: Stores the length of each cut. - ADDR_BAR_VALUE: Stores the speed setting. - ADDR_UNIT_IDX_VALUE: Stores the selected unit of measurement. - ADDR_GEAR_DIAMETER_mm: Stores the gear diameter(mm). - ADDR_GEAR_CALIBRATION_100um: Stores the calibration for the gear circumference(in 100µm units). Version History: - v1.7: Save & Recall Settings using EEPROM. - v1.8: Return to main menu after process completion. - v1.9: Speed control using bar_value. - V1.10: Fixed cut unit in Meter. - V1.11: Fixed integer overflows on large cutting values. - V1.12: Fixed unit index errors. Further details about the project are available at www.plantmate.ca. */ #include <Arduino.h> #include <U8g2lib.h> // U8g2 library for OLED display, U8g2 by oliver #include <MUIU8g2.h> // MUI library for menu interface #include <Versatile_RotaryEncoder.h> // Rotary encoder library, Versatile_RotaryEncoder by ruiseixasm, Rui Seixas Monteiro #include <AccelStepper.h> // Stepper motor control library #include <Servo.h> // Servo motor control library #include <EEPROM.h> // EEPROM library for persistent storage // Speed and Acceleration Settings #define MIN_SPEED 100 // Minimum speed (steps per second), adjust if needed #define MAX_SPEED 3000 // Maximum speed (steps per second), adjust if needed #define MIN_ACCELERATION 50 // Minimum acceleration (steps per second squared), adjust if needed #define MAX_ACCELERATION 2000 // Maximum acceleration (steps per second squared), adjust if needed // Pin Definitions #define clk 3 // Rotary encoder clock pin #define dt 2 // Rotary encoder data pin #define sw 4 // Rotary encoder switch pin #define STEP_PIN A1 // Stepper motor step pin #define DIR_PIN A0 // Stepper motor direction pin #define ENABLE_PIN 12 // Stepper motor enable pin (optional) #define SERVO_PIN 5 // Servo motor pin // EEPROM Addresses #define ADDR_NUM_VALUE 0 // EEPROM address for num_value (quantity) #define ADDR_LEG_VALUE 4 // EEPROM address for leg_value (length) #define ADDR_BAR_VALUE 8 // EEPROM address for bar_value (speed) #define ADDR_UNIT_IDX_VALUE 12 // EEPROM address for unit_idx_value (unit) #define ADDR_GEAR_DIAMETER_mm 16 // EEPROM address for gear diameter (in mm units) #define ADDR_GEAR_CALIBRATION_100um 20 // EEPROM address for gear alibration (in 100µm units) // MUI Message Definitions #define MUIF_MSG_SELECT 1 // Message for field selection // Create encoder object Versatile_RotaryEncoder versatile_encoder(clk, dt, sw); // Initialize OLED display U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE); // Initialize MUI and AccelStepper objects MUIU8G2 mui; AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN); Servo myServo; // Servo object /* Global Variables: - num_value: Number of cuts to perform. - bar_value: Speed setting (1-10). - unit_idx: Selected unit of measurement (0: M, 1: CM, 2: MM, 3: Ft, 4: In). - leg_value: Length of each cut. - QF_value: Number of cuts completed. - IB_value: Progress in process (not actively used). - steps_per_cut: Number of steps required for one cut. - is_cutting: Flag to track if cutting is in progress. */ uint8_t num_value = 0; // Quantity of cuts to perform uint8_t bar_value = 0; // Speed setting (1-10) uint16_t unit_idx = 0; // Selected unit of measurement uint8_t leg_value = 0; // Length of each cut uint8_t QF_value = 0; // Quantity of cuts completed uint8_t IB_value = 0; // Progress in process (not actively used) uint32_t steps_per_cut = 0; // Steps required for one cut bool is_cutting = false; // Flag to track cutting process // Gear and Stepper Motor Settings uint8_t gear_calibration_100um = 0; // gear calibration in 100µm units uint8_t gear_diameter_mm = 0; // gear diameter in mm units const int steps_per_revolution = 200 * 8; // Steps per revolution (with microstepping) /* Adjust Microstepping If your TB6600 driver is set to 1/16 microstepping, the motor will take 3200 steps per revolution (200 × 16), slowing it down. Try setting the driver to 1/8 or 1/4 microstepping by adjusting S1, S2, S3 flipper Microstep S1 S2 S3 Steps per Revolution (TB6600 Drive Microstep) NC ON ON ON NC FullStep ON ON OFF 200 1/2AStep ON OFF ON 400 1/2BStep OFF ON ON 400 1/4 Step ON OFF OFF 800 1/8 Step OFF ON OFF 1600 // <<== *** Recommend this setting for this project *** 1/16Step OFF OFF ON 3200 1/32Step OFF OFF OFF 6400 */ // List of unit names const char *units[] = {"M", "CM", "MM", "FT", "IN"}; // Function to get the number of units uint16_t unit_name_list_get_cnt(void *data) { return sizeof(units) / sizeof(*units); // Number of units } // Function to get the name of a unit by index const char *unit_name_list_get_str(void *data, uint16_t index) { return units[index]; } // Function to draw a horizontal rule uint8_t mui_hrule(mui_t *ui, uint8_t msg) { if (msg == MUIF_MSG_DRAW) { u8g2.drawHLine(0, mui_get_y(ui), u8g2.getDisplayWidth()); } return 0; } // Function to display current settings uint8_t show_my_data(mui_t *ui, uint8_t msg) { if (msg == MUIF_MSG_DRAW) { u8g2_uint_t x = mui_get_x(ui); u8g2_uint_t y = mui_get_y(ui); u8g2.setCursor(x + 5, y); u8g2.print("Length:"); u8g2.setCursor(x + 50, y); u8g2.print(leg_value); u8g2.print(" "); u8g2.print(units[unit_idx]); u8g2.setCursor(x + 5, y + 12); u8g2.print("Qty:"); u8g2.setCursor(x + 50, y + 12); u8g2.print(num_value); u8g2.setCursor(x + 5, y + 24); u8g2.print("Speed:"); u8g2.setCursor(x + 50, y + 24); u8g2.print(bar_value); } return 0; } // Function to calculate steps_per_cut based on length and unit uint32_t calculateStepsPerCut(uint8_t leg_value, uint8_t unit_idx) { int64_t length_um = 0; switch (unit_idx) { case 0: length_um = (int64_t)leg_value * 1000000LL; break; // M case 1: length_um = (int64_t)leg_value * 10000LL; break; // CM case 2: length_um = (int64_t)leg_value * 1000LL; break; // MM case 3: length_um = (int64_t)leg_value * 304800LL; break; // FT case 4: length_um = (int64_t)leg_value * 25400LL; break; // IN } int64_t gear_circumference_um = (int64_t)gear_diameter_mm * 3142LL + (int64_t)gear_calibration_100um * 100LL; int64_t steps = (length_um * steps_per_revolution) / gear_circumference_um; // Safety clamp if (steps > 2000000000LL) steps = 2000000000LL; return (uint32_t)steps; } // Function to display in-process data uint8_t in_process_data(mui_t *ui, uint8_t msg) { if (msg == MUIF_MSG_DRAW) { u8g2_uint_t x = mui_get_x(ui); u8g2_uint_t y = mui_get_y(ui); // Display current data u8g2.setCursor(x + 5, y); u8g2.print("Length:"); u8g2.setCursor(x + 50, y); u8g2.print(leg_value); u8g2.print(" "); u8g2.print(units[unit_idx]); u8g2.setCursor(x + 5, y + 12); u8g2.print("Qty:"); u8g2.setCursor(x + 50, y + 12); u8g2.print(num_value); u8g2.setCursor(x + 75, y + 12); u8g2.print("Speed:"); u8g2.setCursor(x + 115, y + 12); u8g2.print(bar_value); u8g2.setCursor(x + 5, y + 24); // Draw progress bar int bar_x = x + 5; int bar_y = y + 17; int bar_width = 70; int bar_height = 8; int progress = map(QF_value, 0, num_value, 0, bar_width); u8g2.drawFrame(bar_x, bar_y, bar_width, bar_height); u8g2.drawBox(bar_x, bar_y, progress, bar_height); u8g2.setCursor(x + 82, y + 24); u8g2.print(QF_value); u8g2.print("/"); u8g2.print(num_value); // Start cutting process if not already started steps_per_cut = calculateStepsPerCut(leg_value, unit_idx); if (!is_cutting && QF_value < num_value) { is_cutting = true; stepper.move(steps_per_cut);// Move the stepper for one cut } } return 0; } // MUI Field List muif_t muif_list[] = { MUIF_U8G2_FONT_STYLE(0, u8g2_font_helvR08_tr), // Regular font MUIF_U8G2_FONT_STYLE(1, u8g2_font_helvB08_tr), // Bold font MUIF_RO("HR", mui_hrule), // Horizontal rule MUIF_U8G2_LABEL(), // Label field MUIF_RO("GP", mui_u8g2_goto_data), // Goto data field MUIF_BUTTON("GC", mui_u8g2_goto_form_w1_pi), // Goto form button MUIF_U8G2_U8_MIN_MAX("NV", &num_value, 1, 255, mui_u8g2_u8_min_max_wm_mud_pi), // Quantity input MUIF_U8G2_S8_MIN_MAX("LV", &leg_value, 1, 255, mui_u8g2_u8_min_max_wm_mud_pi), // Length input MUIF_U8G2_U8_MIN_MAX_STEP("NB", &bar_value, 1, 10, 1, MUI_MMS_2X_BAR | MUI_MMS_SHOW_VALUE, mui_u8g2_u8_bar_wm_mud_pf), // Speed input MUIF_U8G2_U8_MIN_MAX_STEP("IB", &IB_value, 0, 100, 1, MUI_MMS_2X_BAR | MUI_MMS_SHOW_VALUE, mui_u8g2_u8_bar_wm_mud_pf), // Progress input MUIF_U8G2_U16_LIST("NA", &unit_idx, NULL, unit_name_list_get_str, unit_name_list_get_cnt, mui_u8g2_u16_list_line_wa_mud_pi), // Unit selection MUIF_U8G2_U8_MIN_MAX("MD", &gear_diameter_mm, 10, 50, mui_u8g2_u8_min_max_wm_mud_pi), // Gear diameter in mm MUIF_U8G2_U8_MIN_MAX("MC", &gear_calibration_100um, 1, 255, mui_u8g2_u8_min_max_wm_mud_pi), // Gear calibration in 100µm MUIF_RO("SH", show_my_data), // Show data field MUIF_RO("IP", in_process_data), // In-process data field MUIF_EXECUTE_ON_SELECT_BUTTON("GO", mui_u8g2_btn_goto_wm_fi), // Goto button }; // Function to save settings to EEPROM void saveSettings() { // Read current values from EEPROM uint8_t saved_num_value; uint8_t saved_leg_value; uint8_t saved_bar_value; uint16_t saved_unit_idx; uint8_t saved_gear_diameter_mm; uint8_t saved_gear_calibration_100um; EEPROM.get(ADDR_NUM_VALUE, saved_num_value); EEPROM.get(ADDR_LEG_VALUE, saved_leg_value); EEPROM.get(ADDR_BAR_VALUE, saved_bar_value); EEPROM.get(ADDR_UNIT_IDX_VALUE, saved_unit_idx); EEPROM.get(ADDR_GEAR_DIAMETER_mm, saved_gear_diameter_mm); EEPROM.get(ADDR_GEAR_CALIBRATION_100um, saved_gear_calibration_100um); // Only write to EEPROM if the values have changed if (saved_num_value != num_value) { EEPROM.put(ADDR_NUM_VALUE, num_value); } if (saved_leg_value != leg_value) { EEPROM.put(ADDR_LEG_VALUE, leg_value); } if (saved_bar_value != bar_value) { EEPROM.put(ADDR_BAR_VALUE, bar_value); } if (saved_unit_idx != unit_idx) { EEPROM.put(ADDR_UNIT_IDX_VALUE, unit_idx); } if (saved_gear_diameter_mm != gear_diameter_mm) { EEPROM.put(ADDR_GEAR_DIAMETER_mm, gear_diameter_mm); } if (saved_gear_calibration_100um != gear_calibration_100um) { EEPROM.put(ADDR_GEAR_CALIBRATION_100um, gear_calibration_100um); } } // Function to load settings from EEPROM void loadSettings() { EEPROM.get(ADDR_NUM_VALUE, num_value); EEPROM.get(ADDR_LEG_VALUE, leg_value); EEPROM.get(ADDR_BAR_VALUE, bar_value); EEPROM.get(ADDR_UNIT_IDX_VALUE, unit_idx); EEPROM.get(ADDR_GEAR_DIAMETER_mm, gear_diameter_mm); EEPROM.get(ADDR_GEAR_CALIBRATION_100um, gear_calibration_100um); // Check if the unit index is valid (0-4) if (unit_idx > 4) { //:0=M 1=CM, 2=MM, 3=FT, 4=IN // Initialize all values to defaults num_value = 3; // Inital cut quantify = 3 leg_value = 10; // Inital cut length = 10cm bar_value = 10; // Inital motor speed = 10 (1-10) unit_idx = 1; // Default to CM (index 1) gear_diameter_mm = 28; // Default gear diameter in mm = 28 gear_calibration_100um = 185; //1mm in 100um units(0.1mm) // Save the defaults back to EEPROM EEPROM.put(ADDR_NUM_VALUE, num_value); EEPROM.put(ADDR_LEG_VALUE, leg_value); EEPROM.put(ADDR_BAR_VALUE, bar_value); EEPROM.put(ADDR_UNIT_IDX_VALUE, unit_idx); EEPROM.put(ADDR_GEAR_DIAMETER_mm, gear_diameter_mm); EEPROM.put(ADDR_GEAR_CALIBRATION_100um, gear_calibration_100um); } } // Function to perform an emergency stop void emergencyStop() { stepper.stop(); digitalWrite(ENABLE_PIN, HIGH); // Disable stepper driver myServo.write(0); // Return servo to safe position is_cutting = false; mui.gotoForm(1, 0); // Return to main menu } // Menu Screens fds_t fds_data[] = MUI_FORM(1) // Main menu MUI_STYLE(1) // Bold font=1, regular font=0 MUI_LABEL(3, 8, "Plantmate Tubing Cutter")// The specified "Text" is placed at position x,y on the form MUI_STYLE(0) // Set font to regular MUI_XY("HR", 0, 11) // MUI_XY("ID", x, y): Position of the bar graph input field on the target form. MUI_XY("SH", 0, 23) // "SH" = show_my_data() MUI_XYAT("GO", 105, 60, 12, " Start ") // Start button - goes to form 12 (process) MUI_XYAT("GO", 55, 60, 10, " Modify ") // Modify button - goes to form 10 (modify) MUI_XYAT("GO", 10, 60, 20, " <- ") // Setting button - goes to form 20 MUI_FORM(10) // Modify data menu MUI_STYLE(1) MUI_LABEL(5, 8, "Modify Data") MUI_XY("HR", 0, 11) MUI_STYLE(0) MUI_LABEL(5, 23, "Length:") MUI_LABEL(5, 36, "Qty:") MUI_LABEL(5, 49, "Speed:") MUI_XY("LV", 50, 23) // "LV" = Length Value MUI_XY("NA", 75, 23) // "NA" = Units select(CM,MM.Ft,In) MUI_XY("NV", 50, 36) // "NV" = Qty Value MUI_XYA("NB", 50, 49, 100)// "NB" = speed bar value MUI_XYAT("GO", 114, 60, 1, " OK ") MUI_FORM(12) // In-process menu MUI_STYLE(1) MUI_LABEL(5, 8, "In Process...") MUI_XY("HR", 0, 11) MUI_STYLE(0) MUI_XY("IP", 0, 23) MUI_XYAT("GO", 65, 60, 1, " EMERGENCY STOP ") MUI_FORM(20) // Settings menu MUI_STYLE(1) MUI_LABEL(5, 8, "Settings") MUI_XY("HR", 0, 11) MUI_STYLE(0) MUI_XYAT("GO", 114, 60, 1, " Back ") MUI_LABEL(5, 25, "Gear Diameter:") MUI_XY("MD", 80,25) // "MD" = gear diameter MUI_LABEL(98, 25, "mm") MUI_LABEL(5, 40, "Calibration:") MUI_XY("MC", 62, 40) // "MC" = gear calibration MUI_LABEL(80, 40, " x 0.1mm") // 100um = 0.1m MUI_LABEL(5, 60, "System Info: v1.12"); // Global variables for menu redraw and input event handling uint8_t is_redraw = 1; uint8_t rotate_event = 0; // 0 = not turning, 1 = CW, 2 = CCW uint8_t press_event = 0; // 0 = not pushed, 1 = pushed uint8_t long_press_event = 0; // 0 = not pushed, 1 = pushed // Function to handle rotary encoder rotation void handleRotate(int8_t rotation) { if (rotation > 0) rotate_event = 2; // CW else rotate_event = 1; // CCW } // Function to handle rotary encoder button press void handlePressRelease() { press_event = 1; } // Function to handle rotary encoder long press void handleLongPressRelease() { long_press_event = 1; emergencyStop(); // Call emergency stop } // Setup function void setup(void) { Serial.begin(115200); loadSettings(); // Load saved settings from EEPROM // Initialize rotary encoder handlers versatile_encoder.setHandleRotate(handleRotate); versatile_encoder.setHandlePressRelease(handlePressRelease); versatile_encoder.setHandleLongPressRelease(handleLongPressRelease); // Initialize OLED display and MUI u8g2.begin(); mui.begin(u8g2, fds_data, muif_list, sizeof(muif_list) / sizeof(muif_t)); mui.gotoForm(1, 0); // Start with the main menu // Initialize stepper motor pinMode(ENABLE_PIN, OUTPUT); digitalWrite(ENABLE_PIN, LOW); // Enable stepper motor //stepper.setMaxSpeed(MIN_SPEED); //stepper.setAcceleration(MIN_ACCELERATION); stepper.setMaxSpeed(MAX_SPEED); stepper.setAcceleration(MAX_ACCELERATION); // Initialize servo motor myServo.attach(SERVO_PIN); myServo.write(90); // Set servo to initial position } // Function to handle rotary encoder events void handle_events(void) { if (press_event == 1) { QF_value = 0; saveSettings(); // Save settings on button press mui.sendSelect(); is_redraw = 1; press_event = 0; } if (long_press_event == 1) { mui.sendSelectWithExecuteOnSelectFieldSearch(); is_redraw = 1; long_press_event = 0; } if (rotate_event == 1) { mui.nextField(); is_redraw = 1; rotate_event = 0; } if (rotate_event == 2) { mui.prevField(); is_redraw = 1; rotate_event = 0; } } // Main loop void loop(void) { if (mui.isFormActive()) { if (is_redraw) { u8g2.firstPage(); do { versatile_encoder.ReadEncoder(); // Read encoder input mui.draw(); // Draw the menu } while (u8g2.nextPage()); is_redraw = 0; // Clear redraw flag } versatile_encoder.ReadEncoder(); // Read encoder input handle_events(); // Handle encoder events // Stepper motor control logic if (is_cutting) { stepper.run(); // Run the stepper motor // Set speed and acceleration dynamically float speed = MIN_SPEED + (MAX_SPEED - MIN_SPEED) * (bar_value - 1) / 9.0; float acceleration = MIN_ACCELERATION + (MAX_ACCELERATION - MIN_ACCELERATION) * (bar_value - 1) / 9.0; stepper.setMaxSpeed(speed); stepper.setAcceleration(acceleration); // Check if stepper has completed its movement if (stepper.distanceToGo() == 0) { myServo.write(0); // Move servo to cut delay(2000); // Wait for cut to complete myServo.write(90); // Move servo back delay(50); // Small delay QF_value++; // Increment cuts completed is_cutting = false; // Reset cutting flag // Check if process is done if (QF_value >= num_value) { myServo.write(90); mui.gotoForm(1, 0); // Return to main menu } is_redraw = 1; // Redraw display } } } else { // Restart menu system if inactive mui.gotoForm(1, 0); } }
Happy building!
Based on the information above, let’s get started with the hardware to build this system:
Hardware List:
Simple Step-by-Step Instructions – No Cutting or Drilling Required!
Before we start, please note:
All parts and materials used in this project are widely available. They need no cutting, drilling, or modification. This makes the assembly process smooth and hassle-free.
We do have the Complete Hardware Kit for this Project.
Or if you require any special or replacement parts, please don’t hesitate to contact us. We’re happy to assist you.
Estimated Assembly Time: 2 Hours.
✅ Step 1: Mount the Stepper Motor
Hardware Needed:
- 20100 x 300mm Aluminum Profile Extrusion
- NEMA23 Dual Shaft Stepper Motor
- NEMA23 Motor Mount Bracket
- 3D-Printed Enclosure Holder (Left)
- M4 Slot T-nuts × 6
- M4 × 8mm Hex Round Head Screws × 6
- M4 × 12mm Hex Round Head Screws × 4
- M4 Nuts × 4
Tools Needed:
- 3mm Allen Key
Instructions:
- Place the motor bracket on the extrusion approximately 55mm from the right and 18mm from the bottom.
- Use a pencil to mark the location, and apply masking tape to guide alignment.
- Draw a line 18mm from the bottom edge for precision.
- Secure the bracket using M4 screws and nuts, ensuring it’s mounted square and level.

✅ Step 2: Install Bearings and Pulley System
Hardware Needed:
- 3D-Printed Bearing Mount
- 8mm Pillow Bearing Blocks × 2
- 100mm × 8mm Linear Shaft Rod
- GT2 Timing Pulleys × 2
- U-Groove Pulley Wheels × 2
- 300mm 2MGT Closed Loop Belt
- M4 Slot T-nuts × 4
- M4 × 35mm Hex Round Head Screws × 4
Tools Needed:
- 1.5mm, 2.0mm, and 3.0mm Allen Keys
Instructions:
- Mount the bearing blocks and pulleys to the aluminum profile.
- Install the shaft rod.
- Loop the closed belt around both pulleys.
- Make sure the belt moves freely and remains centered, not too tight.

✅ Step 3: Assemble the Chain Tensioners
Hardware Needed:
- Bicycle Chain Tensioners × 2
- M5 × 35 × 20 Bearing Rubber Pulleys × 2
- 3D-Printed Tubing Cut-in
- 80 × 32mm Angle Fixed Brackets × 2
- M5 Fish-Eye Ring
- 2020 L-Type Brackets × 2
- Spacers, Washers, Nuts, and T-nuts (see full list)
Instructions:
- Disassemble each tensioner and remove the bike chain gear (not needed).
- Adjust tension by inserting the spring pin into your chosen hole and reassemble.
- Replace the chain gear with the rubber pulley.
- Attach the fish-eye ring to the bracket with an M5 nut and spring washer.
- Assemble and align the pulleys, make sure they match the U-groove wheel vertically or are slightly left offset.

✅ Step 4: Install Knife Assembly and Servo
Hardware Needed:
- 3D-Printed Slider Mount
- 3D-Printed Knife Holder
- 3D-Printed Tubing Cut-Out
- 80 × 32mm Angle Bracket
- MGN12H × 100mm Linear Rail Slider
- 18mm Snap-Off Knife Blade
- M3 Linkage Servo Rod
- Servo Motor (80kg recommended)
- 46 × 16mm Connect Plate
- Full screw, nut, and washer list provided
Instructions:
- Attach the linkage rod to the knife holder using M3 x 18 screw, spring washer, and nut.
- Secure the snap-off blade using M5 × 8 screw,but leave slightly loose for later adjustment.
- Mount the connect plate to the knife holder.
- Attach the servo to the slider mount and align it on the base.
- Adjust the knife to move freely and leave a 2mm gap from the tubing cut-out.
- Wrap masking tape over the knife blade for safety during assembly.

✅ Step 5: Wiring and Electronics Setup
Hardware Needed:
- 3D-Printed Enclosure + End Caps (Left/Right)
- AC to DC 24V 3A Power Adapter
- TB6600 Stepper Driver
- Arduino Nano + OLED Display (0.96″)
- Rotary Encoder
- Power Regulator and Connector PCB
- Jumper Wires:
- 20AWG × 12 (8cm)
- Female-to-Female × 9 (10cm)
- Servo Arm, Expansion Tubes, Self-Tapping Screws
- Full screw/nut list included
Instructions:
- Set TB6600 DIP Switches to: OFF, ON, OFF, OFF, OFF, ON (for 1600 steps/rev).
- Connect twelve 8cm wires from TB6600 to the labeled terminals on the PCB.
- Secure TB6600 in the enclosure with M4 × 8 screws and nuts.
- Mount the PCB with M2.6 × 5 screws and connect all labeled wires.
Stepper Motor Wiring (Match colors to PCB):
- Blue → B-
- Yellow → B+
- Green → A-
- Red → A+
Servo Motor Wiring (Match colors to PCB):
- Brown → GND
- Red → DC
- Orange → Signal
Wiring options:
option 1: with the Power Regulator and Wire connector PCB:

option 2: without Power Regulator and Wire connector PCB:

Final Steps:
- Use jumper wires to connect OLED and rotary encoder to the PCB.
- Power on the system: the servo will auto-initialize to the top position.
- Attach servo arm to linkage rod with an M3 × 18 screw (note: not included in hardware list).
- Make sure the sliding knife holder is at the top before tightening the servo arm.

Click Here to Order Hardware Kit.
3D-printed Parts (10 items) Zip File Download.
Click here to download the Arduino Code.