Once upon a time, lifes goes on as usual, boring. Until one day my friend showing me a weird clock on youtube. i said “wow, that was awesome!”, “how it works?”, “i should make one!”. Basically, i have experienced on electronic DIY stuffs. i started to figure out on how to make it, lots of information found. so basicaly, propeller clock based on POV (Persistence of vision)

http://en.wikipedia.org/wiki/Persistence_of_vision

The clock’s graph formed by cheating human eyes, one array of leds burst on constant interval/delay in one motor rotation, continuously (in this case  divided to 360 degree in one rotation, can be more for higher resolution).

After i’ve found, understand, (and yet still confused on how it works :P). i started making prototypes with existing MCU module (atmega8535), wired to array of 8 leds. it works, and not good!! it’s too heavy, no index reference, the text is very unstable. i need to design new board which is lighter and throw out all unnecessary components. there are two web site that i’m using as references, when building this clock.

http://www.microsyl.com/index.php/2010/03/18/propeller-clock/

i used the codes from there with lots of modification, original code is writen using ICCAVR, i converted it to AVR Studio.

http://www.oocities.org/tjacodesign/propclock/propclock.html

i used his isolated power supply, to power up the propeller clock module.

For remote control, it was stolen from here: :D

http://www.dharmanitech.com/2009/01/ir-remote-controlled-car-pwm-motor.html

picture

Isolated Power Supply / Wireless Power Transmission

the propeller clock works properly, only haven’t fix jitter yet when remote control is used ( i used SIRC/ sony IR remote control).

Video

YouTube Preview Image YouTube Preview Image YouTube Preview Image

Schematic

This is my first attempt designing propeller clock, i’ve been thinking using several shift register to drive more leds array.

Propeller Clock

Isolated Power Supply/ Wireless Power Transmission

Partlists Propeller Clock

Part     Value
C1       22pF
C2       22pF
C3       470uF
C4       470uF
C5       470uF
C6       470uF
C7       100nF
C8       10nF
C9       10uF
D1       1N4004
D2       1N4004
D3       1N4004
D4       1N4004
IC1      MEGA16-A
IC2      A1301UA
IC3      LM7805
JP1      AVR-ISP-10
LED1     RED
LED2     RED
LED3     RED
LED4     RED
LED5     RED
LED6     RED
LED7     RED
LED8     RED
LED9     RED
LED10    RED
LED11    RED
LED12    RED
LED13    RED
LED14    RED
LED15    RED
LED16    GREEN
PAD1     P1
PAD2     P2
R1       1k
R2       330
R3       330
R4       330
R5       330
R6       330
R7       330
R8       330
R9       330
R10      330
R11      330
R12      330
R13      330
R14      330
R15      330
R16      330
R17      330
R18      100
S1       RESET
U$1      TSOP1838
X1       16MHz

most of all reststor, led, capacitor are smd1206

Code

#include <avr/interrupt.h>
//#include <avr/wdt.h>
#include <util/delay.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

#define TRUE      0x01
#define FALSE     0x00
#define ANALOG    0x01
#define DIGITAL   0x02
#define WIDTH 9

const unsigned char table[12][6] = 	{{ 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00 }, // 0
                                    { 0x00, 0x21, 0x7f, 0x01, 0x00, 0x00  }, // 1
                                    { 0x21, 0x43, 0x45, 0x49, 0x31, 0x00  }, // 2
                           	  		{ 0x42, 0x41, 0x51, 0x69, 0x46, 0x00  }, // 3
                                    { 0x0c, 0x14, 0x24, 0x5f, 0x04, 0x00  }, // 4
                                    { 0x72, 0x51, 0x51, 0x51, 0x4e, 0x00  }, // 5
                                    { 0x1e, 0x29, 0x49, 0x49, 0x06, 0x00  }, // 6
                                    { 0x40, 0x47, 0x48, 0x50, 0x60, 0x00  }, // 7
                                    { 0x36, 0x49, 0x49, 0x49, 0x36, 0x00  }, // 8
                                    { 0x30, 0x49, 0x49, 0x4a, 0x3c, 0x00  }, // 9
                                    { 0x00, 0x36, 0x36, 0x00, 0x00, 0x00  }, // :
                                    { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  }};// space

