Go Down

Topic: Two-button clock (Read 197 times) previous topic - next topic

odometer

Jun 22, 2016, 02:46 pm Last Edit: Jun 22, 2016, 02:50 pm by odometer Reason: adding one more note
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.

Code: [Select]

// 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
*/


stievenart

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

odometer

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.

odometer

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.

odometer

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.

odometer

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

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy