DreamLab
Published © Apache-2.0

Health and Activity Assistant Based on nRF5340-DK

Portable electronic assistant capable of monitoring the user’s basic physiological and motion parameters

AdvancedFull instructions providedOver 1 day79
Health and Activity Assistant Based on nRF5340-DK

Things used in this project

Hardware components

nRF5340 Development Kit
Nordic Semiconductor nRF5340 Development Kit
×1
PCBWay MAX30100 Heart-Rate Oximeter Pulse Sensor Pulsesensor Module
×1
PCBWay MPU-6050 Module 3 Axis Gyroscope Accelerometer Module
×1

Software apps and online services

Visual Studio Code Extension for Arduino
Microsoft Visual Studio Code Extension for Arduino
Nordic Semiconductor nRF Connect for Desktop
nRF Connect SDK
Nordic Semiconductor nRF Connect SDK

Story

Read more

Schematics

System Diagram

Code

mpu6050.h

C Header File
#ifndef MPU6050_H
#define MPU6050_H

#include <stdint.h>

#define MPU6050_ADDR 0x68
#define MPU6050_PWR_MGMT_1 0x6B
#define MPU6050_ACCEL_XOUT 0x3B

typedef struct {
    int16_t accel_x;
    int16_t accel_y;
    int16_t accel_z;

    int16_t gyro_x;
    int16_t gyro_y;
    int16_t gyro_z;
} mpu6050_data_t;

int mpu6050_init(void);
int mpu6050_read(mpu6050_data_t *data);

#endif

max30100.c

C/C++
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/printk.h>

#include "max30100.h"

#define I2C_NODE DT_NODELABEL(i2c0)

static const struct device *i2c_dev;

static uint32_t last_peak_time = 0;

int max30100_init(void)
{
    uint8_t config;

    i2c_dev = DEVICE_DT_GET(I2C_NODE);

    if (!device_is_ready(i2c_dev)) {
        return -1;
    }

    config = 0x03;

    i2c_reg_write_byte(i2c_dev,
                       MAX30100_ADDR,
                       MAX30100_MODE_CONF,
                       config);

    config = 0x27;

    i2c_reg_write_byte(i2c_dev,
                       MAX30100_ADDR,
                       MAX30100_SPO2_CONF,
                       config);

    config = 0x24;

    i2c_reg_write_byte(i2c_dev,
                       MAX30100_ADDR,
                       MAX30100_LED_CONF,
                       config);

    return 0;
}

int max30100_read(max30100_data_t *data)
{
    uint8_t buffer[4];

    i2c_burst_read(i2c_dev,
                   MAX30100_ADDR,
                   MAX30100_FIFO_DATA,
                   buffer,
                   4);

    data->ir = (buffer[0] << 8) | buffer[1];
    data->red = (buffer[2] << 8) | buffer[3];

    return 0;
}

uint8_t calculate_bpm(uint16_t ir)
{
    static uint16_t threshold = 50000;

    uint32_t now = k_uptime_get_32();

    if (ir > threshold) {

        uint32_t delta = now - last_peak_time;

        if (delta > 300 && delta < 2000) {

            last_peak_time = now;

            return 60000 / delta;
        }
    }

    return 0;
}

uint8_t calculate_spo2(uint16_t red,
                       uint16_t ir)
{
    if (ir == 0) {
        return 0;
    }

    float ratio = (float)red / (float)ir;

    int spo2 = 110 - (25 * ratio);

    if (spo2 > 100)
        spo2 = 100;

    if (spo2 < 0)
        spo2 = 0;

    return (uint8_t)spo2;
}

mpu6050.c

C/C++
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>

#include "mpu6050.h"

#define I2C_NODE DT_NODELABEL(i2c0)

static const struct device *i2c_dev;

int mpu6050_init(void)
{
    i2c_dev = DEVICE_DT_GET(I2C_NODE);

    if (!device_is_ready(i2c_dev)) {
        return -1;
    }

    i2c_reg_write_byte(i2c_dev,
                       MPU6050_ADDR,
                       MPU6050_PWR_MGMT_1,
                       0x00);

    return 0;
}

