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”.
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
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
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
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
// ----- 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
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;
}
}
```