RTC Stopwatch

Pretty hard to help with such a small bit of code. Best to post everything, #includes and all.

Looks like you are working with the seconds register value from the RTC directly. This is indeed BCD and will have to be converted to decimal before subtracting. Then some modular arithmetic to ensure a positive result. Of course this code only works if the elapsed time never exceeds 59 seconds. I didn't go and dig up Macetech's code, but the BCD-to-Decimal conversion function that I use is below. I'd bet it's very similar if not identical. Printing the hex value is not what you want. That will show the BCD value but can't do the calculation with it.

byte seconds, init_seconds, elap_seconds;

int Timer() {
    elapsedTime();
    seconds = ((60 + elap_seconds) - init_seconds) % 60;
}

int initialTime() {
    /*Wire Code*/
    init_seconds=bcd2dec(Wire.read());
}

int elapsedTime() {
    /*Wire Code*/
    elap_seconds=bcd2dec(Wire.read());
}

/*----------------------------------------------------------------------*
 * BCD-to-Decimal conversion                                            *
 *----------------------------------------------------------------------*/
uint8_t bcd2dec(uint8_t n)
{
    return ((n / 16 * 10) + (n % 16));
}

maybe this code helps to make your stopwatch - Arduino Playground - StopWatchClass -

Thanks Rob. I did take a look at that, but I'm trying to see if I can implement the RTC.

Thanks Jack. I printed the HEX value since at first I was only interested in seeing the value (I think my attempt was to return a value in HEX and convert it to an int). Macetech, if I'm understanding their conversion correctly, uses bitwise AND (&) to check against each bit:

seconds = (((seconds & 0b11110000)>>4)*10 + (seconds & 0b00001111)); // convert BCD to decimal

I've seen your BCD-Decimal conversion in a few other posts, but as I'm recalling, I thought there might be another way for it to work. I was able to get a correct time lapse until the 59 second mark and is then restarted, which then gives an incorrect difference. I began to work on this by using an if statement to reset the second time, but haven't gone back to working on it. But here is the code I've been working on. I've have been going back and forth with a few things, so it has gone through some different versions (I was using Macetech's conversion during the last version, seen below).

#include <Wire.h>

const int button1 = 2;
const int button2 = 3;
const int LED = 13;
boolean butt1State;
boolean butt2State;
byte seconds, init_seconds, elap_seconds;

void setup()
{
  Wire.begin();
  Serial.begin(9600);
  
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
 
  // clear /EOSC bit
  // Sometimes necessary to ensure that the clock keeps running on just battery power.
  // Once set, it shouldn't need to be reset but it's a good idea to make sure.
  Wire.beginTransmission(0x68); // address DS3231
  Wire.write(0x0E); // select register
  Wire.write(0b00011100); // write register bitmap, bit 7 is /EOSC
  Wire.endTransmission(); 
  
  //Below was used for decoding and debugging
  //Serial.print("BIN");Serial.print("\t");Serial.print("DEC");Serial.print("\t");Serial.println("HEX");
}
 
void loop() {
  butt1State=digitalRead(button1);
  butt2State=digitalRead(button2);
  if(butt1State==LOW) {
    initialTime();
    Serial.println(init_seconds);
    //Serial.print("\t");Serial.print(init_seconds, DEC);Serial.print("\t");Serial.println(init_seconds, HEX);
    delay(1000);
  }
  if(butt2State==LOW) {
    elapsedTime();
    Timer();  
    digitalWrite(LED, !butt2State);
    Serial.print(elap_seconds);Serial.print("\t");Serial.print(seconds);Serial.print("\t");Serial.print(seconds, BIN);Serial.print("\t");Serial.println(seconds, HEX);
    delay(1000);
  } else {
    digitalWrite(LED, LOW);
  }
}

int Timer() {
  seconds=elap_seconds-init_seconds;
}
int initialTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    init_seconds = Wire.read();
    init_seconds = (((init_seconds & 0b11110000)>>4)*10+(init_seconds & 0b00001111));
    return(init_seconds);
  }
}
int elapsedTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    elap_seconds = Wire.read();
    elap_seconds = (((elap_seconds & 0b11110000)>>4)*10+(elap_seconds & 0b00001111));
    return(elap_seconds);
  }
}

This is the first time I'm working with BCD and Decimal conversions, and my issue may also be my math after the conversions. I will attempt your conversion tomorrow.

I'm not clear on your requirement, do you need to time intervals longer than 59 seconds? If so, how long? In order to time longer intervals, more than the seconds register from the RTC will need to be used.

The result of Macetech's conversion will be exactly the same as mine. His may be more efficient, I'm not sure that the modulo operator (%) is always the best in that regard. I should have a look into that.

macetech's code assumes seconds is in the range 0..59

imho the compiler should be able to optimize this modulo - (n % 16) - as it can also optimize /16


update:
this is almost fastest conversion I can think of. It replaces the /16*10 with 2 shifts and an addition. The masking still exists

int bcd2dec(uint8_t in)
{
  uint8_t out = (in & 0xF0) >> 1; 
  return out + out >> 2 + (in & 0x0F);  
}

robtillaart:
imho the compiler should be able to optimize this modulo - (n % 16) - as it can also optimize /16

Indeed it does, in fact the two approaches turn out to be identical. (I'm always amazed by this compiler when I try to check into something like this, a person really has to jump through some hoops just to ensure everything doesn't get optimized clean away!)

