Project tutorial
Rate of Perceived Exertion Logger

Rate of Perceived Exertion Logger © GPL3+

Record your exertions on the Borg scale.

  • 7 views
  • 0 comments
  • 0 respects

Components and supplies

A000066 iso both
Arduino UNO & Genuino UNO
×1
Deek Robot data logging shield v1.0
×1
LCD shield with analog keypad
×1

Apps and online services

About this project

Rate of perceived exertion is one way of determining how hard you are working when exercising.

You are given an exertion scale of between 6 and 20 (Figure 1), with 6 meaning minimal exertion and 20 meaning flat out and you just mentally rate where you think your level of exertion falls.

This scale was invented by Dr Gunnar Borg and hence is often referred to as the Borg scale [1].

The numbers on the scale may seem a little weird but this range allows you to quickly estimate your heart rate simply by multiplying the scale number by 10, so if you rate your exertion at 20 your heart rate would be around 200 bpm.

I was playing around with a data logging shield and though it would be interesting to automate this idea.

Hardware description:

The hardware consists of an Arduino UNO, a Deek robot data logging shield v1.0, and an LCD shield with buttons.

The UNO, data logging shield and LCD shield are layered together like a cake. The shields take care of all of the connections, there is nothing to wire up.

I have removed pin 10 from my LCD shield, this pin is used for the LCD backlight but has been implemented incorrectly on some shields such as the one I bought [2], this is no loss in the context of this project as the data logging shield uses this pin as chip select.

Software description:

The software was an exercise in code reuse.

I took the clock read functions from project “ds3231.ino” from the RTClib library.

I used some simple code to run the LCD.

I used the Arduino SD ReadWrite example [3] to work out how to write the data to the SD card.

I initially tested the code using the Arduino serial console I think this a good way to debug changes so I have left it in place in the uploaded code; uncomment the line//#define test to switch off the writes to the SD card and send the data to the serial console.

Assumption:

You have already set the clock on the logging shield as the program here does not do that.

Libraries used:

SPI

SD

RTClib

LiquidCrystal

Data format:

The data is stored as comma separated values (CSV) in the format of – Year, Month, Day, Hours, Minutes, Seconds, RPE value.

On pressing select to start the date, time and a RPE value 6 will be written to the data file, on pressing select at the end of a session the date, time and a RPE value zero will be written to the data file.

Functioning:

At turn on the unit initializes the SD card and opens the file to record the data.

All records are appended to the same file whose name is coded into the program (data.txt).

You are warned if the SD card cannot be initialized, the data file opened or the data cannot be written to the card.

Hit the select key to begin logging and keep adjusting the level in line with your perceived exertion using the up and down buttons.

At the end of an exercise session hit the select key to stop recording.

Granularity and limits:

The data is recorded to a minimum resolution of 1 second, if multiple button presses occur within that time frame each will be recorded as having occurred at the same time.

Multiple writes also occur at the limits, when you are at either 6 and press down additional times or at 20 when you press up additional times.

This is something to be aware of if you are going to run the data file through another program say to generate a graph.

References:

[1]https://en.wikipedia.org/wiki/Rating_of_perceived_exertion (Retrieved 18/June/2019)

[2] LCD shield errata link https://forum.arduino.cc/index.php?topic=96747.0(Retrieved 7/June/2019)

[3] SD read write link https://www.arduino.cc/en/Tutorial/ReadWrite(Retrieved – 8/June/2019)

Code

RPE logger code fileC/C++
//RPE logger code file

//CHECK IF LCD SHIELD USES DIGITAL PIN 10 AS THIS IS USED AS CS FOR DATA LOGGING SHIELD
//CHECK TO SEE IF LCD IS ONE OF THE FAULTY ONES
//REFERENCE - https://forum.arduino.cc/index.php?topic=96747.0 (Retrieved 7/June/2019)

