Two-button clock

If you are making a clock, and you would rather not recompile your code every time you want to set the time, here is a way you can do it using only two buttons.

Notes:

  1. This code requires the use of an LCD character display, like for example this one, but the same basic idea would also work with other displays.

  2. I have not yet written support for an external RTC. The point here is not to make a good clock, but rather to demonstrate a procedure for setting a clock.

  3. Buttons are cheaper and easier to put in place than an external RTC. For "quick and dirty" projects, this code might be an acceptable substitute for an external RTC.

// packed time  hhmmssff
uint32_t pt = 0x23594500; // intentionally bogus (for testing)

uint32_t last_tick_usec = 0UL;
const uint32_t ONE_TICK_USEC = 20000UL; // 0.02 second is plenty of resolution
const uint32_t ONE_TICK_PACKED = 0x00000002; // see previous line
char buf[50]; // this is intentionally bigger than I need

uint8_t set_state = 0;
// set_state indicates what is currently being set, as follows:
// 7 for hour tens,   6 for hour ones,
// 5 for minute tens, 4 for minute ones,
// 3 for second tens, 2 for second ones,
// 1 for normal timekeeping mode (i.e. finished setting the time)
// and it is 0 if the time has not yet been set

const uint32_t TIME_SET_MAX = 0x29595999;
// each digit is at max possible value for time

const uint8_t BUTTON_PIN_A = 9; // this button moves the cursor
const uint8_t BUTTON_PIN_B = 8; // this button makes the numbers change

uint8_t button_a_now;
uint8_t button_a_last;
uint8_t button_b_now;
uint8_t button_b_last;

#include <LiquidCrystal.h>

//                RS  EN DB4 DB5 DB6 DB7
LiquidCrystal lcd( 7,  6,  5,  4,  3,  2);

void setup() {
  pinMode(BUTTON_PIN_A, INPUT_PULLUP);
  pinMode(BUTTON_PIN_B, INPUT_PULLUP);
  // pressing either button makes its pin's voltage go low
  button_a_now = (digitalRead(BUTTON_PIN_A)==LOW);
  button_a_last = button_a_now;
  button_b_now = (digitalRead(BUTTON_PIN_B)==LOW);
  button_b_last = button_b_now;
  lcd.begin(16, 2); // LCD dimensions
  disptime();
}

void loop() {
  button_a_last = button_a_now;
  button_b_last = button_b_now;
  // remember, pressing either button makes its pin's voltage go low
  button_a_now = (digitalRead(BUTTON_PIN_A)==LOW);
  button_b_now = (digitalRead(BUTTON_PIN_B)==LOW);
  // has either button just been pushed down?
  uint8_t pushed_a = (button_a_now && !button_a_last);
  uint8_t pushed_b = (button_b_now && !button_b_last);
  // what happens next depends on what mode we're in
  if (set_state <= 1) {
    // if we're in here, then the clock is running
    if (pushed_a) {
      // enter time setting mode
      pt &= 0xFFFFFF00;
      set_state = 7;
      disptime();
    }
    else {
      // this is what makes the clock actually keep track of time
      uint32_t old_pt = pt;      
      while ((micros()-last_tick_usec)>=ONE_TICK_USEC) {
        // figure out how much time has passed
        last_tick_usec += ONE_TICK_USEC;
        pt = timeAdd(pt, ONE_TICK_PACKED);
      } 
      if ((((pt+0x00000030)^(old_pt+0x00000030))&0xFFFFFF80)!=((uint32_t)0)) {
        // the magic in the previous line means "do this once every 1/2 second"
        if (pt>=0x24000000) {
          pt = timeSub(pt, 0x24000000);
          // when I implement a calendar, I will put code here to advance the date
        }
        disptime();
      }
    }
  }
  else if ((set_state >= 2) && (set_state <= 7)) {
    // if we're in here, then we're in the midst of setting the time
    if (pushed_a) {
      // move the cursor
      set_state--;      
      if (set_state <= 1) {
        // if we're finished setting the time, we start the clock running
        last_tick_usec = micros();
      }
      disptime();       
    }
    else if (pushed_b) {
      // change the number that the cursor is pointing to
      // (that is, the number itself gets changed; the cursor does not move)
      uint32_t digit_mask = (((uint32_t)0x0000000F)<<(4*set_state));
      if ((pt & digit_mask) >= (TIME_SET_MAX & digit_mask)) pt &= ~digit_mask;
      else pt += (digit_mask & 0x11111111);
      // show the new time
      disptime();         
    }
  }
  delay(10); // poor man's switch debounce
}

