Introduction: Heartmates - a Companion Tied to Your Heart

Assignment intro

The following project is my first ever arduino project, made as an assignment for the module ‘If This Then That’ at the HKU. 

For this assignment we had to conceptualize and create an original prototype using a microcontroller (mostly-likely Arduino) and with a clean looking presentation. (I.e. casing and wiring) We were given a lot of freedom when it comes to possible ideas, as long as it met the set requirements.

The project

The concept I eventually came up with is a tamagotchi-like companion that reacts to your heartbeat, which I dubbed: a Heartmate, since it is a companion that is synchronized and reacts to changes in your own heartbeat.

It makes use of a heart sensor to keep track of your average heartbeat which it then uses to display the correct emotion on the OLED display. If your and by extension its heart rate gets too high, it will start panicking. The only way to calm it down is to slow down, take a breath and complete a little minigame by pressing the 2 buttons in an alternating Pattern. When done successfully the Heartmate will go back to being happy for a little while until inevitably your heart rate goes back up, or is still up.

Iterations

Due to this being my first real arduino project and the issue of time constraints, the final version doesn’t include several ideas I had when I came up with the concept. Some features that were part of my original design are a wristband and a feature where the heartmate would perish if it was panicking for either too long or too often. I also had to adjust some features, since the scope was getting to high. This includes the way I connected the heart sensor to the finger, as well as the simple beeps & boops it makes when it is in the panicking state.

Supplies

Below is a list of every part I ended up using for this project.

  • 2.42 Inch 128x64 OLED display
  • I2C-UART Bi-Directionele Logic Level Converter 5V-3.3V (only when using the display mentioned above)
  • Heart Rate Sensor and Pulse Oximeter - MAX30102
  • A standard buzzer
  • An external power supply (I used a portable power bank via USB connection)
  • 2 pushbuttons (any will do)
  • 4x6 cm PCB board ( I used a dubbelsided one )
  • Arduino UNO

Casing

  • 3D printer PLA
  • Glue

Tools

  • Solder + Soldering iron


Of course you will also need a few basic parts, such as jumper cables, a few resistors and a breadboard if you want to test the parts.

Step 1: Testing

The first things I did was testing the things I could, and getting to know how they work. I already had a few buttons so I wrote the code for the button minigame and got that working. Once I got the parts I didn't have before, I tested all the parts after soldering the pin headers to the shields. (see the images above for some of the tests).

Step 2: Getting Everything Working

Issues

I had issues with combining the heart sensor with the display. only one would work at a time. This ended up being a library conflict between the SSD1305 library and the MAX30102 library. When I used the SSD1306 library and used the correct I2C addres for the display (0x3C) it worked.

Final code

Once everything is connected you can use the following code i wrote, which is an amalgamation of my own code and the example sketches provided with the libraries I used. Those being the Adafruit SSD1306 and GFX library for the display, and the MAX30102 library by DFRobot for the heart meter. I started with the simplest things without any of the sensors, just the button ‘minigame’. Then I coded the parts individually and merged the code into the main code. Which eventually led to this monstrosity.

Do note that I had to remove a bunch of Serial.prints and unnecessary global variables, because they took up too much of the Arduino UNO's memory.

The bitmaps in the code are for the two faces in the images above.

// import libraries
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#include <MAX30105.h>
#include <heartRate.h>
#include <spo2_algorithm.h>

