Introduction: Mechanical Broken Heart (Temperature Sensitive)

About: Im into all things mechanical with a touch of art and creativity. Just a piece of scrap metal fused with a spark of electricity in the binary world of computers.

This is the design of a broken heart that can open and close mechanically. It can light up with different colors and can open wider depending on the ambient temperature readings. It can be placed on your desk as a decoration which also gives you a sense of the ambient temperature readings and any drastic changes in temperature can be noticed with movements of the broken piece of the heart. It uses a seeed xiao samd21 micro controller at its heart (pun intended) and a LIPO battery is used to power it so that you can carry it around if you wish to do so. It can also be powered by the USB-C port on the micro controller which can be accessed from the cutout slot on the side of the heart.

Many thanks to Seeed Fusion services for providing the PCB and the seeed xiao samd21 module for this project. The 3D model and PCB was designed using Fusion360. The parts were printed using a Fused Deposition Modeling (FDM) 3D printer and I will try my best to document the assembly process in the following steps of this instructable.

Supplies

  • Seeed xiao samd21 microcontroller x 1 link
  • 5V Lipo Battery (1500mAh) x 1
  • N20 motor (6V, 300RPM) with M4 threaded shaft x 1 link
  • M4 Nut x 1
  • 0603 110 Ohm SMD resistor x 3 link
  • 0603 4.7 KOhm SMD resistor x 1 link
  • WS2812B Neo-pixel x 3 link
  • WS2812B Round PCB with soldered SMD resistor x 1 (optional) link
  • Limit switch x 1 link
  • ON/OFF Switch x 1 link
  • DS18B20 Temperature Sensor x 1 link
  • N20 Motor Mount x 1 link
  • L293D Motor Driver x 1 link
  • 28 AWG wires
  • LED Light Diffusing Sheet x 1 link
  • Acrylic Paint (Red, Black)
  • Custom PCB x 1 link
  • SS rod (Diameter-4mm, Length-36mm) x 2 (You might have to cut it to size) link
  • LM4UU Linear Bearing (Inner Diameter-4mm, Outer Diameter-8mm, Length-12mm) x 2 link
  • M2.5 (Length-20mm) hex screw and nut x 2
  • M2.5 (Length-15mm) hex screw and nut x 5
  • Hot glue gun and hot glue sticks
  • Super Glue

Step 1: Heart Design

The heart was designed in four parts namely the heart back, heart front, broken piece and the actuator arm for the linear actuator assembly. The shape of the heart was created using the form tool in Fusion360. A cube with symmetry on left and right was shaped into the heart shape by using the edit form tool by pulling on the edges and faces of the cube. Once the heart shape was created it was created into a solid body and spit in half vertically to form the heart front and heart back sections. After that it was given a shell and hollowed out in order to make room to place the other components.

Step 2: Heart Back Design

The heart back has the allocated space to house the Lipo Battery and the PCB to mount the electronics. The PCB mounting holes were created only after designing the PCB to determine the mounting hole locations. There is a slot on the heart back to fit in the DS18B20 temperature sensor. There's another slot to access the USB-C port on the seeed xiao module once soldered onto the PCB on the right side of the heart for programming later on. You can also power the heart by supplying 5V to the USB-C port on the seeed xiao micro controller.

The slot on the left side is designated to fit the charging port (micro USB port) of the Lipo battery. The charging wires go behind the battery and the micro USB port is glued in place. The slot for fitting in the ON/OFF switch is located on the top right side of the back section of the heart and can be easily pushed in.

Step 3: Heart Front Design

The heart front section houses the lead screw of the linear actuator assembly and the actuator arm. It connects to the broken piece of the heart. It has the seat for placing the N20 Motor with the threaded rod and is mounted in place with the mounting bracket screwed in and tightened with the nut from the front. The stands for routing the 4mm SS rods are on either side which act as guide rails for the linear actuator assembly.

The limit switch is glued in place on the stand which is located on the top right side. This allows for the actuator arm of the linear actuator assembly to come in contact when it reaches the inner limit. Triggering the limit switch allows the program to know the position and how far to move from there on within the stroke length of the linear actuator.

Step 4: Painting

