Average X per hour, then output to lapsPh

I am trying to collect from a timer that I have set (hh:mm:ss) and average a counted quantity (laps) to come up with a running “laps per hour” output that is accurate as possible (up to the second).

my variables are unsigned long and lapsPh is int.

  elapsedTime = millis()/1000 - startTime;
  elapsedSec = elapsedTime%60;
  elapsedMin = (elapsedTime%3600)/60;
  elapsedHr = elapsedTime/3600;

I tried this-

//Laps Per Hour (Average)
  
  if (elapsedSec < 15) //If counter is less than 15sec
  {
      lapsPh = counter * 240;
  }
  else  if (elapsedSec < 30) //If counter is less than 30sec
  {
      lapsPh = counter * 120;
  }
  else  if (elapsedSec < 45) //If counter is less than 45sec 
  {
      lapsPh = counter * 80;
  }

  
  if (elapsedMin >= 1) //If counter is over 1 minute
  {
      lapsPh = (counter * 60) / elapsedMin;
  }
  else if (elapsedHr >= 1) //If countr is over 1 hour, averages minutes as well
  {
      lapsPh = (counter / (((elapsedHr*60)+elapsedMin) / 60)); 
  }

It does work, but on lcd.print(lapsPh); if it gets over 1000 and then drops back down it doesn’t clear all of the printed digits… and I tried if statements to lcd.print(’ ') if a variable is greater than or less than a certain amount but end up with erraneous digits that overflow.

The math is good for each one (verified with fixed numbers in excel), but there should be a more efficient way to do this that also updates better.

Can anyone help steer me in the right direction?

Thanks.

Full code removed

if it gets over 1000 and then drops back down it doesn't clear all of the printed digits.

No surprise there. The simple way to avoid this is to print 4 spaces where you want the numbers to appear before printing the numbers in the same place. If there is room after the number you could unconditionally print 3 spaces after it, but that depends on how you are using the space on the screen.

There are more complicated methods such as printing the number then determining how many trailing spaces to print based on the size of the number or even more complicated methods such as only printing spaces when the number drops below 1000, 100 or 10 but simple is probably best.

You can reduce flickering of the LCD by only printing the number if the value has changed.

Ok, so I have made progress, and the calculation seems to work, but something is off...

The LCD Print function may or may not be the cause....

Full Code removed

When the counter hits 547 (regardless of time) it throws the counter all kinds of crazy.

For example, with 7 minutes on the timer, and 546 Laps, the counter read 4680, counter hits 547, and the laps per hour hit 14050. Doesn't make any sense.

I'm not sure why you wouldn't want to type lapsPh and elapsedHr as floats to handle the shorter time periods with fractional hours. You can certainly recast to integers to truncate for display.

elapsedHr = elapsedTime/3600.0; //add .0 to the constant to force the calculation to be float
lapsPh = counter/elapsedHr;

lcd.print((int)lapsPh);

Concerning your display, clearing spaces, and cursor management--

I have a 20x4 LCD, on the last on line 3 (4th physical line) I am printing "LPH:(lapsPh) _ HH:MMM:SS" so I am utilizing 13 of 20 characters yet, leaving 2 for spaces that leaves 5 spaces total so I have to use some sort of advanced method of appending or inserting spaces.

If MMM is not a typo, please explain what you are doing.

Is this what you want

LPH: starts at 0 and ends at 3 You want 4 to be a space. You want lapsPh to be printed in columns 5 through 9 (5 digits) You want 10 and 11 to be spaces HH:MM:SS starts at 12 and ends at 19

Do you need the number following LPH:_ to be right justified,(i.e last digit always in column 9) or is it OK for them to be left justified(i.e. first digit always at column 5)?

Left justified is easy, just clear the spaces from 5 to 9 (4, 10 and 11 are already clear) with

lcd.setCursor(5,3); 
lcd.print("     ");//5 spaces between quotes

Then set the cursor at 5,3 and print your number.

You appear to have a handle on right justified by determining the size of the number and setting the cursor appropriately. You will clear in the same fashion as the left justified.

 lapsPh = ((counter * 60) / elapsedMin);

The 547*60 defaults to an integer calculation and overflows the max signed int of 32767. If you as the specifier L (long) or UL(unsigned long) to the constant 60 it will not do that.

 lapsPh = ((counter * 60UL) / elapsedMin);

cattledog: lapsPh = ((counter * 60) / elapsedMin);

The 547*60 defaults to an integer calculation and overflows the max signed int of 32767. If you as the specifier L (long) or UL(unsigned long) to the constant 60 it will not do that.

 lapsPh = ((counter * 60UL) / elapsedMin);

Setting it to UL made the fix, I had already worked through the spacing and lcd.print section after my first reply (hit me while I was typing)...

You were also, absolutely correct that MMM was a typo.

Do you or anyone else have an idea on how to program an input/button to pause the timer? or the reset button to pause on one push and start on the next, hold to reset?

Rossm812:
Do you or anyone else have an idea on how to program an input/button to pause the timer? or the reset button to pause on one push and start on the next, hold to reset?

Let’s assume that there are 4 buttons, start, stop, pause and resume.

Use a variable to hold the duration (initialized to 0).
1)
When you press the start button to start, store millis() in a variable (e.g. startTime). Go to (2).
2)
2a)
When you press the pause button, calculate the duration of the lapsed time and add to the duration. Go to (3).
2b)
When you press the stop button, calculate the duration of the lapsed time and add to the duration. Go to (4).
3)
When you press the resume button, store millis() in the startTime variable again. Go to (2).
4)
Done

