9.4- Implementation
Fabrication and Assembly
Fabrication of our final design was accomplished through a combination of laser cutting and 3D printing to balance structural strength with low-friction dynamics.
The main base frame was laser-cut from wood. The precision of the laser cutter ensured that the central slot dictating the slider's linear path was perfectly straight, which was crucial for preventing the mechanism from jamming. The dynamic components—the lower links, upper links, and the slider blocks—were 3D printed.
During assembly, overcoming the fragility of the 3D-printed parts was the primary challenge. Because the initial linkages cracked under torque , we reprinted the links with increased wall thickness at the joint hubs. We successfully press-fit the 8mm metal shafts and utilized M3 screws to finalize the low-friction joints. Finally, the custom 3D-printed motor cradle was attached to anchor the 12V DC motor directly to the wooden base.
Electronics and Circuitry
To actuate the simultaneous dual-leg mechanism, our electronics included the following hardware:
Arduino Microcontroller
Motor Driver (to handle current draw and direction switching)
12V DC Motor
Jumper cables and standard breadboard
The system was routed to the Arduino to control the speed (via PWM) and the direction of the 12V DC motor. The custom-designed cradle ensured the motor remained secure against the high-torque operation required to push both legs simultaneously.
Software The software was developed to logically separate the motor's output into the distinct functional phases, modeled as a Four-State Model.
Phase A (Extension): Upon initialization, the code runs the motor to drive the slider linearly, transitioning the flat 2D chassis into a rigid 3D structure.
Phase B (Retraction): Once the robot is popped up, the software enters an oscillating loop to drive the walking gait. The system shifts between the Four-State Model: State 1 (Pull Extended), State 2 (Buckling), State 3 (Push Bent), and State 4 (Unbuckling).
By operating the software in this sequence, the robot behaves as shifting between separate 4-bar linkages, meaning only 1 input is required to achieve continuous locomotion despite multiple theoretical DOFs. The asymmetric friction logic (anchoring during push, sliding during pull) effectively drives the unified bounding motion.
// Motor 1 Pins
const int IN1 = 2;
const int IN2 = 3;
const int IN3 = 4;
const int IN4 = 5;
// Motor 2 Pins (Mirrored)
const int IN5 = 8;
const int IN6 = 9;
const int IN7 = 10;
const int IN8 = 11;
// Switch and LED pins
const int switchGround = A0;
const int switchInput = A1;
const int ledPin = LED_BUILTIN;
// Movement Configuration
const int initialLiftSteps = 700; // Steps to move up from the "flat" position
const int toggleSteps = 600; // Steps for the back-and-forth movement
const int stepDelay = 1000; // Microseconds between steps
// Step indices for each motor
int i1 = 0;
int i2 = 0;
// State tracking
bool hasDoneInitialLift = false;
void setup() {
// Configure Motor 1 pins
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT);
pinMode(IN4, OUTPUT);
// Configure Motor 2 pins
pinMode(IN5, OUTPUT);
pinMode(IN6, OUTPUT);
pinMode(IN7, OUTPUT);
pinMode(IN8, OUTPUT);
// Configure switch pins
pinMode(switchGround, OUTPUT);
digitalWrite(switchGround, LOW); // Set A0 to LOW so it acts as Ground
pinMode(switchInput, INPUT_PULLUP);
// Configure LED pin
pinMode(ledPin, OUTPUT);
// Ensure motors start de-energized
disableMotors();
}
void loop() {
// Switch closed (LOW) = RUN, Switch open (HIGH) = STOP/RESET
bool isSwitchOn = (digitalRead(switchInput) == LOW);
if (!isSwitchOn) {
// SYSTEM RESET / DISABLED STATE
digitalWrite(ledPin, LOW);
disableMotors();
hasDoneInitialLift = false;
return; // Exit loop early and wait for switch to turn on
}
// SYSTEM ACTIVE STATE
digitalWrite(ledPin, HIGH);
// Perform the initial movement upwards
if (!hasDoneInitialLift) {
if (!moveSteps(initialLiftSteps, true)) return;
hasDoneInitialLift = true;
}
// Toggle back and forth
if (!moveSteps(toggleSteps, false)) return;
if (!delayWithAbort(100)) return; // 500ms pause
if (!moveSteps(toggleSteps, true)) return;
if (!delayWithAbort(100)) return; // 500ms pause
}
// --- Helper Functions ---
// Moves the motors while constantly checking the switch.
// Returns false if the switch is turned off mid-movement.
bool moveSteps(int steps, bool forward) {
for (int stepCount = 0; stepCount < steps; stepCount++) {
// Check switch instantly
if (digitalRead(switchInput) == HIGH) {
return false; // Abort movement!
}
if (forward) {
doHalfStepForward();
} else {
doHalfStepBackward();
}
delayMicroseconds(stepDelay);
}
return true; // Successfully finished movement
}
// A delay function that continually checks the switch
bool delayWithAbort(int milliseconds) {
for (int p = 0; p < milliseconds; p++) {
if (digitalRead(switchInput) == HIGH) {
return false; // Abort delay!
}
delay(1);
}
return true;
}
// Removes holding torque
void disableMotors() {
digitalWrite(IN1, LOW);
digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW);
digitalWrite(IN4, LOW);
digitalWrite(IN5, LOW);
digitalWrite(IN6, LOW);
digitalWrite(IN7, LOW);
digitalWrite(IN8, LOW);
}
void setHalfStep(bool isForward) {
int m1_step = isForward ? 1 : -1;
int m2_step = isForward ? -1 : 1;
int steps[] = {B1000, B1100, B0100, B0110, B0010, B0011, B0001, B1001};
digitalWrite(IN1, bitRead(steps[i1], 0));
digitalWrite(IN2, bitRead(steps[i1], 1));
digitalWrite(IN3, bitRead(steps[i1], 2));
digitalWrite(IN4, bitRead(steps[i1], 3));
digitalWrite(IN5, bitRead(steps[i2], 0));
digitalWrite(IN6, bitRead(steps[i2], 1));
digitalWrite(IN7, bitRead(steps[i2], 2));
digitalWrite(IN8, bitRead(steps[i2], 3));
i1 = (i1 + m1_step + 8) % 8;
i2 = (i2 + m2_step + 8) % 8;
}
void doHalfStepForward(){
setHalfStep(true);
}
void doHalfStepBackward(){
setHalfStep(false);
}