Introduction: Blooming Marvelous Flower Lamp

About: Electrical Engineer by trade, tinkerer by heart.

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

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); } }