Calculating BPM based on input pulse

I have a heart rate sensor that outputs a 0-5V pulse for every beat (5V on the beat). I am trying to calculate the beats per minute using code on the arduino and output to a serial seven segment display. Essentially I am trying to take the number of pulses in a 20 second timespan and multiply it by 3. It's sometimes pretty accurate but sometimes just has abnormally high values. I have been testing it with a function generator at 1Hz to verify that the sensor is not the problem. My code is below:

#include <SoftwareSerial.h>
#include <math.h>
long measurementStartTime = 0;
int beats = 0;
byte pin = 10;
int currentSensorValue;
boolean counted = false;
unsigned long sensorValue;
int beat;
int bpm;
// These are the Arduino pins required to create a software seiral
//  instance. We'll actually only use the TX pin.
const int softwareTx = 8;
const int softwareRx = 7;
const int refreshTime = 20000;
const int delayTime = 400;
const int missThreshhold = 20;

SoftwareSerial s7s(softwareRx, softwareTx);

char tempString[10];  // Will be used with sprintf to create strings

void setup()
{
  // Must begin s7s software serial at the correct baud rate.
  //  The default of the s7s is 9600.
  s7s.begin(9600);
  
  pinMode(pin, INPUT); 
  
  // Clear the display before jumping into loop
  clearDisplay();  
}

void loop() 
{
	measurementStartTime = millis();
        int misses = 0;
        bool done = false;
        beats = 0;
	while ((millis() - measurementStartTime < refreshTime)) {
                currentSensorValue = digitalRead(pin);
		if (currentSensorValue == 1) {
			beats++;
		} else {
                        misses++;
                }
            

		delay(delayTime);
	}

	if (beats > 0 && !done) {
		// update the display
		bpm = beats*3;
		sprintf(tempString, "%4d", bpm);
	} else {
                sprintf(tempString, "%4d", 9999);
        }
        
        // This will output the tempString to the S7S
        s7s.print(tempString);
	setDecimals(0b00000000);  // Sets digit 3 decimal on

Depending on where the signal generator phase is relative to the arduino startup, that 400ms delay may allow your code to count a beat twice. You would be better off looking for a change in the state of the input and just count on a rising edge. Take a look at the IDE's stateChangeExample sketch.

Post a schematic as well. Have you done anything to filter out noise?

Also, I would look into using an interrupt driven counter. Instead of trying to make a measurement every 400ms. You are probably violating the Nyquist frequency by doing this, which gives you inaccurate results. Basically attach an interrupt to an input pin that when it goes high, it increments the counter. Then you reset your counter at the beginning of the measurement, then report the counter at the end. You are basically building a low frequency frequency counter. As I mentioned, just make sure to filter out any noise.

The problem is that there are incomplete partial beats at the start and end of your measuring interval which you are ignoring.

Rather than just count the number of beats, it would be better to record the time of the first and least beats (that fall within your monitoring interval) as well as the number of beats. Then subtract the times to get the elapsed time between the first and last beats. Then instead of scaling up the beat count by (60/20) to get bpm, scale the beat count up by (60 / elapsed).

is that you are using "delay()" in your code.

The "delay()" function does just that - delays all operations for a given time, during which you cannot respond to inputs. It is for crude "demonstration" code only, or to insert necessary brief pauses in data output such as when initialising an LCD display. It has no place in a program which is seeking input.

Well, at least you were not proposing to use interrupts, which would make matters immensely worse (at least in this instance). "delay()" plus interrupts is a really bad mix!

I could have some more constructive advice but - half your code appears to be missing!

Your use of software serial would appear to be quite unnecessary - if the display uses 9600 baud, so does the serial monitor of the IDE so you can simply connect the display to the TX (I think it is) line on the Arduino board and use the hardware serial.

I suggest you should be trying not to count the beats in a 20 second interval, which for an approximately 60 BPM figure will give a synchronisation jitter of 1 in 20 or 5% - ±3 beats, but to start on a beat, count for 20 seconds and then measure the time on the next beat and make your calculation on the actual time and the number of beats counted. This does of course require calculation but this can be performed with integer mathematics - you absolutely do not need floating point. And in fact, it would work just as well for a ten second integration period.

Now you do have a problem if as it seems you are trying to do, you wish to give a continuous output on the display. One part of the problem is that you are actually introducing another "delay". This will probably not be a problem if as I have described, you synchronise your cycle to the actual detected beats (and you need a "debounce" routine with something like 100 ms integration as part of the beat detection - "integration" means that you repeatedly check that the pulse input has not changed at any time in the integration interval) then you can perform the time-consuming display immediately after the beat is detected, knowing that nothing else will happen for a reasonable time.

The ultimate development of the program (which turns out to be dead simple), is to use a FIFO array to keep the running intervals of the last ten beats and after each beat, use that to calculate and display the averaged BPM.

Thank you everyone for your input!

I see how I have been misusing the delay function in my current code. I also never took into account the filtering of the noise which I'll probably have to do when I hook up the actual heart rate receiver instead of the function generator. I'll attempt to look for a state change in the input (with help from the state change example sketch) and then instead of counting the beats in a certain amount of time, I will measure for 10 seconds, and use the number of beats that happen in that timespan for the calculations.

The continuous output is of no importance to me. One accurate heart rate being displayed for a set amount of time should be perfect.

smor1297:
I'll attempt to look for a state change in the input (with help from the state change example sketch) and then instead of counting the beats in a certain amount of time, I will measure for 10 seconds, and use the number of beats that happen in that timespan for the calculations.

Well, that's a start, but do note as I explained that this will be plus or minus one beat (six beats per minute) unless you not only count the number of beats, but measure exactly how long it was from the first to the last (plus one) beat.

smor1297:
The continuous output is of no importance to me. One accurate heart rate being displayed for a set amount of time should be perfect.

That may be, but it actually turns out to be very easy, not a great deal more difficult than the first way.

I would actually consider taking a few measurements over a period of time and averaging the results in a rolling window. This will give you more accurate results as it should account for some of +/- 1 error for incomplete heart beats. It just will take longer to get a good reading, since you'll have to wait through all of the cycles. A continuous mode will probably be easier than a one-shot for more accurate data (More data points). But that all depends on your data.

A possibly more accurate and 'fast' way to measure would be measure 3-4 heart beats and the time in between each (Period). Then convert to BPM (Frequency).