The simplest is probably a small statemachine with a few states. Your program can be in a few states.

  1. WAITSTART; wait for start button to be pressed
  2. RUNNING; wait for pause or stop button to be pressed
  3. PAUSED; wait for resume to be pressed
  4. DONE

The below code demonstrates the basic idea. I’ve added an additional state to display the total time; this prevents the display from being updated at a high rate.

I’m aware that you don’t use 4 buttons but it made it easier to show the principle. There is also no debouncing implemented; with 4 buttons it’s probably not needed (not really thought it through).

// the different states
#define WAITSTART 0
#define RUNNING 1
#define PAUSED 2
#define DISPLAY 3
#define DONE 4

// buttons wired between pin and GND; use INPUT_PULLUP
#define ISPRESSED LOW

// butttons
const byte startButton = 2;
const byte stopButton = 3;
const byte pauseButton = 4;
const byte resumeButton = 5;

// other stuff here
...
...

void setup()
{
  // initialisation
  ...
  ...
  // put your setup code here, to run once:

}

void loop()
{
  static byte currentState = WAITSTART;
  static unsigned long duration = 0, startTime = 0;

  byte startbuttonState = digitalRead(startButton);
  byte stopbuttonState = digitalRead(stopButton);
  byte pausebuttonState = digitalRead(pauseButton);
  byte resumebuttonState = digitalRead(resumeButton);

  switch (currentState)
  {
    case WAITSTART:
      if (startbuttonState == ISPRESSED)
      {
        startTime = millis();
        duration = 0;
        currentState = RUNNING;
      }
      break;
    case RUNNING:
      if (pausebuttonState == ISPRESSED)
      {
        duration += (millis() - startTime);
        currentState = PAUSED;
      }
      if (stopbuttonState == ISPRESSED)
      {
        duration += (millis() - startTime);
        currentState = DISPLAY;
      }
      break;
    case PAUSED:
      // wait for pause button to be released and resume button to be pressed
      if (pausebuttonState != ISPRESSED && resumebuttonState == ISPRESSED)
      {
        startTime = millis();
        currentState = RUNNING;
      }
      break;
    case DISPLAY:
      lcd.print(duration);
      currentState = DONE;
      break;
    case DONE:
      // wait for stop button to be released
      if (stopbuttonState != ISPRESSED)
      {
        currentState = WAITSTART;
      }
      break;
  }
}

It seems like my math is off too- for calculating the laps per hour after the time reaches 1hr + - It works as a formula in excel (exchanging the values for cell numbers) however I know this is code.

keep in mind these are my time variables-

  elapsedTime = millis()/1000 - startTime;
  elapsedSec = elapsedTime%60;
  elapsedMin = (elapsedTime%3600)/60;
  elapsedHr = elapsedTime/3600;

