Chronotis Mk.II – One year on

About a year ago I set out to build a watch from a PIC12F510 microcontroller. It’s a chip with only 6 GPIO lines, and two of those doubles as XTAL inputs. That gives me just 4 PGIO lines to play with, and one of them is strictly not IO, just I (GP3 can only be used for input). This means any sort of traditional display is out, so I settled for counting out the time on one LED, and signaling change-of-digit with another. The time 10:23 would produce the following output: GREEN, RED, GREEN, GREEN, RED, RED, GREEN, RED, RED, RED. The digits of the “display” are found by counting the number of REDs following each GREEN. So the above sequence gives you 1, 0, 2, 3 – 10:23. Best I could come up with with only two LEDs as my “display”. The hardware consists of the microcontroller, a battery, a crystal, a switch, two resistors, two LEDs and two capacitors. That’s it! I never really drew up any schematics for the build, but I’ve made this quick sketch to show how it’s all connected:

Schematics

I built the original on a breadboard, but it was a bit cumbersome to lug around, so I quickly soldered together a perf board version:

Perf board version

And it worked like a charm:

https://vimeo.com/136455863

It kept time pretty well too; after about six months it had slowed approximately a minute. Not to shabby for a single chip microcontroller with no dedicated RTC (real time clock). Best of all; as the battery slowly dwindled away, the skew seemed to move in the other direction, and after about a year, it was back on track keeping real time once again. By this time the amount of juice left in the battery is only enough to be able to film it in darkness, and the green LED doesn’t even show:

https://vimeo.com/136455983

Still; I paid maybe 10 euro-cent for that CR2032 battery on ebay, and the microcontroller has been running in a tight loop for a year and 10 days at this point. I think that’s simply impressive!

In case anyone is still reading; this is how you make a watch in 225 lines of C-code:

#include <xc.h>

#pragma config OSC = LP         // Oscillator Select (LP oscillator with 18 ms DRT)
#pragma config WDT = OFF        // Watchdog Timer Enable bit (WDT disabled)
#pragma config CP = OFF         // Code Protect (Code protection off)
#pragma config MCLRE = OFF      // Master Clear Enable bit (GP3/MCLR pin functions as GP3, MCLR internally tied to VDD)
#pragma config IOSCFS = OFF     // Internal Oscillator Frequency Select bit (4 MHz INTOSC Speed)

#define _XTAL_FREQ 32768

#define GREEN  0b00000001
#define RED    0b00000010
#define BUTTON 0b00001000

#define BUZZER_ON  0b00000100
#define BUZZER_OFF 0b11111011

typedef enum {
    IDLE,
    OUTPUT_TIME, /* Returns to IDLE */
    OUTPUT_COUNT, /* Returns to OUTPUT_TIME */
    OUTPUT_WAIT, /* Returns to OUTPUT_COUNT */
    OUTPUT_LED, /* Returns to OUTPUT_WAIT */
    INPUT_TIME, /* Returns to IDLE */
    INPUT_COUNT /* Returns to INPUT_TIME */
} state_t;

typedef enum {
    NONE,
    CLICK, /* Button clicked */
    HOLD, /* Button held */
    LONG, /* Buttong held long */
    TIMEOUT /* Timeout expired */
} event_t;


typedef enum {
    HOUR_TEN,
    HOUR_ONE,
    MIN_TEN,
    MIN_ONE
} part_t;