volatile int WeelPosition;
volatile unsigned char Pos;
volatile unsigned int Adder;

volatile unsigned char LatchedIrData;
volatile unsigned int Rps, CountRps;

volatile unsigned char Sec;
volatile unsigned char Min;
volatile unsigned char Hrs;

volatile int SecComp;
volatile int MinComp;
volatile int HrsComp;

volatile unsigned char ClockStyle;

unsigned int TimeString[50];
unsigned int *TimeStringPtr;

void One(void);
void Two(void);
void Three(void);
void Six(void);
void Nine(void);
void Hari(void);

unsigned char i;

void Time(unsigned char);
void Display(void);
void CopyData(int Value);
void CopyDot(void);
unsigned int read_IR (void);

int main(void)
{

	//WDTCR |= ((1 << WDE) | (1 << WDP2) | (1 << WDP1));	// Enable WatchDog at 1.0 sec
	MCUCR |= ((1<<ISC11)|(1<<ISC01));	// Int0 Int1 generate int on falling eadge
	GICR |= ((1 << INT0) | (1 << INT1));	// Int0 and Int1 enable
	TCCR0 |= ((1 << CS02) | (1 << CS00));	// Timer0 / 1024
	//TCCR1B |= ((1 << CS11) | (1 << CS10) | (1 << WGM12));	// Timer1 prescaling 8 CTC
	//TCCR1B |= ((1 << CS10) | (1 << ICES1) | (1 << WGM12));	// Timer1 no prescaling
	TCCR1B |= ((1 << CS10) | (1 << WGM12));	// Timer1 no prescaling
	TCCR2 |= ((1 << CS22)|(1 << CS21) | (1 << CS20));
	//TIMSK |= ((1 << OCIE1A) | (1 << TICIE1) | (1 << TOIE0));
	TIMSK |= ((1 << OCIE1A) | (1 << TOIE0));

    DDRA  = 0xFF;
    PORTA = 0x00;

    DDRC = 0xFF;
    PORTC = 0x01;

    DDRD = 0x00;
    PORTD = 0x0C;

	Hrs = 2;
	Min = 40;
	Sec = 10;
	ClockStyle = ANALOG;

	sei();
	//wdt_disable();
	//wdt_enable(WDTO_2S);

	while(1)
	{
		//wdt_reset();
		//for (i=0;i<200;i++);
		//if (LatchedIrData == 16) Time(TRUE);
		//if (LatchedIrData == 18) ClockStyle = DIGITAL;
		//if (LatchedIrData == 19) ClockStyle = ANALOG;

   		//LatchedIrData = 0;
	}
}

void Time(unsigned char Fast)
{
	if (Fast == FALSE)
	{
		Sec++;
	}
	//else Sec += 60;
	else
	{
		Min += 1;
		if (Min > 59)
		{
			Min = 0;
			Hrs++;
			if (Hrs > 11)
			{
				Hrs = 0;
			}
		}
	}

	if (Sec > 59)
	{
		Sec = 0;
		Min++;
		if (Min > 59)
		{
			Min = 0;
			Hrs++;
			if (Hrs > 11)
			{
				Hrs = 0;
			}
		}
	}

	if (ClockStyle == ANALOG)
	{
		SecComp = Sec*6;
		MinComp = Min*6;
		HrsComp = (Hrs*30)+(Min/2);
	}
	else
	{
		TimeStringPtr = &TimeString[0];
		CopyData(Hrs);
		CopyDot();
		CopyData(Min);
		CopyDot();
		CopyData(Sec);
	}
}

