Jump to content
Sign in to follow this  
ILAMtitan

Vetinari's Clock

Recommended Posts

I got distracted in the hardware shop while running an errand, and came back with the cheapest dial clock on the shelf. Vetinari surgery started in the worst possible way when I smashed the mechanism box to bits trying to access the coils. I adapted the code for MSP430G2231 and 12.5 pF watch crystal, with a more compact bit array used to the store sequence timing. I used a chopped down v1.4 Launchpad PCB (Halloween contest prize) and stripboard H bridge with 2xBC546, 2xBC557, all horribly hacked together with lots of glue for my Nephew's 11th birthday.

 

 

Blog post

Share this post


Link to post
Share on other sites

you wanna see my ultra-dirty hack?

here we go....

- dip processor on Veroboard

- old incandescent maglite as battery holder

 

Fancy Dial showing off: "MSP430 Computer controlled" instead

of the "Quartz movement made in Japan China"

 

 

post-37272-0-19981800-1404157520_thumb.jpg

post-37272-0-85514400-1404157531_thumb.jpg

Share this post


Link to post
Share on other sites

I've improved the code a little, so here is my updated version. Fixes include:

1. Using a bit array instead of a byte array, dramatically reduces the amount of RAM used, so that smaller MSP430's can be used. I have been using an F2012, only because it was the cheapest DIP MSP430 my supplier had on hand. It should compile and work with pretty much any of the low end processors. I've tested with the F2012 & G2452, but it also compiles with the G2231, so I'd expect it should operate fine.

2. Previously while the motor was turned on, it used a simple __delay_cycles loop for the timeout. This is quite heavy on power. I have changed it so it utilises TimerA so that it can go down to LPM3 while it is providing the motor pulse.

3. The original code had a bug in that when the coil was off, both PNP transistors were turned on. PNP transistors turn on when their base is brought low, and since in the idle state the original code set those pins to 0, both transistors were turned fully on. This didn't result in current flowing through the motor, but base current was constantly flowing through the transistors - approx 3mA each.  This sapped battery life.

With the original code, I was getting about 1 month on a pair of AA alkaline batteries. With my changes I've had a clock running for about 6 months now.

/******************************************************************************
*
*      Project     : Vetinari Clock
*      Target CPU  : MSP430G2553
*      Compiler    : CCS 5.2.1
*      Copyright   : None
*      Version     : $Revision: 1A
*      \file         main.c
*      \brief        The Vetinari clock is from a book series known as Discworld, where
*      Lord Verinari has a clock in his waiting room which has an irregular tick.  The
*      idea of the clock is to add a sense of unease and anxiety to anyone in the waiting
*      room since their brain doesn't filter out the ticks like a normal clock.
*
*      To accomplish this task on a 430, we create an array of possible time frames to
*      tick the clock, and parse through it at 4Hz.  The array is 32 entries long, so it
*      equates to 32 seconds in the real world.  By randomly setting 32 of the elements high,
*      we create a timing sequence. A high element will generate a tick of the clock.  This
*      means a second on the clock can be as little as 250ms, or as long as 24 seconds, and
*      still keep accurate time.
*****************************************************************************
*
*  Code modified by Graham Fountain to:
*  Use a bit array instead of byte array. This is much more RAM efficient and
*  allows a smaller 128B device to be used instead of needing a 256B device.
*  Tested with an MSP430F2012 (which at the time was the least expensive MSP430
*  in DIP casing my supplier had in stock).
*
*  The delay code that conrols how long the pulse is delivered to the motor
*  has been changed from a simple __delay_cycles to use TimerA. This allows us
*  to drop into LPM3 mode while the motor is pulsing, saving considerable power.
*  In my tests, a pair of AA batteries lasted about 1 month with the __delay_cycles
*  code, while they lasted more than 6 months with the TimerA code.
*
***********************************************************************
*
*   H Bridge Configuration.
*   The below code is configured for the below configuration of the 
*   H Bridge. If you use different pins to control transistors then
*   use different values for COIL_OFF, COIL_ONE & COIL_TWO
*
*   V+ ----------------+-----------+
*                      |           |
*                      E           E  The top two transistors are PNP, eg BC557
*   P1.3<---RESISTOR---B           B---RESISTOR--->P1.2
*                      C           C
*                      |           |
*                      +---MOTOR---+
*                      |           |
*                      C           C
*   P1.1<---RESISTOR---B           B---RESISTOR--->P1.0
*                      E           E  The bottom two transistors are NPN, eg BC547
*                      |           |
*   GND ---------------+-----------+
*  
*
*
*****************************************************************************/