int mpu6050_read(mpu6050_data_t *data)
{
    uint8_t buffer[14];

    i2c_burst_read(i2c_dev,
                   MPU6050_ADDR,
                   MPU6050_ACCEL_XOUT,
                   buffer,
                   14);

    data->accel_x = (buffer[0] << 8) | buffer[1];
    data->accel_y = (buffer[2] << 8) | buffer[3];
    data->accel_z = (buffer[4] << 8) | buffer[5];

    data->gyro_x = (buffer[8] << 8) | buffer[9];
    data->gyro_y = (buffer[10] << 8) | buffer[11];
    data->gyro_z = (buffer[12] << 8) | buffer[13];

    return 0;
}

filters.h

C Header File
#ifndef FILTERS_H
#define FILTERS_H

#include <stdint.h>

uint16_t moving_average_filter(uint16_t input);

#endif

filters.c

C/C++
#include "filters.h"

#define FILTER_SIZE 8

uint16_t moving_average_filter(uint16_t input)
{
    static uint16_t samples[FILTER_SIZE];
    static uint8_t index = 0;

    uint32_t sum = 0;

    samples[index++] = input;

    if (index >= FILTER_SIZE)
        index = 0;

    for (int i = 0; i < FILTER_SIZE; i++) {
        sum += samples[i];
    }

    return sum / FILTER_SIZE;
}

activity.c

C/C++
#include <zephyr/sys/printk.h>
#include <stdlib.h>

#include "activity.h"

#define FALL_THRESHOLD 25000

void detect_activity(mpu6050_data_t *imu)
{
    int magnitude = abs(imu->accel_x)
                  + abs(imu->accel_y)
                  + abs(imu->accel_z);

    if (magnitude < 5000) {
        printk("Idle\n");
    }
    else if (magnitude < 15000) {
        printk("Walking\n");
    }
    else {
        printk("Running\n");
    }
}

void detect_fall(mpu6050_data_t *imu)
{
    int magnitude = abs(imu->accel_x)
                  + abs(imu->accel_y)
                  + abs(imu->accel_z);

    if (magnitude > FALL_THRESHOLD) {
        printk("FALL DETECTED!\n");
    }
}

activity.h

C Header File
#ifndef BLE_SERVICE_H
#define BLE_SERVICE_H

#include <stdint.h>

int ble_init(void);
void ble_update_heart_rate(uint8_t bpm);
void ble_update_spo2(uint8_t spo2);
void ble_update_motion(int16_t x,
                       int16_t y,
                       int16_t z);

#endif

activity.c

C/C++
#include <zephyr/sys/printk.h>
#include <stdlib.h>

#include "activity.h"

#define FALL_THRESHOLD 25000

void detect_activity(mpu6050_data_t *imu)
{
    int magnitude = abs(imu->accel_x)
                  + abs(imu->accel_y)
                  + abs(imu->accel_z);

    if (magnitude < 5000) {
        printk("Idle\n");
    }
    else if (magnitude < 15000) {
        printk("Walking\n");
    }
    else {
        printk("Running\n");
    }
}

void detect_fall(mpu6050_data_t *imu)
{
    int magnitude = abs(imu->accel_x)
                  + abs(imu->accel_y)
                  + abs(imu->accel_z);

    if (magnitude > FALL_THRESHOLD) {
        printk("FALL DETECTED!\n");
    }
}

ble_service.h

C Header File
#ifndef BLE_SERVICE_H
#define BLE_SERVICE_H

#include <stdint.h>

int ble_init(void);
void ble_update_heart_rate(uint8_t bpm);
void ble_update_spo2(uint8_t spo2);
void ble_update_motion(int16_t x,
                       int16_t y,
                       int16_t z);

#endif

ble_service.c

C/C++
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/sys/printk.h>

#include "ble_service.h"

static uint8_t heart_rate = 0;
static uint8_t spo2 = 0;

static int16_t accel_x = 0;
static int16_t accel_y = 0;
static int16_t accel_z = 0;

#define BT_UUID_HEALTH_SERVICE_VAL \
    BT_UUID_128_ENCODE(0x12345678,0x1234,0x5678,0x1234,0x56789abcdef0)

#define BT_UUID_HR_CHAR_VAL \
    BT_UUID_128_ENCODE(0x12345678,0x1234,0x5678,0x1234,0x56789abcdef1)

