Sung Gone Kim
Created April 12, 2019

micro:bit wearable sleep tracker and sonification Project

Listen to your sleep quality. You can hear what your sleep sound by monitoring key parameters using 2 micro:bit processors.

micro:bit wearable sleep tracker and sonification Project

Things used in this project

Hardware components

BBC micro:bit board
BBC micro:bit board
You will need two micro:bit in order to make a wearable device.
×2
SparkFun Particle Sensor Breakout - MAX30105
SparkFun Particle Sensor Breakout - MAX30105
This pulse oximeter sensor read oxygen saturation of blood. I have tested several MAX30105 sensors from different manufacturers but they all work the same.
×1
Edge Connector
This makes wiring much easier.
×2
Breadboard (generic)
Breadboard (generic)
To connect devices without soldering.
×1
SparkFun Sound Detector (with Headers)
SparkFun Sound Detector (with Headers)
This sensor comes with a small microphone unit. It will detect an amplitude change of the environment noise and snore.
×1
midi to usb cable
send data from micro:bit to Puredata
×1
midi connector
×1
Battery Holder, AA x 2
Battery Holder, AA x 2
×1

Software apps and online services

Pure Data
Mu editor
This Mu editor helps you to code with Micropython for micro:bit. You can flash your code.

Story

Read more

Custom parts and enclosures

Puredata patch

This is a Puredata patch of this project.

Schematics

1. microbit wrist band

MAX30105 sensor might look slightly different from the picture, but they all work the same. I have tested both products.

2. microbit connected to PC

If you use an edge connector, it may look slightly different.

Code

1. microbit wrist band

MicroPython
This is code for microbit which is used for wrist band. This read data from in-built accelerometer and MAX30105 sensor. Then, this send the value to another microbit using in-built radio module. Basic code for reading data comes from : https://github.com/flohwie/ubit-MAX30105
from microbit import i2c, sleep, display, Image, accelerometer
from ustruct import unpack
import radio
import math

radio.on()  # activate radio on microbit