int main(void) {
    char hour = 22;
    char min = 24;
    char sec = 0;

    char cnt0;
    char cnt1;

    int button = 0;

    char green = 0;
    char red = 0;

    char strobe = 0;
    char buzz = 0;

    int wait;

    char value;
    char step;

    state_t state = IDLE;
    event_t event;
    part_t part;

    OPTION = 0b10010100;
    //         ||||||||
    //         |||||||+--: Prescaler Rate: 1:32
    //         ||||||+---:
    //         |||||+----:
    //         ||||+-----: Prescaler assigned to Timer0
    //         |||+------: Timer0 Source Edge Increment on high-to-low transition on T0CKI pin
    //         ||+-------: Timer0 Clock Source Internal instruction cycle clock
    //         |+--------: Enabled Weak Pull-ups bit (GP0, GP1, GP3)
    //         +---------: Disabled Wake-up On Pin Change bit (GP0, GP1, GP3)

    CM1CON0 = 0b11110111;
    //          ||||||||
    //          |||||||+--: Wake-up On Comparator Change is disabled
    //          ||||||+---: Comparator Positive Reference: C1IN+ pin
    //          |||||+----: Comparator Negative Reference: C1IN pin
    //          ||||+-----: Comparator is off
    //          |||+------: TMR0 clock source selected by T0CS control bit
    //          ||+-------: Output of comparator is not inverted
    //          |+--------: Output of comparator is NOT placed on the C1OUT pin
    //          +---------: Comparator Output bit: VIN+ > VIN-

    ADCON0 = 0b00111100;
    //         ||||||||
    //         |||||||+--: ADC module is shut-off and consumes no power
    //         ||||||+---: ADC Conversion Status
    //         |||||+----: ADC Channel: 0.6V absolute voltage reference
    //         ||||+-----:
    //         |||+------: ADC Conversion Clock: INTOSC/4
    //         ||+-------:
    //         |+--------: No pins configured for analog input
    //         +---------:

    TRISGPIO = 0b11111000;
    //           ||||||||
    //           |||||||+--: GP0: Output (red led)
    //           ||||||+---: GP1: Output (green led)
    //           |||||+----: GP2: Output (buzzer)
    //           ||||+-----: GP3: Input  (switch)
    //           |||+------: GP4: Input  (used by xtal)
    //           ||+-------: GP5: Input  (used by xtal)
    //           |+--------: Unimplemented
    //           +---------: Unimplemented

    // Seems GPIO input needs time to settle...
    __delay_ms(500);

    while (1) {
        event = NONE;

        /* Check for counter wrap-around */
        if ((cnt0 = TMR0) < cnt1) {
            if (1 == strobe)
                green = !green;

            if (60 == ++sec) {
                sec = 0;
                if (60 == ++min) {
                    min = 0;
                    if (24 == ++hour) {
                        hour = 0;
                    }
                }
            }
        } else {
            wait = wait - (cnt0 - cnt1);

            if (1 > wait)
                event = TIMEOUT;
        }

        cnt1 = cnt0;

        if (0 == (GPIO & BUTTON)) {
            button++;
        } else if (0 != button) {
            if (512 < button) {
                event = LONG;
            } else if (128 < button) {
                event = HOLD;
            } else if (7 < button) {
                event = CLICK;
            }
            button = 0;
        }

        if (NONE != event) {
            switch (state) {
                case IDLE:
                    if (CLICK == event) {
                        part = HOUR_TEN;
                        value = hour;
                        step = 10;
                        wait = 128;
                        green = 1;
                        state = OUTPUT_LED;
                    }
                    break;

                case OUTPUT_LED:
                    if (TIMEOUT == event) {
                        green = red = 0;
                        wait = 64;
                        state = OUTPUT_WAIT;
                    }
                    break;

                case OUTPUT_WAIT:
                    if (TIMEOUT == event) {
                        state = OUTPUT_COUNT;
                    }
                    break;

                case OUTPUT_COUNT:
                    if (value >= step) {
                        value -= step;
                        wait = 64;
                        red = 1;
                        state = OUTPUT_LED;
                    } else {
                        state = OUTPUT_TIME;
                    }
                    break;

                case OUTPUT_TIME:
                    if (HOUR_TEN == part) {
                        part = HOUR_ONE;
                        step = 1;
                    } else if (HOUR_ONE == part) {
                        part = MIN_TEN;
                        value = min;
                        step = 10;
                    } else if (MIN_TEN == part) {
                        part = MIN_ONE;
                        step = 1;
                    } else if (MIN_ONE == part) {
                        state = IDLE;
                        break;
                    }
                    wait = 128;
                    green = 1;
                    state = OUTPUT_LED;
                    break;
            }

        }

        if (buzz) {
            for (buzz = 5; buzz > 1; buzz--) {
                GPIO = GPIO | BUZZER_ON;
                GPIO = GPIO & BUZZER_OFF;
            }
        }

        // GPIO = (green * GREEN) | (red * RED);
        GPIO = (green * RED) | (red * GREEN);
    }
}