update:
this is almost fastest conversion I can think of. It replaces the /16*10 with 2 shifts and an addition. The masking still exists

int bcd2dec(uint8_t in)

{
  uint8_t out = (in & 0xF0) >> 1;
  return out + out >> 2 + (in & 0x0F); 
}

00000140 <_Z8bcd2dec1h>:
}

uint8_t bcd2dec1(uint8_t n)
{
	return ((n / 16 * 10) + (n % 16));
 140:	98 2f       	mov	r25, r24
 142:	92 95       	swap	r25
 144:	9f 70       	andi	r25, 0x0F	; 15
 146:	8f 70       	andi	r24, 0x0F	; 15
}
 148:	2a e0       	ldi	r18, 0x0A	; 10
 14a:	92 9f       	mul	r25, r18
 14c:	80 0d       	add	r24, r0
 14e:	11 24       	eor	r1, r1
 150:	08 95       	ret

00000152 <_Z8bcd2dec2h>:

uint8_t bcd2dec2(uint8_t n)
{
	return ((n & 0b11110000) >> 4) * 10 + (n & 0b00001111);
 152:	98 2f       	mov	r25, r24
 154:	92 95       	swap	r25
 156:	9f 70       	andi	r25, 0x0F	; 15
 158:	8f 70       	andi	r24, 0x0F	; 15
}
 15a:	2a e0       	ldi	r18, 0x0A	; 10
 15c:	92 9f       	mul	r25, r18
 15e:	80 0d       	add	r24, r0
 160:	11 24       	eor	r1, r1
 162:	08 95       	ret

00000164 <_Z8bcd2dec3h>:

int bcd2dec3(uint8_t in)
{
	uint8_t out = (in & 0xF0) >> 1;
 164:	28 2f       	mov	r18, r24
 166:	20 7f       	andi	r18, 0xF0	; 240
	return out + out >> 2 + (in & 0x0F);
 168:	30 e0       	ldi	r19, 0x00	; 0
 16a:	8f 70       	andi	r24, 0x0F	; 15
 16c:	90 e0       	ldi	r25, 0x00	; 0
 16e:	02 96       	adiw	r24, 0x02	; 2
 170:	02 c0       	rjmp	.+4      	; 0x176 <_Z8bcd2dec3h+0x12>
 172:	35 95       	asr	r19
 174:	27 95       	ror	r18
 176:	8a 95       	dec	r24
 178:	e2 f7       	brpl	.-8      	; 0x172 <_Z8bcd2dec3h+0xe>
 17a:	c9 01       	movw	r24, r18
 17c:	08 95       	ret

:blush: :blush: :blush: :slight_smile:

Thank you very much guys. I apologize if my questions were vague, I had a hard time putting into words exactly what I was looking for. The piece of code I was looking for was this:

seconds = ((60 + elap_seconds) - init_seconds) % 60;

This would allow the "counter" to count up to 60 seconds and reset to 0 at 60. Here, I only worked with seconds to keep it a bit simpler, and not deal with minutes just yet. So yes, this code is only concerned with intervals of 59 seconds. I am now able to focus on the minutes (and hours) for the rollover time. My Timer function now looks like this:

int Timer() {
  elapsedTime();
  seconds = ((60 + elap_seconds) - init_seconds) % 60;
  if(seconds%60==0) {
    minutes = ((60 + elap_minutes) - init_minutes) % 60;
  }
  if(minutes%24==0) {
    hours = ((24 + elap_hours) - init_hours) % 24;
  }
}

When a full 60 seconds pass, it rolls over to minutes, and continues counting seconds.

The code I know have is below:

#include <Wire.h>

const int button1 = 2;
const int button2 = 3;
const int LED = 13;
boolean butt1State;
boolean butt2State;
int seconds, minutes, hours, init_seconds, init_minutes, init_hours, elap_seconds, elap_minutes, elap_hours;

void setup() {
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  
  Wire.begin();
  Serial.begin(9600);
  
  // clear /EOSC bit
  // Sometimes necessary to ensure that the clock keeps running on just battery power.
  // Once set, it shouldn't need to be reset but it's a good idea to make sure.
  Wire.beginTransmission(0x68); // address DS3231
  Wire.write(0x0E); // select register
  Wire.write(0b00011100); // write register bitmap, bit 7 is /EOSC
  Wire.endTransmission();
  
  Serial.print("Initial Time");Serial.print("\t");Serial.print("Current Time");Serial.print("\t");Serial.println("Elapsed Time");
}

void loop() {
  butt1State=digitalRead(button1);
  butt2State=digitalRead(button2);
  
  if(butt1State==LOW) {
    initialTime();
    Serial.print(init_hours);Serial.print(":");Serial.print(init_minutes);Serial.print(":");Serial.println(init_seconds);
    digitalWrite(LED, !butt1State);
    delay(1000);
  }
  
  if(butt2State==LOW) {
    Timer();
    Serial.print(init_hours);Serial.print(":");Serial.print(init_minutes);Serial.print(":");Serial.print(init_seconds);Serial.print("\t");
    Serial.print(elap_hours);Serial.print(":");Serial.print(elap_minutes);Serial.print(":");Serial.print(elap_seconds);Serial.print("\t");
    Serial.print(hours);Serial.print(":");Serial.print(minutes);Serial.print(":");Serial.println(seconds);
    digitalWrite(LED, !butt2State);
    delay(1000);
  }
}

int Timer() {
  elapsedTime();
  seconds = ((60 + elap_seconds) - init_seconds) % 60;
  if(seconds%60==0) {
    minutes = ((60 + elap_minutes) - init_minutes) % 60;
  }
  if(minutes%24==0) {
    hours = ((24 + elap_hours) - init_hours) % 24;
  }
}

int initialTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 3);
  
  while(Wire.available()) {
    init_seconds = Wire.read();
    init_minutes = Wire.read();
    init_hours = Wire.read();
    
    init_seconds = (((init_seconds & 0b11110000)>>4)*10 + (init_seconds & 0b00001111)); // convert BCD to decimal
    init_minutes = (((init_minutes & 0b11110000)>>4)*10 + (init_minutes & 0b00001111)); // convert BCD to decimal
    init_hours = (((init_hours & 0b00100000)>>5)*20 + ((init_hours & 0b00010000)>>4)*10 + (init_hours & 0b00001111)); // convert BCD to decimal (assume 24 hour mode)
    return(init_seconds);
  }
}

int elapsedTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 3);
  
  while(Wire.available()) {
    elap_seconds = Wire.read();
    elap_minutes = Wire.read();
    elap_hours = Wire.read();
    
    elap_seconds = (((elap_seconds & 0b11110000)>>4)*10 + (elap_seconds & 0b00001111)); // convert BCD to decimal
    elap_minutes = (((elap_minutes & 0b11110000)>>4)*10 + (elap_minutes & 0b00001111)); // convert BCD to decimal
    elap_hours = (((elap_hours & 0b00100000)>>5)*20 + ((elap_hours & 0b00010000)>>4)*10 + (elap_hours & 0b00001111)); // convert BCD to decimal (assume 24 hour mode
    return(elap_seconds);
  }
}

I just modified the code a bit to include a leading zero for the hour, minute or second < 0

// Program Size: 6,660/32,256 bytes

#include <Wire.h>

const int button1 = 2;
const int button2 = 3;
const int LED = 13;
boolean butt1State, butt2State;
int seconds, minutes, hours, init_seconds, init_minutes, init_hours, elap_seconds, elap_minutes, elap_hours;

void setup() {
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  
  Wire.begin();
  Serial.begin(9600);
  
  // clear /EOSC bit
  // Sometimes necessary to ensure that the clock keeps running on just battery power.
  // Once set, it shouldn't need to be reset but it's a good idea to make sure.
  Wire.beginTransmission(0x68); // address DS3231
  Wire.write(0x0E); // select register
  Wire.write(0b00011100); // write register bitmap, bit 7 is /EOSC
  Wire.endTransmission();
  
  Serial.print("Initial Time");Serial.print("\t");Serial.print("Current Time");Serial.print("\t");Serial.println("Elapsed Time");
}

void loop() {
  butt1State=digitalRead(button1);
  butt2State=digitalRead(button2);
  
  if(butt1State==LOW) {
    initialTime();
    if(init_hours<10) {
      Serial.print("0");Serial.print(init_hours);
    } else {
      Serial.print(init_hours);
    }
    Serial.print(":");
    if(init_minutes<10) {
      Serial.print("0");Serial.print(init_minutes);
    } else {
      Serial.print(init_minutes);
    }
    Serial.print(":");
    if(init_seconds<10) {
      Serial.print("0");Serial.println(init_seconds);
    } else {
      Serial.println(init_seconds);
    }
    digitalWrite(LED, !butt1State);
    delay(1000);
  }
  
  if(butt2State==LOW) {
    Timer();
    if(init_hours<10) {
      Serial.print("0");Serial.print(init_hours);
    } else {
      Serial.print(init_hours);
    }
    Serial.print(":");
    if(init_minutes<10) {
      Serial.print("0");Serial.print(init_minutes);
    } else {
      Serial.print(init_minutes);
    }
    Serial.print(":");
    if(init_seconds<10) {
      Serial.print("0");Serial.print(init_seconds);Serial.print("\t");
    } else {
      Serial.print(init_seconds);Serial.print("\t");
    }
    
    if(elap_hours<10) {
      Serial.print("0");Serial.print(elap_hours);
    } else {
      Serial.print(elap_hours);
    }
    Serial.print(":");
    if(elap_minutes<10) {
      Serial.print("0");Serial.print(elap_minutes);
    } else {
      Serial.print(elap_minutes);
    }
    Serial.print(":");
    if(elap_seconds<10) {
      Serial.print("0");Serial.print(elap_seconds);Serial.print("\t");
    } else {
      Serial.print(elap_seconds);Serial.print("\t");
    }
    if(hours<10) {
      Serial.print("0");Serial.print(hours);
    } else {
      Serial.print(hours);
    }
    Serial.print(":");
    if(minutes<10) {
      Serial.print("0");Serial.print(minutes);
    } else {
      Serial.print(minutes);
    }
    Serial.print(":");
    if(seconds<10) {
      Serial.print("0");Serial.println(seconds);
    } else {
      Serial.println(seconds);
    }
    digitalWrite(LED, !butt2State);
    delay(1000);
  }
}

int Timer() {
  elapsedTime();
  seconds = ((60 + elap_seconds) - init_seconds) % 60;
  if(seconds%60==0) {
    minutes = ((60 + elap_minutes) - init_minutes) % 60;
  }
  if(minutes%24==0) {
    hours = ((24 + elap_hours) - init_hours) % 24;
  }
}

int initialTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 3);
  
  while(Wire.available()) {
    init_seconds = Wire.read();
    init_minutes = Wire.read();
    init_hours = Wire.read();
    
    init_seconds = (((init_seconds & 0b11110000)>>4)*10 + (init_seconds & 0b00001111)); // convert BCD to decimal
    init_minutes = (((init_minutes & 0b11110000)>>4)*10 + (init_minutes & 0b00001111)); // convert BCD to decimal
    init_hours = (((init_hours & 0b00100000)>>5)*20 + ((init_hours & 0b00010000)>>4)*10 + (init_hours & 0b00001111)); // convert BCD to decimal (assume 24 hour mode)
    return(init_seconds);
  }
}

int elapsedTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 3);
  
  while(Wire.available()) {
    elap_seconds = Wire.read();
    elap_minutes = Wire.read();
    elap_hours = Wire.read();
    
    elap_seconds = (((elap_seconds & 0b11110000)>>4)*10 + (elap_seconds & 0b00001111)); // convert BCD to decimal
    elap_minutes = (((elap_minutes & 0b11110000)>>4)*10 + (elap_minutes & 0b00001111)); // convert BCD to decimal
    elap_hours = (((elap_hours & 0b00100000)>>5)*20 + ((elap_hours & 0b00010000)>>4)*10 + (elap_hours & 0b00001111)); // convert BCD to decimal (assume 24 hour mode
    return(elap_seconds);
  }
}

If anyone wants to see the output (the initial time and elapsed time are printed in 24 HR time format):

Initial Time	Current Time	Elapsed Time
15:22:30
15:22:30	15:22:31	00:00:01
15:22:30	15:22:32	00:00:02
15:22:30	15:22:33	00:00:03
15:22:30	15:22:34	00:00:04
15:22:30	15:22:35	00:00:05
...
15:22:30	15:23:25	00:00:55
15:22:30	15:23:26	00:00:56
15:22:30	15:23:27	00:00:57
15:22:30	15:23:28	00:00:58
15:22:30	15:23:29	00:00:59
15:22:30	15:23:30	00:01:00
15:22:30	15:23:31	00:01:01
15:22:30	15:23:32	00:01:02
15:22:30	15:23:33	00:01:03
15:22:30	15:23:34	00:01:04
15:22:30	15:23:35	00:01:05
...
15:22:30	15:24:25	00:01:55
15:22:30	15:24:26	00:01:56
15:22:30	15:24:27	00:01:57
15:22:30	15:24:28	00:01:58
15:22:30	15:24:29	00:01:59
15:22:30	15:24:30	00:02:00
15:22:30	15:24:31	00:02:01

Thanks again for all your help. English is my native language, but programming is still a bit foreign to me XD

You guys have been discussing the accuracy of the BCD conversion, I'm not exactly sure why Macetech's is "more efficient". Is this more of a programming reason, or a mathematical reason (e.g., 10%6 will still return a value, just not an integer)?

My next step with this, is to time different events and add them together. That is;
I walk into a room->timer starts,
I walk out 31 seconds later: T = 00:00:31,
5 minutes later I walk into the room, timer starts again,
I walk out 2 minutes and 14 seconds later: T = 00:02:45
and so on...

Osiris:
Thanks again for all your help. English is my native language, but programming is still a bit foreign to me XD

No problem. Nice job on the code. (BTW, the language that all programmers know best is profanity :wink: )

You guys have been discussing the accuracy of the BCD conversion, I'm not exactly sure why Macetech's is "more efficient". Is this more of a programming reason, or a mathematical reason (e.g., 10%6 will still return a value, just not an integer)?

It's not at all a question of accuracy, I could tell that Macetech's conversion would give the same results as mine. However, I thought that his might be more efficient (fewer instructions). Once I looked at the assembler code, I found that the two were in fact identical.

Choose whichever looks better to you, play rock-paper-scissors to decide, etc. :smiley:

Thanks and I'm well versed in profanity :slight_smile:

I'm now running into a bit of trouble of getting this work as a start/stop timer. Basically, I'd like to hit a button and the timer starts:

00:00:00 // Initial Time
// Button press
00:00:01 
00:00:02
// Button release
// Wait some time, e.g., 10 seconds
// Button press
00:00:03
00:00:04
...

The above code would continuously count after the initial button press, i.e., as soon as the button is pressed the time is reset and begins and continues counting. I was then able to have it start and stop on button presses and declared a new variable t_seconds += seconds, however, let's say the initial timer ended at 00:00:05, during the second start of the timer (which now makes sense), it is going to compound the time so I would get:

00:00:06 //+1
00:00:08 //+2
00:00:11 //+3
etc...

and would potentially miss, and continue counting pass the 60 second mark [currently only focusing on the second timer]. I thought I could solve this by dividing seconds into itself: seconds+=(seconds/seconds), but this would sometimes return unexpected numbers.