#include "msp430.h"
#include <stdint.h>
#include <stdio.h>
#include <string.h>

/* Defines - Too lazy to put these in a header, maybe later */
/* How many clock cycles cycles to keep the io high
 * This will chgange depending on the model of clock movement used */

#define ENERGISE_TIME 820  //time in 1/32768 clock cycles. 820 = 25mS

/* IO Mapping */
#define COIL_OUT		P1OUT
#define COIL_DIR		P1DIR

/* H-Bridge control pins */

/* 
*  NB: PNP Transistors are ON when low, OFF when high
*      NPN Transistors are OFF when low, ON when high
*/

#define COIL_OFF  (BIT2 | BIT3)  //Both PNP's high (off), Both NPN's low (off)
#define COIL_ONE  (BIT2 | BIT0)  //B0101  PNP on P1.3 on,  PNP on P1.2 off, NPN on P1.1 off, NPN on P1.0 on
#define COIL_TWO  (BIT3 | BIT1)  //B1010  PNP on P1.3 off, PNP on P1.2 on,  NPN on P1.1 on,  NPN on P1.0 off

/* Lazy man globals*/
static uint16_t timingSequence[8]; //8 x 16 bits = 128 bits

inline uint16_t GetBit(uint16_t x) //get the current value of bit x
{
  return (timingSequence[x>>4] >> (x & 0xF)) & 0x1; //x>>4 is our index to the array (top 3 bits). x & 0xF is the bit we want.
}

inline void ClearAllBits() //0 the array. This loop takes less code than memset
{
  for(int i=0;i<8;i++) 
    timingSequence[i]=0;
}

inline void SetBit(uint16_t x) //set bit x of the array
{
  timingSequence[x>>4] |= (1<< (x & 0xF));
}

inline void delay()  //delay for the period specified by ENERGISE_TIME.
{
  TA0CCR0= ENERGISE_TIME;  //set what we will count to.
  TA0CCTL0 = CCIE;  //enable CCR0 interrupt
  TA0CTL = TASSEL_1 | MC_1 | TACLR;  //ACLK (the crystal), count up, reset any value currently in the timer
  
  LPM3; //wait until the interrupt happens.
  
  TA0CCTL0 = 0; //reset the timer
  TA0CTL =0;
  
}

/*
 * send a pulse to the clock coil
 */
void pulseClock(void)
{
	/* the polarity on the coil must swap each time it ticks, so we need to keep track of it */
	static uint8_t polarity;

	if (polarity == 0)
	{
		COIL_OUT = COIL_ONE;
		delay();
		COIL_OUT = COIL_OFF;
		polarity = 1;
	}
	else
	{
		COIL_OUT = COIL_TWO;
		delay();
		COIL_OUT = COIL_OFF;
		polarity = 0;
	}
}

/*
 * Using a LFSR, generate a "random" 16-bit number. This was snagged from wikipedia
 */
uint16_t get_rand(uint16_t in)
{
	uint16_t lfsr = in;
	static unsigned bit;
	static unsigned period = 0;

	/* taps: 16 14 13 11; feedback polynomial: x^16 + x^14 + x^13 + x^11 + 1 */
	bit  = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5) ) & 1;
	lfsr =  (lfsr >> 1) | (bit << 15);
	++period;

	return(lfsr);
}