I have the variables lapsPh and LapsPm at my disposal and/or if I need to change any.

I tried this-

  if (elapsedMin >= 1) //If counter is over 1 minute
  {
      lapsPh = (counter * 60) / elapsedMin;
  }
  else if (elapsedHr >= 1) //If counter is over 1 hour, averages minutes as well
  {
      lapsPh = (counter / (((elapsedHr*60)+elapsedMin) / 60)); 
  }

I also just tried this and a couple variations-

  else if (elapsedHr >= 1) //If counter is over 1 hour, averages minutes as well
  {
      lapsPm = ((counter * 60UL) / elapsedMin); //recalculates under-minute timer and stores the value
      lapsPh = counter / (((roundsPh/60.0)) + roundsPm / 2.0); //calculates sum of Pm + Ph and divides by 2
  }

The lines for if the counter is over 1 minute works FINE. I have alternate code also for before the clock hits one minute, and those work. After one hour, the count goes all awry.

I am testing the 2nd set of lines I posted- but not sure they will work. Can anyone weigh in on my average math skills? or apparently lack thereof....

Rossm812: I am testing the 2nd set of lines I posted- but not sure they will work. Can anyone weigh in on my average math skills? or apparently lack thereof....

why not post all of your code?

I did at the top (it is all there in tact), and it has the original formula in it, I didn't figure posting it a second time would achieve anything.

This is what is posted above, and it acts as I describe- so for all intents, it is the entire code.

Full code removed

Also, I am working on figuring out how I can keep it calculating it's own time but also feed it time via serial so I don't have to wait a stinkin hour just to test the formula.... THIS would help my process a ton.

Also, I am working on figuring out how I can keep it calculating it's own time but also feed it time via serial so I don't have to wait a stinkin hour just to test the formula.... THIS would help my process a ton.

 //elapsedTime = millis()/1000 - startTime;
 elapsedTime = millis()/60 - startTime;

You are on the right track here. If you divide millis() by 10, then every thing in your code is one hundredth of the value, or one "hour" will be 36 seconds. Be sure to correct the startTime caluculation as well if you are using the restart button.

Is what you are doing not speeding things up as you wish?

Are you saying that you want millis()/myTimeAccellerator where myTimeAccellerator is input through the serial monitor? For program development, changing the value in the code and just uploading any changes may get you to your goal faster than trying to learn about serial input.

Well, I tried this-

lapsPh = counter / (((elapsedHr * 60) + elapsedMin) / 60);
// = Counter / (convert hours to mins) + (add minutes) / 60

I also tried a variation of just converting minutes to hours and adding them to the elapsedHr variable and I still get goofy results. Assuming 500 laps and the timer running

@ 00:59:00 = 500 @ 01:00:00 = 500 @ 01:01:00 = 30000 @ 01:02:00 = 15000 @ 01:03:00 = 10000 @ 01:04:00 = 7500 @ 01:05:00 = 6000 @ 01:06:00 = 5000 @ 01:07:00 = 4285 @ 01:08:00 = 3750 @ 01:09:00 = 3333 @ 01:10:00 = 3000 @ 01:11:00 = 3000 @ 01:12:00 = 2727 @ 01:13:00 = 2500 @ 01:14:00 = 2307 @ 01:15:00 = 2142 @ 01:16:00 = 2000 ...

I have tried 3 or 4 different formulas that when punched in by hand, end up with the correct results. Insert it into the program and I get the above results.

lapsPh = counter / (((elapsedHr * 60) + elapsedMin) / 60);

Do you understand that with integer math, and counter = 500, this expression will evaluate to 500 for all values from 1:00 to 1:59. What are you expecting?

Please post the exact program which produced the "goofy results" you posted. I do not see how they could come from the above expression. It's always best if you post complete code, so that people trying to help you can run your sketch.

@ 00:59:00 = 500 @ 01:00:00 = 500 @ 01:01:00 = 30000 @ 01:02:00 = 15000 @ 01:03:00 = 10000

Please explain what you are trying to do with these lapsPh calculations? What values do you expect for what times?