I have a few ideas of what it might be, but having a bid hard of a time interpreting it I guess (who am I kidding, it's coming down to math again XD !). If anyone has any ideas, please. Thanks.

A minor fix:

seconds = ((60 + elap_seconds) - init_seconds) % 60;

That is equivalent to:

seconds = (elap_seconds - init_seconds) % 60;

The same type of thing applies to the minutes and hours.

Pete

el_supremo:
A minor fix:

seconds = ((60 + elap_seconds) - init_seconds) % 60;

That is equivalent to:

seconds = (elap_seconds - init_seconds) % 60;

The same type of thing applies to the minutes and hours.

Pete

Hmm. I thought perhaps the "extra" 60 was to prevent negative values when elap_time resets and then becomes less than init_time?

So i understand a bit more of what is going on here...
I'm just going to deal with the seconds here (and is probably the only thing we have to concern ourselves with). Below is the pasted code, but I'l reference.

// Program Size: 6,660/32,256 bytes

#include <Wire.h>

const int button1 = 2;
const int button2 = 3;
const int LED = 13;
boolean butt1State, butt2State;
int seconds, minutes, hours, 
    init_seconds, init_minutes, init_hours, 
    elap_seconds, elap_minutes, elap_hours,
    t_seconds, t_minutes, t_hours;

void setup() {
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  
  Wire.begin();
  Serial.begin(9600);
  
  // clear /EOSC bit
  // Sometimes necessary to ensure that the clock keeps running on just battery power.
  // Once set, it shouldn't need to be reset but it's a good idea to make sure.
  Wire.beginTransmission(0x68); // address DS3231
  Wire.write(0x0E); // select register
  Wire.write(0b00011100); // write register bitmap, bit 7 is /EOSC
  Wire.endTransmission();
}

void loop() {
  butt1State=digitalRead(button1);
  butt2State=digitalRead(button2);
   
  if(butt1State==LOW) {
    initialTime();
    Serial.print(init_hours);Serial.print(":");
    Serial.print(init_minutes);Serial.print(":");
    Serial.println(init_seconds);
    delay(1000);
  }
  
  //Use digitalRead(button2)==LOW for start/stop
  if(digitalRead(button2)==LOW) { 
    runTime();
    //Use t_seconds to store each second that passes when button is pressed
    //divide seconds into itself for compound addition of one
    t_seconds+=(seconds/seconds);
    
    
    //Resets the seconds when t_seconds reaches 60
    if(t_seconds%60==0) {
      t_seconds=0;
      t_minutes++;
    }
    
    Serial.print(init_hours);Serial.print(":");Serial.print(init_minutes);Serial.print(":");Serial.print(init_seconds);Serial.print("\t");
    Serial.print(elap_hours);Serial.print(":");Serial.print(elap_minutes);Serial.print(":");Serial.print(elap_seconds);Serial.print("\t");
    Serial.print(hours);Serial.print(":");Serial.print(minutes);Serial.print(":");Serial.print(seconds);Serial.print("\t");
    Serial.print(t_hours);Serial.print(":");Serial.print(t_minutes);Serial.print(":");Serial.println(t_seconds);
    delay(1000);
  }
  
}

int runTime() {
  
  elapsedTime();
  seconds = ((60 + elap_seconds) - init_seconds) % 60;
  
/* This code was an attempt to reset the initial time and avoid the -1 error
  if(elap_seconds%60==0) {
    elap_minutes++;
    initialTime();
  }
*/

}

int initialTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    init_seconds = Wire.read();
    init_seconds = (((init_seconds & 0b11110000)>>4)*10 + (init_seconds & 0b00001111)); // convert BCD to decimal
    init_seconds = init_seconds - init_seconds; //set init_seconds to zero
  }
}

int elapsedTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    elap_seconds = Wire.read();
    elap_seconds = (((elap_seconds & 0b11110000)>>4)*10 + (elap_seconds & 0b00001111)); // convert BCD to decimal
  }
}

I am using t_seconds to store the seconds ONLY when the button is pressed. This part works, but there are two problems:
(1) When seconds rolls over from 59 to 0, t_seconds=(seconds/seconds) actually becomes 0 divided by 0 and returns -1. This then subtracts 1 from t_seconds, but will continue adding and repeat this at the same time point. I've tried negating the -1 by multiplication, or adding 2 i.e., ((-1)+1=0+1=1), but it will always subtract the 1.
(2) The use of delay waits for the second to pass, removal of the delay, will add +1 as fast as the MCU can, e.g., if I were to set delay(500); than each passing "actual" second the MCU would actually be adding 2 seconds. The delay here was more for debugging, and I am trying to avoid the use of delay.

Therefore, I seem to be back at square one. Any ideas anyone?

Osiris:
Hmm. I thought perhaps the "extra" 60 was to prevent negative values when elap_time resets and then becomes less than init_time?

Correct, leave that first 60 in there.

Not sure I'm following this, or why it's needed:

    //Use t_seconds to store each second that passes when button is pressed
    //divide seconds into itself for compound addition of one
    t_seconds+=(seconds/seconds);

Instead of:

    //Resets the seconds when t_seconds reaches 60
    if(t_seconds%60==0) {
      t_seconds=0;
      t_minutes++;
    }

Why not:

    //Resets the seconds when t_seconds reaches 60
    if(t_seconds >= 60) {
      t_seconds=0;
      t_minutes++;
    }

SImilarly, instead of:

    init_seconds = init_seconds - init_seconds; //set init_seconds to zero

I'd have written:

    init_seconds = 0;  //set init_seconds to zero

Therefore, I seem to be back at square one. Any ideas anyone?

Things are getting mighty complicated. There are four sets of variables for tracking time. The initialTime() and elapsedTime() functions are essentially duplicates. I'd have the function pass back the seconds as the return value, then only one function is needed. Lastly, some switch debouncing is needed.

Hope you don't mind a spoiler, if so read no further :wink:

//DS3231 stopwatch.
//Only utilizes seconds register, therefore cannot
//time intervals > 59 seconds.