/*
 The circuit:
 * SD card attached to SPI bus as follows:
 ** MOSI - pin 11
 ** MISO - pin 12
 ** CLK - pin 13
 ** CS - pin 10 for deek robot data logging shield v1.0
 * 
 * RTC connections (I2C):
 * gnd to gnd
 * + to +5V
 * SDA to A4
 * SCL to A5
 * 
 * LCD shield:
 * Backlight - pin 10 removed
 * B4 to D4
 * B5 to D5
 * B6 to D6
 * B7 to D7 
 * RS to D8
 * E to D9
 * 
 * LCD shield buttons:
 * Analog 0
 * 
 */

//#define test  //uncomment this line to send data to serial console instead of SD card
 
#include <SPI.h>
#include <SD.h>

const int chipSelect = 10;//CS for deek robot data logging shield v1.0

//for real time clock
#include "RTClib.h"

RTC_DS3231 rtc;

//for LCD shield
#include <LiquidCrystal.h>

// select the pins used on the LCD panel
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
// these have been checked with http://www.robotshop.com/content/PDF/dfrobot-lcd-keypad-shield-schematic.pdf


File myFile;

//required to debounce switches
#define MAX_CHECKS 10
volatile uint8_t Debounced_State=0;//accessed by isr and main loop code
uint8_t State[MAX_CHECKS]={0};
uint8_t Index=0;


//rpe
const int rpe_max=20;
const int rpe_min=6;


//where we hold the filename
const String my_filename="data.txt";

void setup()
{

    lcd.begin(16, 2); // start the library
    lcd.setCursor(0,0);

    pinMode(A0,INPUT);//Analog ladder for buttons
          
    // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

  OCR1A = 625;              // compare match register  16MHX/256/100HZ 
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();


  if (!SD.begin(chipSelect)) {
    lcd.print("Not Initialized");
    delay(5000);
    return;
  }


  if (!SD.exists(my_filename)) {
    lcd.print("No File");;
    delay(5000); 
  }

  lcd.setCursor(4,0);
  lcd.print("Stopped");
  lcd.setCursor(4,1);
  lcd.print("RPE ");
  lcd.print(rpe_min);

#ifdef test
  Serial.begin(9600);
  #endif
 
}


void loop()
{
//variables
static bool started=false;
static bool last_entry=false;
static bool do_update=false;
static bool button_up=false;
static bool button_stored=false;
static int Stored_State=0;
static int Decode_Me=0;
static int rpe_count=rpe_min;

    noInterrupts();           // disable all interrupts    
if (Debounced_State>Stored_State){Stored_State=Debounced_State;button_stored=true;}//store and flag as ready
if (Debounced_State<Stored_State){if(button_stored){button_up=true;Decode_Me=Stored_State;}Stored_State=Debounced_State;}//button has been released    
    interrupts();             // enable all interrupts

if(button_up && button_stored)
{
button_stored=false;
button_up=false;
do_update=true;

   if (Decode_Me==1){if(started){rpe_count--;if(rpe_count<rpe_min){rpe_count=rpe_min;}do_update=true;}}//decrement rpe
   if (Decode_Me==2){if(started){rpe_count++;if(rpe_count>rpe_max){rpe_count=rpe_max;}do_update=true;}}//increment rpe
   if (Decode_Me==4){if(started){started=false;last_entry=true;}else{started=true;}rpe_count=rpe_min;do_update=true;}//start stop

}

  if (do_update){
    do_update=false;

  if(!started){lcd.setCursor(0,0);lcd.print  ("    Stopped         ");}
  if(rpe_count==6 && started){lcd.setCursor(0,0);lcd.print       ("  No exertion    ");}
  if(rpe_count>=7 && rpe_count < 9){lcd.setCursor(0,0);lcd.print (" Extremely light ");}
  if(rpe_count>=9 && rpe_count < 11){lcd.setCursor(0,0);lcd.print ("  Very light    ");}
  if(rpe_count>=11 && rpe_count < 13){lcd.setCursor(0,0);lcd.print("     Light     ");}
  if(rpe_count>=13 && rpe_count < 15){lcd.setCursor(0,0);lcd.print("  Somehwat hard ");}
  if(rpe_count>=15 && rpe_count < 17){lcd.setCursor(0,0);lcd.print("     Hard       ");}
  if(rpe_count>=17 && rpe_count < 19){lcd.setCursor(0,0);lcd.print("   Very hard    ");}
  if(rpe_count>=19 && rpe_count < 20){lcd.setCursor(0,0);lcd.print(" Extremely hard ");}
  if(rpe_count==20){lcd.setCursor(0,0);lcd.print("Maximum exertion");}

  lcd.setCursor(8,1);  
  lcd.print(rpe_count);
  if(rpe_count<10){lcd.print(" ");}

//In test mode we write to the serial console when not in test mode we write to the SD card.
#ifdef test  
  if(started){
  Serial.print(get_time());
  Serial.print('-');
  Serial.println(rpe_count);}
  else
  {if(last_entry){
  last_entry=false;
  Serial.print(get_time());
  Serial.print('-');
  Serial.println(0);}}
  #else
  if(started){append_file(my_filename,get_time(),rpe_count);}else{if(last_entry){last_entry=false;append_file(my_filename,get_time(),0);}}
  #endif
  
  
  }

}