/*
 * Reset the clock sequence, runs every 32 seconds
 */
void ResetSequence(void)
{
	uint8_t i=32;
	uint8_t location;
	static uint16_t feedback = 0xACE1;

	/* Zero out all elements */
	//memset(&timingSequence, 0x0, sizeof(timingSequence));
        ClearAllBits();

	/* The array needs to have 32 random elements set high.
	 * To do this we generate 32 different random numbers to use as indexes for the bits that will be set high.
	 * If the index is already set, we just discard that number and try for a new one.
	 */
	do{
		/* get a new random number */
		feedback = get_rand(feedback);

		/* We only want the lower 7 bits since it's a 128 element array
		 * The 16-bit number is still used so that we get a longer
		 * chain in the LFSR before it repeats */
		location = 0x7F & feedback;

		/* If the random location has already been set high, we ignore the random number */
		if(GetBit(location) == 0)
		{
			/* Other wise we set the element */
			SetBit(location);
			/* and decrement the counter */
			i--;
		}

	/* This needs to be done 32 times */
	}while(i);
}

/*
 * I setup the MSP, and run the main program loop
 */
int main(void) {

	/* counter to determine when 32 seconds have past */
	uint8_t counter = 0;
  if (CALBC1_1MHZ ==0xFF || CALDCO_1MHZ == 0xFF)                                     
  {  
    // If calibration constants erased, do not load, trap CPU!!
    while(1);
    //G_uc_SystemFailureCode = McuCalValueFail;
  } 
  // Set DCOCLK to 1MHz
  DCOCTL = 0x00;
  BCSCTL1 = CALBC1_1MHZ;                  
  DCOCTL = CALDCO_1MHZ;

	/* setup watchdog for a .25s interval
	 * WDT ISR set at watchdog_timer(void)*/
	WDTCTL = WDT_ADLY_250;
	IE1 |= WDTIE;

	/* Enable Osc Fault */
	IE1 |= OFIE;

	/* Setup IO */
	COIL_DIR = COIL_ONE | COIL_TWO;
        COIL_OUT = COIL_OFF;
	/* Initialize the pulse sequence */
	ResetSequence();

	/* Enter LPM3 w/interrupt */
	_BIS_SR(LPM3_bits + GIE);

	while(1)
	{
		/* If this element of the sequence is high, we need to tick the clock */
	    if (GetBit(counter))
	    {
	    	pulseClock();
	    }

	    /* Increment the counter to get us closer to 32 sec */
	    counter++;

	    /* The WDT runs at 4Hz, so 32sec at equates to 128 ISR firings
	     * At the 32 sec mark, we want to reset the counter and generate a new pulse sequence */
	    if (counter == 128)
	    {
	    	counter = 0;
	    	ResetSequence();
	    }

	    /* Enter LPM3 w/interrupt */
	    _BIS_SR(LPM3_bits + GIE);
	    /* Once the WDT interrupt fires, it will return here looping back to the start of the while loop */
	}

}

#pragma vector=TIMER0_A0_VECTOR
__interrupt void timera0 (void)
{
  _BIC_SR_IRQ(LPM3_bits);  //we don't need to do anything but make sure we are woken up.
}

/*
 * The watchdog timer is actually useful
 */
#pragma vector=WDT_VECTOR
__interrupt void watchdog_timer (void)
{
	/* Clear LPM3 bits from 0(SR)
	 * This will send us back to the main while loop */
	_BIC_SR_IRQ(LPM3_bits);
}

/*
 * Just in case
 */
#pragma vector=NMI_VECTOR
__interrupt void nmi_ (void)
{
  uint16_t i;
  do
  {
    IFG1 &= ~OFIFG;                         // Clear OSCFault flag
    for (i = 0xFFF; i > 0; i--);            // Time for flag to set
  }
  while (IFG1 & OFIFG);                     // OSCFault flag still set?
  IE1 |= OFIE;                              // Enable Osc Fault
}

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×