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