For my final project, I set out to design and document the system integration of a Linear Autofeeder. The goal was to create a feeder that moves along a rail, stops at intervals to dispense food, and continues its path, repeating the process automatically based on user input.
I broke the project down into three main parts to simplify development and ensure successful integration:
The system is divided into three main parts:
To bring the Linear Autofeeder to life, I designed an electronic system that controls the motors, reads user input, and displays information. The main components include a microcontroller, stepper motors, drivers, sensors, a display, and voltage regulators.
I started by reviewing the pinout of the XIAO ESP32C3, which I chose as the main microcontroller for the project. This helped me plan how each component would connect and ensured I used the available pins efficiently.
Next, I selected the key components for the project, including two NEMA 17 stepper motors with their TB6600 drivers, an LCD display, a rotary encoder, and a limit switch. These components were chosen to handle movement, user input, and safety functions within the system.
After selecting the components, I created a schematic in KiCad to show how everything would be connected. I also added power sources with different voltage levels: 12V for the stepper drivers, 5V for the encoder, and 3.3V for the XIAO ESP32C3. To manage this, I used AMS1117 voltage regulators to step down the voltages as needed for each component.
In KiCad PCB Editor, I routed the connections between components based on the schematic and arranged them to fit neatly on the board. After finalizing the layout, I generated the design files to create a custom PCB for my project.
Here is the PCB layout view, showing the final arrangement and routing of all components before fabrication.
After completing the PCB design, I generated SVG files for the board. Since I was using a Roland MonoFab PCB milling machine, I needed to convert the design into compatible G-code. To do this, I used ModsProject, which allowed me to generate the toolpaths needed for milling.
I then opened the generated G-code in VPanel, the control software for the Roland MonoFab. After setting the origin and securing the copper board, I started the PCB milling process.
The video shows the Roland MonoFab milling the PCB, accurately carving the traces from the copper board.
I designed the mechanical parts to support the feederβs movement and food dispensing. This includes an aluminum rail for smooth travel, a 3D-printed carriage to hold the mechanism, a hopper to store the food, a 3D-printed auger to release it, and an enclosure to protect everything. I aimed to use as many digital fabrication machines as possible from what we learned during Fab Academy. For the rail supports (legs), I used the CNC machine; for the auger and carriage, I used the 3D printer; and for the enclosure, I used the laser cutter.
To visualize the complete system, I designed the entire project in SolidWorks, including all mechanical and structural parts. This helped me check how everything would fit together. This is an exploded view of the assembly, showing all the components clearly.
The assembly
For the legs, I used the CNC machine to cut the parts from MDF, as shown in the picture above. This provided strong and stable support for the rail system.
After cutting, I sanded the CNC parts to make the surfaces smooth and ready for assembly.
I then assembled the CNC-cut legs with the aluminum rail tube, forming the main support structure for the feeder.
For the auger, hopper, and carriage, I saved the designs as STL files, sliced them, and used the 3D printer to fabricate each part. The video above shows the hopper being printed during the process.
For the enclosure, I saved the designed parts as DXF files and used the laser cutter to cut them from 5 mm MDF. The process is shown in the video above.
The final part of the project was writing the firmware, which was the most challenging for me due to my limited coding experience. To make the logic easier to understand, I created a flowchart of the system using Mermaid. For debugging and troubleshooting, I relied on AI tools for guidance and support throughout the process.
As shown in the flowcharts above, I mapped out the logic of the system step by step to guide the firmware development and make the structure easier to follow.
For the coding, I used the Arduino IDE to write and upload the firmware. The full code is shown below.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>
#include <AccelStepper.h>
// LCD Configuration
LiquidCrystal_I2C lcd(0x27, 20, 4); // I2C address 0x27, 20x4 LCD
// Pin Definitions - TESTED AND WORKING
#define RAIL_STEP_PIN D6 // for feeder linear position motor
#define RAIL_DIR_PIN D7 // for feeder linear position motor
#define FEEDER_STEP_PIN D10 // for food extraction motor
#define FEEDER_DIR_PIN D8 // for food extraction motor
#define ENCODER_CLK D2
#define ENCODER_DT D1
#define ENCODER_SW D3
#define ENDSTOP_PIN D0 // for homing limit switch (pulled up)
// Stepper Motor Configuration
AccelStepper railStepper(AccelStepper::DRIVER, RAIL_STEP_PIN, RAIL_DIR_PIN);
AccelStepper feederStepper(AccelStepper::DRIVER, FEEDER_STEP_PIN, FEEDER_DIR_PIN);
// Motor Parameters - SIMPLIFIED 200 SPEED FOR ALL
#define MOTOR_SPEED 200 // All motors: 200 steps/sec
#define MOTOR_ACCELERATION 100 // Acceleration for rail motor
#define STEPS_PER_MM 80 // Steps per millimeter for rail motor
#define FEEDER_FEED_SPEED 200 // Feeder: 200 steps/sec (simplified, reliable)
#define STEPS_PER_REVOLUTION 800 // Both motors: 800 steps = 1 full revolution
// System Constants - UPDATED FOR 800 STEPS/REV
#define MAX_POSITIONS 3
#define MIN_FEED_TIME 1
#define MAX_FEED_TIME 60
#define DEBOUNCE_DELAY 50
#define LONG_PRESS_TIME 2000
#define MAX_RAIL_POSITION 8000 // Increased for 800 steps/rev (was 3000)
#define HOMING_SPEED 100 // Slower homing speed for safety
#define HOMING_TIMEOUT 30000 // Homing timeout in milliseconds
// EEPROM Addresses
#define EEPROM_POSITIONS 0
#define EEPROM_FEED_TIMES 20
#define EEPROM_SETTINGS_SAVED 50
#define EEPROM_HOME_POSITION 60
// System Variables
volatile int encoderPos = 0;
volatile bool encoderChanged = false;
int lastEncoderPos = 0;
bool lastButtonState = HIGH;
unsigned long buttonPressTime = 0;
unsigned long lastDebounceTime = 0;
// Homing Variables
bool isHomed = false;
long homePosition = 0;
bool homingInProgress = false;
unsigned long homingStartTime = 0;
// System State
enum SystemState {
STARTUP,
HOMING,
MAIN_MENU,
SET_POSITIONS,
SET_FEED_TIMES,
FEEDING_MODE,
RUNNING_CYCLE,
MANUAL_CONTROL
};
SystemState currentState = STARTUP;
int menuIndex = 0;
int editingPosition = 0;
bool isEditing = false;
// Feeding System Variables - UPDATED DEFAULT POSITIONS
long feedingPositions[MAX_POSITIONS] = {1600, 4000, 6400}; // Updated for 800 steps/rev
int feedingTimes[MAX_POSITIONS] = {5, 5, 5}; // Default feed times in seconds
int currentFeedingPosition = 0;
bool feedingActive = false;
unsigned long feederStartTime = 0;
bool feederRunning = false;
bool railMoving = false;
// Menu Structure
const char* mainMenuItems[] = {
"1.Start Feeding",
"2.Set Positions",
"3.Set Feed Times",
"4.Manual Control",
"5.Home System"
};
// Function declarations
void loadSettings();
void saveSettings();
void encoderISR();
void handleEncoder();
void handleButton();
void handleShortPress();
void handleLongPress();
void startHoming();
void handleHoming();
void displayMainMenu();
void selectMenuItem();
void displayHomingRequired();
void displaySetPositions();
void displaySetFeedTimes();
void displayFeedingMode();
void displayManualControl();
void displaySystemInfo();
void handleManualControl();
void handleManualMovement(int delta);
void toggleFeeder();
void startFeedingCycle();
void handleRunningCycle();
void displayFeedingCountdown();
void moveToFeedingPosition(long targetPosition);
void startFeeder();
void stopFeeder();
void stopAllMotors();
void stopFeeding();
void completeFeedingCycle();
void setup() {
// Initialize serial FIRST
Serial.begin(9600);
delay(1000); // Give serial time to initialize
Serial.println("Animal Feed System Starting...");
// Initialize LCD
lcd.init();
lcd.backlight();
lcd.clear();
// Initialize pins AFTER serial is stable
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
pinMode(ENCODER_SW, INPUT_PULLUP);
pinMode(ENDSTOP_PIN, INPUT_PULLUP);
// Initialize motor pins
pinMode(RAIL_STEP_PIN, OUTPUT);
pinMode(RAIL_DIR_PIN, OUTPUT);
pinMode(FEEDER_STEP_PIN, OUTPUT);
pinMode(FEEDER_DIR_PIN, OUTPUT);
// Set initial states - ALL LOW
digitalWrite(RAIL_STEP_PIN, LOW);
digitalWrite(RAIL_DIR_PIN, LOW);
digitalWrite(FEEDER_STEP_PIN, LOW);
digitalWrite(FEEDER_DIR_PIN, LOW);
// Attach interrupts for encoder
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoderISR, FALLING);
// Initialize stepper motors - ALL 200 SPEED
railStepper.setMaxSpeed(MOTOR_SPEED); // 200 max
railStepper.setAcceleration(MOTOR_ACCELERATION);
feederStepper.setMaxSpeed(FEEDER_FEED_SPEED); // 200 max
feederStepper.setAcceleration(MOTOR_ACCELERATION);
Serial.println("Motors configured - ALL at 200 steps/sec");
// Load settings from EEPROM
loadSettings();
// Display welcome message
lcd.setCursor(0, 0);
lcd.print("Animal Feed System");
lcd.setCursor(0, 1);
lcd.print("SIMPLIFIED - 200sps");
lcd.setCursor(0, 2);
lcd.print("Focus: MOTOR SPIN");
lcd.setCursor(0, 3);
lcd.print("Initializing...");
delay(3000);
// Start homing sequence
currentState = HOMING;
startHoming();
Serial.println("Setup complete - Starting homing");
}
void loop() {
handleEncoder();
handleButton();
//...
β¬οΈ Download Full Arduino Code
The Linear Autofeeder is an automatic feeding system that travels along a rail to feed your animals at multiple locations. Think of it as a smart robot that moves food where it's needed, when it's needed without you having to be there.
Action | What It Does |
---|---|
TURN | Navigate menus, adjust values, move system |
PRESS | Select option, confirm choice, toggle feeder |
HOLD | Go back, exit to main menu, emergency stop |
Symbol | Meaning |
---|---|
> | Currently selected menu item |
[5s] | Value being edited (brackets) |
H:OK | System is homed and ready |
H:NO | System needs homing first |
LIVE | System moving in real-time |
MOVE | Motor currently running |
STOP | Motor has stopped |
Before assembling all the parts, I tested the code separately to ensure each function worked correctly. The video above shows the code testing process in action.
After testing the code, I assembled all the mechanical and electronic parts to see how the complete system would work together. I was pleased to find that the Linear Autofeeder functioned successfully as planned.