Introduction: Blooming Marvelous Flower Lamp
UPDATE
I am very pleased to say that the latest revision of this project was featured in Make Magazine, so you should check that out if you need more info on the Arduino Nano version especially.
About
This project breaks into far more delicate, feminine, territory for me, so I hope y'all don't object. Don't fret too much though, I'll be back to RC hotrods and the like soon.
I decided to make a 3D printed night-light for my soon-to-be-born baby girl's room and, as is the norm with my projects, I got a bit carried away, so I present to you the fully-articulated, wifi-enabled, 3d-printed night-light (patent not pending).
Parts List
Printed Parts
- Material: PETG preferable for translucency
- Thingiverse Link: Get the STL files here
Mechanical Parts
- M3 nuts (5)
- M3x6 screws(5)
- M3x10 screws (10)
- M3x30 screw (1)
- 3mmx15mm pins (or screws with heads cut off)
- Stainless Cable (~20cm)
Electrical Parts
These parts are what I used, but really that is just because I had them lying around from my light plotter. You may want to control the whole business with a push-button and an Arduino, or even better, an ATtiny and not much else, so I won't go into ridiculous detail regarding the electronics side of things (but I will give you links where you can learn more if needed).
- LEDs [I used 3V purple] (9)
- Resistors [I used 120ohm - depends on your colour choice and power supply] (3)
- 12V power supply
- RC Servo
- ESP8266 Wifi Module
- 5V regulator
- 3.3V regulator
Step 1: Printing
There are a handful of things to keep in mind when printing
Material
I chose PETG because it prints beautifully and I had it in white, but most of all, for its translucency.
Nozzle
I designed this for a 0.4mm nozzle, thin elements like the petals are multiples of 0.4mm thick (0.8mm in the case of the petals and diffusor ball)
Support
Very little support material required, and none needed on outer visible surfaces.
Have a look at my photos for ideas of how I orientated parts and where I used supports, but the parts should all be pretty obvious, they each have a flat surface to go on the print bed.
Make sure that you don't get any support structures inside the various 3mm holes (for example, the slots that the petal's pivots lie in)
Arrangement
Some of the parts can be printed together, such as the mechanical linkages, but I chose to print each petal and the diffusor ball as individual pieces. This is a giant pain in the butt, but it helps maintain perfect surfaces, especially avoiding the nasty stringing that PETG is prone to.
Foundation
The majority of the parts don't need any brim or raft, but I found it neccesary on the "axis" part, since it has such a small footprint.
Lead-In
You may want to adjust the lead in (start point of each loop, probably called different things in different slicers), or you may end up with a visible line up the middle of each petal, where every loop begins and ends.
Step 2: Wire the LEDS
Before you can mount the LED ball you need to wire up the LEDS.
There are many articles on the web that go into great detail, so I won't rehash it here, here is one particularly nice one for beginners: Adafruit Led Overview
I chose to use pink/purple LEDs, which have a forward voltage drop of about 3V.
I made three strings of 3 LEDs in series, with a 120ohm resistor in each string, to limit the current to ~20mA. The three strings were arranged in parallel.
Step 3: Assembly: Petal Mechanism
Follow the attached images in order to complete the assembly, or just watch the video in step 1.
Fasteners
I used M3 fasteners throughout, which, on my printer at least, tap their own threads nicely into the 2.8mm holes.
For some of the 3mm holes, such as the various pivot points, I had to turn a 3mm drill through them by hand to clean them out.
Pivot Pins for Petals
I couldn't find any smooth 3mm rod in my scrap box, so I just cut lengths of 3mm threaded rod (or long screws) with a hacksaw, to make ~16mm long pins of 3mm diameter.
Step 4: Assembly: Light Ball
The two halves of the light ball, once stuffed full of LEDs, are simply fastened to the top of the "axis" piece by an M3x30mm screw that skewers right through them.
Step 5: Servo and Cable Assembly
The movement of the petals is handled by a thin cable that travels through a sleeve in the stem (this is one of the coolest examples where 3D printing excels, imagine trying to make that curved sleeve with regular manufacturing methods!).
Mount Servo
As you can see in my photos, I went decidedly low-tech with the servo mount, good old hot glue!
I did make provision in the printed parts to glue in some hex standoffs (hence the twelve hexagonal holes inside the "pot"), but got impatient and never used them.
Test Servo Limits
I use the following little piece of code when I am doing arduino-based projects and need to determine the range of motion of a servo.
- Make sure that the "servoPin" variable refers the the pin that your servo is connected to
- Load the code and open a serial terminal to the device
- enter the characters 'q' or 'e' to test the servos outer limits
- enter 'w' to move to the mid point
- enter 'o' or 'p' to step in either direction (up to the limit)
- The servos current position is reported in the serial interface, so you can determine what limits you would like to use in your final code.
/* Test servo limits Jason Suter www.ossum.co.za */ #include <Servo.h> //pin details int servoPin = 2; static int minMicros = 1100; //flower opened static int midMicros = 1500; static int maxMicros = 1900; //flower closed Servo servoUnderTest; // create servo object to control a servo int posMicros = 1500; // variable to store the servo position void setup() { servoUnderTest.attach(servoPin); //configure serial port Serial.begin(115200); } void loop() { if (Serial.available() > 0) { char inByte = Serial.read();; //incoming serial byte if (inByte == 'q') { posMicros = minMicros; } else if (inByte == 'w') { posMicros = midMicros; } else if (inByte == 'e') { posMicros = maxMicros; } else if (inByte == 'o') { posMicros = max(posMicros-5,minMicros); } else if (inByte == 'p') { posMicros = min(posMicros+5,maxMicros); } } //report current position Serial.print(posMicros); servoUnderTest.write(posMicros); }
Step 6: System Control
Circuit Overview
I won't go in to detail about my circuit, since I just re-purposed the board I had left over from my Light Plotter project, you can no doubt build something more streamlined and purpose built (but if anyone is desperate for more info just shout and I will elaborate).
Processing and Connectivity
I am using a ESP8266-07 module as the brains, it is complete overkill but it is also disposably cheap. We only need two GPIO pins (one for leds and one for the servo), but the wifi option sure is nice.
Power Supplies
The wall-wart that powers my whole circuit is 12V but the ESP requires 3.3V and the servo expects something under 6V so I added two small switch-mode power-supply modules to generate those voltages.
Transitor for LEDs
As shown in the "Wiring the LEDs" step, I used an NPN transistor to switch the LEDs on and off, since the ESP8266 cannot supply the current (or handle the voltage) directly.
Step 7: ESP8266 Wifi Control
The following is some code that allows control of the lamp both via a serial interface to teh ESP8266 or via a HTML interface that is served up via the ESP's own access point.
I am using the Arduino environment to program my ESP, the GitHub page will have the most up-to-date instructions if you need help on getting started.
/* Articulated Flower Lamp Code Smashed together from various examples, especially that of Rui Santos from http://randomnerdtutorials.com/ Jason Suter www.ossum.co.za */ #include //ESP8266 Core WiFi Library #include //Local DNS Server used for redirecting all requests to the configuration portal #include //Local WebServer used to serve the configuration portal #include #include #include //server details MDNSResponder mdns; const char* ssid = "BLOOM"; String webPage = ""; ESP8266WebServer server(80); //status variables int movementDirection = 0; //0 = stopped, 1 closing, -1 closing // timing variables int frameDuration = 5000; //number of milliseconds for complete movement int frameElapsed = 0; //how much of the frame has gone by, will range from 0 to frameDuration unsigned long previousMillis = 0; //the last time we ran the position interpolation unsigned long currentMillis = 0; //current time, we will update this continuosly int interval = 0; //servo variables static int servoPin = 2; static int servoOpenMicros = 1100; //flower opened static int servoClosedMicros = 1900; //flower closed Servo petalServo; //LED variables static int ledPin = 13; static int ledOnPWM = 1023; //flower opened static int ledOffPWM = 0; //flower closed void setupServer(void){ webPage += "Flower Lamp
Light
"; delay(1000); Serial.begin(115200); WiFi.softAP(ssid); Serial.println(""); IPAddress myIP = WiFi.softAPIP(); Serial.println(""); Serial.println("AP SSID"); Serial.println(ssid); if (mdns.begin("esp8266", WiFi.localIP())) { Serial.println("MDNS responder started"); } server.on("/", [](){ server.send(200, "text/html", webPage); }); server.on("/socket1On", [](){ server.send(200, "text/html", webPage); movementDirection = 1; Serial.println("opening"); petalServo.attach(servoPin); }); server.on("/socket1Off", [](){ server.send(200, "text/html", webPage); movementDirection = -1; Serial.println("closing"); petalServo.attach(servoPin); }); server.begin(); Serial.println("HTTP server started"); } void setup() { //configure serial port Serial.begin(115200); setupServer(); } void getInput() { //input from web - causes jerkiness, so don't do it while moving if (movementDirection == 0) { server.handleClient(); } //input from serial if (Serial.available() > 0) { char inByte = Serial.read();; //incoming serial byte if (inByte == 'o') { movementDirection = 1; Serial.println("opening"); petalServo.attach(servoPin); } else if (inByte == 'c') { movementDirection = -1; Serial.println("closing"); petalServo.attach(servoPin); } } } void loop() { getInput(); unsigned long currentMillis = millis(); interval = currentMillis - previousMillis; previousMillis = currentMillis; frameElapsed += movementDirection*interval; float frameElapsedRatio = float(frameElapsed)/float(frameDuration); if (frameElapsed < 0) { movementDirection = 0; frameElapsed = 0; Serial.println("closed"); analogWrite(ledPin,ledOffPWM); petalServo.detach(); } if (frameElapsed > frameDuration) { movementDirection = 0; frameElapsed = frameDuration; Serial.println("opened"); analogWrite(ledPin,ledOnPWM); petalServo.detach(); } if (movementDirection != 0) { //determine new position/brightness by interpolation between endpoints int newLedValue = (ledOffPWM + int(frameElapsedRatio*(ledOnPWM - ledOffPWM))); int newServoMicros = (servoClosedMicros + int(frameElapsedRatio*(servoOpenMicros - servoClosedMicros))); //update brightness & position analogWrite(ledPin,newLedValue); petalServo.writeMicroseconds(newServoMicros); } }