After you've 3D printed all the parts you can use some sandpaper to clean up the rough edges and sand down some of the more noticeable layer lines. You can then go onto paint the inside of the parts. Even though most of the inside will not be visible once illuminated there will chances of the white filament leaking some light out. You could apply one coat of acrylic paint on the inside to prevent or reduce it from happening. You can paint the outside once all the parts are assembled since during assembly you are prone to touch those parts more often.

I used a red coat of paint on all the parts and used some black color to on the edges to give some shading. After the paint has dried you can fit in the Lipo battery and the battery charging micro USB port in the allocated slot.

Step 5: Lead Screw Linear Actuator Assembly

The linear actuator assembly consists of mainly two parts which are the N20 motor and threaded rod (lead screw) and the Actuator arm. The linear bearings are pushed in through the allocated holes on either side of the actuator arm and the M4 nut is fitted into the slot in the middle. You can apply some super glue on the sides of the nut before pushing it in but I didn't use any glue as it was a snug fit.

On top of the actuator arm there is a hole cut out with a groove to mount the external Neo-pixel if you decide to use it. This extra Neo-pixel can add extra illumination but you could do without it as well since there is enough lighting with the three Neo-pixels on the PCB itself.

The inner side of the broken piece of the heart has a protruding arm which can be seated on the end of the actuator arm and super glued in place so that the linear actuator can move the broken piece of the heart.

Step 6: Mounting Lead Screw Linear Actuator

The first step is to fit the M4 nut inside the allocated space in the actuator arm. Next you need to push the linear bearings in the actuator arm and then screw in the actuator arm to the threaded rod (lead screw) attached the N20 motor. Position it half way and then seat the assembly with the motor mount in the allocated space for the motor and screw in the M2.5 (Length-20mm) hex screws and nuts. The top side of the actuator arm has a protruding piece to push the limit switch when moving inwards. Make sure you have the correct orientation when assembling.

Once the actuator arm is in place with the motor, the SS rods (Diameter-4mm, Length-36mm) needs to be pushed in through the holes in the front stand and then through the linear bearings onto the hole on the back stand. You might need to cut the SS rods as you might not be able to get the required length. I got a 100mm length rod and cut off two pieces of identical length of 36mm.

Once the rods are in place you can move onto glue in the broken piece to the end of the actuator arm. After assembling the parts make sure to check if the linear actuator moves smoothly by applying 5V to the leads of the motor. It should move without any restrictions.

Step 7: Component Description

The heart is designed so that it can mechanically break apart and move back together with the use of the linear actuator assembly. In order to control the linear actuator attached to the lead screw we need to use the L293D motor driver. The PCB was designed in a way the Neo-pixels were placed at break points to light up as the broken piece moves apart. The DS18B20 temperature sensor sends in ambient temperature information which is then interpreted by the micro controller and the corresponding functions are performed. All the functions are controlled by the tiny but multi functional seeed xiao samd21 micro controller.

Since we will be controlling one motor we will be using one channel on the L293D motor controller although it is able to control dual motors. The EN A pin is used to control the rotation speed of the motor by providing a Pulse Width Modulation (PWM) signal. The IN 1 and IN2 pins are connected to two digital pins on the micro controller to control the rotation directions of the motor. The OUT 1 and OUT 2 pins are connected to the motor leads. The VCC2 pin is supplied with the voltage to drive the motor which in our case is 5V from the Lipo battery and VCC1 is also supplied with 5V for driving the chip. One of the GND pin is tied to GND of the battery and the GND pin on the micro controller.

The following pins of the seeed xiao are used to control all the functions of the heart.

  • Digital Pin 7 (D7) - Neo-pixel signal
  • Digital Pin 8 (D8) - DS18B20 Temperature signal
  • Digital Pin 9 (D9) - Limit Switch
  • Digital Pin 3 (D3) - IN 1 of L293D
  • Digital Pin 4 (D4) - IN 2 of L293D
  • Digital Pin 2 (D2) - EN A of L293D (Speed Control/Enable)

Step 8: External Wiring

Although the use of a PCB reduces most of the wiring, there is some amount of external wiring which needs to be done to complete this project. The discharge wires from the Lipo battery need to be routed and connected to the ON/OFF switch which is then connected to the power pads on the PCB. In addition to this the wires from the DS18B20 temperature sensor needs to be routed from the back to access the pads on the PCB.

If you decide to use the external Neo-pixel you would also need to connect the wires to the corresponding pads on the PCB. If you decide not to use it you can leave the pads without any connection on the PCB. The motor leads and the leads from the limit switch also need to connect to the corresponding pads on the PCB. The pads on the PCB are labelled so that you will have no confusion connecting the wires.