// bitmaps for the faces
// 'Happy(neutral)', 64x64px
const unsigned char epd_bitmap_Happy_neutral_[] PROGMEM = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
  0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
  0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
  0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x01,
  0x80, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x01,
  0x80, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x80, 0x07, 0x9e, 0x00, 0x00, 0x79, 0xe0, 0x01,
  0x80, 0x07, 0x9e, 0x00, 0x00, 0x79, 0xe0, 0x01, 0x80, 0x07, 0x9e, 0x00, 0x00, 0x79, 0xe0, 0x01,
  0x80, 0x07, 0x9e, 0x00, 0x00, 0x79, 0xe0, 0x01, 0x80, 0x3c, 0x03, 0xc0, 0x03, 0xc0, 0x3c, 0x01,
  0x80, 0x3c, 0x03, 0xc0, 0x03, 0xc0, 0x3c, 0x01, 0x80, 0x3c, 0x03, 0xc0, 0x03, 0xc0, 0x3c, 0x01,
  0x80, 0x3c, 0x03, 0xc0, 0x03, 0xc0, 0x3c, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x0f, 0x00, 0x00, 0xf0, 0x00, 0x01, 0x80, 0x00, 0x0f, 0x00, 0x00, 0xf0, 0x00, 0x01,
  0x80, 0x00, 0x0f, 0xe0, 0x07, 0xf0, 0x00, 0x01, 0x80, 0x00, 0x0f, 0xe0, 0x07, 0xf0, 0x00, 0x01,
  0x80, 0x00, 0x01, 0xfc, 0x3f, 0x80, 0x00, 0x01, 0x80, 0x00, 0x01, 0xff, 0xff, 0x80, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
  0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
  0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
  0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
// 'Panicking', 64x64px
const unsigned char epd_bitmap_Panicking[] PROGMEM = {
  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
  0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
  0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
  0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x3c, 0x03, 0xc0, 0x03, 0xc0, 0x3c, 0x01,
  0x80, 0x3c, 0x03, 0xc0, 0x03, 0xc0, 0x3c, 0x01, 0x80, 0x3c, 0x03, 0xc0, 0x03, 0xc0, 0x3c, 0x01,
  0x80, 0x3c, 0x03, 0xc0, 0x03, 0xc0, 0x3c, 0x01, 0x80, 0x07, 0x9e, 0x00, 0x00, 0x79, 0xe0, 0x01,
  0x80, 0x07, 0x9e, 0x00, 0x00, 0x79, 0xe0, 0x01, 0x80, 0x07, 0x9e, 0x00, 0x00, 0x79, 0xe0, 0x01,
  0x80, 0x07, 0x9e, 0x00, 0x00, 0x79, 0xe0, 0x01, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x01,
  0x80, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x80, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x01,
  0x80, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x01, 0xe0, 0x1e, 0x00, 0x00, 0x01, 0x80, 0x00, 0x01, 0xe0, 0x1e, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x01, 0xe0, 0x1e, 0x00, 0x00, 0x01, 0x80, 0x00, 0x01, 0xe0, 0x1e, 0x00, 0x00, 0x01,
  0x80, 0x03, 0xcf, 0x3c, 0xf3, 0xcf, 0x00, 0x01, 0x80, 0x03, 0xcf, 0x3c, 0xf3, 0xcf, 0x00, 0x01,
  0x80, 0x03, 0xcf, 0x3c, 0xf3, 0xcf, 0x00, 0x01, 0x80, 0x03, 0xcf, 0x3c, 0xf3, 0xcf, 0x00, 0x01,
  0x80, 0x00, 0x78, 0x07, 0x80, 0x78, 0x00, 0x01, 0x80, 0x00, 0x78, 0x07, 0x80, 0x78, 0x00, 0x01,
  0x80, 0x00, 0x78, 0x07, 0x80, 0x78, 0x00, 0x01, 0x80, 0x00, 0x78, 0x07, 0x80, 0x78, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
  0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
  0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
  0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f,
  0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 1584)
const int epd_bitmap_allArray_LEN = 3;
const unsigned char* epd_bitmap_allArray[3] = {
  epd_bitmap_Happy_neutral_,
  epd_bitmap_Panicking
};

// pin connected to potentiometer for testing without a heart rate monitor
//int dialPin = A1;

// states
enum States {
  neutral = 0,   // when average heart rate is normal
  happy = 1,     // unused
  panicked = 2,  // when heart rate exceeds panicking threshold
  dead = 3       // unused
};
States currentState;  // the state your heartmate is currently in

