Final Project

Final Result

a teaching heart
a teaching heat

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.

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.

Slicing
Slicing
on print bed
on print bed
removed from print bed
removed from print bed

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.

RV window
RV window
LV window
LV window
both removable windows in place
both removable windows in place

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.

during the 3D print, after gluing the embedded magnet in place
during the 3D print, after gluing the embedded magnet in place

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

front traces
front traces
in cut
in cut
drill holes
drill holes

out cut
out cut
back traces
back traces

  1. 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

  2. Stuff board

    Schematic
    Schematic
    Layout
    Layout

    Add vias

    solder components as usual

  1. 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.
  2. Connect the Xiao esp32c3
  3. Test the battery
  4. Connect the magnet switch

Programming

heart board with a cable connected for programming
heart board with a cable connected for 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).

  1. 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.

separating sections of the ribbon cable to make soldering easier
separating sections of the ribbon cable to make soldering easier

preparing to solder the LEDs to the ribbon cable

  1. Feed the LED strands through the slots in the atria piece, one into the left atrium and one into the right atrium.

    gently feeding the led strands into the atria chambers through holes left for that purpose

  2. Connect the ulta skinny LED strands to the board via the header. Test function.

    connecting the leds to the board via the ribbon cable connector

  3. Feed the battery through the cavity ahead of the board so it will rest between the atria piece and the ventricles piece.

    feeding the battery through and behind the atria so it will be concealed between the atria and ventricles

  4. 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.

hot gluing the LED strip to the cardiac chambers

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:

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.

tracing the top component of the base to determine bounds for the bottom layer of acrylic
tracing the top component of the base to determine bounds for the bottom layer of acrylic

File: base bottom | dxf

laser cutting 6mm thick acrylic to enclose the bottom of the base

Electronics

Files:

Traces
Traces
Holes
Holes
in cut
in cut
out cut
out cut

  1. Mill 1/64" endmill for traces 1/32" endmill for holes, in cuts, and out cut (in that order)

  2. Stuff board

    Schematic
    Schematic
    Layout
    Layout

  3. connect to Xiao esp32c3 module

  4. 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).

Built with Hugo | Using a personally adjusted version of Nadieh Bremer's website and the theme Stack