class ParticleSensor(object):  # class for MAX30105 sensor reading
    def __init__(self, HEX_ADDRESS):  # init function
        self._address = HEX_ADDRESS
        self._led_mode = None
        self._pulse_width_set = None
        try:
            i2c.read(self._address, 1)
        except OSError as error:
            raise SystemExit(error)
        else:
            print("Found MAX30105 ParticleSensor: [%s]" % hex(self._address))

    def i2c_read_register(self, REGISTER, n_bytes=1):
        i2c.write(self._address, bytearray([REGISTER]))
        return i2c.read(self._address, n_bytes)

    def i2c_set_register(self, REGISTER, VALUE):
        i2c.write(self._address, bytearray([REGISTER, VALUE]))
        return

    def set_bitMask(self, REGISTER, MASK, NEW_VALUES):
        newCONTENTS = (ord(self.i2c_read_register(REGISTER)) & MASK) | NEW_VALUES
        self.i2c_set_register(REGISTER, newCONTENTS)
        return

    def setup_sensor(self, LED_MODE=2, LED_POWER=0x1F, PULSE_WIDTH=0):
        self.set_bitMask(0x09, 0xBF, 0x40)
        sleep(1000)
        # 3: 69 (15-bit), 2: 118 (16-bit), 1: 215 (17-bit), 0: 411 (18-bit)          
        self.set_bitMask(0x0A, 0xFC, PULSE_WIDTH)
        self._pulse_width_set = PULSE_WIDTH

        if LED_MODE not in [1, 2, 3]:
            raise ValueError('wrong LED mode:{0}!'.format(LED_MODE))
        elif LED_MODE == 1:
            self.set_bitMask(0x09, 0xF8, 0x02)
            self.i2c_set_register(0x0C, LED_POWER)
        elif LED_MODE == 2:
            self.set_bitMask(0x09, 0xF8, 0x03)
            self.i2c_set_register(0x0C, LED_POWER)
            self.i2c_set_register(0x0D, LED_POWER)
        elif LED_MODE == 3:
            self.set_bitMask(0x09, 0xF8, 0x07)
            self.i2c_set_register(0x0C, LED_POWER)
            self.i2c_set_register(0x0D, LED_POWER)
            self.i2c_set_register(0x0E, LED_POWER)
            self.i2c_set_register(0x11, 0b00100001)
            self.i2c_set_register(0x12, 0b00000011)
        self._led_mode = LED_MODE

        self.set_bitMask(0x0A, 0xE3, 0x00)  # sampl. rate: 50
        # 50: 0x00, 100: 0x04, 200: 0x08, 400: 0x0C,
        # 800: 0x10, 1000: 0x14, 1600: 0x18, 3200: 0x1C

        self.set_bitMask(0x0A, 0x9F, 0x00)  # ADC range: 2048
        # 2048: 0x00, 4096: 0x20, 8192: 0x40, 16384: 0x60

        self.set_bitMask(0x08, ~0b11100000, 0x00)  # FIFO sample avg: (no)
        # 1: 0x00, 2: 0x20, 4: 0x40, 8: 0x60, 16: 0x80, 32: 0xA0

        self.set_bitMask(0x08, 0xEF, 0x10)  # FIFO rollover: enable
        # 0x00/0x01: dis-/enable

        self.i2c_set_register(0x04, 0)
        self.i2c_set_register(0x05, 0)
        self.i2c_set_register(0x06, 0)

    def FIFO_bytes_to_int(self, FIFO_bytes):
        value = unpack(">i", b'\x00' + FIFO_bytes)
        return (value[0] & 0x3FFFF) >> self._pulse_width_set

    def read_sensor_multiLED(self, pointer_position):
        self.i2c_set_register(0x06, pointer_position)
        fifo_bytes = self.i2c_read_register(0x07, self._led_mode * 3)
        red_int = self.FIFO_bytes_to_int(fifo_bytes[0:3]) 
        IR_int = self.FIFO_bytes_to_int(fifo_bytes[3:6])
        green_int = self.FIFO_bytes_to_int(fifo_bytes[6:9])
        # print("[Red:", red_int, " IR:", IR_int, " G:", green_int, "]", sep='')
        return red_int, IR_int, green_int  # return red, IR, green sensor integer values

    def CreateImage(self, value):  
        # this shows data input to LED so that user can check its working
        unit = (2 ** (18 - self._pulse_width_set)) // (250)
        image_p1 = (value // (unit * 50)) * (str(9) * 5)
        image_p2 = ((value % (unit * 50)) // (unit * 10)) * str(9)
        points = (((value % (unit * 50)) % (unit * 10))) // unit
        if points > 0:
            image_p3 = str(points)
        else:
            image_p3 = ""
        image_p4 = ((25) - len(image_p1 + image_p2 + image_p3)) * str(0)
        tmp_image = image_p1 + image_p2 + image_p3 + image_p4
        return ':'.join([tmp_image[i:i+5] for i in range(0, len(tmp_image), 5)])

last_xyz = 0
MAX30105 = ParticleSensor(HEX_ADDRESS=0x57)
part_id = MAX30105.i2c_read_register(0xFF)
rev_id = MAX30105.i2c_read_register(0xFE)
MAX30105.setup_sensor(LED_MODE=3, LED_POWER=0x1F, PULSE_WIDTH=0)


while True:
    for FIFO_pointer in range(32):
        sensor_data = MAX30105.read_sensor_multiLED(FIFO_pointer)
        brightness = MAX30105.CreateImage(int(sensor_data[1]))  # amount of LED light
        sensor_image = Image(brightness)
        display.show(sensor_image)
        x = accelerometer.get_x()
        y = accelerometer.get_y()
        z = accelerometer.get_z()
        # get x, y, z value of accelerometer
        current_xyz = math.sqrt(x**2 + y**2 + z**2)
        # calculate magnitude of movement 
        xyz = math.floor(last_xyz - current_xyz)
        # calculate amount of movement and convert it to integer
        xyz = abs(xyz)
        int1 = sensor_data[0]  # extract red sensor value from tuple 'sensor_data'
        int2 = sensor_data[1]  # extract IR sensor value from tuple 'sensor_data'
        int3 = sensor_data[2]  # extract green sensor value from tuple 'sensor_data'
        radio.send(str(int1) + ", " + str(int2) + ", " + str(int3) + ", " + str(xyz))
        last_xyz = current_xyz

2. microbit connected to PC

MicroPython
This code allows microbit to receive string data from another microbit and split and conver it to multiple midi signals and send them to Puredata.
from microbit import *
import radio
import math

radio.on()  # activate radio 

# function to send accelerometer value thru midi to Puredata
def motionChange(chan, n, value):
    MIDI_CC = 0xB0
    if chan > 15:
        return
    if n > 127:
        return
    if value > 127:
        return
    msg = bytes([MIDI_CC | chan, n, value])
    uart.write(msg)  
    
# function to send SPO2 value thru midi to Puredata
def sensorChange(chan, n, value):
    MIDI_CC = 0xB0
    if chan > 15:
        return
    if n > 127:
        return
    if value > 127:
        return
    msg = bytes([MIDI_CC | chan, n, value])
    uart.write(msg)

# function to send lightlevel value thru midi to Puredata
def lightChange(chan, n, value):
    MIDI_CC = 0xB0
    if chan > 15:
        return
    if n > 127:
        return
    if value > 127:
        return
    msg = bytes([MIDI_CC | chan, n, value])
    uart.write(msg)


def midiNoteOn(chan, n, vel):
    MIDI_NOTE_ON = 0x90
    if chan > 15:
        return
    if n > 127:
        return
    if vel > 127:
        return
    msg = bytes([MIDI_NOTE_ON | chan, n, vel])
    uart.write(msg)


def midiNoteOff(chan, n, vel):
    MIDI_NOTE_OFF = 0x80
    if chan > 15:
        return
    if n > 127:
        return
    if vel > 127:
        return
    msg = bytes([MIDI_NOTE_OFF | chan, n, vel])
    uart.write(msg)

# start sending midi signal with pin0
def Start():
    uart.init(baudrate=31250, bits=8, parity=None, stop=1, tx=pin8)

Start()
lastA = False
lastB = False
BUTTON_A_NOTE = 36
BUTTON_B_NOTE = 39

lastlight = 0  # initial value for lightlevel
while True:
    # sound_level = (pin0.read_analog() - 511) / 100
    # print(sound_level)
    a = button_a.is_pressed()
    b = button_b.is_pressed()
    if a is True and lastA is False:
        midiNoteOn(0, BUTTON_A_NOTE, 127)
    elif a is False and lastA is True:
        midiNoteOff(0, BUTTON_A_NOTE, 127)
    if b is True and lastB is False:
        midiNoteOn(0, BUTTON_B_NOTE, 127)
    elif b is False and lastB is True:
        midiNoteOff(0, BUTTON_B_NOTE, 127)
    lastA = a
    lastB = b
    inc = radio.receive()  # store radio string value to inc
    if inc is not None:  # following code works only for valid input
        mylist = eval(inc)  # convert string to tuple to extract numbers
        motion = mylist[3]  # extract 4th number of the tuple 
        motion = math.floor(motion / 1028 * 127)  # convert it for midi CC
        motionChange(0, 22, motion)  # send thru 22 midi
        red = mylist[0]  # extract 1st number of the tuple 
        IR = mylist[1]  # extract 2nd number of the tuple 
        green = mylist[2]  # extract 3rd number of the tuple 
        # calculate SPO2 value according to the datasheet
        SPO2 = math.floor(red / IR * 100)  
        sensorChange(0, 23, SPO2)  # send thru 23 midi
        # print(motion)
    currentlight = display.read_light_level()  # read light level on receiving microbit
    if currentlight != lastlight:
        mod_light = math.floor((currentlight) / 255 * 127)  # convert value for midi CC
        lightChange(0, 24, mod_light)  # send thru 24 midi
        # print(currentlevel)
        lastlight = currentlight
    sleep(100)

This is a well-written explanation of using MAX30105 with microbit

Credits

Sung Gone Kim

Sung Gone Kim

1 project • 0 followers

Comments