#define BT_UUID_SPO2_CHAR_VAL \
    BT_UUID_128_ENCODE(0x12345678,0x1234,0x5678,0x1234,0x56789abcdef2)

#define BT_UUID_MOTION_CHAR_VAL \
    BT_UUID_128_ENCODE(0x12345678,0x1234,0x5678,0x1234,0x56789abcdef3)

static struct bt_uuid_128 health_service_uuid =
    BT_UUID_INIT_128(BT_UUID_HEALTH_SERVICE_VAL);

static struct bt_uuid_128 hr_char_uuid =
    BT_UUID_INIT_128(BT_UUID_HR_CHAR_VAL);

static struct bt_uuid_128 spo2_char_uuid =
    BT_UUID_INIT_128(BT_UUID_SPO2_CHAR_VAL);

static struct bt_uuid_128 motion_char_uuid =
    BT_UUID_INIT_128(BT_UUID_MOTION_CHAR_VAL);
    
static ssize_t read_hr(struct bt_conn *conn,
                       const struct bt_gatt_attr *attr,
                       void *buf,
                       uint16_t len,
                       uint16_t offset)
{
    return bt_gatt_attr_read(conn,
                             attr,
                             buf,
                             len,
                             offset,
                             &heart_rate,
                             sizeof(heart_rate));
}

static ssize_t read_spo2(struct bt_conn *conn,
                         const struct bt_gatt_attr *attr,
                         void *buf,
                         uint16_t len,
                         uint16_t offset)
{
    return bt_gatt_attr_read(conn,
                             attr,
                             buf,
                             len,
                             offset,
                             &spo2,
                             sizeof(spo2));
}

static ssize_t read_motion(struct bt_conn *conn,
                           const struct bt_gatt_attr *attr,
                           void *buf,
                           uint16_t len,
                           uint16_t offset)
{
    int16_t motion[3] = {
        accel_x,
        accel_y,
        accel_z
    };
    


    return bt_gatt_attr_read(conn,
                             attr,
                             buf,
                             len,
                             offset,
                             motion,
                             sizeof(motion));
}

BT_GATT_SERVICE_DEFINE(health_service,

    BT_GATT_PRIMARY_SERVICE(&health_service_uuid),

    BT_GATT_CHARACTERISTIC(&hr_char_uuid.uuid,
                           BT_GATT_CHRC_READ |
                           BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ,
                           read_hr,
                           NULL,
                           &heart_rate),

    BT_GATT_CHARACTERISTIC(&spo2_char_uuid.uuid,
                           BT_GATT_CHRC_READ |
                           BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ,
                           read_spo2,
                           NULL,
                           &spo2),

    BT_GATT_CHARACTERISTIC(&motion_char_uuid.uuid,
                           BT_GATT_CHRC_READ |
                           BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ,
                           read_motion,
                           NULL,
                           NULL)
);

static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS,
                  (BT_LE_AD_GENERAL |
                   BT_LE_AD_NO_BREDR)),

    BT_DATA(BT_DATA_NAME_COMPLETE,
            CONFIG_BT_DEVICE_NAME,
            sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};

int ble_init(void)
{
    int err;

    err = bt_enable(NULL);

    if (err) {
        printk("BLE initialization failed\n");
        return err;
    }

    printk("Bluetooth initialized\n");

    err = bt_le_adv_start(BT_LE_ADV_CONN,
                          ad,
                          ARRAY_SIZE(ad),
                          NULL,
                          0);

    if (err) {
        printk("Advertising failed\n");
        return err;
    }

    printk("Advertising started\n");

    return 0;
}

void ble_update_heart_rate(uint8_t bpm)
{
    heart_rate = bpm;

    bt_gatt_notify(NULL,
                   &health_service.attrs[1],
                   &heart_rate,
                   sizeof(heart_rate));
}

void ble_update_spo2(uint8_t value)
{
    spo2 = value;

    bt_gatt_notify(NULL,
                   &health_service.attrs[3],
                   &spo2,
                   sizeof(spo2));
}

void ble_update_motion(int16_t ax,
                       int16_t ay,
                       int16_t az)
{
    int16_t motion[3];

    accel_x = ax;
    accel_y = ay;
    accel_z = az;

    motion[0] = accel_x;
    motion[1] = accel_y;
    motion[2] = accel_z;

    bt_gatt_notify(NULL,
                   &health_service.attrs[5],
                   motion,
                   sizeof(motion));
}