Step 9: PCB Design

The PCB for this project was designed using Fusion360 as it allowed to create the shape of the PCB from the design of the 3D model itself. The outline of the PCB was created with reference to the 3D model which eliminated any measuring of dimensions. The PCB outline was created from sketch, generated a 2D PCB and exported to the electronics design suite in Fusion360 for the components to be placed from the Fusion electronics library. Most of the components were available hence there was no need to create any custom libraries. Once the schematic and the 2D PCB were linked and Gerber files generated it was time to place the order for the PCB from seeed studio fusion.

Seeed Studio Fusion is a global one-stop online platform for PCB manufacturing, assembly, and hardware customization. Whether you need prototyping, mass production, custom solutions for open-source products, or the transformation of your creative ideas into profitable products, Seeed Studio Fusion can meet your requirements.

Step 10: Mounting the PCB and Final Assembly

Once all the components were soldered to the PCB, it should be tested to check if everything works. By now if you decide to use the external Neo-pixel you should solder it to the pads on the PCB. You can now mount the PCB on the standoffs on the heart back by using the M2.5 (Length 15mm) hex screws and nuts.

To diffuse the LED lights you can cut the LED diffusing sheet to the shape which covers all the LEDs and paste it onto the point where the actuator arm and the broken piece join together as shown in the picture. You can opt not to use the LED diffusing sheet as well if you prefer but I think using it gives a more gradual lighting across the gap when the broken piece of the heart moves apart.

After making all the external connections to the PCB, you can close up the heart by joining the back and front sections of the heart and fastening by screwing the two M2.5 hex screws and nuts. There are two holes to join both pieces together, one on the top and one on the left side.

Step 11: Programming

You can access the USB-C port on the seeed xiao module from the slot on the right side of the heart after final assembly for programming and powering if required. We will be using the Arduino IDE for programming seeed xiao module. You will need to add the seeed xiao board to the Arduino IDE. Here is a great article on how to add the seeed xiao board to the Arduino IDE. Once you've added the board you can then download two libraries, one to control the Neo-pixels and the other to interface with the DS18B20 temperature sensor. First download the FastLed zip library. Then on the tools menu go to Sketch->Include library->Add Zip Library and then select the downloaded zip library to use the library.

To install the library to read the temperature values navigate to Sketch -> Include Library -> Manage Libraries and wait for the Library Manager to download the library index and update the list of installed libraries. Type in DS18B20 and a list of libraries will appear of which you should select the Dallas Temperature by Miles Burton and select install. Now you are done with installing the libraries.

In order to control the motor which drives the lead screw you need to give a HIGH signal to EN A (Enable Pin) on the L293D chip. Depending on the way you connected the motor leads to the OUT 1 and OUT 2 determines the direction in which the motor turns. Either way you need to check before writing any code since the limit switch needs to be triggered in order to avoid moving further than the stroke length of the threaded rod on the lead screw. In my case giving a HIGH pulse to IN1 and LOW pulse to IN2 allowed the broken piece to close up. Therefore to move apart would be the opposite which is to give a HIGH pulse to IN2 and LOW pulse to IN1. Once that has been established you can write a piece of code to constantly poll to see if the limit switch is triggered in the loop function of the code and once triggered bring the motor to a halt by sending a LOW pulse to both IN1 and IN2. After triggered make sure to back up the actuator arm so that it does not keep pressing in the limit switch constantly. Now we have determined the home position and from there determine how long we to keep the movement signal to reach the desired position of the broken piece. In my code I have written the code to move the broken piece further apart if the temperature readings are lower (cooler) and closer if the temperature readings are higher (warmer). The values received from the DS18B20 temperature sensor also determines the colors on the Neo-pixels.

I have defined some boolean variables to indicate thresholds for different temperatures such as freeze, cold, cool, warm, room, comfy, hot and boil. All going from most cold to most hot respectively. These values are defined true depending on the temperature reading and the necessary lighting and movement of the broken piece of heart to the distance is triggered. By setting them as true the function does not keep repeating itself unless there is change in the temperature which affects the set threshold values. Once a change is triggered in temperature all the boolean variables are reset and the triggered threshold is set to true.

#include <FastLED.h>
#include <OneWire.h>
#include <DallasTemperature.h>

