A couple of months ago I took a break from working on a precision pendulum clock project to build this. I call it a capricious clock for reasons that will be obvious after watching the video below. It ticks and tocks in an unnerving and aperiodic or spasmodic manner that anthropomorphically demands your attention. Even as the author of the software, I find myself lured in, staring the capricious clock down, face to face, anticipating and wondering when and sometimes if, the next tick will occur. Mercurial, you say? Precisely, but alliteration won out and "Capricious Clock" it is.
The most marvelous part of all of this, of course, is that the clock keeps accurate time over the long term. Neglecting inaccuracies inherent to quartz clocks, this clock could conceivably be off by no more than 8 seconds at any point in time. However, at no point in time do you ever feel the displayed time is something to be believed. I would like to place this clock in a public area to gauge peoples reactions. For now, it is on a show and tell basis, hanging on a wall at home.
Here is a video of the Capricious Clock running next to a regular AC wall clock. In the video, I fast forward by 500% at 1:12 until near the very end. This video is illustrative of the erratic ticking of the capricious clock on the right next to a smooth AC clock motor sweeping second hand.
The idea for a mind-melting ticking machine came from the author Terry Pratchett. There is "Lord Vetinari's Clock" in the series Discworld. I'm not exactly sure how, as it seems everyone around me is familiar with Discworld , but I am just finding out about it now. Actually, it was a similar clock that someone else built that got me thinking about how I would go about making something like this for myself.
That is the what and why, now on to the how...
In the pictures below you can see how the quartz clock movement was modified to intercept the 1pps signal and output my own clock "motor" drive pulse. I cut the traces going to and from the "black blob" IC on the circuit board for the 1pps signal and motor drive pulse. These were then soldered to fine gauge enameled wire and brought out to a strip PCB for later access.
In the picture below you can see how the quartz clock movement works. The black cylinder on the end of the white plastic gear is a magnet. It's poles are aligned perpendicular to its axis of rotation. When the movement is assembled, the copper wire electromagnet is pulsed once per second, alternating polarity and consequently magnetic poles to drive the movement. The relative location of the gear magnet and electromagnet ensure that the clock moves in the clockwise direction usually. It is possible with different electromagnet pulse durations to sometimes generate an anticlockwise tick. Nothing reliable enough for an open loop system like this though.
Below is the quartz movement with enameled wire soldered to the cut PCB traces. You may notice this is a different quartz movement than the one finally installed in the clock. I had to modify a new movement after accidentally applying 5v to one of the black blob IC traces and consequently rendered it useless.
Here you can see the fine enameled wire brought out to some blank PCB for more robust attachment of wires that will be controlling the clock and receiving the 1 pps signal.
At this point you could jumper the correct traces back together and the quartz motor would function as originally intended, but next a microcontroller comes in to modify the 1 pps to something a little less phlegmatic.
Here is the schematic for what we have done so far to the quartz movement and what is to come by adding a uC.
I mention in at least 4 places ( here included ) that after programming the chip you have to set the RSTDISBL fuse if you want to use PB5 as an IO pin. Once you do this, you cannot use ISP programming and instead have to do high voltage programming to bring the reset line up to 12v. For this reason, I have yet to test my code using PB5 as the intended "Mute" switch. I want to use it to toggle on or off the ability for the uC to make a tick-tock sound with the piezo. Until I am happy with the code, and I think I am nearly, I don't want to commit to the RSTDISBL fuse programming. I guess this capricious clock is not currently mutable?... 0_0
One way around the RSTDISBL / PB5 issue I describe is to use a boot loader on the ATtiny. I may do this because I don't like the idea of not being able to easily change the firmware at a later date and I have never used a boot loader on an ATtiny before and it may be fun.
A couple of other ideas I had for erratic ticking I had were to tick faster from 0s-30s and slower from 30s-60s as if the clock was operating in a larger gravitational field than its surroundings and the motor just barely keeps up. Or, the same erratic ticking over a longer time though so that it drifts +/- 5 minutes an hour or so. I think this will eventually catch someones attention, but it would take longer to notice. For these reasons I think I have just convinced myself to remake this project with an added bootloader on the ATtiny85.
Here are a couple of pictures from the inside of the clock. This clock made it really easy to shoehorn the additional electronics inside.
// software
The code keeps track of the 1pps pulses coming from the quartz clock movement, it delays for a random period of time then moves the second hand. If the "Real Time" elapsed is greater than the displayed time the program will delay anywhere from 1-0.125s to catch up to the "Real Time". If the display time is faster, the program will select a random time from 1-8s to slow down so that "Real Time" can catch up.
Rather than just turning the thing on and letting it run in an endless loop I used external interrupts, nested interrupts and the watchdog timer with power down sleep mode to wake up from a sleep period to update the display.
Using the aforementioned capabilities of the ATtiny85 was necessary for the reliability of counting the 1pps pulse and the ability to conserve battery power - a very important thing to do in battery powered devices. You can see in the calculations below the clock will theoretically run for ~225 days or ~61.6% of a year before needing new batteries. Now, that is theoretical and does not account for sleep current, wake up current and time or battery self discharge. However, in this case, where the ratio of active to sleep current is so great, you can safely ignore those calculations. By entering into power down sleep mode the battery life is extended by nearly 3 months.
Here is the code. As previously stated the switch on PB5 is commented out in the code so it will not work. I did this because I am not done using the ISP and will probably use a bootloader in the end.
/* Program Title: Capricious Clock Author: Pete Mills Date: 2011.12.09 petemills.blogspot.com Filename: capricious_clock_v1.0_main.c Int. RC Osc. 8 MHz; Start-up time PWRDWN/RESET: 6 CK/14 CK + 0 ms ck div by 8 for 1MHz system clock Set RSTDISBL fuse after programming to allow use of PB5 After setting RSTDISBL you will have to use HV programming to reprogram the AVR - no more ISP */ // Includes #include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> #include <avr/sleep.h> // Definitions #define IO_PORT PORTB #define LED PB0 // LED pin PORTB Pin 4 #define BUZZER PB1 // piezo output buzzer #define PPS_IN PB2 // 1 Hz input signal #define SEC_MOT_1 PB3 // output to drive seconds motor coil on quartz clock #define SEC_MOT_2 PB4 // another output to seconds motor coil on quartz clock assy #define MUTE_SW PB5 // input switch for muting piezo and ...? // function prototypes void setup(void); void toggle_led(void); void inc_sec(void); void tick_tock(void); // Global Constants volatile uint32_t rt_sec = 0; // actual time elapsed volatile uint32_t my_sec = 0; // seconds displayed on the clock volatile uint8_t do_tick = 0; // decide to tick or not int main(void) { setup(); sei(); set_sleep_mode(SLEEP_MODE_PWR_DOWN); while(1) { /* // turn on or off the ticking sound if ( bit_is_clear( PINB, MUTE_SW ) ) { ++do_tick; if( do_tick > 1 ) { do_tick = 0; } } */ asm("nop"); // ISR's return to main to execute a minimum of one instruction asm("nop"); // before executing pending interrupts sleep_mode(); // go back to sleep } } // functions void setup(void) { // Port Configuration DDRB |= ( ( 1 << SEC_MOT_1 ) | ( 1 << SEC_MOT_2 ) | ( 1 << BUZZER ) | ( 1 << LED ) ) ; // set PB1::PB4 to "1" for output IO_PORT &= ~( ( 1 << SEC_MOT_1 ) | ( 1 << SEC_MOT_2 ) | ( 1 << BUZZER ) | ( 1 << LED ) ); // set the outputs low DDRB &= ~( ( 1 << PPS_IN ) | ( 1 << MUTE_SW ) ); // set PB0 and PB5 to "0" for input IO_PORT |= ( 1 << MUTE_SW ); // enable internal pullup on input switch // Interrupt Configuration // External Interrupt INT0 ( INT0_vect ) // low level on INT0 causes interrupt GIMSK |= ( 1 << INT0 ); // enable external interrupt on INT0 // Watchdog Timer Interrupt ( WDT_vect ) WDTCR |= ( ( 1 << WDCE ) | ( 1 << WDE ) ); WDTCR &= ~( 1 << WDE ); WDTCR |= ( 1 << WDIE ); // watchdog timeout interrupt enable WDTCR |= ( ( 1 << WDP0 ) | ( 1 << WDP2 ) ); // half second sleep } void toggle_led() { IO_PORT ^= (1<<LED); } // function to drive the quartz clock motor void inc_sec() { if( my_sec % 2 == 0 ) { IO_PORT |= ( 1 << SEC_MOT_1 ); } else { IO_PORT |= ( 1 << SEC_MOT_2 ); } _delay_ms(30); // empirically derived pulse duration IO_PORT &= ~( ( 1 << SEC_MOT_1 ) | ( 1 << SEC_MOT_2 ) ); } // making the tick tock escapement sound void tick_tock() { if( rt_sec % 2 == 0 ) { IO_PORT |= ( 1 << BUZZER ); _delay_us(200); IO_PORT &= ~( 1 << BUZZER ); } else { IO_PORT |= ( 1 << BUZZER ); _delay_ms(1); IO_PORT &= ~( 1 << BUZZER ); } } // this ISR is called once per second as the quartz clock movement outputs its pps signal ISR( INT0_vect ) { ++rt_sec; if( do_tick ) { tick_tock(); //toggle_led(); } while( bit_is_clear( PINB, PPS_IN ) ) { // stay in ISR until input signal is high again to stop multiple triggers } } // this ISR is called to update the output display ISR( WDT_vect ) { sei(); // allow this interrupt to be interrupted... uint8_t rand_num = (uint8_t) rand(); uint8_t case_sel = 0; ++my_sec; // increment displayed time counter inc_sec(); // move the second hand rand_num = (uint8_t) rand(); case_sel = rand_num % 4 ; if( my_sec > rt_sec ) case_sel += 3; switch (case_sel) // set sleep time { case 0: WDTCR &= ~( ( 1 << WDP3 ) | ( 1 << WDP2 ) ); WDTCR |= ( ( 1 << WDP1 ) | ( 1 << WDP0 ) ); // ~0.125s sleep break; case 1: WDTCR &= ~( ( 1 << WDP3 ) | ( 1 << WDP1 ) | ( 1 << WDP0 ) ); WDTCR |= ( 1 << WDP2 ); // ~0.25s sleep break; case 2: WDTCR &= ~( ( 1 << WDP3 ) | ( 1 << WDP1 ) ); WDTCR |= ( ( 1 << WDP2 ) | ( 1 << WDP0 ) ); // ~0.50s sleep break; case 3: WDTCR &= ~( ( 1 << WDP3 ) | ( 1 << WDP0 ) ); WDTCR |= ( ( 1 << WDP2 ) | ( 1 << WDP1 ) ); // ~1.00s sleep break; case 4: WDTCR &= ~( ( 1 << WDP3 ) ); WDTCR |= ( ( 1 << WDP2 ) | ( 1 << WDP1 ) | ( 1 << WDP0 ) ); // ~2.00s sleep break; case 5: WDTCR &= ~( ( 1 << WDP2 ) | ( 1 << WDP1 ) | ( 1 << WDP0 ) ); WDTCR |= ( 1 << WDP3 ); // ~4.00s sleep break; case 6: WDTCR &= ~( ( 1 << WDP2 ) | ( 1 << WDP1 ) ); WDTCR |= ( ( 1 << WDP3 ) | ( 1 << WDP0 ) ); // ~8.00s sleep break; default: WDTCR &= ~( ( 1 << WDP3 ) | ( 1 << WDP0 ) ); // should never occur WDTCR |= ( ( 1 << WDP2 ) | ( 1 << WDP1 ) ); // but set the WDT for 1s if it does } }
Comments
If so how would I go about porting the code?
Yes, you can use the ATMega328 running the arduino bootloader just fine for this project. I am not exactly sure how to describe how to port the code but...
I did a quick search for " Arduino Clocks " and came up with this site http://www.cibomahto.com/2008/03/controlling-a-clock-with-an-arduino/
In the link above the author is controlling a clock motor with an arduino similar to what I am doing in the Capricious Clock.
Perhaps you could get that code working then it should be a trivial matter of adding your own randomness to the time output. Basically you want to keep track of the time, but modify they output to look capricious.
I would recommend isolating each section of the project ie time keeping, clock motor driving, randomness etc and tackle each one separately. Eventually you will have all the parts necessary to build your own sketch.
I believe you can use the arduino with the ArduinoISP sketch from the IDE examples as an ISP. If you have any ATTiny85 µC's you could use the ArduinoISP to download the code posted here for the ATTiny85.
Good luck and let me know how it turns out for you!
I STILL think about making this awesome clock, maybe soon...