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)

Schematics:

Code:

//#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);
}