// Temperature sensor Pin
#define ONE_WIRE_BUS 8

// Neopixel definitions
#define NUM_LEDS 4
#define DATA_PIN 7
#define BRIGHTNESS  255

CRGB leds[NUM_LEDS];

const int en1 = 2;
const int in1 = 3;
const int in2 = 4;
const int limit_switch = 9;

bool home_pos = false;

bool boil = false;
bool hot = false;
bool warm = false;
bool comfy = false;
bool room = false;
bool cool = false;
bool cold = false;
bool freeze = false;

float temp_reading;

// Setup a oneWire instance to communicate with any OneWire device
OneWire oneWire(ONE_WIRE_BUS);  

// Pass oneWire reference to DallasTemperature library
DallasTemperature sensors(&oneWire);

void setup() {
  sensors.begin();  // Start up the library
  Serial.begin(9600);
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(  BRIGHTNESS );
  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);
  pinMode(en1, OUTPUT);
  digitalWrite(en1, HIGH);
  pinMode(limit_switch, INPUT_PULLUP);
  leds[0] = CRGB::Blue;
  leds[1] = CRGB::Cyan;
  leds[2] = CRGB::Aqua;
  leds[3] = CRGB::Black;
  FastLED.show();
  for (int i=255; i>=0; i--){
    FastLED.setBrightness(i);
    FastLED.delay(5);
    FastLED.show();
  }
  for (int i=0; i<=255; i++){
    FastLED.setBrightness(i);
    FastLED.delay(5);
    FastLED.show();
  }
  digitalWrite(in1, LOW);
  digitalWrite(in2, HIGH);
  delay(3500);
  digitalWrite(in1, LOW);
  digitalWrite(in2, LOW);
}

void loop() {

  int switchVal = digitalRead(limit_switch);
  if (switchVal == LOW){
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    home_pos = true;
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(500);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
  }
//   close up
  if (home_pos == false){
    digitalWrite(in1, HIGH);
    digitalWrite(in2, LOW);
  }

  sensors.requestTemperatures();
  temp_reading = sensors.getTempCByIndex(0);
  check_freeze();
  check_cold();
  check_cool();
  check_warm();
  check_room();
  check_comfy();
  check_hot();
  check_boil();
}

void check_freeze(){
  if (home_pos == true && freeze == false && temp_reading <= 0){
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(2200);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(100);
    turn_off_lights();
    leds[0] = CRGB::DarkMagenta;
    leds[1] = CRGB::BlueViolet;
    leds[2] = CRGB::Amethyst;
//    leds[3] = CRGB::DarkOrchid;
    FastLED.show();
    home_pos = false;
    reset_temperatures();
    freeze = true;
  }
}

void check_cold(){
  if (home_pos == true && cold == false && (temp_reading > 0 && temp_reading <= 10)){
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(2000);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(100);
    turn_off_lights();
    leds[0] = CRGB::Blue;
    leds[1] = CRGB::Cyan;
    leds[2] = CRGB::Aqua;
//    leds[3] = CRGB::Aquamarine;
    FastLED.show();
    home_pos = false;
    reset_temperatures();
    cold = true;
  }  
}

void check_cool(){
  if (home_pos == true && cool == false && (temp_reading > 10 && temp_reading <= 15)){
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(1800);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(100);
    turn_off_lights();
    leds[0] = CRGB::Lime;
    leds[1] = CRGB::LimeGreen;
    leds[2] = CRGB::MediumTurquoise;
//    leds[3] = CRGB::PaleGreen;
    FastLED.show();
    home_pos = false;
    reset_temperatures();
    cool = true;
  }    
}

void check_comfy(){
  if (home_pos == true && comfy == false && (temp_reading > 15 && temp_reading <= 20)){
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(1500);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(100);
    turn_off_lights();
    leds[0] = CRGB::LightSeaGreen;
    leds[1] = CRGB::LightSkyBlue;
    leds[2] = CRGB::LightBlue;
//    leds[3] = CRGB::LightCyan;
    FastLED.show();
    home_pos = false;
    reset_temperatures();
    comfy = true;
  }  
}