main.c

C/C++
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/sys/printk.h>

#include "max30100.h"
#include "mpu6050.h"
#include "filters.h"
#include "activity.h"
#include "ble_service.h"

#define SAMPLE_DELAY_MS 100

static max30100_data_t hr_data;
static mpu6050_data_t imu_data;

int main(void)
{
    int ret;

    printk("Health Assistant Start\n");

    ret = ble_init();
    if (ret) {
        printk("BLE init failed\n");
    }

    ret = max30100_init();
    if (ret) {
        printk("MAX30100 init failed\n");
    }

    ret = mpu6050_init();
    if (ret) {
        printk("MPU6050 init failed\n");
    }

    while (1) {

        max30100_read(&hr_data);

        hr_data.filtered_ir = moving_average_filter(hr_data.ir);

        hr_data.bpm = calculate_bpm(hr_data.filtered_ir);

        hr_data.spo2 = calculate_spo2(hr_data.red,
                                      hr_data.ir);

        mpu6050_read(&imu_data);

        detect_activity(&imu_data);

        detect_fall(&imu_data);

        ble_update_heart_rate(hr_data.bpm);

        ble_update_spo2(hr_data.spo2);

        ble_update_motion(imu_data.accel_x,
                          imu_data.accel_y,
                          imu_data.accel_z);

        printk("BPM: %d  SpO2: %d\n",
               hr_data.bpm,
               hr_data.spo2);

        k_msleep(K_MSEC(100));
    }

    return 0;
}

prj.conf

C/C++
CONFIG_GPIO=y
CONFIG_I2C=y
CONFIG_SERIAL=y
CONFIG_CONSOLE=y
CONFIG_PRINTK=y

CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_DEVICE_NAME="HealthAssistant"
CONFIG_BT_DEVICE_APPEARANCE=833

CONFIG_BT_MAX_CONN=1
CONFIG_BT_MAX_PAIRED=1
CONFIG_BT_SETTINGS=y
CONFIG_SETTINGS=y

CONFIG_HEAP_MEM_POOL_SIZE=16384

CMakeLists.txt

C/C++
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(health_assistant)

target_sources(app PRIVATE
    src/main.c
    src/max30100.c
    src/mpu6050.c
    src/filters.c
    src/activity.c
    src/ble_service.c
)

max30100.h

C Header File
#ifndef MAX30100_H
#define MAX30100_H

#include <stdint.h>

#define MAX30100_ADDR 0x57

#define MAX30100_FIFO_DATA 0x05
#define MAX30100_MODE_CONF 0x06
#define MAX30100_SPO2_CONF 0x07
#define MAX30100_LED_CONF  0x09

typedef struct {
    uint16_t ir;
    uint16_t red;
    uint16_t filtered_ir;
    uint8_t bpm;
    uint8_t spo2;
} max30100_data_t;

int max30100_init(void);
int max30100_read(max30100_data_t *data);
uint8_t calculate_bpm(uint16_t ir);
uint8_t calculate_spo2(uint16_t red,
                       uint16_t ir);

#endif

I2C Scanner Example

C/C++
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/sys/printk.h>

#define I2C_NODE DT_NODELABEL(i2c0)

const struct device *i2c_dev;

int main(void)
{
    int ret;

    printk("I2C Scanner Start\n");

    i2c_dev = DEVICE_DT_GET(I2C_NODE);

    if (!device_is_ready(i2c_dev)) {
        printk("I2C not ready\n");
        return 0;
    }

    while (1) {

        for (uint8_t addr = 1; addr < 127; addr++) {

            ret = i2c_write(i2c_dev,
                             NULL,
                             0,
                             addr);

            if (ret == 0) {
                printk("Device found at 0x%02X\n", addr);
            }
        }

        printk("Scan complete\n\n");

        k_sleep(K_SECONDS(5));
    }

    return 0;
}

activity.h

C Header File
#ifndef ACTIVITY_H
#define ACTIVITY_H

#include "mpu6050.h"

void detect_activity(mpu6050_data_t *imu);
void detect_fall(mpu6050_data_t *imu);

#endif

Credits

DreamLab
9 projects • 12 followers

Comments