Fall Detection Arduino Nano 33 BLE Rev2
Table of Contents ▼
01 Overview
The Fall Detection Monitoring System is a wearable belt-mounted device designed to detect sudden falls using the built-in IMU of an Arduino Nano 33 BLE Rev2. When a fall is detected, the system provides local alerts through a buzzer, LED, and vibration motor, while also sending a notification to a connected smartphone for caregiver or monitoring personnel awareness.
The system is intended for patients or individuals who may require fall monitoring, providing a portable and stable method of detecting sudden movements and alerting others when a possible fall occurs.
Project Use Case
This project is designed for patients, elderly individuals, and other people who may be at risk of falling, with caregivers or monitoring personnel serving as the intended recipients of alerts. It addresses the problem of a person experiencing a fall without someone nearby to immediately notice or respond. The wearable device can be attached to the user's belt and used in environments such as homes, care facilities, or other areas where continuous movement monitoring is needed. When the system detects a possible fall, it activates the buzzer, LED, and vibration motor while sending a FALL DETECTED notification through Bluetooth Low Energy to the connected monitoring device.
02 Hardware and Software Components
Gather everything below before you start. This is your checklist — names, models, and versions only.
Hardware Components
| Component | Description |
|---|---|
| Arduino Nano 33 BLE Rev2 | Main microcontroller and motion-processing unit with a built-in IMU. |
| Built-in IMU | Measures acceleration and gyroscope movement to detect sudden changes associated with a fall. |
| Buzzer | Provides an audible local alert when a fall is detected. |
| LED | Provides a visual indication of the system or fall alert status. |
| Vibration Motor | Provides a tactile alert to the wearer. |
| Push Button | Provides user interaction with the device, including alert-related control. |
| 220 Ohm Resistor | Used to limit current flow through the LED and other components. |
| Breadboard | Used to prototype and test the circuit before building the final product. |
| Jumper Wire (Male to Male) | Used to connect various components on the breadboard. |
Software Tools
| Software | Version / Details |
|---|---|
| Arduino IDE | Used to program and upload the firmware to the Arduino Nano 33 BLE Rev2. |
| Arduino Programming Language | Used to implement motion monitoring, fall detection, alerts, and BLE communication. |
| Bluetooth Low Energy (BLE) | Used for wireless communication between the wearable device and smartphone. |
| Web Bluetooth Monitor | Browser-based interface for monitoring acceleration, gyroscope data, motion state, orientation, and fall alerts. |
03 Application Discussion
Here is what each component does and why it is part of this project.
Arduino Nano 33 BLE Rev2
The Arduino Nano 33 BLE Rev2 serves as the main controller of the fall detection system. It processes the movement data from the built-in IMU, determines whether a possible fall has occurred, controls the alarm outputs, and manages Bluetooth Low Energy (BLE) communication with the monitoring device.
Built-in IMU
The built-in Inertial Measurement Unit (IMU) measures the user's acceleration and rotational movement. The acceleration values are used by the system to calculate the total acceleration magnitude and identify a period of very low acceleration that may indicate free-fall movement.
Vibration Motor
The vibration motor provides a tactile warning to the user when a fall has been confirmed. It is activated together with the buzzer and LED so that the alert can be noticed through physical vibration.
Active Buzzer
The active buzzer provides an audible alarm when a fall is detected. This helps notify people near the monitored person that an alert condition has occurred.
LED
The LED provides a visual indication when the fall alarm is active. It turns on together with the buzzer and vibration motor after a fall has been confirmed.
Push Button
The push button allows the user to cancel an active fall alert. The button is connected to ground and uses the Arduino's internal pull-up configuration. When pressed during a confirmed or active detection state, the alarm is stopped and the system sends a cancellation status.
Bluetooth Low Energy (BLE)
BLE provides wireless communication between the Arduino Nano 33 BLE Rev2 and the connected monitoring device. The system uses BLE to transmit acceleration and gyroscope data and to send alert messages such as "FALL DETECTED" and "FALL CANCELLED." The BLE device is named FallDetector. The system uses separate BLE characteristics for alerts, sensor data, and commands.
Smartphone / Web BLE Monitor
The monitoring interface receives the sensor data and alert messages from the Arduino through BLE. It can display acceleration and gyroscope information, monitor the system status, and notify the monitoring person when a fall has been detected.
Belt-Mounted Enclosure
The device is designed to be worn around the user's belt area. Mounting the device in a stable body position helps provide more consistent movement measurements than a loosely positioned device and keeps the sensing unit in a fixed orientation relative to the body.
04 Hardware Setup
Wire the components to the board using the tables below.
Arduino Nano and Components Pin Connection
| Arduino Nano Pin | Connects To |
|---|---|
| D5 | Vibration motor module input |
| D6 | Active buzzer |
| D7 | LED through 220 Ω resistor |
| D8 | Push button |
| GND | Push button ground |
Components Connections
| Component Pin | Arduino Nano Pin | Description |
|---|---|---|
| Vibration module input | D5 | Activates the vibration alert |
| Active buzzer input | D6 | Activates the audible alarm |
| LED anode | D7 through 220 Ω resistor | Activates the visual alarm |
| LED cathode | GND | LED return connection |
| Push button terminal | D8 | Reads the button state |
| Push button terminal | GND | Provides the button ground connection |
| Built-in IMU | Built into Nano 33 BLE Rev2 | Measures acceleration and gyroscope data |
Assembly Instructions
- Mount the Arduino Nano 33 BLE Rev2 securely inside or on the belt-mounted enclosure.
- Connect the vibration motor module input to D5, the active buzzer to D6, and the LED to D7 through a 220 Ω resistor.
- Connect the push button between D8 and GND.
- Make sure all connections are secure before powering the system.
- Position the device securely on the belt so that it does not move excessively during normal operation.
- Connect the Arduino to the computer using a USB cable for programming and testing.
The built-in IMU of the Arduino Nano 33 BLE Rev2 does not require an external sensor connection. The push button uses the Arduino's internal pull-up, so it is connected between D8 and GND.
05 Software Setup
Follow these steps in order. Do not skip any step.
Step 1 — Prepare the Arduino IDE
- Install and open the Arduino IDE.
- Open the provided
FallDetector_BLE_Arduino_NO_FREEFALL_DISPLAY(1).inofile. - Connect the Arduino Nano 33 BLE Rev2 to the computer using USB.
Step 2 — Board Settings
Use exactly these settings in your IDE. Wrong settings will cause upload failures.
| Setting | Value |
|---|---|
| Board | Arduino Nano 33 BLE Rev2 |
| Port | The COM port assigned to the Arduino board |
| Serial Monitor Baud Rate | 115200 |
Step 3 — Install Libraries
Install the following libraries via the Library Manager:
- Arduino_BMI270_BMM150
- ArduinoBLE
The Arduino_BMI270_BMM150 library provides access to the Nano 33 BLE Rev2's built-in IMU, while ArduinoBLE provides the Bluetooth Low Energy communication functions used by the project.
Step 4 — Upload the Code
- Select Arduino Nano 33 BLE Rev2 as the target board.
- Select the correct COM port.
- Open the
FallDetector_BLE_Arduino_NO_FREEFALL_DISPLAY(1).inosketch. - Verify that the required libraries are installed.
- Click Upload.
After uploading, open the Serial Monitor and set the baud rate to 115200. The system should display the fall detection system ready message and its operating parameters.
06 Code
Copy each file below into the correct location as described in the Software Setup section. Read the Code Breakdown section to understand what each part does.
FallDetector_BLE_Arduino_NO_FREEFALL_DISPLAY(1).ino
#include <Arduino_BMI270_BMM150.h>
#include <ArduinoBLE.h>
#include <math.h>
// ============================================================
// FALL DETECTOR - ARDUINO NANO 33 BLE REV2
// ============================================================
const int VIBRATION_PIN = 5;
const int BUZZER_PIN = 6;
const int LED_PIN = 7;
const int BUTTON_PIN = 8;
const char* DEVICE_NAME = "FallDetector";
BLEService fallService("19b10000-e8f2-537e-4f6c-d104768a1214");
BLEStringCharacteristic alertCharacteristic("19b10001-e8f2-537e-4f6c-d104768a1214", BLERead | BLENotify, 30);
BLECharacteristic dataCharacteristic("19b10002-e8f2-537e-4f6c-d104768a1214", BLERead | BLENotify, 80);
BLEStringCharacteristic commandCharacteristic("19b10003-e8f2-537e-4f6c-d104768a1214", BLEWrite, 20);
float ax, ay, az;
float gx, gy, gz;
float smoothAX, smoothAY, smoothAZ;
float smoothGX, smoothGY, smoothGZ;
const float GRAPH_ALPHA = 0.05f;
bool firstReading = true;
const float FREE_FALL_THRESHOLD = 3.5f; // m/s^2
const unsigned long FREE_FALL_TIME = 70; // ms
bool inFreeFall = false;
unsigned long freeFallStart = 0;
bool fallConfirmed = false;
bool previousButtonState = HIGH;
unsigned long lastButtonPress = 0;
const unsigned long BUTTON_DEBOUNCE = 50;
unsigned long lastDataTime = 0;
const unsigned long DATA_INTERVAL = 20; // ~50 Hz BLE stream
void sendAlert(const char* message) {
alertCharacteristic.writeValue(message);
Serial.print("ALERT,");
Serial.println(message);
}
void stopAlarm() {
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(LED_PIN, LOW);
digitalWrite(VIBRATION_PIN, LOW);
}
void startAlarm() {
digitalWrite(BUZZER_PIN, HIGH);
digitalWrite(LED_PIN, HIGH);
digitalWrite(VIBRATION_PIN, HIGH);
}
void confirmFall() {
inFreeFall = false;
freeFallStart = 0;
fallConfirmed = true;
startAlarm();
sendAlert("FALL DETECTED");
}
void cancelFall() {
inFreeFall = false;
freeFallStart = 0;
fallConfirmed = false;
stopAlarm();
sendAlert("FALL CANCELLED");
}
void checkButton() {
bool currentButtonState = digitalRead(BUTTON_PIN);
if (currentButtonState == LOW && previousButtonState == HIGH && millis() - lastButtonPress > BUTTON_DEBOUNCE) {
lastButtonPress = millis();
if (fallConfirmed || inFreeFall) {
cancelFall();
}
}
previousButtonState = currentButtonState;
}
void checkBluetoothCommand() {
if (!commandCharacteristic.written()) return;
String command = commandCharacteristic.value();
command.trim();
if (command == "CANCEL") {
if (fallConfirmed || inFreeFall) {
cancelFall();
} else {
sendAlert("NO ACTIVE FALL");
}
}
}
void detectFreeFall(float accelerationMagnitude) {
if (fallConfirmed) return;
bool belowThreshold = accelerationMagnitude < FREE_FALL_THRESHOLD;
if (belowThreshold) {
if (!inFreeFall) {
inFreeFall = true;
freeFallStart = millis();
}
if (millis() - freeFallStart >= FREE_FALL_TIME) {
confirmFall();
}
} else {
if (inFreeFall) {
inFreeFall = false;
freeFallStart = 0;
sendAlert("NORMAL");
}
}
}
void setup() {
Serial.begin(115200);
pinMode(VIBRATION_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT_PULLUP);
stopAlarm();
if (!IMU.begin()) {
Serial.println("IMU initialization failed!");
while (true) {}
}
if (!BLE.begin()) {
Serial.println("BLE initialization failed!");
while (true) {}
}
BLE.setLocalName(DEVICE_NAME);
BLE.setDeviceName(DEVICE_NAME);
BLE.setAdvertisedService(fallService);
fallService.addCharacteristic(alertCharacteristic);
fallService.addCharacteristic(dataCharacteristic);
fallService.addCharacteristic(commandCharacteristic);
BLE.addService(fallService);
alertCharacteristic.writeValue("SYSTEM READY");
dataCharacteristic.writeValue("DATA,0,0,0,0,0,0");
BLE.advertise();
Serial.println("FALL DETECTION SYSTEM READY");
}
void loop() {
BLE.poll();
checkBluetoothCommand();
checkButton();
if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()) {
IMU.readAcceleration(ax, ay, az);
IMU.readGyroscope(gx, gy, gz);
ax *= 9.80665f;
ay *= 9.80665f;
az *= 9.80665f;
float accelerationMagnitude = sqrtf(ax * ax + ay * ay + az * az);
detectFreeFall(accelerationMagnitude);
if (firstReading) {
smoothAX = ax; smoothAY = ay; smoothAZ = az;
smoothGX = gx; smoothGY = gy; smoothGZ = gz;
firstReading = false;
}
smoothAX += GRAPH_ALPHA * (ax - smoothAX);
smoothAY += GRAPH_ALPHA * (ay - smoothAY);
smoothAZ += GRAPH_ALPHA * (az - smoothAZ);
smoothGX += GRAPH_ALPHA * (gx - smoothGX);
smoothGY += GRAPH_ALPHA * (gy - smoothGY);
smoothGZ += GRAPH_ALPHA * (gz - smoothGZ);
if (millis() - lastDataTime >= DATA_INTERVAL) {
lastDataTime = millis();
char packet[80];
snprintf(packet, sizeof(packet), "DATA,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f", smoothAX, smoothAY, smoothAZ, smoothGX, smoothGY, smoothGZ);
dataCharacteristic.writeValue(packet);
Serial.println(packet);
}
}
delay(2);
}
FallDetector_Web_Display_HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<title>Fall Detector • BLE Monitor</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;width:100%;height:100%;overflow:hidden}
body{
font-family:Arial,sans-serif;
background:#0f172a;
color:#f8fafc;
}
header{
height:48px;
display:flex;
align-items:center;
justify-content:space-between;
padding:0 12px;
background:#1e293b;
border-bottom:1px solid #334155;
}
h1{font-size:16px;margin:0}
.badge{
padding:5px 10px;
border-radius:15px;
font-size:11px;
font-weight:bold;
}
.disconnected{background:#334155;color:#cbd5e1}
.connected{background:#064e3b;color:#6ee7b7}
.normal{background:#064e3b;color:#6ee7b7}
.warning{background:#78350f;color:#fde68a}
.danger{background:#991b1b;color:white}
#alertBar{
height:46px;
display:flex;
align-items:center;
justify-content:space-between;
padding:0 12px;
border-bottom:1px solid #334155;
font-size:12px;
font-weight:bold;
}
#alertBar.normal{background:#052e2b}
#alertBar.warning{background:#78350f}
#alertBar.danger{
background:#991b1b;
animation:pulse 1s infinite;
}
@keyframes pulse{
50%{opacity:.78}
}
.controls{
height:44px;
display:flex;
align-items:center;
gap:6px;
flex-wrap:wrap;
padding:5px 8px;
background:#0b1220;
border-bottom:1px solid #334155;
}
button,select{
font:11px Arial,sans-serif;
padding:6px 9px;
border:1px solid #475569;
border-radius:5px;
background:#1e293b;
color:#f8fafc;
}
button{cursor:pointer}
button:disabled{opacity:.45;cursor:not-allowed}
#connect{background:#2563eb;border-color:#2563eb}
#cancel{
background:#111827;
border-color:#f8fafc;
}
label{font-size:11px;color:#cbd5e1}
.main{
height:calc(100vh - 138px);
min-height:0;
display:grid;
grid-template-columns:38% 62%;
gap:7px;
padding:7px;
}
.left{
min-height:0;
display:grid;
grid-template-rows:1fr 90px;
gap:7px;
}
.card{
min-height:0;
background:#111827;
border:1px solid #334155;
border-radius:7px;
padding:7px;
}
.title{
height:18px;
display:flex;
justify-content:space-between;
align-items:center;
margin-bottom:4px;
color:#cbd5e1;
font-size:11px;
font-weight:bold;
}
#orientCanvas{
width:100%;
height:calc(100% - 22px);
display:block;
border-radius:5px;
background:#020617;
}
.state{
display:flex;
flex-direction:column;
justify-content:center;
gap:4px;
}
.stateRow{
display:flex;
justify-content:space-between;
font-size:11px;
}
.value{font-weight:bold}
.graphGrid{
min-height:0;
display:grid;
grid-template-columns:1fr 1fr;
grid-template-rows:repeat(3,1fr);
gap:7px;
}
.graphCard{
min-height:0;
display:flex;
flex-direction:column;
}
.graphCanvas{
flex:1;
width:100%;
min-height:0;
display:block;
background:#020617;
border-radius:4px;
}
.note{
color:#94a3b8;
font-size:9px;
padding-left:4px;
}
@media(max-width:850px){
html,body{overflow:auto}
.main{
height:auto;
min-height:calc(100vh - 138px);
grid-template-columns:1fr;
}
.left{grid-template-rows:360px 90px}
.graphGrid{
height:660px;
grid-template-rows:repeat(3,1fr);
}
}
</style>
</head>
<body>
<header>
<h1>Fall Detection • BLE IMU Monitor</h1>
<div id="connection" class="badge disconnected">Disconnected</div>
</header>
<div id="alertBar" class="normal">
<span id="alertText">SYSTEM READY — No fall detected</span>
<button id="cancel" hidden>Cancel Alarm</button>
</div>
<div class="controls">
<button id="connect">Connect BLE</button>
<button id="disconnect" disabled>Disconnect</button>
<button id="pause" disabled>Pause</button>
<button id="clear">Clear</button>
<label>
Window
<select id="window">
<option value="100">100</option>
<option value="250" selected>250</option>
<option value="500">500</option>
<option value="1000">1000</option>
</select>
</label>
<span class="note">
X = Left/Right • Y = Forward/Backward • Z = Up/Down
</span>
</div>
<div class="main">
<div class="left">
<div class="card">
<div class="title">
<span>3D Orientation • Roll Ignored</span>
<span id="angles">Pitch 0° • Yaw 0°</span>
</div>
<canvas id="orientCanvas"></canvas>
</div>
<div class="card state">
<div class="stateRow">
<span>Motion</span>
<span id="motion" class="value">Waiting</span>
</div>
<div class="stateRow">
<span>Acceleration magnitude</span>
<span id="accMag" class="value">0.00 m/s²</span>
</div>
<div class="stateRow">
<span>Last confirmed event</span>
<span id="lastEvent" class="value">None</span>
</div>
</div>
</div>
<div class="graphGrid">
<div class="card graphCard">
<div class="title"><span>Acceleration X</span><span>Left / Right</span></div>
<canvas class="graphCanvas" id="ax"></canvas>
</div>
<div class="card graphCard">
<div class="title"><span>Gyroscope X</span><span>°/s</span></div>
<canvas class="graphCanvas" id="gx"></canvas>
</div>
<div class="card graphCard">
<div class="title"><span>Acceleration Y</span><span>Forward / Backward</span></div>
<canvas class="graphCanvas" id="ay"></canvas>
</div>
<div class="card graphCard">
<div class="title"><span>Gyroscope Y</span><span>°/s</span></div>
<canvas class="graphCanvas" id="gy"></canvas>
</div>
<div class="card graphCard">
<div class="title"><span>Acceleration Z</span><span>Up / Down</span></div>
<canvas class="graphCanvas" id="az"></canvas>
</div>
<div class="card graphCard">
<div class="title"><span>Gyroscope Z</span><span>°/s</span></div>
<canvas class="graphCanvas" id="gz"></canvas>
</div>
</div>
</div>
<script>
// ============================================================
// BLE CONFIG - MUST MATCH ARDUINO
// ============================================================
const DEVICE_NAME = "FallDetector";
const SERVICE_UUID =
"19b10000-e8f2-537e-4f6c-d104768a1214";
const ALERT_UUID =
"19b10001-e8f2-537e-4f6c-d104768a1214";
const DATA_UUID =
"19b10002-e8f2-537e-4f6c-d104768a1214";
const COMMAND_UUID =
"19b10003-e8f2-537e-4f6c-d104768a1214";
// ============================================================
// STATE
// ============================================================
let device = null;
let server = null;
let alertChar = null;
let dataChar = null;
let commandChar = null;
let connected = false;
let paused = false;
let sampleCount = 0;
let maxSamples = 250;
let pitch = 0;
let yaw = 0;
let lastTime = null;
const axes = ["ax","ay","az","gx","gy","gz"];
const data = {
ax:[],
ay:[],
az:[],
gx:[],
gy:[],
gz:[]
};
// ============================================================
// DOM
// ============================================================
const connectionEl = document.getElementById("connection");
const alertBar = document.getElementById("alertBar");
const alertText = document.getElementById("alertText");
const connectBtn = document.getElementById("connect");
const disconnectBtn = document.getElementById("disconnect");
const pauseBtn = document.getElementById("pause");
const clearBtn = document.getElementById("clear");
const cancelBtn = document.getElementById("cancel");
const windowSelect = document.getElementById("window");
const anglesEl = document.getElementById("angles");
const motionEl = document.getElementById("motion");
const accMagEl = document.getElementById("accMag");
const lastEventEl = document.getElementById("lastEvent");
const decoder = new TextDecoder();
const encoder = new TextEncoder();
// ============================================================
// UI
// ============================================================
function setConnection(isConnected) {
connected = isConnected;
connectionEl.textContent =
isConnected ? "Connected" : "Disconnected";
connectionEl.className =
"badge " + (isConnected ? "connected" : "disconnected");
connectBtn.disabled = isConnected;
disconnectBtn.disabled = !isConnected;
pauseBtn.disabled = !isConnected;
}
function setAlert(type, message) {
alertBar.className = type;
alertText.textContent = message;
cancelBtn.hidden = type === "normal";
if (type !== "normal") {
lastEventEl.textContent = message;
}
}
function setMotion(accMag) {
accMagEl.textContent =
accMag.toFixed(2) + " m/s²";
if (accMag < 2.5) {
motionEl.textContent = "FREE-FALL RANGE";
} else if (accMag > 20) {
motionEl.textContent = "HIGH MOTION";
} else {
motionEl.textContent = "Normal";
}
}
// ============================================================
// NOTIFICATION
// ============================================================
async function enableNotifications() {
if (!("Notification" in window)) {
return;
}
try {
if (Notification.permission === "default") {
await Notification.requestPermission();
}
} catch (e) {
console.log("Notification permission:", e);
}
}
function phoneNotification(message) {
if (
!("Notification" in window) ||
Notification.permission !== "granted"
) {
return;
}
try {
new Notification("Fall Detector", {
body: message,
tag: "fall-detector"
});
} catch (e) {
console.log("Notification error:", e);
}
}
function phoneVibration() {
if ("vibrate" in navigator) {
navigator.vibrate([300,150,300,150,600]);
}
}
// ============================================================
// BLE CONNECT
// ============================================================
async function connectBLE() {
if (!("bluetooth" in navigator)) {
alert(
"Web Bluetooth is not available in this browser. " +
"Use a supported Chromium-based browser."
);
return;
}
try {
await enableNotifications();
device = await navigator.bluetooth.requestDevice({
filters: [
{ name: DEVICE_NAME }
],
optionalServices: [SERVICE_UUID]
});
device.addEventListener(
"gattserverdisconnected",
handleDisconnect
);
server = await device.gatt.connect();
const service =
await server.getPrimaryService(SERVICE_UUID);
alertChar =
await service.getCharacteristic(ALERT_UUID);
dataChar =
await service.getCharacteristic(DATA_UUID);
commandChar =
await service.getCharacteristic(COMMAND_UUID);
// Subscribe to notifications.
await alertChar.startNotifications();
await dataChar.startNotifications();
alertChar.addEventListener(
"characteristicvaluechanged",
handleAlert
);
dataChar.addEventListener(
"characteristicvaluechanged",
handleData
);
// Read current states.
try {
const currentAlert =
await alertChar.readValue();
handleAlert({ target:{ value:currentAlert } });
} catch (e) {}
try {
const currentData =
await dataChar.readValue();
handleData({ target:{ value:currentData } });
} catch (e) {}
setConnection(true);
setAlert("normal", "CONNECTED — Monitoring");
} catch (e) {
console.error(e);
setConnection(false);
setAlert(
"warning",
"Connection failed — " + e.message
);
}
}
// ============================================================
// BLE DISCONNECT
// ============================================================
async function disconnectBLE() {
try {
if (device && device.gatt && device.gatt.connected) {
device.gatt.disconnect();
}
} catch (e) {
console.error(e);
}
cleanupBLE();
}
function handleDisconnect() {
cleanupBLE();
setAlert("normal", "DISCONNECTED");
}
function cleanupBLE() {
connected = false;
alertChar = null;
dataChar = null;
commandChar = null;
server = null;
setConnection(false);
}
// ============================================================
// BLE DATA
// ============================================================
function handleData(event) {
const text = decoder.decode(event.target.value);
// DATA,ax,ay,az,gx,gy,gz
if (!text.startsWith("DATA,")) {
return;
}
const parts = text.trim().split(",");
if (parts.length !== 7) {
return;
}
const values = parts
.slice(1)
.map(Number);
if (values.some(v => !Number.isFinite(v))) {
return;
}
const [ax,ay,az,gx,gy,gz] = values;
// The graph/3D display can be paused without stopping BLE.
if (!paused) {
pushData("ax",ax);
pushData("ay",ay);
pushData("az",az);
pushData("gx",gx);
pushData("gy",gy);
pushData("gz",gz);
updateOrientation(ax,ay,az,gx,gy,gz);
}
setMotion(
Math.sqrt(
ax*ax + ay*ay + az*az
)
);
sampleCount++;
}
// ============================================================
// BLE ALERTS
// ============================================================
function handleAlert(event) {
const text =
decoder.decode(event.target.value).trim();
switch (text) {
case "SYSTEM READY":
setAlert("normal","SYSTEM READY — No fall detected");
break;
case "FREE FALL":
// Internal detection stage only; do not display it.
break;
case "NORMAL":
setAlert("normal", "MONITORING — No confirmed fall");
break;
case "FALL DETECTED":
setAlert("danger",
"FALL DETECTED — CHECK THE PATIENT");
phoneNotification("Fall detected. Check the monitored person.");
phoneVibration();
break;
case "FALL CANCELLED":
setAlert("normal",
"FALL CANCELLED — User pressed cancel");
break;
case "NO ACTIVE FALL":
setAlert("normal","No active fall");
break;
default:
setAlert("normal",text || "Normal");
break;
}
}
// ============================================================
// SEND COMMAND TO ARDUINO
// ============================================================
async function sendCommand(command) {
if (!commandChar) {
return;
}
const bytes = encoder.encode(command);
try {
if (
typeof commandChar.writeValueWithResponse === "function"
) {
await commandChar.writeValueWithResponse(bytes);
} else {
await commandChar.writeValue(bytes);
}
} catch (e) {
console.error("BLE command error:", e);
}
}
// ============================================================
// GRAPH DATA
// ============================================================
function pushData(axis,value) {
data[axis].push(value);
while (data[axis].length > maxSamples) {
data[axis].shift();
}
}
function clearGraphs() {
axes.forEach(axis => {
data[axis].length = 0;
});
sampleCount = 0;
drawGraphs();
}
// ============================================================
// PAUSE / PLAY
// ============================================================
function togglePause() {
paused = !paused;
pauseBtn.textContent =
paused ? "Play" : "Pause";
}
// ============================================================
// ORIENTATION
// ============================================================
//
// Roll is intentionally ignored.
//
// Pitch:
// calculated from accelerometer.
//
// Yaw:
// integrated from Z gyro.
// Yaw will slowly drift over time because a gyro alone
// cannot provide an absolute yaw reference.
//
// For your belt mounting:
// X = Left / Right
// Y = Forward / Backward
// Z = Up / Down
//
function updateOrientation(ax,ay,az,gx,gy,gz) {
const now = performance.now();
if (lastTime === null) {
lastTime = now;
return;
}
let dt = (now - lastTime) / 1000;
lastTime = now;
// Keep unexpected browser timing gaps from creating
// a huge yaw jump.
if (dt < 0 || dt > 0.2) {
dt = 0.02;
}
const accPitch =
Math.atan2(
-ax,
Math.sqrt(ay*ay + az*az)
) * 180 / Math.PI;
// Simple complementary filter for pitch.
pitch =
0.98 * (pitch + gy * dt) +
0.02 * accPitch;
// Yaw from gyro only. This is intentionally simple.
yaw += gz * dt;
while (yaw > 180) yaw -= 360;
while (yaw < -180) yaw += 360;
anglesEl.textContent =
"Pitch " + pitch.toFixed(1) +
"° • Yaw " + yaw.toFixed(1) + "°";
drawOrientation();
}
// ============================================================
// RESET ORIENTATION
// ============================================================
function resetOrientation() {
pitch = 0;
yaw = 0;
lastTime = null;
anglesEl.textContent =
"Pitch 0° • Yaw 0°";
drawOrientation();
}
// ============================================================
// 3D CANVAS
// ============================================================
function drawOrientation() {
const canvas = document.getElementById("orientCanvas");
const ctx = canvas.getContext("2d");
resizeCanvas(canvas);
const w = canvas.width;
const h = canvas.height;
ctx.clearRect(0,0,w,h);
// Grid
ctx.strokeStyle = "#1e293b";
ctx.lineWidth = 1;
for (let x=0; x<w; x+=30) {
ctx.beginPath();
ctx.moveTo(x,0);
ctx.lineTo(x,h);
ctx.stroke();
}
for (let y=0; y<h; y+=30) {
ctx.beginPath();
ctx.moveTo(0,y);
ctx.lineTo(w,y);
ctx.stroke();
}
// Convert degrees to radians.
const py = pitch * Math.PI / 180;
const ya = yaw * Math.PI / 180;
// Device dimensions.
const sx = Math.min(w,h) * 0.46;
const sy = Math.min(w,h) * 0.18;
// Three axes in screen space.
// Roll is deliberately not used.
const forwardX =
Math.cos(ya) * sx;
const forwardY =
-Math.sin(py) * sy -
Math.sin(ya) * sx * 0.18;
const sideX =
-Math.sin(ya) * sx * 0.55;
const sideY =
Math.cos(ya) * sy * 0.55;
const cx = w/2;
const cy = h/2;
// Rectangle corners projected as a simple 3D-like plate.
const p1 = {x:cx-forwardX/2-sideX/2, y:cy-forwardY/2-sideY/2};
const p2 = {x:cx+forwardX/2-sideX/2, y:cy+forwardY/2-sideY/2};
const p3 = {x:cx+forwardX/2+sideX/2, y:cy+forwardY/2+sideY/2};
const p4 = {x:cx-forwardX/2+sideX/2, y:cy-forwardY/2+sideY/2};
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.lineTo(p3.x,p3.y);
ctx.lineTo(p4.x,p4.y);
ctx.closePath();
ctx.fillStyle = "#2563eb";
ctx.fill();
ctx.strokeStyle = "#93c5fd";
ctx.lineWidth = 2;
ctx.stroke();
// Forward arrow.
const ax1 = cx;
const ay1 = cy;
const ax2 = cx + Math.cos(ya) * sx * 0.35;
const ay2 = cy - Math.sin(py) * sy * 0.35
- Math.sin(ya) * sx * 0.18;
ctx.beginPath();
ctx.moveTo(ax1,ay1);
ctx.lineTo(ax2,ay2);
ctx.strokeStyle = "#fff";
ctx.lineWidth = 3;
ctx.stroke();
// Arrow head.
const a = Math.atan2(ay2-ay1,ax2-ax1);
ctx.beginPath();
ctx.moveTo(ax2,ay2);
ctx.lineTo(
ax2 - 12*Math.cos(a-0.5),
ay2 - 12*Math.sin(a-0.5)
);
ctx.lineTo(
ax2 - 12*Math.cos(a+0.5),
ay2 - 12*Math.sin(a+0.5)
);
ctx.closePath();
ctx.fillStyle = "#fff";
ctx.fill();
// Labels.
ctx.fillStyle = "#cbd5e1";
ctx.font = "12px Arial";
ctx.fillText(
"X: Left / Right",
10, 18
);
ctx.fillText(
"Y: Forward / Back",
10, 36
);
ctx.fillText(
"Z: Up / Down",
10, 54
);
ctx.fillText(
"Roll ignored",
10, h - 12
);
}
// ============================================================
// 2D GRAPHS
// ============================================================
function drawGraphs() {
const ranges = {
ax: 15,
ay: 15,
az: 15,
gx: 250,
gy: 250,
gz: 250
};
axes.forEach(axis => {
const canvas = document.getElementById(axis);
const ctx = canvas.getContext("2d");
resizeCanvas(canvas);
const w = canvas.width;
const h = canvas.height;
ctx.clearRect(0,0,w,h);
// Background grid
ctx.strokeStyle = "#1e293b";
ctx.lineWidth = 1;
for (let i=1; i<4; i++) {
const y = h*i/4;
ctx.beginPath();
ctx.moveTo(0,y);
ctx.lineTo(w,y);
ctx.stroke();
}
// Zero line
ctx.strokeStyle = "#475569";
const zeroY = h/2;
ctx.beginPath();
ctx.moveTo(0,zeroY);
ctx.lineTo(w,zeroY);
ctx.stroke();
// Plot
const arr = data[axis];
if (arr.length < 2) {
return;
}
const range = ranges[axis];
ctx.strokeStyle = "#38bdf8";
ctx.lineWidth = 1.5;
ctx.beginPath();
arr.forEach((value,i) => {
const x =
i * w / (maxSamples - 1);
const clamped =
Math.max(-range,Math.min(range,value));
const y =
h/2 -
(clamped/range) * (h/2 - 4);
if (i === 0) {
ctx.moveTo(x,y);
} else {
ctx.lineTo(x,y);
}
});
ctx.stroke();
// Scale text
ctx.fillStyle = "#94a3b8";
ctx.font = "9px Arial";
ctx.fillText(
"+" + range,
3, 10
);
ctx.fillText(
"0",
3, h/2 - 2
);
ctx.fillText(
"-" + range,
3, h - 3
);
});
}
// ============================================================
// CANVAS RESIZE
// ============================================================
function resizeCanvas(canvas) {
const dpr = Math.min(
window.devicePixelRatio || 1,
2
);
const rect = canvas.getBoundingClientRect();
const w = Math.max(
1,
Math.floor(rect.width * dpr)
);
const h = Math.max(
1,
Math.floor(rect.height * dpr)
);
if (
canvas.width !== w ||
canvas.height !== h
) {
canvas.width = w;
canvas.height = h;
}
}
// ============================================================
// RESET EVERYTHING
// ============================================================
function resetPage() {
axes.forEach(axis => {
data[axis].length = 0;
});
pitch = 0;
yaw = 0;
lastTime = null;
sampleCount = 0;
anglesEl.textContent =
"Pitch 0° • Yaw 0°";
motionEl.textContent = "Waiting";
accMagEl.textContent = "0.00 m/s²";
lastEventEl.textContent = "None";
drawGraphs();
drawOrientation();
}
// ============================================================
// EVENTS
// ============================================================
connectBtn.addEventListener(
"click",
connectBLE
);
disconnectBtn.addEventListener(
"click",
disconnectBLE
);
pauseBtn.addEventListener(
"click",
togglePause
);
clearBtn.addEventListener(
"click",
clearGraphs
);
cancelBtn.addEventListener(
"click",
async () => {
await sendCommand("CANCEL");
}
);
windowSelect.addEventListener(
"change",
() => {
maxSamples =
Number(windowSelect.value);
axes.forEach(axis => {
while (data[axis].length > maxSamples) {
data[axis].shift();
}
});
drawGraphs();
}
);
window.addEventListener(
"resize",
() => {
drawGraphs();
drawOrientation();
}
);
// ============================================================
// DRAW LOOP
// ============================================================
function animationLoop() {
drawGraphs();
requestAnimationFrame(animationLoop);
}
resetPage();
setConnection(false);
animationLoop();
</script>
</body>
</html>
Download the complete project repository at the link below:
https://github.com/abdurakibhadjula/Fall-Detection-Monitoring-System
07 Code Breakdown
Here is what each part of the code does. Read this after uploading.
Libraries
| Library | Purpose |
|---|---|
| <Arduino_BMI270_BMM150.h> | Provides access to the built-in IMU of the Arduino Nano 33 BLE Rev2. |
| <ArduinoBLE.h> | Provides Bluetooth Low Energy functionality for wireless communication. |
| <math.h> | Provides mathematical functions such as sqrtf() used to calculate acceleration magnitude. |
Key Functions
sendAlert()
Sends an alert message through the BLE alert characteristic and also prints the alert to the Serial Monitor.
startAlarm()
Turns on the buzzer, LED, and vibration motor when a fall is confirmed.
stopAlarm()
Turns off all three local alert devices.
confirmFall()
Changes the system to the confirmed-fall state, activates the local alarms, and sends FALL DETECTED through BLE.
cancelFall()
Cancels the current fall alert, stops the alarm outputs, and sends FALL CANCELLED.
checkButton()
Reads the push button and allows the user to cancel a confirmed or active fall detection.
checkBluetoothCommand()
Checks whether a command has been received through BLE. The CANCEL command can be used by the monitoring interface to cancel an active alert.
detectFreeFall()
Checks the acceleration magnitude against the free-fall threshold. A fall is confirmed when the acceleration remains below 3.5 m/s² for at least 70 ms.
setup()
Initializes the Serial connection, output pins, push button, IMU, BLE service, and BLE characteristics before starting the BLE advertisement.
loop()
Continuously handles BLE communication, button input, IMU readings, fall detection, sensor-data smoothing, and BLE data transmission.
General Program Workflow
- The Arduino initializes the IMU, alert outputs, push button, and BLE communication.
- The system advertises itself as FallDetector.
- The IMU continuously measures acceleration and gyroscope values.
- Acceleration values are converted from g to m/s².
- The program calculates the acceleration magnitude.
- If the acceleration magnitude remains below 3.5 m/s² for 70 ms, the system confirms a possible fall.
- The buzzer, LED, and vibration motor are activated.
- The Arduino sends FALL DETECTED through BLE.
- The connected monitoring interface receives the alert and sensor data.
- The user can cancel the alarm using the physical push button or the BLE CANCEL command.
- Sensor data continues to be transmitted at approximately 50 Hz.
08 Testing and Calibration
After uploading, verify each of the following to confirm the system is working correctly.
Power and Initialization Test
- Connect the Arduino Nano 33 BLE Rev2 to power.
- Open the Serial Monitor at 115200 baud.
- Verify that the system displays FALL DETECTION SYSTEM READY.
- Verify that the IMU initializes without displaying IMU initialization failed!.
- Verify that BLE initializes without displaying BLE initialization failed!.
BLE Communication Test
- Open the BLE monitoring interface using a supported Chromium-based browser.
- Search for the BLE device named FallDetector.
- Connect to the device.
- Verify that sensor data is received in the expected format: DATA,ax,ay,az,gx,gy,gz
Alert Output Test
- Verify that the buzzer, LED, and vibration motor remain off during normal operation.
- Perform controlled movement tests.
- When the programmed fall-detection condition is reached, verify that the buzzer, LED, and vibration motor activate.
- Verify that the BLE monitor displays the fall alert.
Push Button Test
- Trigger the fall alert.
- Press the push button.
- Verify that the buzzer, LED, and vibration motor stop.
- Verify that the system sends FALL CANCELLED.
Calibration Parameters
The current prototype uses the following programmed detection parameters:
| Parameter | Value |
|---|---|
| Free-fall acceleration threshold | 3.5 m/s² |
| Free-fall confirmation time | 70 ms |
| Graph smoothing alpha | 0.05 |
| BLE data interval | 20 ms |
| Approximate data rate | 50 Hz |
| Button debounce time | 50 ms |
If the system does not detect a fall, check the IMU initialization, board selection, wiring of alert components, BLE connection, and programmed threshold values. The current prototype detects a fall specifically through the low-acceleration condition, so ordinary sudden movement alone will not trigger the alarm.
09 System Demonstration
The images and video below show the working system in action.
Flat Position
The image above shows the circuit at flat position.
Tilted Position
The image above shows the circuit when tilted.
Activated Alarm
The image above shows the circuit when activated.
Video Demonstration
10 Conclusion
The Fall Detection Monitoring System demonstrates a wearable approach to detecting possible falls using the built-in IMU of the Arduino Nano 33 BLE Rev2. The system continuously monitors acceleration and uses a programmed free-fall condition to identify a possible fall event. The use of BLE allows the system to provide remote monitoring through a connected device while maintaining local alert mechanisms for the wearer and people nearby.
Possible Improvements and Future Enhancements
- Improve the fall-detection algorithm by combining free-fall, acceleration, orientation, and post-fall inactivity instead of relying only on the free-fall condition.
- Add additional filtering and calibration procedures to reduce false-positive and false-negative detections.
- Improve the wearable enclosure for comfort, durability, and secure belt mounting.
- Add a dedicated mobile application for easier caregiver monitoring and notifications.
- Add event history or logging so previous fall events can be reviewed.
- Add battery monitoring and low-battery warnings for portable operation.
- Conduct more extensive testing using different movement patterns and controlled fall scenarios.
11 References
- Arduino. Arduino Nano 33 BLE Rev2 documentation and board information.
- Arduino. ArduinoBLE library documentation.
- Arduino. Arduino_BMI270_BMM150 library documentation.
- Project source code: FallDetector_BLE_Arduino_NO_FREEFALL_DISPLAY(1).ino.
- Project monitoring interface: Fall Detector • BLE Monitor.
12 Project Authors
- Chris Alexander R. Asequia
- Abdu-Rakib A. Hadjula