Did you try the approach using floating point math as I suggested in reply #4? What was wrong with the lapsPh values produced that way?

It is all in reply 10, same code, the one line is all that is giving me fits- and it has to be in the calculation. Do I need to post the complete code, every time for one line change?

I also don't quite get how to apply floats to this situation, which variables would I use as such and how would I assign them? I looked at the reference page and feel it isn't explained well, or I am not 100% grasping their usage.

Sorry if my last post sounded snippy, I had been asked a couple times for complete code, when it had been posted, and one or no lines had been changed.

As far as my expectations, assuming the counter stayed stationary at 500....

Time Results Expected 1:00 500 500 1:01 30000 492 1:02 15000 484 1:03 10000 476 1:04 7500 469 1:05 6000 462 1:06 5000 455 1:07 4285 448 1:08 3750 441 1:09 3333 435 1:10 3000 429 1:11 2727 423 1:12 2500 417 1:13 2307 411 1:14 2142 405 1:15 2000 400 1:16 1875 395 1:17 1764 390 1:18 1666 385 1:19 1578 380 1:20 1500 375 1:21 1428 370

It is all in reply 10, same code, the one line is all that is giving me fits- and it has to be in the calculation. Do I need to post the complete code, every time for one line change?

OK. Looking at the code in reply#10

  if (elapsedMin >= 1) //If counter is over 1 minute
  {
      lapsPh = ((counter * 60) / elapsedMin);
  }
  else if (elapsedHr >= 1) //If countr is over 1 hour, averages minutes as well
  {
      lapsPh = ((counter * 60) / ((elapsedHr * 60) + elapsedMin));
  }

Any time that elapsedMin >= than 1 the first if statement will be true, and the else if will be ignored. Try with if instead of else if. But, I'm still not sure of which calculation you want to use.

Edit: lapsPh = ((counter * 60) / ((elapsedHr * 60) + elapsedMin)); This looks correct.

This makes sense… the first one would actually have to read something like

if (elapsedMin >= 1 && elapsedHr < 1)

But making the second else if, a simple if, may be the better solution.

I want to use the first if, if and only if the timer has no hours on it, the second if, only if there is 1 or more hours elapsed. Hopefully this makes sense. Maybe I am going about it the wrong way completely if it doesn’t.

Would a float still be better?

. Do I need to post the complete code, every time for one line change?

It would be easier all round if you posted a small but complete program that shows the problem. Use fixed values for the variable or change them with a for loop to experiment with a range of values.

Once the problem is resolved then apply the solution to your main program.

I think I finally pinned it down.

First, I had an epiphany, I have buttons… put two extras on for now, program one to make counter = counter +50 and one to make startTime = startTime - 600 (to simulate adding 10 minutes at a time) this was simple and efficient.

Next, plug in a few options and this is what did it.

  if (elapsedMin >= 1 && elapsedHr < 1) //If counter is over 1 minute
  {
      lapsPh = ((counter * 60UL) / elapsedMin);
  }
  
  if (elapsedHr >= 1) //If counter is over 1 hour, averages minutes as well
  {
      lapsPh = ((counter * 60UL) / ((elapsedHr * 60UL) + elapsedMin));
  }

I needed to separate the if statements, and work more at the minute level than at the hour level which resulted in more accurate and up to the minute calculations. Second by second calculations seemed needlessly unnecessary.

Next time I will keep it in mind to post full code lines- sorry for the back and forth, I wish this place had a more reasonable response time- can’t even edit a stinkin response unless you wait, then you can’t post again for 5 more.

What was complicating life with the full code too- I had already gone ahead and added code in to make my two buttons, function as 6 (press, press and hold, and press and hold longer) so this took my sketch from about 150 lines to over 360 with comments… Maybe for troubleshooting I need to pare down the program initially.

I have added a +1 Karma for both of you (Cattledog and UKHeliBob) (waiting my hour in between… of course) Thank you all so much.

I want to make sure I am asking educated and informed questions and not coming here first without trying to do it self. I was getting irritated because it made me sick trying something, waiting an hour to find it fail- and then going back to the drawing board. I guess sometimes simple is the best answer.