#include <Wire.h>
#include <Button.h>                  //http://github.com/JChristensen/Button
const int START_BTN = 2;
const int STOP_BTN = 3;
const boolean PULLUP = true;
const boolean INVERT = true;
const int DEBOUNCE_MS = 25;

Button btnStart(START_BTN, PULLUP, INVERT, DEBOUNCE_MS);
Button btnStop(STOP_BTN, PULLUP, INVERT, DEBOUNCE_MS);
int startSeconds;
int stopSeconds;
int lapse;

void setup(void)
{
    Wire.begin();
    Serial.begin(9600);
    Serial.println("DS3231 Stopwatch");
}

void loop(void)
{
    btnStart.read();
    btnStop.read();
    
    if (btnStart.wasPressed()) {
        startSeconds = getRtcSeconds();
        Serial.print("Start=");
        Serial.println(startSeconds, DEC);
    }
    else if (btnStop.wasPressed()) {
        stopSeconds = getRtcSeconds();
        lapse = ((60 + stopSeconds) - startSeconds) % 60;
        Serial.print("Start=");
        Serial.print(startSeconds, DEC);
        Serial.print(" Stop=");
        Serial.print(stopSeconds, DEC);
        Serial.print(" Lapse=");
        Serial.println(lapse, DEC);
    }
}

int getRtcSeconds(void)
{
    Wire.beginTransmission(0x68);    //DS3231 device address
    Wire.write(0x00);                //seconds register
    Wire.endTransmission();
    Wire.requestFrom(0x68, 1);
    return bcd2dec(Wire.read());
}

//BCD-to-Decimal conversion
uint8_t bcd2dec(uint8_t n)
{
    return ((n / 16 * 10) + (n & 0x0F));
}

I did not use

if(t_seconds>=60) {
  // code
}

because originally I had t_seconds as seconds, so the value never exceeded 59 seconds. Mostly a copy/paste of the code from before.

As for subtracting init_seconds, I could have just set it to zero. However, in keeping an initialTime(); I can reset the time and the elapsedTime(); will then start at zero. Whereas if I were to subtract elap_seconds-(0) I would return a value anywhere from 0-59.

Below is the code I have right now. I've added some if statements and more conditions for increasing the minutes and while trying to avoid using a delay. I've removed some of the extra variables I wasn't using, and some of the redundant ones should only be for the current debugging purposes.

// Program Size: 6,660/32,256 bytes

#include <Wire.h>

const int button1 = 2;
const int button2 = 3;
const int LED = 13;
boolean butt1State, butt2State;
int seconds, minutes, hours, 
    init_seconds, init_minutes, init_hours, 
    elap_seconds, elap_minutes, elap_hours,
    t_seconds, t_minutes, t_hours,
    last_minute;
boolean passed_second=true;

void setup() {
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  
  Wire.begin();
  Serial.begin(9600);
  
  // clear /EOSC bit
  // Sometimes necessary to ensure that the clock keeps running on just battery power.
  // Once set, it shouldn't need to be reset but it's a good idea to make sure.
  Wire.beginTransmission(0x68); // address DS3231
  Wire.write(0x0E); // select register
  Wire.write(0b00011100); // write register bitmap, bit 7 is /EOSC
  Wire.endTransmission();
}

void loop() {
  butt1State=digitalRead(button1);
  butt2State=digitalRead(button2);
   
  if(butt1State==LOW) {
    initialTime();
    Serial.print(init_hours);Serial.print(":");Serial.print(init_minutes);Serial.print(":");Serial.println(init_seconds);
    delay(1000);
  }
   
  if(digitalRead(button2)==LOW) { // Use digitalRead(button2)==LOW for start/stop
    elapsedTime();
    // When 60 seconds has been reached and if the last minute has changed after a second has gone by
    // Increase the minute by 1
    if(seconds%60==0 && minutes==last_minute && passed_second==true) {
      minutes++; // 1 minute increase
      last_minute++; // last minute has now increased
      passed_second=false; // A second has not passed yet, wait for it to pass
    }
    
    // Wait for a full second to pass
    if((elap_seconds-init_seconds)==1) {
      passed_second=true;
    }
    
    Serial.print(init_hours);Serial.print(":");Serial.print(init_minutes);Serial.print(":");Serial.print(init_seconds);Serial.print("\t");
    Serial.print(elap_hours);Serial.print(":");Serial.print(elap_minutes);Serial.print(":");Serial.print(elap_seconds);Serial.print("\t");
    Serial.print(hours);Serial.print(":");Serial.print(minutes);Serial.print(":");Serial.print(seconds);Serial.print("\t");
    Serial.print(t_hours);Serial.print(":");Serial.print(t_minutes);Serial.print(":");Serial.println(t_seconds);
   
  } 
}

int initialTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    init_seconds = Wire.read();
    init_seconds = (((init_seconds & 0b11110000)>>4)*10 + (init_seconds & 0b00001111)); // convert BCD to decimal
  }
}

int elapsedTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    elap_seconds = Wire.read();
    elap_seconds = (((elap_seconds & 0b11110000)>>4)*10 + (elap_seconds & 0b00001111)); // convert BCD to decimal
    seconds = (((60 + elap_seconds) - init_seconds) % 60);
  }
}

Hopefully my explanation doesn't become too confusing for what my goal is here. BTW, thanks again to all for the codes and library references and different conversion iterations, it is all appreciated. Please bear with me though, as I am the type of person that once an idea in their head, will not quit until it's accomplished, or has fully exhausted all possibilities.

