
Crystal Ball - Biofeedback And Meditation
About the project
Use at your own risk!
Project info
Difficulty: Moderate
Platforms: Arduino
Estimated time: 3 days
License: GNU General Public License, version 3 or later (GPL3+)
Items used in this project
Hardware components
Story
SeeedStudio has very good ear-lobe heartbeat sensor. I combined it with Illuminated LED display stand for crystal ball and installed RGB controllable LEDs. So you attach sensor to your earlobe, and crystal ball begins flashing in sync with your hearbeats, and the color of flash depends on your pulse rate. It is green when your pulse is at rest, and turns into bluish when the pulse slows down or to red/purple when the heart beats faster. For your convenience device also displays your current pulse rate. After some experimenting I found that flashing on every heartbeat is somewhat disturbing (most people will be surprised to see how fast the heart works even at rest); So I made it flashing on every other heartbeat and this produces pleasant and relaxing rhythm.

Custom parts and enclosures
Schematics:
Code:

Details
So the crucial question for any project -

Well, it is a nice aid for meditation/crystal ball gazing practice;
Besides, one can exercise biofeedback, trying to slow down heartbeat with mental efforts (no instruction given!) and expect improved mind/body balance.
The most important part of this project - pulse sensor from SeeedStudio. And the most important part of this sensor - proprietary controller that comes with sensor:

Controller ( a tiny circuit board inside this white box) generates a short square pulses when heartbeat is sensed. Works like a charm. Looks like this sensor and controller originates from Kyto Electronics product.
This is how assembled project looks:
Here is a video demonstrating how flash color changes with pulse rate:
This is how it works with sensor attached to the ear lobe:
I am trying to change pulse rate with mental effort:
Custom parts and enclosures
Innards
Small green PCB at the lower right - pulse sensor controller (originally was enclosed in a white box connected to pulse sensor)


//#define DEBUG 1 #include <FastLED.h> #include <Adafruit_GFX.h> #include "Adafruit_LEDBackpack.h" #include <RunningAverage.h> #define DATA_PIN 11 #define CLOCK_PIN 13 #define NUM_LEDS 4 Adafruit_AlphaNum4 alpha4 = Adafruit_AlphaNum4(); volatile unsigned long current_time,previous_time, rr_raw, button_time; unsigned int heart_rate;//the measurement result of heart rate unsigned int rr, n1,n2; boolean no_sensor=true; RunningAverage myRA(10); //initialize running average internal array for 8 measurements CRGB strip[NUM_LEDS]; int v_delay = 10; byte n,j1,j2,j3; int hue; CRGB v_rgbcolor=CRGB::Blue; volatile bool data_arrived=false; byte skip_counter=0; byte skip_limit=2; //const byte skip_max=4; boolean start_flash=true; boolean show_dot=true; void setup() { #ifdef DEBUG Serial.begin(9600); #endif alpha4.begin(0x70); alpha4.clear(); alpha4.writeDisplay(); pinMode(6, INPUT_PULLUP); //side button setup; for debugging only FastLED.addLeds<APA102, DATA_PIN, CLOCK_PIN, BGR>(strip, NUM_LEDS); FastLED.clear(); FastLED.show(); delay(50); for (int i = 0 ; i <3; i++) { fill_solid(strip,NUM_LEDS,v_rgbcolor); FastLED.show(); delay(400); FastLED.clear(); FastLED.show(); delay(400); } FastLED.clear(); FastLED.show(); myRA.clear(); n=0; previous_time=millis(); cli();//stop interrupts attachInterrupt(0, interrupt, RISING);//set interrupt 0,digital port 2 sei();//allow interrupts } void show_Alpha(char c1, char c2,boolean v_show_dot=false) { alpha4.writeDigitAscii(2, c1); alpha4.writeDigitAscii(3, c2,v_show_dot); alpha4.writeDisplay(); } void loop() { if (millis()>current_time+3000) { no_sensor=true; myRA.clear(); n=0; //when no data reset counter previous_time=0; } else no_sensor=false; if (no_sensor) { idle(); } if (data_arrived){ if (n<11) n++; //dont show first 10 measurements of heart rate myRA.addValue(rr_raw); rr=myRA.getAverage(); heart_rate=60000.0/rr; if (heart_rate<100) { n1=heart_rate/10+48; n2=heart_rate%10+48; } else { n1=(heart_rate-100)/10+48; n2=(heart_rate-100)%10+48; } if (n>10) show_Alpha(char(n1),char(n2),show_dot); else show_Alpha('_','_',show_dot); show_dot=!show_dot; if (rr_raw<100) { data_arrived=false; return; } v_delay=(rr_raw-50)/35; //if (v_delay>40) v_delay=25; #ifdef DEBUG Serial.print("v_delay=");Serial.println(v_delay); #endif hue=map(heart_rate,60,90,0,170); if (heart_rate>100) heart_rate=100; fill_solid(strip, NUM_LEDS, CHSV(170-hue,255,255)); #ifdef DEBUG Serial.print("Skip Counter=");Serial.print(skip_counter); Serial.print("Skip_limit-1=");Serial.println(skip_limit-1); #endif if (skip_counter>=skip_limit-1) { skip_counter=0; FastLED.show(); while(j1<35) { j1++; fadeLightBy(strip,4,20); FastLED.show(); delay(v_delay); // check_button(); } } else skip_counter++; delay(v_delay); data_arrived=false; // check_button(); j1=0; //while(j1<35 && !data_arrived) { } } //loop void interrupt(){ data_arrived=true; current_time=millis(); if (previous_time==0) previous_time=current_time-833; rr_raw=current_time-previous_time; previous_time=current_time; } void idle(){ FastLED.clear(); delay(50); FastLED.show(); int i=random(4); randomSeed(analogRead(0)); j1=random(255); j2=random(255); j3=random(255); strip[i].setHSV(j1,j2,j3); FastLED.show(); show_Alpha('*','*'); delay(50); }