//my_functions

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
//
//read buttons
  uint8_t temp=0x00;

  
 if (analogRead(0) > 60 && analogRead(0) < 200){temp|=1<<1;} //btnUP
 if (analogRead(0) > 200 && analogRead(0) < 400){temp|=1<<0;} //btnDOWN  
 if (analogRead(0) > 600 && analogRead(0) < 800){temp|=1<<2;} //btnSELECT
 
  
//debounce
  uint8_t i,j;

//for right hand
  State[Index]= temp;
  ++Index;
  j=0xFF;
  for (i=0;i<MAX_CHECKS;i++){j=j&State[i];}
  Debounced_State=j;
  if(Index>=MAX_CHECKS){Index=0;} 

}


//function to get time from clock and return it as a string
String get_time(void){
//check rtc available
  if (! rtc.begin()) {
    lcd.print("No RTC");
    delay(5000);
  }

  String dataString = "";
 
     DateTime now = rtc.now();
     
    dataString += String(now.year());
    dataString += ",";
    dataString += String(now.month());
    dataString += ",";
    dataString += String(now.day());
    dataString += ",";
    dataString += String(now.hour());
    dataString += ",";
    dataString += String(now.minute());
    dataString += ",";
    dataString += String(now.second());

    return dataString;
    }
    
//function to append the rpe value and time to the data file
void append_file(String filename,String dataString,int rpe)
{
   
//append rate perceived exertion value to time dataString    
    dataString += ',';
    dataString += rpe;
    dataString += ',';
    

//write to file

  myFile = SD.open(filename, FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    myFile.println(dataString);
    // close the file:
    myFile.close();
  } else {
    lcd.print("Can't open file");//error file didn't open when writing data
    delay(5000);
  }

return;
}
//End

Comments

Similar projects you might like

Temperature and Humidity Logger Per Hour

Project tutorial by lefteris1993

  • 2,102 views
  • 0 comments
  • 12 respects

Measure Heart Rate and SpO2 with MAX30102

Project tutorial by SurtrTech

  • 27,253 views
  • 30 comments
  • 125 respects

Heart Rate Monitoring System

Project tutorial by HRMS

  • 20,111 views
  • 5 comments
  • 33 respects

Temperature and Humidity Data Logger

Project tutorial by Wimpie van den Berg

  • 27,644 views
  • 2 comments
  • 25 respects

MKR Zero Weather Data Logger

Project tutorial by Arduino_Genuino

  • 27,702 views
  • 18 comments
  • 56 respects

Temperature and Humidity Logger (Using Arduino)

Project showcase by lmsousa

  • 10,415 views
  • 7 comments
  • 37 respects
Add projectSign up / Login