Week 09 β Input Devices
This week is about input devices that can be connected to microcontrollers to measure real-world phenomena such as motion, temperature, light, sound, pressure, humidity, distance, acceleration, orientation, and electric or magnetic fields.
The goal is to add a sensor to my microcontroller that I built last week and then sense data from the surrounding environment mostly sense data that are related to my final project input and read it.
Assignment Overview
- Probe an input device's analog levels and digital signals
- Measure something: add a sensor to a microcontroller board that you have designed and read it
| Day | Activity | Status |
|---|---|---|
| Thursday | Week Planning π , Research π on input devices for my project | In progress |
| Friday | Holiday | Holiday |
| Monday | Sensor Documentation | Completed |
| Designed Breakout Board for MAX30100 module | Completed | |
| Tuesday | Regional Review π¨βπ«π¬ | Completed |
| Breakout Board Milling π | Completed | |
| Sensor Testing π | Completed | |
| Documentation | Completed | |
| Sensor Calibration and Accuracy π | In progress | |
| Wednesday | Go the Extra Mile - On Final ProjectβοΈ | Pending |
| NuEval - Final Documentation π | Pending | |
| Make instagram Reel π¬π | Pending |
Group Assignment
In this weekβs group assignment, we used input device rotary encoder module to prob and understand its digital signals and we probed joystick module sensor to understand analog levels, and we also understand usage of an oscilloscope and its different functions such as Trigger.
Individual Assignment
This week, I am designed, fabricated and assembled a custom PCB breakout board for MAX30100/30102 (integrated pulse oximeter and heart-rate monitor module).
The MAX30100/30102 is an integrated pulse oximetry and heart-rate monitor module. It includes internal LEDs, photodetectors, optical elements, and low-noise electronics with ambient light rejection. The MAX30100/30102 provides a complete system solution to ease the design-in process for mobile and wearable devices.
In my final project (computational textile) I just don't want to blink the LEDs, I want it to blink based on concept of affective computing - to detect, interpret, process, and respond to human emotions. Thus, MAX30100/30102 becomes one of the sensors for me to derive human emotions.
MAX30100/30102 - Heart-Rate Sensor for Wearable Health
Working Principle
It works on the principle of photo-plethysmo-graphy (PPG). It emits red (660nm) and infrared (940nm) light into the skin and uses a photodetector to measure changes in light absorption caused by blood pulses, which determines heart rate.
IC MAX30100 Datasheet Vs.IC MAX30102 Datasheet
System Block Diagram
MAX30100 is low-power, I2C-based module can measure both heart rate.
Including presence detection, temperature reading, and heart rate, beat by beat!
The module has MAX30100 IC that has one light source as a RED LED and IR LED these LEDs throw light into our skin and a photoreceptor detects and measures the light that has bounces back. Special optics and signal processing β that make sure the measurements are accurate. The RED and IR LED works on 3.3V and the main chip works on 1.8V thus the module has two different voltage regulator. The MAX30100 module consumes really less current , in normal operation it consumes 600uA, in standby mode (not actively measuring) it consumes 0.7uA. The in-build temp sensors which measure IC internal temp to have precise results. It has I2C interface with fixed I2C addresses, 0xAE for writing data and 0xAF for reading data. The MAX30100 collects data faster than a microcontroller can process it thus there is FIFO buffer (First In, First Out) to hold the data and prevent from data loss. It works like temporary storage while the main controller is busy. The buffer can hold up to 16 sets of readings. This gives the microcontroller plenty of time to finish other tasks before coming back for the data.
Power-up / Detecting the sensor
I checked IΒ²C scanner first to confirm the sensor is detected before writing any sensor code, since the sensor module was not having the JST connector, I used jumper wires to connect the sensor to my board.
#include // Include I2C communication library
void setup() {
Serial.begin(115200);
// Start serial communication at 115200 baud rate
pinMode(22, INPUT_PULLUP);
// Enable internal pull-up resistor on SDA pin (GPIO22)
pinMode(23, INPUT_PULLUP);
// Enable internal pull-up resistor on SCL pin (GPIO23)
Wire.begin(22, 23);
// Initialize I2C communication, SDA = GPIO22, SCL = GPIO23
Serial.println("Scanning I2C bus...");
// Loop through all valid I2C addresses (1 to 126)
for (byte addr = 1; addr < 127; addr++) {
Wire.beginTransmission(addr);
// Start communication with device at this address
if (Wire.endTransmission() == 0) {
// endTransmission() returns 0 if device responds (ACK)
Serial.print("Device found at 0x");
Serial.println(addr, HEX);
// Print address in hexadecimal format (common for I2C)
}
}
Serial.println("Done."); // Indicate scan is complete
}
void loop() {
}
This result shows that the sensor module is connected to I2C address 0X57.
Version Check MAX30100 or MAX30102
Next, I did is to check the version or part number of the sensor, whether it is MAX30100 or MAX30102?
AI prompt: "Can you please give code to read the Part ID register over IΒ²C, to confirm which IC it is MAX30100 or MAX30102?"
#include
// Include I2C library for communication
#define MAX3010x_ADDRESS 0x57
// I2C address of MAX3010x sensors (MAX30100, MAX30102, etc.)
#define PART_ID_REG 0xFF
// Register address that stores the chip's Part ID
// Function to read 1 byte from a specific register of a device
uint8_t readRegister(uint8_t address, uint8_t reg) {
Wire.beginTransmission(address);
// Start communication with device at given I2C address
Wire.write(reg);
// Tell the device which register we want to read
Wire.endTransmission(false);
// Send the register address but KEEP connection active
// 'false' = repeated start (important for proper I2C read sequence)
Wire.requestFrom(address, (uint8_t)1);
// Request 1 byte of data from the device
if (Wire.available()) {
// Check if data is available to read
return Wire.read();
// Read and return the byte from the register
}
return 0x00;
// If no data received, return 0 (indicates failure/no response)
}
void setup() {
Serial.begin(115200);
// Start serial communication for debugging output
delay(1000);
// Small delay to allow Serial Monitor to initialize
pinMode(22, INPUT_PULLUP);
// Enable internal pull-up resistor on SDA (GPIO22)
pinMode(23, INPUT_PULLUP);
// Enable internal pull-up resistor on SCL (GPIO23)
Wire.begin(22, 23);
// Initialize I2C with SDA = 22, SCL = 23
Serial.println("Reading MAX3010x Part ID register (0xFF)...");
Serial.println("------------------------------------------");
// Read the Part ID register (0xFF) from sensor at address 0x57
uint8_t partID = readRegister(MAX3010x_ADDRESS, PART_ID_REG);
Serial.print("Raw Part ID value: 0x");
Serial.println(partID, HEX);
// Print the value in hexadecimal format
Serial.println();
// Check which chip is connected based on Part ID
if (partID == 0x11) {
Serial.println("Chip identified: MAX30100");
Serial.println("Note: MAX30100 is discontinued.");
Serial.println("Use library: oxullo/MAX30100lib");
}
else if (partID == 0x15) {
Serial.println("Chip identified: MAX30102");
Serial.println("Note: MAX30102 is the current active chip.");
Serial.println("Use library: SparkFun MAX3010x");
}
else if (partID == 0x00) {
Serial.println("No response from sensor.");
Serial.println("Check wiring β SDA, SCL, VCC, GND.");
}
else {
Serial.print("Unknown Part ID: 0x");
Serial.println(partID, HEX);
Serial.println("Could be MAX30101 or a clone IC.");
}
Serial.println("------------------------------------------");
}
void loop() {
}
From serial monitor, we can see that this module is having Part ID : 0X11, which means it is MAX30100 IC module.
Measuring the Pulse
First, I installed arduino library MAX30100lib by oxullo and then flashed below code to get the readings.
#include
// I2C communication library
#include "MAX30100_PulseOximeter.h"
// Library that handles MAX30100 sensor (heart rate + SpO2)
// ββ Tuning parameters ββββββββββββββββββββββββββββββββββββββββββββ
#define REPORTING_PERIOD_MS 2000
// Print results every 2 seconds
#define FINGER_THRESHOLD 3000
// If IR value is below this β no finger detected
#define LED_CURRENT MAX30100_LED_CURR_11MA
// LED brightness (affects signal strength)
#define SAMPLING_RATE MAX30100_SAMPRATE_100HZ
// Sensor sampling rate (100 samples/sec)
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Create PulseOximeter object
PulseOximeter pox;
// Store last time data was printed
uint32_t tsLastReport = 0;
// Count number of detected heartbeats
uint32_t beatCount = 0;
// Track whether finger is placed on sensor
bool fingerOn = false;
// Callback function: called automatically when a heartbeat is detected
void onBeatDetected() {
beatCount++;
// Increase beat counter
Serial.println(" [BEAT " + String(beatCount) + "]");
// Print beat number
}
void setup() {
Serial.begin(115200);
// Start serial communication
delay(2000);
// Wait 2 seconds β allow sensor to stabilize after power ON
pinMode(22, INPUT_PULLUP);
// Enable pull-up resistor on SDA
pinMode(23, INPUT_PULLUP);
// Enable pull-up resistor on SCL
Wire.begin(22, 23);
// Initialize I2C (SDA=22, SCL=23)
Wire.setClock(100000);
// Set I2C speed to 100kHz (more stable than 400kHz)
Serial.println("Initializing MAX30100...");
if (!pox.begin()) {
// Try to initialize sensor
Serial.println("ERROR: MAX30100 not found. Check wiring.");
while (1);
// Stop program if sensor not detected
}
// ββ Sensor configuration βββββββββββββββββββββββββββββββββββββββ
pox.setIRLedCurrent(LED_CURRENT);
// Set IR LED brightness
pox.setSamplingRate(SAMPLING_RATE);
// Set how fast sensor collects data
pox.setLEDsPulseWidth(MAX30100_SPC_PW_1600US_16BITS);
// Set pulse width β longer pulse = better signal
pox.setHighresModeEnabled(true);
// Enable 16-bit resolution (higher accuracy)
// ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
pox.setOnBeatDetectedCallback(onBeatDetected);
// Register callback β triggers when heartbeat detected
Serial.println("MAX30100 ready. Place finger GENTLY on sensor.");
Serial.println("ββββββββββββββββββββββββββββββββββββββββββββββββ");
}
void loop() {
pox.update();
// VERY IMPORTANT:
// Continuously processes sensor data
// Must run as fast as possible (no delay here)
// ββ Finger detection ββββββββββββββββββββββββββββββββββββββββββ
bool currentlyOn = (pox.getIR() > FINGER_THRESHOLD);
// Check IR value β if high, finger is present
if (currentlyOn != fingerOn) {
// If finger status changed (placed or removed)
fingerOn = currentlyOn;
if (fingerOn) {
Serial.println("Finger detected β warming up (~5 sec)...");
beatCount = 0;
// Reset beat count when finger placed
} else {
Serial.println("Finger removed.");
}
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// ββ Periodic report ββββββββββββββββββββββββββββββββββββββββββ
if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
// Check if 2 seconds have passed
tsLastReport = millis();
// Update last report time
if (!fingerOn) {
Serial.println("No finger detected. Place fingertip on sensor.");
return;
// Skip rest if no finger
}
float bpm = pox.getHeartRate();
// Get calculated heart rate
uint8_t sp = pox.getSpO2();
// Get oxygen saturation %
// Sanity filter β discard clearly wrong readings
if (bpm < 40 || bpm > 200) {
Serial.println("Reading unstable β keep finger still...");
return;
// Ignore unrealistic values
}
Serial.print("Heart rate : ");
Serial.print(bpm, 1);
// Print BPM with 1 decimal
Serial.print(" bpm | SpO2 : ");
Serial.print(sp);
// Print oxygen %
Serial.print(" % | IR raw : ");
Serial.println(pox.getIR());
// Print raw IR signal value
}
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
}
I can see that I was getting random reading on the serial monitor, there was not consistency in the reading and most of them were out of the range of normal human heart beat which is 60 bmp to 90 bpm.
What Went Wrong
As, I see in the serial monitor above the model is giving erratic beats per minute (bpm) readings. The readings are not stable, and the module is turning off randomly.
- Random and not accurate BPM readings
- Unstable readings
- Module turns off randomly
The human hart rate (bpm) range in resting positing is between 60 - 100 bpm, whereas I am getting 35 bpm, 45 bpm, ans sometimes even 0 bpm and 150 bpm.
What I Learned
First learning, pull-up resisters of SDA and SCL shall be pull-up to 3.3V not 1.8V because my controller work on 3.3V so to have stable communication the pull-up shall be to 3.3V. Here, the MAX30100 IC module was having pull-up to 1.8V.
Secund learning, do not use jumper wires to connect sensor module to controller. It creates unstable contacts, and when touching the sensor module it, looses the power or ground and turns-off.
Third, MAX30100 is obsolete and the best and most accurate upgrade in that lineage is MAX30102 IC.
| Feature | π§ͺ MAX30100 | βοΈ MAX30102 β |
|---|---|---|
| Basic Function | Basic SpOβ + heart rate | Improved SpOβ + heart rate sensing |
| Signal Quality | Lower signal quality | More stable and accurate PPG signal |
| Noise & Motion | More noise and motion sensitivity | Better ambient light rejection |
| Power Consumption | Higher power usage | Lower power consumption |
| Accuracy | Less accurate BPM readings | More accurate BPM readings |
| Status | Now considered outdated | Modern and widely used β |
Correction Steps
I removed the resisters from the MAX30100 module, so that I can pull-up in the XIAO-ESP32C6, to narrow down to the rootcause of erratic BPM readings.
Next, my instructor advised to design and make breakout board with 3.3 pull-up resisters connected to SDA, SCL and also INT pins, and also use JST 4P connector to connect the I2C between sensor module and my development board. so by doing this I can correct the pull-up resistors connect of sensor module and also eliminate the jumper wires for de-stable connectivity issue.
For the new design, I made two breakout board one for MAX30100 Heart rate sensor and another for IMU6050 module. Here I added 4.7K pull-up resisters so on to breakout board. Because the MAX30100 module has wrongly connect pull-up resisters to 1.8V instead of 3.3V. thus I removed the 4.7k smd resisters from the MAX30100 module.
Below are the design snaps of making breakout board for sensor.
Then I milled the PCBs for detailed information on PCB milling / production please refer my week 8 documentation.
my instructor Saheen suggested to apply the Kapton Tape to insulate modelβs electronics from disturbance, when placed on to the skin.
Now, I tested again to check if the reading are more accurate and stable or not.
AI prompt: I tried above code, however it is giving erratic reading and sometimes get stuck at one reading.!
The key fixes in this version compared to the previous one:
Wire.setClock(100000) β forces IΒ²C to 100kHz (standard mode). Because the module's noisy pull-ups struggle at 400kHz fast mode, causing corrupted samples that produce wild readings.
pox.update() with no delay() in loop β the oxullo library needs update() called as fast as possible. Any delay() in the loop starves the algorithm of samples and causes it to get stuck.
Finger detection gate β readings below FINGER_THRESHOLD are suppressed so you don't get garbage values when no finger is present. Sanity filter on BPM β anything below 40 or above 200 is silently discarded while the algorithm stabilizes.
Finger placement tip β this is often the real culprit. Rest your fingertip very lightly on the sensor window β the pad of your index finger, not the tip. Do not press hard. Movement and pressure changes are the number one cause of erratic readings with this sensor.
Try Changing #define LED_CURRENT MAX30100_LED_CURR_11MA a higher value like - try MAX30100_LED_CURR_27_1MA or MAX30100_LED_CURR_33_8MA if readings are weak. The options in increasing brightness are:
- MAX30100_LED_CURR_7_6MA
- MAX30100_LED_CURR_11MA
- MAX30100_LED_CURR_11MA
- MAX30100_LED_CURR_14_2MA
- MAX30100_LED_CURR_17_4MA
- MAX30100_LED_CURR_20_8MA
- MAX30100_LED_CURR_24MA
- MAX30100_LED_CURR_27_1MA
- MAX30100_LED_CURR_30_6MA
- MAX30100_LED_CURR_33_8MA
- MAX30100_LED_CURR_37MA
- MAX30100_LED_CURR_40_2MA
- MAX30100_LED_CURR_43_6MA
- MAX30100_LED_CURR_46_8MA
- MAX30100_LED_CURR_50MA
#include // Arduino I2C library β handles SDA/SCL communication
#include "MAX30100_PulseOximeter.h" // oxullo MAX30100 library β provides PulseOximeter class
// ββ Timing constants ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
#define SAMPLE_PERIOD_MS 20 // call pox.update() and sample BPM every 20ms
#define REPORT_PERIOD_MS 1000 // print to Serial Plotter once every 1 second
#define WARMUP_MS 10000 // wait 10 seconds after finger detected before trusting readings
// ββ Smoothing constant ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
#define SMOOTH_FACTOR 0.15 // controls how fast display reacts to BPM changes
// 0.0 = never changes, 1.0 = no smoothing (jumpy)
// 0.15 = gentle smoothing β stable but still responsive
// ββ Beat-based BPM calculation ββββββββββββββββββββββββββββββββββββββββββββββββ
#define MIN_BEAT_INTERVAL_MS 300 // ignore beats faster than 300ms apart (> 200 BPM) β noise rejection
#define MAX_BEAT_INTERVAL_MS 1500 // ignore beats slower than 1500ms apart (< 40 BPM) β noise rejection
#define BEAT_HISTORY_SIZE 8 // number of recent beat intervals to average β more = smoother
// ββ Global objects ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PulseOximeter pox; // main interface object to MAX30100 sensor
// ββ Timing variables ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
uint32_t tsLastSample = 0; // timestamp of last 20ms sample
uint32_t tsLastReport = 0; // timestamp of last 1 second Serial print
uint32_t tsFingerOn = 0; // timestamp of when finger was first placed on sensor
uint32_t tsLastBeat = 0; // timestamp of last valid heartbeat detected
// ββ BPM calculation variables βββββββββββββββββββββββββββββββββββββββββββββββββ
float smoothedBPM = 0; // exponentially smoothed BPM β what gets printed to plotter
float beatIntervals[BEAT_HISTORY_SIZE]; // circular buffer storing last 8 beat-to-beat intervals in ms
uint8_t beatIndex = 0; // current write position in circular buffer (0 to 7)
uint8_t beatsFilled = 0; // how many slots in buffer have valid data (0 to 8)
bool fingerPresent = false; // true when finger is detected on sensor
bool warmedUp = false; // true after 10 second warmup period has passed
// ββ Beat callback β called automatically by library on every detected heartbeat
void onBeatDetected() {
uint32_t now = millis(); // get current time in milliseconds
uint32_t interval = now - tsLastBeat; // calculate time since last beat in milliseconds
if (tsLastBeat == 0) { // first beat ever β no previous timestamp to compare
tsLastBeat = now; // just store timestamp, skip interval calculation
return; // exit early β need at least 2 beats to get an interval
}
if (interval < MIN_BEAT_INTERVAL_MS || interval > MAX_BEAT_INTERVAL_MS) {
// interval is outside valid human heart rate range
// likely noise, motion artifact, or false detection
tsLastBeat = now; // update timestamp but discard this interval
return; // exit early β don't add bad data to buffer
}
beatIntervals[beatIndex] = interval; // store valid interval in circular buffer at current position
beatIndex = (beatIndex + 1) % BEAT_HISTORY_SIZE;
// advance write position β wraps back to 0 after reaching size 8
if (beatsFilled < BEAT_HISTORY_SIZE) { // if buffer not yet completely filled
beatsFilled++; // increment filled count until we reach BEAT_HISTORY_SIZE
}
tsLastBeat = now; // update last beat timestamp for next interval calculation
}
// ββ Calculate BPM from stored beat intervals ββββββββββββββββββββββββββββββββββ
float calculateBPM() {
if (beatsFilled == 0) return 0; // no beats collected yet β return 0 (no reading)
float sum = 0; // will accumulate sum of all valid intervals
for (uint8_t i = 0; i < beatsFilled; i++) {
// loop through all filled slots in buffer
sum += beatIntervals[i]; // add each interval to sum
}
float avgInterval = sum / beatsFilled; // average interval in milliseconds between beats
return 60000.0 / avgInterval; // convert ms interval to BPM
// 60000ms = 1 minute, divide by avg interval = beats per minute
}
void setup() {
Serial.begin(115200); // start Serial at 115200 baud β must match Serial Plotter baud rate
delay(3000); // wait 3 seconds β gives MAX30100 stable power before init attempt
Wire.begin(22, 23); // initialise I2C bus β GPIO22 = SDA, GPIO23 = SCL on XIAO ESP32-C6
Wire.setClock(100000); // set I2C to 100kHz standard mode β reliable for this module
while (!pox.begin()) { // keep retrying until MAX30100 responds β prevents silent failure
Serial.println("MAX30100 not found β retrying..."); // inform user of retry
delay(1000); // wait 1 second between retries
}
pox.setIRLedCurrent(MAX30100_LED_CURR_27_1MA);
// set IR LED current to 27.1mA
// higher = stronger signal through finger tissue
// 27.1mA is optimal balance for most fingers
pox.setOnBeatDetectedCallback(onBeatDetected);
// register onBeatDetected() as beat callback
// library calls this function every time it detects a heartbeat peak
Serial.println("MAX30100 ready β place finger gently on sensor");
}
void loop() {
pox.update(); // MUST be called every loop iteration β no delay() anywhere in loop
// reads new samples from MAX30100 hardware FIFO buffer
// runs peak detection algorithm on each sample
// triggers onBeatDetected() callback when beat found
// any delay() here starves algorithm and causes missed beats
// ββ Finger presence detection ββββββββββββββββββββββββββββββββββββββββββββββ
float rawBPM = pox.getHeartRate(); // get library's raw BPM estimate β used only for finger detection here
bool currentFinger = (rawBPM > 0); // if library returns any BPM > 0, finger is present on sensor
if (currentFinger && !fingerPresent) { // finger just placed β was absent, now present
fingerPresent = true; // update finger state to present
tsFingerOn = millis(); // record time finger was placed β used for warmup countdown
warmedUp = false; // reset warmup flag β need fresh 10 second warmup
beatsFilled = 0; // clear beat history β old data from previous session is invalid
beatIndex = 0; // reset circular buffer write position to start
smoothedBPM = 0; // reset smoothed value β start fresh for new session
tsLastBeat = 0; // reset last beat timestamp β first beat will set this
Serial.println("Finger detected β warming up 10 seconds...");
}
if (!currentFinger && fingerPresent) { // finger just removed β was present, now absent
fingerPresent = false; // update finger state to absent
warmedUp = false; // reset warmup β next placement needs fresh warmup
Serial.println("Finger removed.");
}
// ββ Check warmup completion ββββββββββββββββββββββββββββββββββββββββββββββββ
if (fingerPresent && !warmedUp) { // finger is on but warmup not yet complete
if (millis() - tsFingerOn > WARMUP_MS) {
// check if 10 seconds have passed since finger was placed
warmedUp = true; // warmup complete β readings are now trustworthy
Serial.println("Warmup complete β reading BPM...");
}
}
// ββ Sample and smooth BPM every 20ms ββββββββββββββββββββββββββββββββββββββ
if (millis() - tsLastSample > SAMPLE_PERIOD_MS) {
// check if 20ms has passed since last sample
tsLastSample = millis(); // reset 20ms sample timer
if (fingerPresent && warmedUp) { // only update smoothed value if finger on AND warmed up
float bpm = calculateBPM(); // calculate BPM from our own beat interval buffer
if (bpm > 40 && bpm < 200) { // only accept physiologically valid BPM values
if (smoothedBPM == 0) { // first valid reading after warmup
smoothedBPM = bpm; // initialise smoothed value directly β no history to blend with
} else {
smoothedBPM = smoothedBPM + SMOOTH_FACTOR * (bpm - smoothedBPM);
// exponential moving average formula:
// new smoothed = old smoothed + 0.15 * (new reading - old smoothed)
// blends new reading gently into running average
// reduces effect of single noisy samples on displayed value
}
}
}
}
// ββ Print to Serial Plotter every 1 second ββββββββββββββββββββββββββββββββ
if (millis() - tsLastReport > REPORT_PERIOD_MS) {
// check if 1 second has passed since last print
tsLastReport = millis(); // reset 1 second report timer
if (!fingerPresent) { // no finger on sensor
Serial.println("BPM:0"); // print 0 β tells plotter signal is absent
// NOTE: this will show as a dip on plotter β expected behaviour
} else if (!warmedUp) { // finger present but still in warmup period
uint32_t remaining = (WARMUP_MS - (millis() - tsFingerOn)) / 1000;
// calculate remaining warmup seconds
Serial.print("BPM:0 β warming up, "); // print 0 during warmup
Serial.print(remaining); // print remaining seconds
Serial.println("s remaining..."); // complete the message
} else { // finger present, warmed up, valid reading available
Serial.print("BPM:"); // label for Serial Plotter line
Serial.println(smoothedBPM, 1); // print smoothed BPM with 1 decimal place
// println adds newline β tells plotter this data point is complete
}
}
}
What Went Wrong
I achieved stability in the reading however I am still not getting accurate reading, my module shows 85-92 BPM which is quite high. I have ordered the MAX30102 and I will try reading heart rate aging soon .......
Additionally, I will be adding sound sensor, and IMU sensor as they are related to my final project.
Hero Shot
OLD Designed board
Note: below is for my future reference, I designed SAMD21 developments board but did not provided for milling.
Actually, I started designing a new board with SAMD21E but later my instructor told that as I am not embedding or designing any input on the board so I shall not proceed with it and instead design a breakout board to plug the MAX30100/30102 module.
However, referring SAMD21 datasheet while designing the CKT was good experience and learning about also learned good points from its such as it has flexible peripheral system called SERCOM (Serial Communication) β unlike AVR chips where IΒ²C is fixed to specific pins, the SAMD21 lets you assign IΒ²C (and SPI, UART) to multiple pin combinations.
SDAM21 Datasheet and SAMD21E PIN Details
AI prompt: "Can you please explain me what is REF, ADC, AC, PTC, DAC in column B ?, can you please explain me what is TC/TCC in column E?, can you please explain me what is TCC in column F?, can you please explain me what is COM in column G?, can you please explain me what is AC/GCLK in column H?"
Key Learnings This Week
- Always keep power and ground line fatter then signal lines. To do this go to Board Setup >> Net Class >> Add.
- Remove island after PCB Milling because this islands can become bridges and can create short or unwanted connections.
- I need to try out thinner traves that acts as fuse, I can blow this traces with current and check.
- Explore PID Control -It constantly watches whatβs happening, compares it to what should happen, and nudges the system until reality matches intention.
Downloads & Useful Links
Reflection
To deal with accuracy and stability of sensor's reading, first fix hardware variations such as avoid jumpers wires (loose connections), use connectors and breakout board, use Kapton tape and minimize external disturbances and then do the software optimization such as using averaging or using pi-filters.
Most Important again keep things simple, I tried to add human emotions into my projects and added new sensors like pulse sensor and GSR. nby adding new sensors, I missed the chance to try IMU and Sound sensors which are related to basic functions to my project. I did not follow my week plan properly and unnecessary wasted 2 to 3 days in developing SAMD21E development board. Communicate frequently with Saheen.