A biomedical patient monitoring system is an IoT-based project that records and transmits a patient's vital signs. It helps doctors monitor patients remotely in hospitals or at home. This guide builds a system that tracks three parameters: body temperature (LM35 sensor), heart rate and oxygen saturation SpO2 (MAX30102 sensor), and shows them on an LCD while uploading them to ThingSpeak over Wi-Fi. Blood pressure and ECG are optional extras — they need extra hardware and are not part of this build.
Important: this is a student model for learning, NOT a medical device. Its readings are estimates for demonstration. Never use it to diagnose or treat anyone — always compare with a clinical device, and see a doctor for real health decisions.

| Component | Price / link |
|---|---|
| Arduino UNO R3 DIP | Arduino UNO R3 DIP ATmega328P with USB Cable — Rs 1,200 |
| MAX30102 pulse oximeter sensor | No QKZEE listing — available at the Hall Road shop on request |
| LM35 temperature sensor | No QKZEE listing — available at the Hall Road shop on request |
| Pulse Sensor (optional — see note below) | No QKZEE listing — available at the Hall Road shop on request |
| LCD1602 16×2 display with I2C module | LCD1602 16x2 Display with I2C Module — Rs 660 |
| NodeMCU ESP8266 CH340 WiFi board | NodeMCU ESP8266 CH340 WiFi Board — Rs 590 |
| MB102 breadboard (for testing on the bench) | MB102 Breadboard 830 Points — Rs 250 |
| 40-pin jumper wires | 40-Pin Jumper Wires 30cm — Rs 180 |
About the Pulse Sensor: the parts list includes both a Pulse Sensor and a MAX30102, but both measure heart rate — you only need one. This guide uses the MAX30102 (it also gives SpO2). The Pulse Sensor is listed as an optional alternative: it connects to A1, and its heart-rate number comes from the PulseSensor Playground library. Use the Arduino UNO as the sensor board — the NodeMCU has only one analog pin (A0) and this build needs two analog inputs.
| LM35 pin | Arduino pin |
|---|---|
| OUT | A0 |
| VCC | 5V |
| GND | GND |
| Pulse Sensor pin | Arduino pin |
|---|---|
| OUT (signal) | A1 |
| VCC | 5V |
| GND | GND |
| MAX30102 pin | Arduino pin |
|---|---|
| SDA | A4 |
| SCL | A5 |
| VCC | 3.3V |
| GND | GND |
| LCD pin | Arduino pin |
|---|---|
| SDA | A4 |
| SCL | A5 |
| VCC | 5V |
| GND | GND |
The MAX30102 and the LCD share the same I2C bus (A4/A5) — that is normal for I2C; both devices work together on the same two wires.
| NodeMCU pin | Arduino pin |
|---|---|
| RX (GPIO3) | UNO TX (D1) — through a 1k + 2k voltage divider |
| GND | UNO GND |
The UNO's TX pin is 5V, but the NodeMCU RX pin is 3.3V logic — the divider is required: UNO TX → 1k resistor → NodeMCU RX, and NodeMCU RX → 2k resistor → GND. Join the grounds: without a common GND the serial link will not work. The NodeMCU is powered by its own USB 5V cable.
The old version of this post said "Use 5V regulated supply to power ESP8266". That is wrong and can damage the board. The ESP8266 chip (and the NodeMCU board's logic pins) run on 3.3V. Power the NodeMCU through its own USB port (which has a 3.3V regulator built in), and never connect the UNO's 5V TX pin straight to the NodeMCU's RX pin — always use the divider above. The MAX30102 also takes 3.3V on its VCC pin, not 5V.
Install these two from the Arduino IDE Library Manager (Sketch → Include Library → Manage Libraries):
MAX30105.h and the SpO2 helper)Wire is built into the Arduino IDE — no install needed. On the NodeMCU side, ESP8266WiFi is built into the ESP8266 board package.
/*
Biomedical Patient Monitoring — ARDUINO UNO side
Reads LM35 temperature (A0) and MAX30102 heart rate + SpO2 (I2C),
shows them on the 16x2 LCD, and sends one line to the NodeMCU:
"T:36.5 H:72 S:98". The Pulse Sensor is optional (A1) — see the note
in the parts list; this sketch uses the MAX30102 for heart rate.
Student model — NOT a medical device. Readings are estimates.
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
LiquidCrystal_I2C lcd(0x27, 16, 2); // try 0x3F if the display stays blank
MAX30105 particleSensor;
const int lm35Pin = A0;
uint32_t irBuffer[100];
uint32_t redBuffer[100];
int32_t spo2;
int8_t validSPO2;
int32_t heartRate;
int8_t validHeartRate;
void setup() {
Serial.begin(9600); // to the NodeMCU (through the 1k + 2k divider)
lcd.init();
lcd.backlight();
lcd.print("Patient Monitor");
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
lcd.setCursor(0, 1);
lcd.print("MAX30102 missing");
while (1);
}
particleSensor.setup();
particleSensor.setPulseAmplitudeRed(0x0A);
particleSensor.setPulseAmplitudeGreen(0);
}
void loop() {
// Collect 100 samples from the MAX30102
for (int i = 0; i < 100; i++) {
while (!particleSensor.available()) particleSensor.check();
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample();
}
maxim_heart_rate_and_oxygen_saturation(irBuffer, 100, redBuffer,
&spo2, &validSPO2, &heartRate, &validHeartRate);
float tempC = analogRead(lm35Pin) * 5.0 / 1024.0 * 100.0;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("T:");
lcd.print(tempC, 1);
lcd.print("C HR:");
lcd.print(validHeartRate ? heartRate : 0);
lcd.setCursor(0, 1);
lcd.print("SpO2:");
lcd.print(validSPO2 ? spo2 : 0);
lcd.print("%");
Serial.print("T:");
Serial.print(tempC, 1);
Serial.print(" H:");
Serial.print(validHeartRate ? heartRate : 0);
Serial.print(" S:");
Serial.println(validSPO2 ? spo2 : 0);
}
/*
Biomedical Patient Monitoring — NodeMCU (ESP8266) side
Receives "T:36.5 H:72 S:98" lines from the Arduino on Serial (RX = GPIO3)
and uploads temperature, heart rate and SpO2 to ThingSpeak fields 1-3.
Arduino TX (5V) -> 1k + 2k divider -> NodeMCU RX (3.3V). Common GND.
The NodeMCU is powered by its own USB 5V cable.
*/
#include <ESP8266WiFi.h>
const char* ssid = "Your_SSID";
const char* password = "Your_PASS";
const char* host = "api.thingspeak.com";
String apiKey = "YOUR_API_KEY";
void setup() {
Serial.begin(9600); // USB monitor + the Arduino link share this port
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
}
void loop() {
if (Serial.available()) {
String line = Serial.readStringUntil('\n');
line.trim();
if (line.startsWith("T:")) {
float tempC = line.substring(2, line.indexOf(' ', 2)).toFloat();
int hr = valueAfter(line, "H:");
int spo2 = valueAfter(line, "S:");
sendToThingSpeak(tempC, hr, spo2);
}
}
}
int valueAfter(String line, String key) {
int i = line.indexOf(key);
if (i < 0) return 0;
return line.substring(i + key.length()).toInt();
}
void sendToThingSpeak(float tempC, int hr, int spo2) {
if (WiFi.status() != WL_CONNECTED) return;
WiFiClient client;
if (!client.connect(host, 80)) return;
String url = "/update?api_key=" + apiKey
+ "&field1=" + String(tempC, 1)
+ "&field2=" + String(hr)
+ "&field3=" + String(spo2);
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
client.stop();
}
analogRead(A0) × 5.0 / 1024 × 100.T:36.5 H:72 S:98 — over serial.T:, H: and S:, and sends them to ThingSpeak fields 1, 2 and 3 with a plain HTTP GET request. You can watch all three values on your ThingSpeak channel charts.T:.. H:.. S:.. lines while the system runs.Wire the LM35 (A0), MAX30102 (A4/A5, 3.3V) and LCD (A4/A5, 5V) as in the tables above. Join all grounds together. Fit the 1k + 2k divider between the UNO TX and the NodeMCU RX.
Install the two libraries above, upload the Arduino sketch to the UNO and the NodeMCU sketch to the NodeMCU board. Put your own Wi-Fi name, password and ThingSpeak API key in the NodeMCU sketch first (create a free ThingSpeak account and channel with three fields).
Place a fingertip flat on the MAX30102 sensor and hold still for a few seconds. The LCD should show a temperature near 36–37 °C, a heart rate, and an SpO2 value. Compare the temperature with an ordinary thermometer — if it is off by more than a degree, check the LM35 wiring.
Open your ThingSpeak channel after a few minutes — fields 1, 2 and 3 should plot temperature, heart rate and SpO2 over time. If the charts stay flat, check the NodeMCU Wi-Fi connection and the API key.
T: value should read near room temperature (25–30 °C) with nothing touching the sensor, and rise when you hold it between your fingers.0x3F instead of 0x27) and check SDA → A4, SCL → A5.T:.. H:.. S:.. lines — if it does, the Arduino side is fine and the fault is on the NodeMCU side. Check the divider and the common GND.0x27, some use 0x3F. Also check SDA → A4 and SCL → A5, and that the backlight is on.The old version of this post listed prices that are no longer current (Arduino UNO at Rs 1,800, and the Pulse Sensor missing from the total). At today's live QKZEE prices, the parts with listings cost: Arduino UNO Rs 1,200 + LCD Rs 660 + NodeMCU Rs 590 + breadboard Rs 250 + jumper wires Rs 180 = Rs 2,880. Add the three sensors (LM35, MAX30102, Pulse Sensor) — ask at the Hall Road shop for their current prices, as they are not listed on the site.
| Challenge | Solution |
|---|---|
| Signal noise from sensors | Use proper filtering and shielded cables |
| Wi-Fi disconnection | Add reconnection logic in code |
| Sensor calibration | Compare readings with clinical devices |
You can send data to cloud platforms like ThingSpeak, Firebase or Blynk. Using the NodeMCU, you can monitor patient data on a smartphone dashboard in real time. We use the same ThingSpeak approach in our IoT smart parking guide — the NodeMCU sketch there sends a single number to field1, and this project extends the same pattern to three fields (temperature, heart rate, SpO2).
The Arduino UNO, LCD, NodeMCU, breadboard and jumper wires are available at QKZEE Technologies, Shop 20, 1st Floor, Al-Khalil Center, Hall Road, Lahore, and online at qkzeetech.com with delivery all over Pakistan — see the links above. The LM35, MAX30102 and Pulse Sensor are available at the Hall Road shop on request.
If a sensor is not found, or ThingSpeak never updates, message us — tell us what the Serial Monitor shows and what the LCD displays, and we will help you find the fault.
Is this a real medical device?
No. It is a student model for learning. Its readings are estimates and can be wrong. Never use it to diagnose or treat anyone.
Why does the heart rate show 0 sometimes?
The sensor needs a still, flat fingertip. Movement, cold hands and bright light break the reading. Hold still for a few seconds and it should appear.
Can I use the Pulse Sensor instead of the MAX30102?
Yes — connect it to A1 and use the PulseSensor Playground library for the heart-rate number. You lose SpO2, which only the MAX30102 gives.
Why does the NodeMCU need a voltage divider?
The Arduino TX pin sends 5V, but the NodeMCU RX pin is 3.3V logic. The 1k + 2k divider drops the signal to about 3.3V. Without it the NodeMCU may read garbage or be damaged over time.
Why 3.3V for the MAX30102?
The MAX30102 chip runs on 3.3V logic. Powering it from 5V can damage it. The UNO's 3.3V pin is the right supply.
Can I add blood pressure or ECG?
Yes, with extra hardware — a pressure sensor with a cuff for blood pressure, an AD8232 module for ECG. They are separate additions, not part of this build.
A biomedical patient monitoring system is an innovative and practical IoT-based project suitable for students, electrical engineers and medical technology enthusiasts. It merges electronics, sensors and software to create a working demonstration of remote health monitoring. Build it with the correct 3.3V wiring, test it against a real thermometer and a proper pulse oximeter, and report your measured accuracy honestly in your project report.