The purpose of t_seconds was supposed to be a new holder for seconds. Currently, when the code is executed, seconds returns the time difference (?T in seconds) from when the first button was pushed. The ?T (from when the time was reset) is then displayed when button 2 is pushed. What I'd like to happen is this: the first button is pushed and resets the time and begins counting from zero. When the second button is pushed, the ?T is stored in t_seconds. This works well for the first time. Now after some time, a button is pressed, and t_seconds begins counting again from where it left off - despite the fact that elapsedTime(); has been continuously running.

I hope that makes a bit more sense.

Okay...

So I believe this is what I've wanted to accomplish. It may not be pretty, but it is a starting point. Basically, if you press "Button 1" the timer starts. Pressing "Button 2" will then start a secondary timer. The secondary timer will continue to increment as long as Button 2 is pressed. Releasing Button 2 will stop the secondary timer. If you press Button 2 again, the timer will continue from where it left off.

// Program Size: 6,660/32,256 bytes

#include <Wire.h>

const int button1 = 2;
const int button2 = 3;
const int LED = 13;
boolean butt1State, butt2State;
int seconds, minutes, hours, 
    init_seconds, init_minutes, init_hours, 
    elap_seconds, elap_minutes, elap_hours,
    t_seconds, t_minutes, t_hours,
    last_second;
boolean passed_second=true;

void setup() {
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  
  Wire.begin();
  Serial.begin(9600);
  
  // clear /EOSC bit
  // Sometimes necessary to ensure that the clock keeps running on just battery power.
  // Once set, it shouldn't need to be reset but it's a good idea to make sure.
  Wire.beginTransmission(0x68); // address DS3231
  Wire.write(0x0E); // select register
  Wire.write(0b00011100); // write register bitmap, bit 7 is /EOSC
  Wire.endTransmission();
}

void loop() {
  butt1State=digitalRead(button1);
  butt2State=digitalRead(button2);
   
  if(butt1State==LOW) {
    initialTime();
    Serial.print(init_hours);Serial.print(":");Serial.print(init_minutes);Serial.print(":");Serial.println(init_seconds);
    Serial.print("Ti");Serial.print("\t");Serial.print("T");Serial.print("\t");Serial.print("Te");Serial.print("\t");Serial.println("Tt");
    delay(1000);
  }
   
  if(digitalRead(button2)==LOW) { // Use digitalRead(button2)==LOW for start/stop
    elapsedTime();
    
    if(last_second != seconds && (seconds%60!=0)) {
      t_seconds+=seconds/seconds;
      last_second=seconds;
    } else if(seconds%60==0 && passed_second==true) {
      t_seconds++;
      passed_second=false;
    }
    if(t_seconds==60) {
      t_seconds=0;
      t_minutes++;
    }
    // When 60 seconds has been reached and if the last minute has changed after a second has gone by
    // Increase the minute by 1
    if(seconds%60==0 && passed_second==true) {
      minutes++; // 1 minute increase
      passed_second=false; // A second has not passed yet, wait for it to pass
    }
    
    // Wait for a full second to pass
    if((elap_seconds-init_seconds)==1) {
      passed_second=true;
    }
    
    Serial.print(init_hours);Serial.print(":");Serial.print(init_minutes);Serial.print(":");Serial.print(init_seconds);Serial.print("\t");
    Serial.print(elap_hours);Serial.print(":");Serial.print(elap_minutes);Serial.print(":");Serial.print(elap_seconds);Serial.print("\t");
    Serial.print(hours);Serial.print(":");Serial.print(minutes);Serial.print(":");Serial.print(seconds);Serial.print("\t");
    Serial.print(t_hours);Serial.print(":");Serial.print(t_minutes);Serial.print(":");Serial.println(t_seconds);
   
  } 
}

int initialTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    init_seconds = Wire.read();
    init_seconds = (((init_seconds & 0b11110000)>>4)*10 + (init_seconds & 0b00001111)); // convert BCD to decimal
  }
}

int elapsedTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    elap_seconds = Wire.read();
    elap_seconds = (((elap_seconds & 0b11110000)>>4)*10 + (elap_seconds & 0b00001111)); // convert BCD to decimal
    seconds = (((60 + elap_seconds) - init_seconds) % 60);
    
  }
}

EDIT: Now this code will time how long each button is pressed. initialTime(); is called once in Setup and only once again in Loop.
N.B. Here are some of the things to work out: (1)This code can not count both buttons at the same time (not needed for my purposes); (2)the code is repetitive, try and make a new function; (3)Overall Timer does not increment minutes

// Program Size: 6,660/32,256 bytes

#include <Wire.h>

const int button1 = 2;
const int button2 = 3;
const int LED = 13;
boolean butt1State, butt2State;
int seconds, minutes, hours, 
    init_seconds, init_minutes, init_hours, 
    elap_seconds, elap_minutes, elap_hours,
    b1_seconds, b1_minutes, b1_hours,
    b2_seconds, b2_minutes, b2_hours,
    last_second;
boolean passed_second=true;
boolean start=true;

void setup() {
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  
  Wire.begin();
  Serial.begin(9600);
  
  // clear /EOSC bit
  // Sometimes necessary to ensure that the clock keeps running on just battery power.
  // Once set, it shouldn't need to be reset but it's a good idea to make sure.
  Wire.beginTransmission(0x68); // address DS3231
  Wire.write(0x0E); // select register
  Wire.write(0b00011100); // write register bitmap, bit 7 is /EOSC
  Wire.endTransmission();
  
  initialTime();
  Serial.print("Start Time: ");Serial.print(init_hours);Serial.print(":");Serial.print(init_minutes);Serial.print(":");Serial.println(init_seconds);
  Serial.print("Time");Serial.print("\t");Serial.print("B1 Timer");Serial.print("\t");Serial.println("B2 Timer");
}