void disptime() {
  // set cursor to beginning of top row
  lcd.setCursor(0, 0);
  //                                01234567890123456 
  if      (set_state==0) lcd.print("Set me, please! ");
  else if (set_state==1) lcd.print("Current time:   ");
  else if (set_state<=7) lcd.print("Set the time:   ");
  else                   lcd.print("Oops, Error!    ");
  // set cursor to beginning of bottom row
  lcd.setCursor(0, 1);
  // make "tick-tock" animation
  char ticky = ' ';
  if ((set_state == 0) && (((byte)pt) < 0x50)) ticky = '?';
  if ((set_state == 1) && (((byte)pt) < 0x50)) ticky = '.';  
  //                                                    0123456789012345
  // show the time on the display, using this format:   HH:MM:SS.
  //            01  234  567  8 90123456
  sprintf(buf, "%02x:%02x:%02x%c       ", ((byte)(pt>>24)), ((byte)(pt>>16)), ((byte)(pt>>8)), ticky);
  lcd.print(buf);
  switch (set_state) {
    case 7: lcd.setCursor(0, 1); lcd.cursor(); break; // cursor under hour tens
    case 6: lcd.setCursor(1, 1); lcd.cursor(); break; // cursor under hour ones
    case 5: lcd.setCursor(3, 1); lcd.cursor(); break; // cursor under minute tens
    case 4: lcd.setCursor(4, 1); lcd.cursor(); break; // cursor under minute ones
    case 3: lcd.setCursor(6, 1); lcd.cursor(); break; // cursor under second tens
    case 2: lcd.setCursor(7, 1); lcd.cursor(); break; // cursor under second ones
    default: lcd.noCursor();                          // if not seting the time, then no cursor
  }
}



// some old time manipulation functions I dug up for this

uint32_t timeAdd (uint32_t x, uint32_t y) {
  // no sanity checking of input
  // format is hhmmssff with ff being decimal fractions of a second
  // "out of range" results are e.g. A0000000 for 100 hours
  uint32_t binsum = x + y;
  uint32_t carry = ((binsum + 0x06A6A666) ^ x ^ y) & 0x11111110;
  return (binsum + ((carry - (carry>>4)) & 0x06A6A666));  
}

uint32_t timeSub (uint32_t x, uint32_t y) {
  // no sanity checking of input
  // format is hhmmssff with ff being decimal fractions of a second
  // "negative" results are e.g. F9595999 for -0.01 second
  uint32_t bindiff = x - y;
  uint32_t borrow = (bindiff ^ x ^ y) & 0x11111110;
  return (bindiff - ((borrow - (borrow>>4)) & 0x06A6A666) );
}

/*
   Usage example:

   2 h 58 min 30.98 s --> 0x02583098
   0 h  1 min 44.06 s --> 0x00014406
   
   Addition:
   timeAdd(0x02583098, 0x00014406) gives 0x03001504
                             which means 3 h 0 min 15.04 s
                             
   Subtraction:
   timeSub(0x02583098, 0x00014406) gives 0x02564692
                             which means 2 h 56 min 46.92 s
*/

Hello odometer,

Which RTC clock chip are you using. I see no reference in you code or the body of your post. I am working on a DS1307 2-button and was looking for inspiration.

Thanks in advance and I apologize if I just missed the information,
stievenart

stievenart:
Which RTC clock chip are you using. I see no reference in you code or the body of your post.

There is no reference because there is no external RTC.

I did some work on support for the date as well as the time.
Here is what I have so far.

Note: This is a work in progress. It might well contain bugs I haven't found yet. It also isn't as "clean" as it could be.

settable_clock_test_2c.ino (11 KB)

Here is what I have now.

This will work with or without an external RTC.

Disclaimer: I finished this version only a few hours ago so it might have bugs.

settable_clock_test_2d_bkup_5.ino (14 KB)

I sort of cleaned up what I had, and now here is what I have.

Features:

  • Date and time display, crammed into one line of a 16x2 character LCD
  • Date and time manually settable by means of two push buttons
  • Support for an external RTC, but will still run without an RTC

Limitations:

  • No support for Daylight Saving Time
  • No sanity checking of dates (it will happily let you set the date to Feb. 30)
  • No alarms, no bells, no whistles, just the date and time

settable_clock_optional_rtc.ino (15.3 KB)

Now it chimes the hours, too. (Yes, I know, feature creep.)

Note #1: The chimes use the ToneAC library.

Note #2: In order to make room for the speaker, I had to move the buttons to different pins. See comments in the code.

settable_clock_optional_rtc_m2.ino (17.6 KB)

1 Like