Pulse sensor
# Platform: BBC micro:bit
# Language: MicroPython
# Category: BBC micro:bit > Input sensors > Pulsesensors.com
# Purpose : Signal plotting + BPM calculation inc. moving average.
# - Beat detection uses fixed on/off thresholds (vs self-calculated from signal).
# Dependencies:
# - For full function requires Mu editor with REPL and Plotter features.
# Author : Paul Knighton, 03/04/2018
from microbit import *
# Define any functions:
def log_microbit(message, message_delay=130):
# message_delay - how many ms to display message for (larger means longer)
display.scroll(message, delay=message_delay, wait=True, loop=False, monospace=False)
def log_REPL(message):
print(message)
def summary():
return "BPM last {}, average {}.".format(bpm, bpm_avg)
# Configure constants and variables before main loop:
# Timing settings
SECONDS_PER_MINUTE = 60
MILLISECONDS_PER_SECOND = 1000
MILLISECONDS_PER_MINUTE = SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND
# Sampling rate & interval settings (ms)
# Caution:
# Sending output too frequently may exceed bandwidth per second ...
# - we can send through REPL between micro:bit and PC/host device.
# - Effect may show as backlog/lag/staggering in REPL output or Plotter graph.
SAMPLING_RATE = 25 # (Hz) typically 25-100 samples per second
SAMPLING_INTERVAL = int(MILLISECONDS_PER_SECOND / SAMPLING_RATE)
# Detect pulse on/off:
# Beat detection: Use fixed on/off thresholds (vs self-calculated from signal)
# - tracks beat wave starting/end using threshold on/off levels vs signal
# - modify thresholds to see what happens with different people :)
threshold_on = 550
threshold_off = 500
beat = False
# BPM (beats per minute) calculation using moving average buffer.
bpm = 0
bpm_avg = 0
# used buffer to calculate average BPM over buffer's values
beats_buffer = []
BEATS_BUFFER_SIZE = 15
# calculate BPM using counts and times of samples_between_beats
# count how many samples occur between beats.
samples_between_beats = 0
# track time between start and end of a single beat.
current_time = running_time()
start_time = current_time
# Sampling loop:
while True:
# read signal from input in pin 2 (currently PulseSensor.com)
# Signal: Original = 0-1000.
Signal = pin2.read_analog()
# increment count of samples_between_beats
samples_between_beats += 1
# display pulse on microbit using images:
# in pulse wave peak ?
if beat is False and (Signal > threshold_on):
beat = True
display.show(Image.HEART)
# display.set_pixel(2, 2, 9) # LED pixel on
# not in pulse wave peak ?
if beat is True and (Signal < threshold_off):
beat = False
display.show(Image.HEART_SMALL)
# display.clear()
# display.set_pixel(2, 2, 0) # LED pixel off
# calculate latest BPM (beats per minute):
# BPM = (ms in one minute / time passed since last beat)
bpm = MILLISECONDS_PER_MINUTE // (running_time() - start_time)
# append latest BPM to end of buffer
beats_buffer.append(bpm)
# ensure buffer only contains tail of maximum size elements.
if len(beats_buffer) > BEATS_BUFFER_SIZE:
beats_buffer = beats_buffer[-BEATS_BUFFER_SIZE:]
# calculate moving average of bpm = sum(bpm's) // count(bpm's)
bpm_avg = sum(beats_buffer) // len(beats_buffer)
# reset count of samples and time between beats
samples_between_beats = 0
start_time = running_time()
# Events - Buttons:
# if button_a pressed, display on microbit BPM summary,
if button_a.is_pressed():
message = summary()
log_microbit(message, 90)
# if button b pressed, exit forever loop
if button_b.is_pressed():
break
# output signal value to REPL
# Scaling: x2 then subtract 1000 to use full y-range of Plotter graph.
Signal = 2*Signal - 1000
# print("({})".format(Signal))
# print("({}, {}, {})".format(samples_between_beats*10, Signal, bpm*10))
print("({}, {})".format(Signal, bpm*10))
# pause between samples
sleep(SAMPLING_INTERVAL)
# quitting program
message = "Exit: " + summary()
log_REPL(message)
log_microbit(message, 60)