Linear Actuator
Linear Actuator
I chose to study linear actuator, because it is a common mechanical component in robots, and I am interested in building robot.
I built a box monster which opens and closes mouth by placing two linear actuators inside the box. As the linear actuators extend, the box monster opens its mouth. When the linear actuators retract, it closes its mouth.
Images of final creative/exploratory output
final prototype
Process images from development towards creative/exploratory output
3D model of the box and its joint
Placing linear actuator inside the box
Process and reflection:
I spent most of my time first learning how to wire a linear actuator. I realized that whether the actuator extends or retracts depends entirely on the polarity of power, which meant I needed to use an H-bridge in order to switch polarity through code. Once I understood the wiring, I spent a lot of time thinking about possible creative uses of the actuator within the limited timeframe. Eventually, I chose to build a “box monster” because it felt silly and fun to look at, even if it wasn’t exactly useful.
I started by designing the box in Rhino, especially focusing on the joints between the lid and the box so that it could open and close consistently. Afterward, I 3D-printed the box and added decorations to make it look more like a monster. Finally, I installed two linear actuators inside, and it works!
Technical details
Block Diagram
/*
60-223 Intro to Physical Computing, fall 2025
Domain-specific Skill Building exercise:Box Monster
Pin mapping:
Arduino pin | role | details
------------------------------
11 input Ultrasonic ranger - echo
10 input Ultrasonic ranger - trlg
7 output linear actuator A signalA
8 output linear actuator A signalB
3 output linear actuator B signalA
2 output linear actuator B signalB
*/
// =============================== ALL THE CODE BELOW IS WRITTEN BY CHAT GPT!!!------------------------------
// Two Linear Actuators (mirrored) + Ultrasonic control
// Fixed timing ratio: retract time = 2 * extend time (independent of distance)
//
// Actuator A driver: IN1A=D7, IN2A=D8
// Actuator B driver: IN1B=D3, IN2B=D2
// Ultrasonic HC-SR04: TRIG=D10, ECHO=D11
// External motor supply required. Tie all grounds together.
// ===============================
// ---- Actuator pins ----
const int IN1A = 7; // Actuator A dir1
const int IN2A = 8; // Actuator A dir2
const int IN1B = 3; // Actuator B dir1
const int IN2B = 2; // Actuator B dir2
// OPTIONAL: PWM speed control (uncomment if your driver(s) have EN/PWM pins)
// Option 1: single EN shared
// #define USE_EN
// const int EN1 = 6;
// Option 2: dual ENs (one per driver)
// #define USE_EN
// #define DUAL_EN
// const int EN1 = 6; const int EN2 = 5;
// ---- Ultrasonic pins ----
const int TRIG = 10;
const int ECHO = 11;
// ---- Distance behavior ----
// We still vary the *extend* time with distance,
// but the retract time is always 2x extend time.
const int NEAR_CM = 20; // nearest bound
const int FAR_CM = 120; // beyond this -> stop
const unsigned long EXT_FAST_MS = 350; // extend time when very close
const unsigned long EXT_SLOW_MS = 2000; // extend time when far (but < FAR_CM)
// Sensor refresh rate
const unsigned long MEAS_PERIOD_MS = 100;
// ---- Internal state ----
enum Dir { STOPPED, EXTEND, RETRACT };
Dir dir = STOPPED;
unsigned long lastToggleMs = 0;
unsigned long lastMeasMs = 0;
unsigned long extendPeriodMs = EXT_SLOW_MS; // chosen from distance
unsigned long retractPeriodMs = EXT_SLOW_MS * 2; // always 2x extend
int lastGoodCm = 0;
// ===============================
// Low-level actuator helpers
// ===============================
void bothLow() {
digitalWrite(IN1A, LOW); digitalWrite(IN2A, LOW);
digitalWrite(IN1B, LOW); digitalWrite(IN2B, LOW);
}
void bothExtendRaw() {
digitalWrite(IN1A, HIGH); digitalWrite(IN2A, LOW);
digitalWrite(IN1B, HIGH); digitalWrite(IN2B, LOW);
}
void bothRetractRaw() {
digitalWrite(IN1A, LOW); digitalWrite(IN2A, HIGH);
digitalWrite(IN1B, LOW); digitalWrite(IN2B, HIGH);
}
#ifdef USE_EN
#ifdef DUAL_EN
byte dutyForCm(int cm) {
if (cm < NEAR_CM) cm = NEAR_CM;
if (cm > FAR_CM) cm = FAR_CM;
long duty = map(cm, FAR_CM, NEAR_CM, 150, 235); // closer -> higher duty
if (duty < 120) duty = 120;
if (duty > 255) duty = 255;
return (byte)duty;
}
void setDutyFromCm(int cm) {
byte d = dutyForCm(cm);
analogWrite(EN1, d);
analogWrite(EN2, d);
}
#else
byte dutyForCm(int cm) {
if (cm < NEAR_CM) cm = NEAR_CM;
if (cm > FAR_CM) cm = FAR_CM;
long duty = map(cm, FAR_CM, NEAR_CM, 150, 235);
if (duty < 120) duty = 120;
if (duty > 255) duty = 255;
return (byte)duty;
}
void setDutyFromCm(int cm) { analogWrite(EN1, dutyForCm(cm)); }
#endif
#endif
void stopActuators() {
#ifdef USE_EN
#ifdef DUAL_EN
analogWrite(EN1, 0); analogWrite(EN2, 0);
#else
analogWrite(EN1, 0);
#endif
#endif
bothLow();
dir = STOPPED;
}
void extendAtProximity(int cm) {
bothExtendRaw();
#ifdef USE_EN
setDutyFromCm(cm);
#endif
dir = EXTEND;
}
void retractAtProximity(int cm) {
bothRetractRaw();
#ifdef USE_EN
setDutyFromCm(cm);
#endif
dir = RETRACT;
}
// ===============================
// Ultrasonic helpers
// ===============================
long pingOnceUS() {
digitalWrite(TRIG, LOW); delayMicroseconds(2);
digitalWrite(TRIG, HIGH); delayMicroseconds(10);
digitalWrite(TRIG, LOW);
return pulseIn(ECHO, HIGH, 25000UL); // ~25ms timeout (~4m)
}
int readDistanceCm() {
const int N = 5;
unsigned long samples[N];
int count = 0;
for (int i = 0; i < N; ++i) {
unsigned long us = pingOnceUS();
if (us > 0) samples[count++] = us;
delay(10);
}
if (count == 0) return -1;
// insertion sort
for (int i = 1; i < count; ++i) {
unsigned long v = samples[i]; int j = i - 1;
while (j >= 0 && samples[j] > v) { samples[j+1] = samples[j]; j--; }
samples[j+1] = v;
}
unsigned long medUS = samples[count/2];
int cm = (int)((medUS / 2.0) / 29.1); // (us/2)/29.1
return cm;
}
// Map distance -> extend period (only), retract period = 2x extend
unsigned long extendPeriodForCm(int cm) {
if (cm <= NEAR_CM) return EXT_FAST_MS;
if (cm >= FAR_CM) return 0; // indicates stop
long ext = map(cm, FAR_CM, NEAR_CM, EXT_SLOW_MS, EXT_FAST_MS);
if (ext < 120) ext = 120; // safety floor
return (unsigned long)ext;
}
void maybeUpdateDistanceAndTiming() {
unsigned long now = millis();
if (now - lastMeasMs < MEAS_PERIOD_MS) return;
lastMeasMs = now;
int cm = readDistanceCm();
if (cm <= 0) {
extendPeriodMs = 0; // treat as far/no target -> stop
retractPeriodMs = 0;
return;
}
lastGoodCm = cm;
extendPeriodMs = extendPeriodForCm(cm);
retractPeriodMs = (extendPeriodMs == 0) ? 0 : extendPeriodMs * 2; // <-- fixed 2:1 ratio
}
void maybeToggleDirection() {
// Handle stop condition
if (extendPeriodMs == 0) {
if (dir != STOPPED) stopActuators();
#ifdef USE_EN
} else {
// keep updating speed while moving (if using PWM)
if (dir != STOPPED) setDutyFromCm(lastGoodCm);
#endif
}
if (extendPeriodMs == 0) return;
unsigned long now = millis();
unsigned long period = (dir == EXTEND) ? extendPeriodMs : retractPeriodMs;
if (dir == STOPPED) {
extendAtProximity(lastGoodCm);
lastToggleMs = now;
return;
}
if (now - lastToggleMs >= period) {
if (dir == EXTEND) {
retractAtProximity(lastGoodCm);
} else {
extendAtProximity(lastGoodCm);
}
lastToggleMs = now;
}
}
// ===============================
// Setup & Loop
// ===============================
void setup() {
pinMode(IN1A, OUTPUT); pinMode(IN2A, OUTPUT);
pinMode(IN1B, OUTPUT); pinMode(IN2B, OUTPUT);
#ifdef USE_EN
pinMode(EN1, OUTPUT);
analogWrite(EN1, 0);
#ifdef DUAL_EN
pinMode(EN2, OUTPUT);
analogWrite(EN2, 0);
#endif
#endif
stopActuators();
pinMode(TRIG, OUTPUT);
pinMode(ECHO, INPUT);
Serial.begin(115200);
delay(50);
Serial.println("Two-actuator proximity control with 2:1 timing (retract = 2x extend)");
Serial.println("Actuator A: IN1=D7, IN2=D8 | Actuator B: IN1=D3, IN2=D2");
Serial.println("Ultrasonic TRIG=D10, ECHO=D11");
Serial.println("Use external motor supply; common GND with Arduino.");
}
void loop() {
maybeUpdateDistanceAndTiming();
maybeToggleDirection();
// Debug every 600 ms
static unsigned long lastDbg = 0;
if (millis() - lastDbg > 600) {
lastDbg = millis();
Serial.print("dist(cm)="); Serial.print(lastGoodCm);
Serial.print(" EXT(ms)="); Serial.print(extendPeriodMs);
Serial.print(" RET(ms)="); Serial.print(retractPeriodMs);
Serial.print(" dir="); Serial.println(dir==EXTEND?"EXTEND":dir==RETRACT?"RETRACT":"STOP");
}
}