void CopyData(int Value)
{
	if (Value < 10)
	{
		for (i=0;i<6;i++) *TimeStringPtr++ = table[0][i];
   		for (i=0;i<6;i++) *TimeStringPtr++ = table[Value][i];
	}
	else
	{
		for (i=0;i<6;i++) *TimeStringPtr++ = table[Value/10][i];
   		for (i=0;i<6;i++) *TimeStringPtr++ = table[Value-((Value/10)*10)][i];
	}
}

void CopyDot(void)
{
	for (i=0;i<6;i++) *TimeStringPtr++ = table[10][i];
}

ISR(INT0_vect)
{
	unsigned int LastWeel;

	LastWeel = WeelPosition;	// get lasst wheel position either less/more than 360
	WeelPosition = 0;	// reset wheel count

	// this will make degree lock to 360
	if (LastWeel > 360)
	{
		Adder += 1;
	}
	else if (LastWeel < 360)
	{
		Adder -= 1;
	}

	CountRps++;

	OCR1A = 1852 + Adder;	// set TIMER1 compare value + offset (+/-)
	TIFR = (1 << OCF1A);	// reset TIMER1
	Display();
}

ISR(TIMER1_COMPA_vect)
{
	OCR1A = 1852 + Adder;
	Display();
}

void Display(void)
{
	unsigned char Min_Portc[5] = {0xF8, 0xFC, 0xFE, 0xFC, 0xF8};
	unsigned char Min_Porta[5] = {0X00, 0xF0, 0xFF, 0xF0, 0x00};

	unsigned char Hrs_Portc[5] = {0x00, 0x00, 0x80, 0x00, 0x00};
	unsigned char Hrs_Porta[5] = {0x70, 0xF8, 0xFF, 0xF8, 0x70};

	unsigned char port_c;
	//unsigned char port_a;
	PORTC = 0x00;
	PORTA = 0x00;

	if (ClockStyle == ANALOG)
	{
		// BEGIN HOURS
		// BEGIN HOUR on 12
		if ((WeelPosition == HrsComp) && (WeelPosition == 0))
		{
			PORTC |= Hrs_Portc[2];
			PORTA |= Hrs_Porta[2];
		}
		if (((WeelPosition - 1) == HrsComp) && (WeelPosition == 1))
		{
			PORTC |= Min_Portc[3];
			PORTA |= Min_Porta[3];
		}
		if (((WeelPosition - 2) == HrsComp) && (WeelPosition == 2))
		{
			PORTC |= Hrs_Portc[4];
			PORTA |= Hrs_Porta[4];
		}
		if (((WeelPosition - 358) == HrsComp) && (WeelPosition == 358))
		{
			PORTC |= Hrs_Portc[0];
			PORTA |= Hrs_Porta[0];
		}
		if (((WeelPosition - 359) == HrsComp) && (WeelPosition == 359))
		{
			PORTC |= Hrs_Portc[1];
			PORTA |= Hrs_Porta[1];
		}
		// END HOUR on 12

		if (((WeelPosition + 2) == HrsComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Hrs_Portc[0];
			PORTA |= Hrs_Porta[0];
		}
		if (((WeelPosition + 1) == HrsComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Hrs_Portc[1];
			PORTA |= Hrs_Porta[1];
		}
		if ((WeelPosition == HrsComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Hrs_Portc[2];
			PORTA |= Hrs_Porta[2];
		}
		if (((WeelPosition - 1) == HrsComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Hrs_Portc[3];
			PORTA |= Hrs_Porta[3];
		}
		if (((WeelPosition - 2) == HrsComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Hrs_Portc[4];
			PORTA |= Hrs_Porta[4];
		}
		// END HOURS

		// BEGIN MINUTE
		// BEGIN MINUTE on 12
		if ((WeelPosition == MinComp) && (WeelPosition == 0))
		{
			PORTC |= Min_Portc[2];
			PORTA |= Min_Porta[2];
		}
		if (((WeelPosition - 1) == MinComp) && (WeelPosition == 1))
		{
			PORTC |= Min_Portc[3];
			PORTA |= Min_Porta[3];
		}
		if (((WeelPosition - 2) == MinComp) && (WeelPosition == 2))
		{
			PORTC |= Min_Portc[4];
			PORTA |= Min_Porta[4];
		}
		if (((WeelPosition - 358) == MinComp) && (WeelPosition == 358))
		{
			PORTC |= Min_Portc[0];
			PORTA |= Min_Porta[0];
		}
		if (((WeelPosition - 359) == MinComp) && (WeelPosition == 359))
		{
			PORTC |= Min_Portc[1];
			PORTA |= Min_Porta[1];
		}
		// END MINUTE on 12

		if (((WeelPosition + 2) == MinComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Min_Portc[0];
			PORTA |= Min_Porta[0];
		}
		if (((WeelPosition + 1) == MinComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Min_Portc[1];
			PORTA |= Min_Porta[1];
		}
		if ((WeelPosition == MinComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Min_Portc[2];
			PORTA |= Min_Porta[2];
		}
		if (((WeelPosition - 1) == MinComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Min_Portc[3];
			PORTA |= Min_Porta[3];
		}
		if (((WeelPosition - 2) == MinComp) && (WeelPosition > 2) && (WeelPosition < 358))
		{
			PORTC |= Min_Portc[4];
			PORTA |= Min_Porta[4];
		}
		// END MINIUTE

		// second in circle bar mode
		if (WeelPosition == SecComp)
		{
			//PORTC |= 0x02;
			//PORTA |= 0x00;
			PORTC |= 0xFE;
			PORTA |= 0xFF;
		}

		// dot every 5 minutes
		if ((WeelPosition == 0) ||
			(WeelPosition == 30) ||
			(WeelPosition == 60) ||
			(WeelPosition == 90) ||
			(WeelPosition == 120) ||
			(WeelPosition == 150) ||
			(WeelPosition == 180) ||
			(WeelPosition == 210) ||
			(WeelPosition == 240) ||
			(WeelPosition == 270) ||
			(WeelPosition == 300) ||
			(WeelPosition == 330))
		{
			PORTC |= 0x02;
			PORTA |= 0x00;
		}

		//green
		if (((WeelPosition % 6) == 0) && (WeelPosition < 360)) PORTC |= 0x01;

		One();
		Two();
		Three();
		Six();
		Nine();
		Hari();
	}
	else
	{
		if (((WeelPosition % 6) == 0) && (WeelPosition < 360)) PORTC |= 0x01;

		Pos = ((WeelPosition-100) / 3);
		if (Pos < 49)
		{
			// shifted by 1 to MSB , we have different wiring
			port_c = (TimeString[48-Pos] << 1);
			PORTC |= port_c;
		}
	}
	WeelPosition++;
}

ISR(TIMER0_OVF_vect)
{
	static unsigned char Tick;
	Tick++;
	if (Tick > 62)
	{
		Time(FALSE);
		Tick = 0;
		Rps = CountRps;
		CountRps=0;
	}
	TCNT0 = 0x04; // reload counter
}

ISR(INT1_vect)
{
	unsigned char count; //, code, address;
	unsigned int IR_input;

	PORTC = 0x00;
	PORTA = 0x00;

	TCNT2 = 0;
	while(!(PIND & 0x08));
	count = TCNT2;

	if(count < 30) 		  //to verify start pulse (2.4 ms long)
	{
	  //delay_ms(20);
	  //ENABLE_INT0;
	  _delay_ms(20);
	  sei();
	  return;
	}

	//PORTC |= 0x20;

	IR_input = read_IR ();

	//code = (unsigned char) ((IR_input & 0xff00) >> 8);
	//address = (unsigned char) (IR_input & 0x00ff);

	//motorControl ( code, address );
	LatchedIrData = (unsigned char) ((IR_input & 0xFF00) >> 8);
	if (LatchedIrData > 0)
	{
		if ((LatchedIrData & 0xFF) == 0x10) Time(TRUE);
		if ((LatchedIrData & 0xFF) == 0x12) ClockStyle = DIGITAL;
		if ((LatchedIrData & 0xFF) == 0x13) ClockStyle = ANALOG;
		LatchedIrData = 0;
	}

	_delay_ms(250);
	//PORTC &= ~0x20;
}

unsigned int read_IR (void)
{
    unsigned char pulseCount=0,  code = 0, address = 0, timerCount;
	unsigned int IR_input;

	while(pulseCount < 7)
	{
	   while(PIND & 0x08);
	   TCNT2 = 0;

	   while(!(PIND & 0x08));
	   pulseCount++;

	   timerCount = TCNT2;

	   if(timerCount > 14)
	      code = code | (1 << (pulseCount-1));
	   else
	 	  code = code & ~(1 << (pulseCount-1));
	 }

	 pulseCount = 0;
	 while(pulseCount < 5)
	 {
	   while(PIND & 0x08);
	   TCNT2 = 0;

	   while(!(PIND & 0x08));
	   pulseCount++;

	   timerCount = TCNT2;

	   if(timerCount > 14)
	      address = address | (1 << (pulseCount-1));
	   else
	 	  address = address & ~(1 << (pulseCount-1));
	 }

	 IR_input = (((unsigned int)code) << 8) | address;

	 return(IR_input);
}

void One(void)
{
	unsigned char Two_Portc[3] = {0x10, 0xF8, 0x00};
	unsigned char Two_Porta[3] = {0x40, 0xC0, 0x40};

	if ((WeelPosition >= 357) && (WeelPosition <= 359))
	{
		PORTC |= Two_Portc[WeelPosition-357];
		PORTA |= Two_Porta[WeelPosition-357];
	}
}

void Two(void)
{
	unsigned char Two_Portc[5] = {0x10, 0x08, 0x88, 0x48, 0x30};
	unsigned char Two_Porta[5] = {0x40, 0xC0, 0x40, 0x40, 0x40};

	if ((WeelPosition >= 2) && (WeelPosition <= 6))
	{
		PORTC |= Two_Portc[WeelPosition-2];
		PORTA |= Two_Porta[WeelPosition-2];
	}
}

void Three(void)
{
	unsigned char Two_Portc[7] = {0xF8, 0x10, 0x20, 0x10, 0x08, 0x88, 0x70};

	if ((WeelPosition >= 88) && (WeelPosition <= 94))
	{
		PORTC |= Two_Portc[WeelPosition-88];
	}
}

void Six(void)
{
	unsigned char Two_Portc[5] = {0x30, 0x48, 0x48, 0x48, 0xF0};
	unsigned char Two_Porta[5] = {0x00, 0x40, 0x40, 0x80, 0x00};

	if ((WeelPosition >= 178) && (WeelPosition <= 182))
	{
		PORTC |= Two_Portc[WeelPosition-178];
		PORTA |= Two_Porta[WeelPosition-178];
	}
}

void Nine(void)
{
	unsigned char Two_Portc[7] = {0x30, 0x40, 0x80, 0xF0, 0x88, 0x88, 0x70};
	if ((WeelPosition >= 267) && (WeelPosition <= 273))

	{
		PORTC |= Two_Portc[WeelPosition-267];
	}
}

void Hari(void)
{
	unsigned char P_Porta[65] = { 	0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00,
									0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x3E, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00,
									0x34, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00,
									0x3C, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00,
									0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00 };

	if ((WeelPosition >= 148) && (WeelPosition <= 211))
	{
		PORTA |= P_Porta[WeelPosition-148];
	}
}

Share