// variables
//minigame
bool miniGameStarted = false;
int maxCount = 1; // amount of times buttons need to be pressed to calm heartmate down

// data
int leftValue; // left button value
int rightValue; // right button value

unsigned long currentTime; // time elapsed since beginning

// neutral data
bool isHappy = false;

// panick data
bool isPanicked = false;
int panickThreshold = 70; // heartmate will panick when average heartrate is above this value
bool onCoolDown = false; // if the heartmate was just calmed down
unsigned long panickTimer; // timer for the panicking cooldown
int panickInterval = 12000; // duration of the cooldown

// minigame data
int count = 0;
// keep track of what button was pressed last
bool leftButtonPressed = true;
bool leftSinglePress = false;
bool rightButtonPressed = true;
bool rightSinglePress = false;

// buzzer data
int buzzerInterval = 100;  // interval between beeps in milliseconds
int toneDuration = 100; // duration of a beep
bool isBuzzing = false;
bool elapsedDuration = false;
// buzzer timer
unsigned long timer = 0;
unsigned long durationTimer;

// // Heart monitor data
MAX30105 particleSensor;
int dialValue;
float beatsPerMinute;
int beatAvg;
const byte RATE_SIZE = 4;  //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE];     //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0;  //Time at which the last beat occurred

// Display data
Adafruit_SSD1306 display(128, 64, &Wire, -1);

void setup() {
  Serial.begin(9600);

  // Initialize  the heart sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST))  //Use default I2C port, 400kHz speed
  {
    Serial.println("MAX30105 was not found. Please check wiring/power. ");
    while (1)
      ;
  }
  Serial.println("Place your index finger on the sensor with steady pressure.");

  particleSensor.setup();                     //Configure sensor with default settings
  particleSensor.setPulseAmplitudeRed(0x0A);  //Turn Red LED to low to indicate sensor is running
  particleSensor.setPulseAmplitudeGreen(0);   //Turn off Green LED

  // initialize the oled display
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;  // Don't proceed, loop forever
  }

  // set the right text size
  display.setTextSize(3);

  // pin setup
  pinMode(4, INPUT);
  pinMode(2, INPUT);

  pinMode(A0, OUTPUT);
  //pinMode(dialPin, INPUT);  // dial for simulating BPM

  // change to starting state to neutral
  SwitchState(neutral);
}

void loop() {
  // get the time since start
  currentTime = millis();

  // gets all the data from the input sensors
  GetInputs();

  // resets the cursor to the topleft corner
  display.setCursor(0, 0);

  // state machine that handles the logic of the current state (if you can call it a 'state machine')
  switch (currentState) {
    case neutral:
      // all the code for the neutral state
      if (isHappy == 0) { // everything within this if statement only gets called once in this state
        isHappy = true;

        // display neutral face bitmap
        UpdateDisplay();
        display.display();
      }

      // if average heartbeat is higher than threshold and panick is not on a cooldown, switch the currentstate to the panicked state
      if (beatAvg >= panickThreshold && !onCoolDown) {
        onCoolDown = true;
        SwitchState(panicked);
      }
      break;
    case happy:
      break;
    case panicked:
      // all the code for the panicked state here
      if (isPanicked == 0) { // everything within this if statement only gets called once in this state
        isPanicked = true;

        // display panicked face bitmap
        UpdateDisplay();
        display.display();
      }

      // if the minigame has not started yet, start it
      if (!miniGameStarted) {
        ToggleMiniGame();
      }

      // run minigame and buzzer logic
      MiniGame();
      Buzzer();

      break;
    case dead:
      // all the dead code (if I have time)
      break;
  }

  // when the cooldown has ended after it was started, reenable switching to the panicked state.
  if (panickTimer <= currentTime && onCoolDown) {
    onCoolDown = false;
  }

  // Get all heartbeat data
  GetBeatsPerMinute();
}