void loop() {
  if(start==true) {
    initialTime();
    start=false;
  }
  
  butt1State=digitalRead(button1);
  butt2State=digitalRead(button2);
   
  if(digitalRead(button1)==LOW) { // Use digitalRead(button#)==LOW for start/stop
    elapsedTime();
    
    if(last_second != seconds && (seconds%60!=0)) {
      b1_seconds+=seconds/seconds;
      last_second=seconds;
    } else if(seconds%60==0 && passed_second==true) {
      b1_seconds++;
      passed_second=false;
    }
    if(b1_seconds==60) {
      b1_seconds=0;
      b1_minutes++;
    }
    // When 60 seconds has been reached and if the last minute has changed after a second has gone by
    // Increase the minute by 1
    if(seconds%60==0 && passed_second==true) {
      //minutes++; // 1 minute increase
      passed_second=false; // A second has not passed yet, wait for it to pass
    }
    
    // Wait for a full second to pass
    if((elap_seconds-init_seconds)==1) {
      passed_second=true;
    }
    
    Serial.print(hours);Serial.print(":");Serial.print(minutes);Serial.print(":");Serial.print(seconds);Serial.print("\t");
    Serial.print(b1_hours);Serial.print(":");Serial.print(b1_minutes);Serial.print(":");Serial.print(b1_seconds);Serial.print("\t");
    Serial.print(b2_hours);Serial.print(":");Serial.print(b2_minutes);Serial.print(":");Serial.println(b2_seconds);
  } 
   
  if(digitalRead(button2)==LOW) { // Use digitalRead(button#)==LOW for start/stop
    elapsedTime();
    
    if(last_second != seconds && (seconds%60!=0)) {
      b2_seconds+=seconds/seconds;
      last_second=seconds;
    } else if(seconds%60==0 && passed_second==true) {
      b2_seconds++;
      passed_second=false;
    }
    if(b2_seconds==60) {
      b2_seconds=0;
      b2_minutes++;
    }
    // When 60 seconds has been reached and if the last minute has changed after a second has gone by
    // Increase the minute by 1
    if(seconds%60==0 && passed_second==true) {
      minutes++; // 1 minute increase
      passed_second=false; // A second has not passed yet, wait for it to pass
    }
    
    // Wait for a full second to pass
    if((elap_seconds-init_seconds)==1) {
      passed_second=true;
    }
    
    Serial.print(hours);Serial.print(":");Serial.print(minutes);Serial.print(":");Serial.print(seconds);Serial.print("\t");
    Serial.print(b1_hours);Serial.print(":");Serial.print(b1_minutes);Serial.print(":");Serial.print(b1_seconds);Serial.print("\t");
    Serial.print(b2_hours);Serial.print(":");Serial.print(b2_minutes);Serial.print(":");Serial.println(b2_seconds);
   
  } 
}

int initialTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    init_seconds = Wire.read();
    init_seconds = (((init_seconds & 0b11110000)>>4)*10 + (init_seconds & 0b00001111)); // convert BCD to decimal
  }
}

int elapsedTime() {
  Wire.beginTransmission(0x68); // 0x68 is DS3231 device address
  Wire.write(0x00); // start at register
  Wire.endTransmission();
  Wire.requestFrom(0x68, 1);
  
  while(Wire.available()) {
    elap_seconds = Wire.read();
    elap_seconds = (((elap_seconds & 0b11110000)>>4)*10 + (elap_seconds & 0b00001111)); // convert BCD to decimal
    seconds = (((60 + elap_seconds) - init_seconds) % 60);
    
  }
}

You can work with BCD numbers directly, if you use these functions I wrote.
(The result will also be a BCD number)

inline byte bcdAdd (byte x, byte y) { // quick and dirty
   // "out of range" results will be e.g. A0 for 100
   byte z = (x&15)+(y&15);
   if (z<10) return (x + y);
   return (x + y + 6);
}

inline byte bcdSub (byte x, byte y) { // quick and dirty
   // "negative" results will be e.g. F9 for -1, F8 for -2...
   if ((x&15)>=(y&15)) return (x - y);
   return ((x - y) - 6);
}

And the way you exchange 60 seconds = 1 minute is quite straightforward:

if (seconds >= 0x60) {
  seconds = bcdSub(seconds, 0x60);
  minutes = bcdAdd(minutes, 0x01);
}

The way you add and subtract times (hh:mm:ss) is almost exactly like the way you learned to add and subtract in elementary school. This is true even if you do not use BCD.

Addition:

  • Add the hours, minutes, and seconds each individually.
  • If more than 60 seconds, then subtract 60 from the seconds and then add 1 to the minutes.
  • If more than 60 minutes, subtract 60 from the minutes and then add 1 to the hours.

Subtraction:

  • First, add 59 to the minutes and 60 to the seconds. This ensures that neither the minutes nor the seconds will go negative when we try to subtract.
  • Subtract the hours, minutes, and seconds, each individually.
  • Perform carries as in addition.
  • The minutes and seconds we added in the first step equal exactly 1 hour, so finish up by subtracting 1 from the hours.