Week 12: Mechanical Design & Machine Design
Week 12 focused on machine design, and unlike previous weeks, this was a group-based project where multiple systems had to come together - mechanical, electronics, firmware, and interaction.
To begin with, we referred to previous years’ documentation to understand the scope and limitations of what could be realistically achieved within the given time.
During discussions around tabletop systems, especially setups where objects are arranged or shared, I was reminded of the rotating lazy Susan used in the Chinese dining contexts. That became the starting point for the idea:
An automated rotary table that can bring specific items to specific users using voice commands (Alexa).
I was assigned the development of the firmware, specifically focusing on stepper motor control, including precise positioning, homing logic, and overall motion control of the rotary system. In addition to the technical role, I was also responsible for project management and preparing the presentation slide.
Team and Roles
Mechanical Design - Abhishek
Electronics - Ardra
Fabrication & Assembly - Ali
Firmware - Mishael
Alexa Integration - Ashtami
Although roles were distributed, the system required continuous coordination.Say, for example: Firmware decisions depended on mechanical constraints (gear ratio), and interaction logic depended on physical positioning.
System Definition
The system was defined as:
Design Thinking Before Implementation
A significant portion of my time was spent imagining system behavior before testing.
Key questions:
This early reasoning helped identify core issues before hardware testing:
Hardware Overview: Stepper Motor Fundamentals
What is a Stepper Motor?
A stepper motor rotates in discrete angular steps, enabling precise position control.
Typical NEMA 17:
Bipolar Stepper (Used in this system)
How does a Stepper Motor work?
Why a Driver is Required
What is Motor Driver?Microcontrollers cannot directly drive motors.
The DRV8825 stepper motor driver:
Control Signals
| Signal | Function |
|---|---|
| STEP | Each pulse = one step |
| DIR | Controls direction |
| ENABLE | Enables/disables motor |
Hall Sensor - AH49E
Processing approach:
Step Resolution and Core Problem
Base Calculation
200 x 16 = 3200 (motor steps) 3200 x 5 = 16000 (table steps per revolution)
Derived Values
Critical Issue
The non-integer value (2666.67) leads to:
Shift to Absolute Positioning
Instead of calculating movement incrementally, I moved to:
Defining fixed, absolute positions for every (Compartment, Home) pair
Core Concept
Position = (Compartment Offset + Home Offset) % 16000
Key Insight
Position control is not about movement—it is about reference.
Step Mapping (Before LUT)
Compartments (6 → 60° each)
| Compartment | Angle | Ideal Steps | Rounded |
|---|---|---|---|
| C1 | 0° | 0 | 0 |
| C2 | 60° | 2666.67 | 2665 |
| C3 | 120° | 5333.33 | 5332 |
| C4 | 180° | 8000 | 7999 |
| C5 | 240° | 10666.67 | 10666 |
| C6 | 300° | 13333.33 | 13333 |
Homes (Initial: 4 → 90° each)
| Home | Angle | Steps |
|---|---|---|
| H1 | 0° | 0 |
| H2 | 90° | 4000 |
| H3 | 180° | 8000 |
| H4 | 270° | 12000 |
Lookup Table (LUT)
Each row = compartment
Each column = home
Each value = absolute step position
const long LUT[7][5] = {
{0, 0, 0, 0, 0 },
{0, 0, 4000, 8000, 12000 },// C1
{0, 2665, 6665, 10665, 14665 },// C2
{0, 5332, 9332, 13332, 1332 },// C3
{0, 7999, 11999, 15999, 3999 },// C4
{0, 10666, 14666, 2666, 6666 },// C5
{0, 13333, 1333, 5333, 9333 },// C6
};
Homing Logic (Reference Establishment)
Since the system relies on absolute positioning, a physical reference is required at startup.
Homing Flow
SEARCH (fast CW) → detect magnet → BACKOFF (CCW) → APPROACH (slow CW) → detect magnet again → SET POSITION = 0
Why This Works
System Architecture (FSM)
HOMING → IDLE → POSITIONING → IDLE
States
Motion Control Fundamentals
Motion Logic Evolution
Stage 1: Clockwise Only
delta = target - current if (delta < 0) delta += 16000
Stage 2: Shortest Path
delta = target - current if (delta > 8000) delta -= 16000 if (delta < -8000) delta += 16000
Concept
If movement > half → reverse direction
Final Mapping (After Alexa Integration)
Homes → People
| Home | Person |
|---|---|
| H1 | Abhishek |
| H2 | Ardra |
| H3 | Ali |
| H4 | Ashtami |
| H5 | Mishael |
Compartments → Items
| Compartment | Item |
|---|---|
| C1 | Nachos |
| C2 | Juice |
| C3 | Lays |
| C4 | Snickers |
| C5 | Popcorn |
| C6 | Cookies |
Updated Step Mapping (5 Homes)
Home Offsets
| Home | Steps |
|---|---|
| H1 | 0 |
| H2 | 3200 |
| H3 | 6400 |
| H4 | 9600 |
| H5 | 12800 |
Combined Positions (Sample)
| Compartment | H1 | H2 | H3 | H4 | H5 |
|---|---|---|---|---|---|
| Nachos | 0 | 3200 | 6400 | 9600 | 12800 |
| Juice | 2667 | 5867 | 9067 | 12267 | 15467 |
| Lays | 5333 | 8533 | 11733 | 14933 | 2133 |
| Snickers | 8000 | 11200 | 14400 | 1600 | 4800 |
Code Logic Overview
Stepper Setup
Hall Sensor Processing
Homing
Movement
Refinement
Code Explanation (Core Logic Breakdown)
Homing Logic (State Machine Implementation)
Homing is required because the system uses absolute positioning. Without a reference, the step count has no real-world meaning.
Code Snippet
switch (state) {
caseSEARCH:
stepper.setSpeed(SEARCH_SPEED);
stepper.runSpeed();
if (currentHall&&!lastHall) {
state =BACKOFF;
}
break;
caseBACKOFF:
stepper.setSpeed(-BACKOFF_SPEED);
stepper.runSpeed();
if (!currentHall&&lastHall) {
state =APPROACH;
}
break;
caseAPPROACH:
stepper.setSpeed(APPROACH_SPEED);
stepper.runSpeed();
if (currentHall&&!lastHall) {
stepper.setCurrentPosition(0);
state =DONE;
}
break;
}
Block-by-Block Explanation
SEARCH (Fast Clockwise Rotation)
if (currentHall&&!lastHall)
Detects rising edge→ entering magnetic field
BACKOFF (Reverse Movement)
if (!currentHall&&lastHall)
Detects falling edge → fully exited magnet zone
APPROACH (Slow Precision Alignment)
stepper.setCurrentPosition(0);
This defines:
Absolute zero (C1 aligned with H1)
Why This Works
Shortest Path Logic
Once absolute positions are known, the next problem is:
Which direction should the motor rotate?
Code Snippet
longdelta =target-turntableStepPos; if (delta>STEPS_PER_REV/2)delta-=STEPS_PER_REV; if (delta<-STEPS_PER_REV/2)delta+=STEPS_PER_REV; if (delta==0)return;
Block-by-Block Explanation
Raw Difference
longdelta =target-turntableStepPos;
Normalization to Shortest Path
if (delta>8000)delta-=16000; if (delta<-8000)delta+=16000;
Logic:
If movement is more than half a circle → go the other way
Zero Movement Check
if (delta==0)
Prevents unnecessary motion
Key Insight
This separation is what makes the system stable.
Movement Execution
stepper.moveTo(stepper.currentPosition()+delta);
Explanation
Lookup Table Integration
longtarget =LUT[c][h];
Explanation
This converts:
User command → exact step position
No runtime calculation required.
Hall Sensor Processing
filtered = 0.8f * filtered + 0.2f * raw;
- Low-pass filter → removes noise
if (!hallState && filtered > ON_THRESHOLD) hallState = true; if (hallState && filtered < OFF_THRESHOLD) hallState = false;
- Hysteresis prevents flickering near threshold
System State Machine
switch (sysState) {
case HOMING:
runHoming();
break;
case POSITIONING:
if (stepper.distanceToGo() != 0) {
stepper.run();
} else {
sysState = IDLE;
}
break;
case IDLE:
if (Serial.available()) {
parseCommand(cmd);
}
break;
}
Explanation
HOMING
- Runs homing FSM
- Establishes reference
POSITIONING
- Executes movement
- Checks completion using
stepper.distanceToGo()
IDLE
- Waits for user input
- Accepts Serial and MQTT (Alexa) commands
Crawl Speed (Final Refinement)
if (abs(stepper.distanceToGo()) < CRAWL_THRESHOLD) {
stepper.setMaxSpeed(CRAWL_SPEED);
}
Why This Matters
- Reduces speed near target
- Improves stopping accuracy
- Prevents overshoot
Final System Behavior (End-to-End)
1. System starts → HOMING
2. Position set to 0
3. Wait for command
4. Command parsed → LUT lookup
5. Shortest path calculated
6. Motor moves
7. System returns to IDLE
Development Process
Due to design and fabrication delays, time was used to:
- Understand stepper behavior
- Study microstepping
- Design motion logic
- Anticipate failure cases
Testing Approach
- Stepper basic motion
- Hall sensor testing
- Homing
- Positioning (CW only)
- Shortest path
- Alexa integration
Each subsystem was tested independently before integration.
Code Annotations
Core Setup and Definitions
#include// PIN DEFINITIONS #define STEP_PIN D1 #define DIR_PIN D0 #define EN_PIN D3 #define M0_PIN D2 #define M1_PIN D4 #define M2_PIN D5 #define HALL_PIN D8 // SYSTEM CONSTANTS #define MOTOR_STEPS 200 #define MICROSTEPS 16 #define GEAR_RATIO 5 #define STEPS_PER_REV (MOTOR_STEPS * MICROSTEPS * GEAR_RATIO) // SPEED SETTINGS #define SEARCH_SPEED 800 #define BACKOFF_SPEED 400 #define APPROACH_SPEED 200 #define MOVE_MAX_SPEED 1200 #define MOVE_ACCEL 800 #define CRAWL_SPEED 300 #define CRAWL_THRESHOLD 200
- Defines hardware interface and system resolution
- STEPS_PER_REV = 16000 is the basis of positioning
Lookup Table (Absolute Positions)
const long LUT[7][6] = {
{0, 0, 0, 0, 0, 0},
{0, 0, 3200, 6400, 9600, 12800}, // Nachos
{0, 2667, 5867, 9067, 12267, 15467}, // Juice
{0, 5333, 8533, 11733, 14933, 2133}, // Lays
{0, 8000, 11200, 14400, 1600, 4800}, // Snickers
{0, 10667, 13867, 1067, 4267, 7467}, // Popcorn
{0, 13333, 533, 3733, 6933, 10133} // Cookies
};
- Row → compartment
- Column → home
- Value → absolute step position
This eliminates rounding errors and positional drift.
Stepper Initialization
AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN);
- Uses STEP + DIR mode
- Handles motion control internally
Movement Function (Shortest Path)
long currentPos = 0;
void moveToTarget(int c, int h) {
long target = LUT[c][h];
long delta = target - currentPos;
if (delta > 8000) delta -= 16000;
if (delta < -8000) delta += 16000;
if (delta == 0) return;
stepper.moveTo(stepper.currentPosition() + delta);
currentPos = target;
sysState = POSITIONING;
}
- Computes shortest rotation
- Executes efficient movement
- Maintains absolute position tracking
Main Loop
void loop() {
switch (sysState) {
case HOMING:
runHoming();
break;
case POSITIONING:
if (stepper.distanceToGo() != 0) {
if (abs(stepper.distanceToGo()) < CRAWL_THRESHOLD) {
stepper.setMaxSpeed(CRAWL_SPEED);
} else {
stepper.setMaxSpeed(MOVE_MAX_SPEED);
}
stepper.run();
} else {
sysState = IDLE;
}
break;
case IDLE:
// waiting for commands
break;
}
}
Observations
- Homing became reliable after filtering
- Shortest path improved speed significantly
- Crawl mode improved accuracy
- LUT removed positional drift
Limitations
- No feedback for missed steps (open-loop)
- Requires homing at startup
- No dynamic recalibration
Future Improvements
- Advanced motion profiling
- Error detection and recovery
- Dynamic calibration
Final Note
This project shifted the focus from writing code to designing system behavior.