Skip to content

Final Project

The concept for my final project is a motion activated light that slowly opens and closes. The opeing and closing mechanism will be comprised of six elements aranged in a hexagonal pattern. For lack of a better term I will refer to the whole structure as a “flower” and each individual element as a “pettal”.

../../images/finalproject/mech_sketch3d.jpg

../../images/finalproject/mech_sketch.jpg


Mechanical Mechanisms

Using itterative design principals I worked from the most basic mechanism that acheived the motion I wanted and developed this into the full mechanical design.

Basic opening and closing

I first prototyped the opening and closing of a single element or pettal.

Six of these elements are arranged around a center shaft that moves latterally to create the opeing and closing motion. After understanding the basic motion, I extrapolated this to a second, mirrired element and determined how to create the lateral motion along the shaft.

Skrew Conveyor

In order to acheive, slow and precise lateral motion, I decided that a skrew conveyor was a logical choice to acheive the desired motion.

Skrew conveyor sketch

../../images/finalproject/mech_sketch3d.jpg

Skrew conveyor prototype

Motor Selection

The citeria for motor selection included: * controlable speed * controlable position * quiet * low voltage

A stepper motor best fit these criteria see output devices for more motor info.

When I started looking through the available stepper motors that our lab has onhand, I was lucky to come across an old assembly for the 3D printer. This part can be repurposed and fits the design of my project nearly perfectly. I used an angle grinder to cut off the back plate, leaving only the componenets nessesary for my project.

3D Printer Part - Repurposed

../../images/finalproject/mech_sketch3d.jpg


Asthetic Componentents

The mechanical structure is covered by a substructure and material that is intended to be more visually apealling to the viewer.

Auxetic Material

Example Material


Sensors and software

Version 1

PIR and temperature controled LED

../../images/finalproject/sensor_proto.jpg

Basic functionality of the circuit:

  • When motion is detected the LED turns on slowly

  • Brightness of the LED is controled through PWM

  • The color of the LED is determined from the temperature reading on TRD termperature sensor

Code for the prototype sensors

``` #include #include

// ----- Pins (adjust if needed) -----
static const int PIN_PIR = 4;     
static const int PIN_DHT = 15;

// Common-cathode RGB LED pins (each via resistor)
static const int PIN_RED   = 12;
static const int PIN_GREEN = 13;
static const int PIN_BLUE  = 14;

// ----- PWM settings -----
static const int PWM_FREQ = 5000;
static const int PWM_BITS = 8; // 0..255

// ----- Motion/LED behavior -----
static const uint32_t HOLD_MS       = 2000;
static const uint8_t  TARGET_BRIGHT = 255;
static const uint8_t  FADE_STEP     = 3;
static const uint16_t STEP_DELAY_MS = 10;

// ----- DHT -----
DHTesp dht;
uint32_t lastDhtReadMs = 0;
static const uint32_t DHT_PERIOD_MS = 2000;

// ----- Temperature -> color tuning -----
static const float COLD_C = 18.0;
static const float HOT_C  = 28.0;

// State
uint32_t lastMotionMs = 0;
uint8_t  currentBright = 0;
float lastTempC = NAN;

// Clamp helpers
static inline uint8_t clamp8(int v) {
if (v < 0) return 0;
if (v > 255) return 255;
return (uint8_t)v;
}

static inline float clampf(float v, float lo, float hi) {
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}

// COMMON CATHODE: no inversion needed
void writeLed(int pin, uint8_t brightness) {
ledcWrite(pin, brightness);
}

// Apply global brightness scaling
void setRgbScaled(uint8_t r, uint8_t g, uint8_t b, uint8_t globalBright) {
uint8_t rs = (uint8_t)((r * (uint16_t)globalBright) / 255);
uint8_t gs = (uint8_t)((g * (uint16_t)globalBright) / 255);
uint8_t bs = (uint8_t)((b * (uint16_t)globalBright) / 255);

writeLed(PIN_RED,   rs);
writeLed(PIN_GREEN, gs);
writeLed(PIN_BLUE,  bs);
}

// Temperature -> RGB gradient
void colorFromTemp(float tC, uint8_t &r, uint8_t &g, uint8_t &b) {
if (isnan(tC)) {
    r = 180; g = 0; b = 180;
    return;
}

float t = clampf(tC, COLD_C, HOT_C);
float x = (t - COLD_C) / (HOT_C - COLD_C);

if (x <= 0.5f) {
    float u = x / 0.5f;
    r = 0;
    g = clamp8((int)(255.0f * u));
    b = clamp8((int)(255.0f * (1.0f - u)));
} else {
    float u = (x - 0.5f) / 0.5f;
    r = clamp8((int)(255.0f * u));
    g = clamp8((int)(255.0f * (1.0f - u)));
    b = 0;
}
}

void setup() {
Serial.begin(115200);

pinMode(PIN_PIR, INPUT_PULLDOWN);

ledcAttach(PIN_RED,   PWM_FREQ, PWM_BITS);
ledcAttach(PIN_GREEN, PWM_FREQ, PWM_BITS);
ledcAttach(PIN_BLUE,  PWM_FREQ, PWM_BITS);

setRgbScaled(0,0,0,0);

dht.setup(PIN_DHT, DHTesp::DHT11);

Serial.println("Common Cathode version running.");
}

void loop() {
uint32_t now = millis();

// PIR
bool motionNow = (digitalRead(PIN_PIR) == HIGH);
if (motionNow) lastMotionMs = now;

bool motionActive = (now - lastMotionMs) < HOLD_MS;
uint8_t targetBright = motionActive ? TARGET_BRIGHT : 0;

// DHT read
if (now - lastDhtReadMs >= DHT_PERIOD_MS) {
    lastDhtReadMs = now;

    TempAndHumidity th = dht.getTempAndHumidity();
    if (!isnan(th.temperature)) {
    lastTempC = th.temperature;
    Serial.print("Temp: ");
    Serial.print(lastTempC, 1);
    Serial.print(" C  Humidity: ");
    Serial.println(th.humidity, 0);
    }
}

// Determine base color
uint8_t baseR, baseG, baseB;
colorFromTemp(lastTempC, baseR, baseG, baseB);

// Smooth fade
if (currentBright < targetBright) {
    currentBright = (uint8_t)min((int)targetBright, (int)currentBright + (int)FADE_STEP);
} 
else if (currentBright > targetBright) {
    currentBright = (uint8_t)max((int)targetBright, (int)currentBright - (int)FADE_STEP);
}

setRgbScaled(baseR, baseG, baseB, currentBright);

delay(STEP_DELAY_MS);
}

```

