Go Down

Topic: Setting the time on a clock with only one button (Read 846 times) previous topic - next topic

odometer

Jun 16, 2016, 01:49 pm Last Edit: Jun 16, 2016, 01:51 pm by odometer Reason: clarify title
I wanted to make a (minimal) Arduino clock on which I could use buttons to set the time. Unfortunately, I had only one button handy.

This is what I made. Besides the Arduino, it uses a 16x2 LCD display and one push-button switch. Here is the code:

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 = 50000UL; // 0.05 second is plenty of resolution
const uint32_t ONE_TICK_PACKED = 0x00000005; // 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 pause after setting time,
// 0 for normal timekeeping mode

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

const uint8_t BUTTON_PIN = 8;

uint8_t button_now;
uint8_t button_last;
uint32_t button_down_usec; // timestamp

#include <LiquidCrystal.h>

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

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  // low voltage on the pin means the button is being pressed
  button_now = (digitalRead(BUTTON_PIN)==LOW);
  button_last = button_now;
  lcd.begin(16, 2); // LCD dimensions
  //         01234567890123456
  lcd.print("Some bogus time:");
  disptime();
}

void loop() {
  button_last = button_now;
  // remember, low voltage on the pin means the button is being pressed
  button_now = (digitalRead(BUTTON_PIN)==LOW);
  if (button_now) button_down_usec = micros();
  // Has the button just been pressed?
  if (button_now && !button_last) {
    if (set_state == 0) {
      // enter time setting mode
      lcd.setCursor(0, 0);
      //         01234567890123456
      lcd.print("Set the time:   ");
      pt &= 0xFFFFFF00;
      set_state = 7;
    }
    else if (set_state == 1) {
      // start the clock running
      last_tick_usec = micros();
      lcd.setCursor(0, 0);
      //         01234567890123456
      lcd.print("Current time:   ");
      set_state = 0;
    }
    else if (set_state <= 7) {
      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);      
    }
    // after every button press, we need to update the display
    disptime();
  }
  else if (!button_now && ((micros() - button_down_usec) >= 1200000)) {
    if ((set_state >= 2) && (set_state <= 7)) {
       // move to next digit position
       set_state--;
       // reset button timer
       button_down_usec = micros();
       // update display
       disptime();
    }
  }
  if (set_state==0) {
    // normal timekeeping mode
    if ((micros()-last_tick_usec)>=ONE_TICK_USEC) {
      // make the clock "tick"
      last_tick_usec += ONE_TICK_USEC;
      pt = timeAdd(pt, ONE_TICK_PACKED);
      if (pt>=0x24000000) pt = timeSub(pt, 0x24000000);
      // show the new time
      disptime();
    }
  }
  delay(10); // poor man's switch debounce
}

void disptime() {
  // set cursor to beginning of bottom row
  lcd.setCursor(0, 1);
  // make "kachi-kachi" animation
  char kachi1 = ' ';
  char kachi2 = ' ';
  if (set_state==0) {    
    if (((byte)pt)<0x25) kachi1=(char)182;
    else if (((byte)pt)<0x50) kachi2=(char)193;
    else if (((byte)pt)<0x75) kachi1=(char)182;
    else  kachi2=(char)193;
  }
  else if (set_state==1) {
    kachi1='O';
    kachi2='K';
  }
  // 012345678901
  // HH:MM:SS **
  //            01  234  567  89 0 123456
  sprintf(buf, "%02x:%02x:%02x %c%c     ", ((byte)(pt>>24)), ((byte)(pt>>16)), ((byte)(pt>>8)), kachi1, kachi2);
  // actually show the time on the display
  lcd.print(buf);
  switch (set_state) {
    case 7: lcd.setCursor(0, 1); lcd.cursor(); break;
    case 6: lcd.setCursor(1, 1); lcd.cursor(); break;
    case 5: lcd.setCursor(3, 1); lcd.cursor(); break;
    case 4: lcd.setCursor(4, 1); lcd.cursor(); break;
    case 3: lcd.setCursor(6, 1); lcd.cursor(); break;
    case 2: lcd.setCursor(7, 1); lcd.cursor(); break;
    case 1: lcd.setCursor(9, 1); lcd.cursor(); break;
    default: lcd.noCursor();
  }
}



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



Go Up