void GetInputs() {
  // button inputs
  leftValue = digitalRead(4);
  rightValue = digitalRead(2);

  // get values from potentiometer instead of heart rate monitor for testing
  //dialValue = analogRead(dialPin);
  //beatAvg = map(dialValue, 0, 1023, 60, 140);
}

void Buzzer() {
  // calculate the frequency of the tone, depending on how far the minigame is
  int frequency = (maxCount * 100 + 100) - (count * 100);

  // play tone for a set duration on an interval
  if (timer <= currentTime) {
    tone(A0, frequency, toneDuration);
    timer = currentTime + toneDuration + buzzerInterval;
  }
}

// starts the calming minigame
void ToggleMiniGame() {
  miniGameStarted = !miniGameStarted;
}

// minigame logic to be ran inside loop
void MiniGame() {
  // if the minigame has started
  if (miniGameStarted) {
    // Press left and right button one after the other
    if (leftValue == HIGH) {
      if (rightButtonPressed) {
        count++;
        leftButtonPressed = true;
        rightButtonPressed = false;
      } else if (leftButtonPressed && !leftSinglePress) {
        count = 0;
      }
      leftSinglePress = true;
    } else if (rightValue == HIGH) {
      if (leftButtonPressed) {
        count++;
        leftButtonPressed = false;
        rightButtonPressed = true;
      } else if (rightButtonPressed && !rightSinglePress) {
        count = 0;
      }
      rightSinglePress = true;
    } else {
      leftSinglePress = false;
      rightSinglePress = false;
    }

    // when count reaches specified value, calm down
    if (count >= maxCount) {
      isBuzzing = false;
      count = 0;

      // start cooldown timer
      panickTimer = currentTime + panickInterval;
      onCoolDown = true;
      ToggleMiniGame();
      SwitchState(neutral);
    }
  }
}

// updates all the info on the display buffer
void UpdateDisplay() {
  display.clearDisplay();
  display.setCursor(0, 0);

  switch (currentState) {
    case neutral:
      display.drawBitmap(0, 0, epd_bitmap_Happy_neutral_, 64, 64, WHITE);
      display.setTextSize(2);
      display.setCursor(88, 24);
      display.setTextColor(WHITE);  // 'inverted' text
      display.println(beatAvg);
      break;
    case dead:
      display.drawBitmap(0, 0, epd_bitmap_Dead_v2, 64, 64, WHITE);
      display.setTextSize(2);
      display.setCursor(88, 24);
      display.setTextColor(WHITE);  // 'inverted' text
      display.println(beatAvg);
      break;
    case panicked:
      display.drawBitmap(0, 0, epd_bitmap_Panicking, 64, 64, WHITE);
      display.setTextSize(2);
      display.setCursor(88, 24);
      display.setTextColor(WHITE);  // 'inverted' text
      display.println(beatAvg);
      break;
  }
}

// switch to the specified state
void SwitchState(States _state) {
  display.clearDisplay();

  leftButtonPressed = true;
  rightButtonPressed = false;
  currentState = _state;

  UpdateDisplay();

  isHappy = false;
  isPanicked = false;

  //Serial.print("Changed state to: ");
  //Serial.println(_state);
}

// get the heart data
void GetBeatsPerMinute() {
  long irValue = particleSensor.getIR();

  if (checkForBeat(irValue) == true) {
    //We sensed a beat!
    long delta = millis() - lastBeat;
    lastBeat = millis();

    beatsPerMinute = 60 / (delta / 1000.0);

    if (beatsPerMinute < 255 && beatsPerMinute > 20) {
      rates[rateSpot++] = (byte)beatsPerMinute;  // store this reading in the array
      rateSpot %= RATE_SIZE;                     // wrap variable

      //Take average of readings
      beatAvg = 0;
      for (byte x = 0; x < RATE_SIZE; x++)
        beatAvg += rates[x];
      beatAvg /= RATE_SIZE;
    }
  }

  Serial.println(beatAvg);
}