void check_room(){
  if (home_pos == true && room == false && (temp_reading > 20 && temp_reading <= 22)){
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(1200);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(100);
    turn_off_lights();
    leds[0] = CRGB::NavajoWhite;
    leds[1] = CRGB::Moccasin;
    leds[2] = CRGB::MistyRose;
//    leds[3] = CRGB::MintCream;
    FastLED.show();
    home_pos = false;
    reset_temperatures();
    room = true;
  }  
}

void check_warm(){
  if (home_pos == true && warm == false && (temp_reading > 22 && temp_reading <= 25)){
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(1200);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(100);
    turn_off_lights();
    leds[0] = CRGB::Gold;
    leds[1] = CRGB::Goldenrod;
    leds[2] = CRGB::Khaki;
//    leds[3] = CRGB::LemonChiffon;
    FastLED.show();
    home_pos = false;
    reset_temperatures();
    warm = true;
  }  
}

void check_hot(){
  if (home_pos == true && hot == false && (temp_reading > 25 && temp_reading <= 28)){
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(1200);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(100);
    turn_off_lights();
    leds[0] = CRGB::Chocolate;
    leds[1] = CRGB::Coral;
    leds[2] = CRGB::BurlyWood;
//    leds[3] = CRGB::LightSalmon;
    FastLED.show();
    home_pos = false;
    reset_temperatures();
    hot = true;
  }  
}

void check_boil(){
  if (home_pos == true && boil == false && temp_reading > 28){
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
    delay(1200);
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
    delay(100);
    turn_off_lights();
    leds[0] = CRGB::DarkRed;
    leds[1] = CRGB::Crimson;
    leds[2] = CRGB::FireBrick;
//    leds[3] = CRGB::IndianRed;
    FastLED.show();
    home_pos = false;
    reset_temperatures();
    boil = true;
  }  
}

void reset_temperatures(){
  boil = false;
  hot = false;
  warm = false;
  comfy = false;
  room = false;
  cool = false;
  cold = false;
  freeze = false;
}

void turn_off_lights(){
  leds[0] = CRGB::Black;
  leds[1] = CRGB::Black;
  leds[2] = CRGB::Black;
  leds[3] = CRGB::Black;
  FastLED.show();
  FastLED.delay(10);
}

Step 12: Color Scheme Settings

Different color schemes have been set to denote a certain range of temperature readings. For each of the boolean variable defined which was mentioned in the previous step the three Neo-pixels display the respective colors which are shown in the above pictures.

You could directly type in the name of the colors to display them on the Neo-pixels. Feel free to explore the your choice of colors although you can get a reference of the color names from this as well. Below is the list of colors used for each setting.

  • Freeze - pixel 1(DarkMagenta), pixel 2(BlueViolet), pixel 3 (Amethyst)
  • Cold - pixel 1(Blue), pixel 2(Cyan), pixel 3(Aqua)
  • Cool - pixel 1(Lime), pixel 2(LimeGreen), pixel 3(MediumTurquoise)
  • Comfy - pixel 1(LightSeaGreen), pixel 2(LightSkyBlue), pixel 3(LightBlue)
  • Room - pixel 1(NavajoWhite), pixel 2(Moccasin), pixel 3(MistyRose)
  • Warm - pixel 1(Gold), pixel 2(Goldenrod), pixel 3(Khaki)
  • Hot - pixel 1(Chocolate), pixel 2(Coral), pixel 3(BurlyWood)
  • Boil - pixel 1(DarkRed), pixel 2(Crimson), pixel 3(FireBrick)

Step 13: Conclusion

This design of the broken heart has minimal parts to 3D print and with the use of the custom PCB requires very little external wiring. There are some key points to take care when taking on this project such as to remove any supports after 3D printing carefully which might break off some parts inside the heart structure. Other than that during programming make sure to take note of the direction of rotation of the motors before writing any code and to program the limit switch to trigger the end stop before any motion to avoid over run of the lead screw.

If I was to redo this I would have preferred to 3D print the parts using a resin printer for finer finishing as the surface of the heart had pretty noticeable layer lines. You could get rid of them by applying some wood putty and sanding them off but it would take up a bit of your time.

Since I was working in room temperature I was unable to test cooler temperatures which I had initially programmed to make the broken piece to move further apart as the temperature got cooler. These details can be tweaked to your preference in the program. Overall its a nice design which can be kept on the table to notify of ambient temperature readings and changes in temperature can be vividly noticed with the movement of the broken piece of the heart. Thank you for reading till the end and hope you liked this instructable.

3D model and code files