DIY Tubing Measurement and Cutter Project

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 settingsadjustable 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:

  1. The servo rotates 90° to cut.
  2. After a delay, it returns to 0°.
  3. 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

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)

S1S2S3MicrostepSteps/Revolution
ONONONFull Step200
ONOFFON1/2 Step400
OFFONON1/2 Step400
ONOFFOFF1/4 Step800
OFFONOFF1/8 Step1600
OFFOFFON1/16 Step3200
OFFOFFOFF1/32 Step6400

How to Use

  1. Power on the device.
  2. Navigate the menu using the rotary encoder.
  3. Set length, quantity, speed, and unit.
  4. Press “Start” to start cutting.
  5. 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:

  1. Place the motor bracket on the extrusion approximately 55mm from the right and 18mm from the bottom.
  2. Use a pencil to mark the location, and apply masking tape to guide alignment.
  3. Draw a line 18mm from the bottom edge for precision.
  4. 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:

  1. Mount the bearing blocks and pulleys to the aluminum profile.
  2. Install the shaft rod.
  3. Loop the closed belt around both pulleys.
  4. 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:

  1. Disassemble each tensioner and remove the bike chain gear (not needed).
  2. Adjust tension by inserting the spring pin into your chosen hole and reassemble.
  3. Replace the chain gear with the rubber pulley.
  4. Attach the fish-eye ring to the bracket with an M5 nut and spring washer.
  5. 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:

  1. Attach the linkage rod to the knife holder using M3 x 18 screw, spring washer, and nut.
  2. Secure the snap-off blade using M5 × 8 screw,but leave slightly loose for later adjustment.
  3. Mount the connect plate to the knife holder.
  4. Attach the servo to the slider mount and align it on the base.
  5. Adjust the knife to move freely and leave a 2mm gap from the tubing cut-out.
  6. 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:

  1. Set TB6600 DIP Switches to: OFF, ON, OFF, OFF, OFF, ON (for 1600 steps/rev).
  2. Connect twelve 8cm wires from TB6600 to the labeled terminals on the PCB.
  3. Secure TB6600 in the enclosure with M4 × 8 screws and nuts.
  4. 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.


Leave a Reply

Your email address will not be published. Required fields are marked *