Step 3: Creating a Casing

The design

When it comes to the design of the casing. I wanted a semi-compact design with enough, but not too much, room for all the wiring and components. I also wanted to make sure it was as accessible as possible for the people who would have to review and grade my assignment. Hence why there is a large opening in the side into which I can slide the Arduino.

These requirements led me to choose 3D-printing as my best option as printing would give me more flexibility in what I could achieve as someone with little experience in that particular field.

I made the design for the 3D printer using Tinkercad. You can find the design at the following link:

https://www.tinkercad.com/things/dgSw7ilTfzl-heartmate-casing?sharecode=31o59FebhfEvEaNxGBdbOFrNmyYF59C5d3cYcBMg6KI

Printing

3D printing from thereon out was relatively simple. Even though I had to make a few tweaks to the design. (the link above is up-to-date)

For the settings I used a 0.8mm nozzle at a layer height of 0.2 mm which was probably a higher resolution than I really needed. The print ended up taking around 5 hours. I used this time to start with the next step: soldering.


Step 4: Soldering

This is the part I was personally dreading since a lot could and would go wrong here.

I started by soldering all the individual components onto the board while making sure all the connections are the same as when we got everything working in Step 2. I had a few wires break after soldering the stripped part into the board, so I had to reroute some parts. After all that was done I could start connecting everything to each other and the wires that will end up in the Arduino.


Step 5: Final Assembly

Putting everything together was a pain. My soldering job was very messy, with wires of different lengths sticking out everywhere. On top of that the casing was too small and not pleasant to work with. At first I also missed one connection to the display, so that wasn’t working and I had to do some extra soldering.

Base

The Arduino UNO slides snugly onto the bottom of the print through the opening in the side. In the same opening is where you attach the power source, In my case a power bank via USB.

The hole in the back is meant for the wires connecting to the heart sensor attached to the ledge sticking out. This way the sensor is semi-stable when pressing your finger against it.

Pcb part

The pcb with all the connections and wires can be lowered into the 2nd part of the print (the part with the slit) perpendicularly and it should stay there. Because of the way I soldered the wires it didn’t fit, so my casing couldn’t be sealed.

Lid

The buttons can be attached through the holes on the lids and the display can be glued to the bottom with the display part sticking into the large opening. The buttons still need to be soldered to the wires on the pcb.

When every part of the print is ready and it is all connected in the right way, you can connect the different bits to each other and it should work.



Step 6: Conclusion

Final result

Sadly my final result is not what I wanted it to be. In the end I was starting to run out of time and I had to make do with what I had: A messy soldering job and a casing that was way too small. Because of which the buttons and the buzzer ended up not working.

At the very least I had the OLED and the heart sensor working together to display the right emotion when your average heartrate crosses a certain threshold.

Looking back

This project was my first time using Arduino, soldering electronics and using a 3D printer. So everything required quite a bit of research and thinking through to find the best option. A big help in this was my supervisor who helped me solve the problems I encountered like a library conflict and answered the questions I had.

This project kinda forced me to familiarize myself with all the facilities I have at my disposal at the school and I think I'll find it easier to use them now when I need or want to.

Overall I wouldn’t say I enjoyed the project, I was constantly stressed because this project was one of 4 other deadlines that required just as much time. I could not really afford to make mistakes or do something over, Time wasn’t on my side.

If this was the only project or one of two I would have enjoyed the process way more than I did now, since I did actually like learning to solder and like parts of the testing process.

In the end I feel like I did everything pretty okay until I had to solder. Everything became a mess and I lost the motivation to work on it even though it was close to the deadline. Next time I’m definitely gonna try to make sure the layout is okay before soldering and give myself more space to work with in my casing to make it all easier on myself. I spent alot of time on this project so it's a bummer that I didn't get it working the way I wanted.

End

Thank you for reading and taking an interest. If you have any ideas on how the concept, execution, or instructable could be improved feel free to give suggestions :)