Sunday, December 11, 2011

Capricious Clock




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
   
  }
  
}