Fall Detection Arduino Nano 33 BLE Rev2

Fall Detection Arduino Nano 33 BLE Rev2

Fall Detection Logo
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.

Fall Detection Circuit Diagram

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

  1. Mount the Arduino Nano 33 BLE Rev2 securely inside or on the belt-mounted enclosure.
  2. Connect the vibration motor module input to D5, the active buzzer to D6, and the LED to D7 through a 220 Ω resistor.
  3. Connect the push button between D8 and GND.
  4. Make sure all connections are secure before powering the system.
  5. Position the device securely on the belt so that it does not move excessively during normal operation.
  6. Connect the Arduino to the computer using a USB cable for programming and testing.
Note:

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

  1. Install and open the Arduino IDE.
  2. Open the provided FallDetector_BLE_Arduino_NO_FREEFALL_DISPLAY(1).ino file.
  3. 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

  1. Select Arduino Nano 33 BLE Rev2 as the target board.
  2. Select the correct COM port.
  3. Open the FallDetector_BLE_Arduino_NO_FREEFALL_DISPLAY(1).ino sketch.
  4. Verify that the required libraries are installed.
  5. 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

Arduino / C++

#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

Web / 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

  1. The Arduino initializes the IMU, alert outputs, push button, and BLE communication.
  2. The system advertises itself as FallDetector.
  3. The IMU continuously measures acceleration and gyroscope values.
  4. Acceleration values are converted from g to m/s².
  5. The program calculates the acceleration magnitude.
  6. If the acceleration magnitude remains below 3.5 m/s² for 70 ms, the system confirms a possible fall.
  7. The buzzer, LED, and vibration motor are activated.
  8. The Arduino sends FALL DETECTED through BLE.
  9. The connected monitoring interface receives the alert and sensor data.
  10. The user can cancel the alarm using the physical push button or the BLE CANCEL command.
  11. 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
Common Issue:

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

Circuit at flat position

The image above shows the circuit at flat position.

Tilted Position

Circuit when tilted

The image above shows the circuit when tilted.

Activated Alarm

Circuit when activated

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

Fall Detection Guide — CreateLabz

Accelerometer, Arduino nano 33 ble rev2, Fall detection, Gyroscope, Imu sensor, Iot

Leave a comment

All comments are moderated before being published