Verison 2

Basic functionality of the circuit:

  • When motion is detected the LED turns on slowly and the motor is activated to start the opening of the flower structure.

  • Brightness of the LED is controled through PWM

  • The flower closes and the LEDs dim to off after a certain amount of time has passes with no motion detected.

Draft Electrical Skematics

../../images/finalproject/sensor_proto.jpg

PIR controlled motor and Neopixels

Code for the prototype sensors

``` #include

#define STEP_PIN 14
#define DIR_PIN 13

#define HOME_SWITCH 12
#define PIR_PIN 15

#define PIXEL_PIN 16
#define NUM_PIXELS 8

#define STEP_DELAY 4000
#define FORWARD_STEPS 1700
#define FADE_OFFSET_STEPS 100

#define NO_MOTION_DELAY 30000  // 30 seconds

#define MAX_BRIGHTNESS 100

Adafruit_NeoPixel pixels(NUM_PIXELS, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

bool cycleActive = false;

void setAllPixels(int brightness) {

brightness = constrain(brightness, 0, MAX_BRIGHTNESS);

for(int i = 0; i < NUM_PIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(brightness, brightness, brightness));
}

pixels.show();
}

void stepOnce() {

digitalWrite(STEP_PIN, HIGH);
delayMicroseconds(STEP_DELAY);

digitalWrite(STEP_PIN, LOW);
delayMicroseconds(STEP_DELAY);
}

void moveForwardWithFade() {

Serial.println("Moving Forward + Fading Up");

digitalWrite(DIR_PIN, HIGH);

int fadeSteps = FORWARD_STEPS - FADE_OFFSET_STEPS;

for(int i = 0; i < FORWARD_STEPS; i++) {

    int brightness;

    if(i < fadeSteps) {
    brightness = map(i, 0, fadeSteps, 0, MAX_BRIGHTNESS);
    } else {
    brightness = MAX_BRIGHTNESS;
    }

    setAllPixels(brightness);
    stepOnce();
}

setAllPixels(255);

Serial.println("Forward Complete");
}

void homeMotorWithFadeDown() {

Serial.println("Returning Home + Fading Down");

digitalWrite(DIR_PIN, LOW);

int estimatedHomeSteps = FORWARD_STEPS;
int fadeSteps = estimatedHomeSteps - FADE_OFFSET_STEPS;

int stepCount = 0;

while(digitalRead(HOME_SWITCH) == HIGH) {

    int brightness;

    if(stepCount < fadeSteps) {
    brightness = map(stepCount, 0, fadeSteps, MAX_BRIGHTNESS, 0);
    } else {
    brightness = 0;
    }

    setAllPixels(brightness);
    stepOnce();

    stepCount++;
}

setAllPixels(0);

Serial.print("Home Position Found After Steps: ");
Serial.println(stepCount);
}

void homeMotor() {

Serial.println("Homing Started");

digitalWrite(DIR_PIN, LOW);

while(digitalRead(HOME_SWITCH) == HIGH) {
    stepOnce();
}

setAllPixels(0);

Serial.println("Home Position Found");
}

void setup() {

pinMode(STEP_PIN, OUTPUT);
pinMode(DIR_PIN, OUTPUT);

pinMode(HOME_SWITCH, INPUT_PULLUP);
pinMode(PIR_PIN, INPUT);

Serial.begin(115200);

pixels.begin();
pixels.clear();
pixels.show();

homeMotor();
}

void loop() {

int motionState = digitalRead(PIR_PIN);

if(motionState == HIGH && cycleActive == false) {

    cycleActive = true;

    Serial.println("Motion Detected - Starting Cycle");

    moveForwardWithFade();

    Serial.println("Holding at Furthest Position");
}

if(cycleActive == true) {

    unsigned long lastMotionTime = millis();

    while(true) {

    motionState = digitalRead(PIR_PIN);

    if(motionState == HIGH) {
        lastMotionTime = millis();
        Serial.println("Motion Detected - Timer Reset");
    }

    if(millis() - lastMotionTime > NO_MOTION_DELAY) {
        Serial.println("No Motion for 30 Seconds - Returning Home");
        break;
    }

    delay(200);
    }

    homeMotorWithFadeDown();

    Serial.println("Cycle Complete");

    cycleActive = false;
}
}

```