Final Result
The music track in the video is called Gangster by Hey Pluto.
Attribution: Music from #Uppbeat (free for Creators!): https://uppbeat.io/t/hey-pluto/gangster License code: BZL4P0GSQJRKAOQB
Thank yous
I would be remiss not to mention the following contributions:
- Lena & Caroline, thank you for your help with putting together my video and slide. I am indebted to SYAP.
- Gerard, thank you for the professional quality video footage and images
- To my team at BCH: thank you for supporting me as I took on the demands of Fab Academy!
- To all the Fab Lab BCN instructors - Josep, Julia, and Adai: thank you for all the time and energy you poured into all of us. Your mentorship made this project possible.
- To all of my Fab Academy BCN classmates: you enriched my Fab Academy experience in innumerable ways. Thank you, thank you.
Links
To see the nitty gritty of the development process, see the Project Development page stored alongside my weekly assignments.
To see further detail on the systems integration thought process, design considerations, and steps, see the Systems Integration page.
The header images for all of my weekly assignments were generated with the help of the Microsoft AI Text-to-Image Creator tool.
BOM
Since there are two distinct assemblies in my project, I’ve split the fabrication process into two parts. Part A is for the Heart, and Part B is for the Base.
Heart Assembly
Component | # | Source | Price/Unit |
---|---|---|---|
resin 3D printed parts (ventricles, atria, LV window, RV window) - 1 each | ~390mL | white ANYCUBIC resin from amazon | $18.99/kg* |
FDM 3D printed part (VSD patch) | 1 | white TPU | - |
6mm dia x 2mm magnets | 8 | amazon | $0.10 |
8mm dia x 5mm magnet | 1 | amazon | $0.10 |
XIAO ESP32C3 | 1 | seeedStudio | $4.99 |
CONN HEADER VERT 7POS 2.54MM | 2 | digikey | $0.15 |
CONN HEADER VERT 2X05POS 1.27MM | 2 | digikey | $0.88 |
ribbon cable | 1 | digikey | $11.75 |
Switch_Slide_RightAngle_CnK | 1 | digikey | $1.95 |
ultra skinny NeoPixel 1515 LED strip | 2 | adafruit | $24.95 (75 LEDs per strip) |
magnet switch | 1 | digikey | $1.03 |
3.7V 220mAh Battery | 1 | amazon | $3.68 |
*the ANYCUBIC slicing software estimated a cost of $85.86 for the 390mL print, but I don’t know how accurate that is
Base Assembly
Component | # | Source | Price/Unit |
---|---|---|---|
FDM 3D printed part (upper base component) | 1 | black PLA | - |
laser cut part (lower base component) | 1 | 6mm acrylic | - |
XIAO ESP32C3 | 1 | seeedStudio | $4.99 |
CONN HEADER VERT 7POS 2.54MM MALE | 2 | digikey | $0.15 |
CONN HEADER HORIZONTAL 8POS 2.54MM | 1 | digikey | $0.93 |
CONN HEADER VERT 7POS FEMALE | 2 | digikey | $0.70 |
potentiometer | 1 | digikey | $1.02 |
A. Heart Fabrication Process
Preparing the components
Main body (ventricles & great vessels), atria, & removable windows
I used an ANYCUBIC resin printer to 3D print the main body (Ventricles & great vessels), atria (where the electronics & battery would be concealed/contained) and two removeable ventricular viewing windows for the heart.
Material: white resin
I really would suggest printing this on a resin printer if you have access to one, as the quality of the resulting parts and light diffusion of the resin is preferable. Also, the resin is more amenable to some modification with a dremel- this was required to attach the atria component (which housed the electronics) to the main body of the chambers and outflow tracts. It’s a snug fit!
Depending on the size of your resin printer you may have to print in several batches.
After the print completed, I cleaned the parts in alcohol and carefully removed the support material.
Files:
- ventricles & great vessels piece | STL
- atria piece | STL
- RV window piece | STL
- LV window piece | STL
Incorporating magnets for the removable windows
Once the resin prints were completed, I superglued the 4 pairs of magnets into place (making sure each pair was properly oriented to attract rather than repell its partner), 2 pairs per removeble ventricular window piece.
The magnets I used are disks with a diameter of 6mm and thickness of 1.7mm.
Removeable VSD portion:
I used the Bambu labs to 3D print the removeable VSD portion/patch which has a magnet embedded inside. I made sure to slice with a support setting of ‘support on bed only’ so that internal supports wouldn’t be added in the cavity where my magnet would go. In the proprietary Bambulabs slicer program I made a note of which layer to add the magnet (right before the cavity started to be enclosed on the top side). You can theoretically add a pause at this point with the slicer, but I just watched the print and paused manually to add the magnet.
Note: make sure to GLUE the magnet in place- otherwise it will attach itself to the nozzle and cause the print to fail.
Print material: white TPU (the VSD patch should be made from a slightly flexible material so it can fit and hold snugly into the VSD hole. Theoretically molding and casting could also be a process to fabricate this piece, still embedding the magnet inside, so long as the resulting piece was opaque to hide the magnet embedded within and flexible to give some tolerance for fitting into the VSD opening).
The magnet I used was one we had in the lab. The dimensions are approximately 8mm in diameter by 5mm tall.
If you do not have access to a magnet of the exact dimensions I used, you can modify the VSD patch piece to fit the magnet you do have available. You’ll want to use a magnet that’s larger rather than smaller to ensure it’s strong enough to be detected by the magnet switch sensor through the resin.
Files:
- VSD patch | STL
Electronics
Files:
- heart board design files | kicad project ZIP
- Traces front | png
- In Cut (to access postitive battery pad on the back of the Xiao) | png
- Drill holes (to connect to the Xiao; vias) | png
- Out cut | png
- Traces back | png
-
Mill board 1/64" endmill: Front traces switch to 1/32" endmill, rezero z In cut –> drill holes outcut flip board switch back to 1/64" endmill; rezero z Back traces
-
Stuff board
Add vias solder components as usual
- Connect battery - positive terminal directly soldered to the batt + pad on the back of the Xiao ESP32C3, and the GND terminal connected to the appropriate leg of a small slide switch mounted on the board.
- Connect the Xiao esp32c3
- Test the battery
- Connect the magnet switch
Programming
Download the code for the heart board here or copy and paste the code from below into a new Arduino IDE sketch and upload.
#include <esp_now.h>
#include <WiFi.h>
// Define the structure for receiving data
typedef struct struct_message {
int value;
} struct_message;
// Create a struct_message called myData
struct_message myData;
#include <FastLED.h>
// How many leds in your strip?
#define NUM_LEDS1 56
#define NUM_LEDS2 50
// For led chips like Neopixels, which have a data line, ground, and power, you just
// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806, define both DATA_PIN and CLOCK_PIN
#define DATA_PIN1 D10
#define DATA_PIN2 D2
#define HALL_SENSOR D0
int BPM = 70;
// Define the array of leds
CRGB leds1[NUM_LEDS1];
CRGB leds2[NUM_LEDS2];
void setup() {
Serial.begin(115200);
Serial.println("resetting");
FastLED.addLeds<WS2812, DATA_PIN1, GRB>(leds1, NUM_LEDS1);
FastLED.addLeds<WS2812, DATA_PIN2, GRB>(leds2, NUM_LEDS2);
FastLED.setBrightness(84);
pinMode(HALL_SENSOR, INPUT_PULLUP);
WiFi.mode(WIFI_STA);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Register for a callback function to receive data
esp_now_register_recv_cb(OnDataRecv);
}
void fadeall1() {
for (int i = 0; i < NUM_LEDS1; i++) { leds1[i].nscale8(230); }
}
void fadeall2() {
for (int i = 0; i < NUM_LEDS2; i++) { leds2[i].nscale8(230); }
}
void loop() {
// Pulse rate: 60 pulses per minute (1 pulse per second)
float delayTimeR = (((60.0/BPM)*1000.0) / (NUM_LEDS1)); // Delay time in milliseconds for each LED;
float delayTimeL = (((60.0/BPM)*1000.0) / (NUM_LEDS2)); // Delay time in milliseconds for each LED;
Serial.println(BPM);
Serial.println(BPM);
// First slide the LED in one direction for blue LEDs
for (int i = 0; i < NUM_LEDS1; i++) {
if (digitalRead(HALL_SENSOR) && i>11) {
leds1[i] = CRGB::Purple;
}
else{
leds1[i] = CRGB::Blue;
}
FastLED.show();
fadeall1();
fadeall2();
delay(delayTimeR);
}
//delay(500);
//First slide the LED in one direction for red LEDs
for (int i = 0; i < NUM_LEDS2; i++) {
leds2[i] = CRGB::Red;
FastLED.show();
fadeall2();
fadeall1();
delay(delayTimeL);
}
}
void OnDataRecv(const esp_now_recv_info *info, const uint8_t *incomingData, int len) {
memcpy(&myData, incomingData, sizeof(myData));
Serial.print("Bytes received: ");
Serial.println(len);
Serial.print("Received value: ");
BPM = myData.value;
Serial.println(BPM);
}
Assembly & Integration
NOTE: This part requires at least two sets of delicate hands, a lot of patience, and a little luck. Use a dremel to carefully remove excess resin where needed (in order to get the atria piece to snap onto the ventricle piece; in order to allow the battery to slide through the board cavity so it would sit between the atria and ventricles inside of its own cavity).
- Solder two ulta skinny LED strands to the header pin connector. Ensure GND-GND, data-pin, and VCC-VCC are correct. Test that the connection is good and both strands are lighting up.
-
Feed the LED strands through the slots in the atria piece, one into the left atrium and one into the right atrium.
-
Connect the ulta skinny LED strands to the board via the header. Test function.
-
Feed the battery through the cavity ahead of the board so it will rest between the atria piece and the ventricles piece.
-
Being mindful of the wires and connections, carefully snap the atria piece with the board, battery, and led strips into place onto the ventricles piece. Tuck the magnet switch into the cavity behind the VSD, then tuck the battery in behind it.
LEDs
I hot glued two LED stips into place, one on the right side of the heart and one on the left side of the heart. The excess of each strip will stick out from both the aorta and pulmonary artery.
After testing both strands were lighting up correctly, I changed the code and trimmed both LED strips to end at the edge of the geometry rather than extending beyond it.
B. Base Fabrication Process
3D print upper component of the base
I 3D printed the upper portion of the base using the Bambu labs FDM printer, with black PLA filament.
File:
- Base | STL
Laser cut lower component of the base
I laser cut the bottom of the base from 6mm thick clear acrylic to enclose the electronics but still allow me to access them from underneath the base if need be.
File: base bottom | dxf
Electronics
Files:
- base board design files | kicad project ZIP
- Traces | png
- Holes | png
- In cut | png
- Out cut | png
-
Mill 1/64" endmill for traces 1/32" endmill for holes, in cuts, and out cut (in that order)
-
Stuff board
-
connect to Xiao esp32c3 module
-
connect potentiometer
Programming
Code for the base board is below, or you can download a file here or copy and paste from below into a new sketch in the Arduino IDE:
#include <esp_now.h>
#include <WiFi.h>
#define POT_PIN D0 // Potentiometer connected to pin D0
// Define the structure for sending data
typedef struct struct_message {
int value;
} struct_message;
struct_message myData;
// Define the peer address (MAC address of the receiver)
uint8_t broadcastAddress[] = {0xD4, 0xF9, 0x8D, 0x00, 0xF9, 0x18}; // Replace with the receiver's MAC address
// Callback function that gets called when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("Last Packet Send Status: ");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
// Global variables
int bpm = 60; // Default BPM
unsigned long lastToggleTime = 0;
unsigned long lastReadTime = 0;
const unsigned long readInterval = 500; // Delay for reading the potentiometer (in milliseconds)
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
Serial.println("WiFi mode set to STA");
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
Serial.println("ESP-NOW initialized");
// Register the send callback
esp_now_register_send_cb(OnDataSent);
// Register peer
esp_now_peer_info_t peerInfo;
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
Serial.println("Failed to add peer");
return;
}
Serial.println("Peer added");
}
void loop() {
// Get the current time
unsigned long currentTime = millis();
// Check if it's time to read the potentiometer and send data
if (currentTime - lastReadTime >= readInterval) {
// Read the analog value from the potentiometer
int analogValue = analogRead(POT_PIN); // Pin D1 corresponds to ADC1 (GPIO1)
// Map the analog value to the range 30 to 180
bpm = map(analogValue, 0, 4095, 30, 180); // Assuming a 12-bit ADC resolution
// Print the values for debugging
Serial.print("Analog Value: ");
Serial.print(analogValue);
Serial.print(" | Mapped Value: ");
Serial.println(bpm);
// Prepare data to send
myData.value = bpm;
// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
if (result == ESP_OK) {
Serial.println("Sent with success");
} else {
Serial.println("Error sending the data");
}
// Update the last read time
lastReadTime = currentTime;
}
}
Assemble & integrate
Fixate the board to the base (hot glue works) so the connector for the cable is accessible via the hole designed for that purpose in the back of the base. Stick the stem of the potentiometer through the hole in the front of the base and push the dial over it, securing it in place. Hot glue the bottom acrylic piece in place under the top part of the base, with the electronics packaged inside.
Overall Integration
The two pieces are modular with each other, meaning that I can individually update components or aspects of either the heart or the base and still have them work together. For instance, in the future I could add additional means of user interaction to the base, such a capacitive plates which can be used to highlight specific chambers when touched. Or a piezoelectric sensor which reads the user’s heartbeat and send it to the Heart to update the ‘heartbeat’ in real time to match that of the user. Since they two parts communicate wirelessly, I would just need to update the code in both pieces as needed after changing/adding additional user inputs.
For more information on my systems integration approach, see the Systems Integration page
License
This project is licensed under a Attribution-ShareAlike 4.0 International (CC BY-SA 4.0).