Wednesday, 1 April 2015

Build An LED Propeller Clock

                          Build An LED Propeller Clock

My "Propeller Clock" is a mechanically scanned LED clock with seven light emitting diodes that spin, giving the illusion of numbers floating in the air. This is the first clock I ever built. I've built a few LED signs, but they get boring because I already know the message.

This clock utilizes only a few relatively inexpensive electronic components and a recycled motor from a VCR or floppy drive. 

The Propeller Clock (Bundle) Parts List

C1, C2 - 33pF ceramic capacitors
C3, C6 - 0.1µF ceramic capacitors
C4 - 47µF electrolytic capacitors
C5 - 47,000µF supercap (memory cap)

D1-D7 - light emitting diodes
D8-16 - 1N4001 general purpose 1 amp rectifiers

R1 - 120Ω DIP array or seven 120 ohm resistors
R2-R6 - 10kΩ resistors

J1 - Straight pin male header
SW1-SW3 - normally open pushbutton switches
U1 - 16F84 programmed with mclock code
XTAL1 - 4MHz crystal
MOTOR - Use or recycle any DC motor, preferably meant for 12 volts so the speed will not be too great when operated at approximately 6.2 volts.

How this clock works:

A motor spins the "propeller", and a small microprocessor keeps track of time and changes the pattern on seven LEDs with exact timing to simulate a 7 x 30 array of LEDs. It is an illusion, but it works nicely. 

If you want to build this clock, you will need a few things:
  • Skill with motors and mechanical things
  • Prior electronic experience
  • A dead VCR or floppy drive or other source of a suitable motor and miscellaneous parts
  • A programmer that will program a 16F84 microprocessor

The clock is on a spinning piece of perfboard, but it must get power. I thought of many ways to do this, including using two motors (motor one has its shaft fixed to a base, and motor two spins the body of motor one, generating electricity), making arotary transformer, or using slip rings. Eventually, I decided to do it another way, taking power from the spinning armature of a plain DC motor. In order to run the wires 

out of the motor, I removed the bearing from one end of the motor, leaving a big hole. 

There are three terminals inside most small DC motors, and it acts a lot like three-phase alternating current, so it must be rectified back to DC. A nice side effect of this is that the position of the motor can be detected by taking one of the phases straight into the microprocessor

Step One: Mangle a Motor

Find a VCR, perhaps a Sharp or a Samsung, with a flat reel motor. The motor I have is marked JPA1B01, but Sharp knows it by the number RMOTV1007GEZZ (author's note: Sharp motor is obsolete. Use any DC motor, preferably meant for 12 volts so the speed will not be too great when operated at approximately 6.2 volts). Take it apart without mangling the brushes (there are little holes to slip a paperclip into to move the brushes out of the way), and notice that it has one ball bearing and one sleeve bearing. 

Knock the sleeve bearing out of the case and glue or solder it to the other end of the motor, as an extension of the ball bearing. The shaft of the motor will have to be repositioned slightly to get the right height, press it in a vise with a hollow spacer on one end. Take a Berg connector with three wires and solder them to the three terminals on the motor's armature. Glue a short threaded spacer to the shaft at the end that will stick out the hole, and reassemble the motor (be careful with the brushes). You can glue the motor to a VCR head as a weighted base. 

Step Two: Build the Circuit

I used perfboard (Vectorboard) and hand-wired the circuit together. Use an 18-pin socket for the 16F84 because it needs to be programmed before putting it in the circuit. For the 7 current-limit resistors I used a DIP resistor array because it made it easy to experiment with LED brightness. I settled on 120Ω. You can use seven regular resistors, because 120 ohms works fine, though it puts the peak current right at the limit for the 16C84. Think about balance while you build this circuit, and reference my pictures, so you don't have to add a lot of balancing weight later. Substitute for any part values you like. Note that I used a47000µF supercap, it is to keep the clock running after turning it off so you can set the time. The LEDs get power separate from this. Don't substitute a ceramic resonator for the 4MHz crystal, this is a clock and should be accurate. 

Step Three: Program the PIC16C84A

You'll need a programmer that will program a PIC16C84A. If you found this file/web page, you can find plans to build a 16C84 programmer. Program it using the hex file accompanying this document. I have included the source code (.asm) just for your amusement. When programming the chip, set the chip options to: watchdog timer (WDT) ON and oscillator to normal XTcrystal

Step Four: Throw It Together and Keep Time

Screw the circuit board to the motor, and plug the three wire connector in. Apply power to the motor. The preferred voltage is 6.2 volts, but it will run from 5 volts to about 7.5 volts. Note that 5 volts gets to the circuit when 6.2 volts is applied to the motor, because of diode 252BNAV losses. The clock may be working at this point, displaying 12:00. If it isn't, there was probably some voltage on the supercap (memory cap) when you plugged in the chip. 

Turn off the power and momentarily short pins 5 and 4 together (ground and /mclr) to reset the chip. Now when you apply power the clock should work, and you can set it by turning off the power and pushing the buttons (hours, 10 minutes, minutes) the right number of times. If the numbers appear backwards, reverse the polarity to the motor to make it spin the other way. You might experiment with balancing the clock, and the use of foam under the base to reduce vibrtion. 

Step Five: Modifications

If you look closely at the source code, you'll see that the "dot rate" is adjusted to the speed of the motor to make the display a consistent width regardless of the motor's speed. The motor I used has brushes set 90 degrees apart, and gives two indexes each revolution. The clock displays on two sides, 180 degrees apart. If you use a motor with the brushes 180 apart, the clock will only display on one side, and the numbers will be too wide. You'll want to modify the program, in the section marked D_lookup_3. The value in the W register when Delay gets called effects the width of the digits. You might try sending half of the period_calc value to Delay; perhaps by rotating period_calc right into W (remember to clear the carry flag first). 

The source code in Microchip MPASM format.

; mclock8.asm
; "The Propeller" mechanically scanned LED clock
; some changes since last version -
; modified table etc for compatiblility with 8th LED
; watchdog timer used to ensure startup
; Bob Blick February 12, 1997
; Licensed under the terms of the GNU General Public License,
; No warranties expredded or implied
; Bob Blick February 18, 2002
  list p=16C84
  radix hex
  include ""
; remember to set blast-time options: OSC=regular xtal, WDT=ON
; timings all based on 4 MHz crystal
; are these equates already in the include file? someday I'll look.
w  equ 0
f  equ 1
; Start of available RAM.
 cblock 0x0C
  safe_w  ;not really temp, used by interrupt svc
  safe_s  ;not really temp, used by interrupt svc
  period_count ;incremented each interrupt
  period_dup ;copy of period_count safe from interrupt
  period_calc ;stable period after hysteresis calc.
  flags  ;b2=int b1=minute b4=edge
  dot_index ;which column is being displayed
  digit_index ;which digit is being displayed
  hours  ;in display format, not hex(01-12)
  minutes  ;00 to 59
  bigtick_dbl ;incremented each interrupt
  keys  ;key value
  scratch  ;scratch value
  tick  ;used by delay
; Start of ROM
  org 0x00  ;Start of code space
  goto Start
  org 0x04  ;interrupt vector
Intsvc  movwf safe_w  ;save w
  swapf STATUS,w ;swap status, w
  movwf safe_s  ;save status(nibble swap, remember)
; done saving, now start working
; clear watchdog timer to ensure startup
; increment period count
  incf period_count,f
  btfsc STATUS,Z ;zero set means overflow
  decf period_count,f
; 234375 interrupts every minute. Increment the bigtick each time.
  incf bigtick_lo,f
  btfsc STATUS,Z
  incf bigtick_hi,f
  btfsc STATUS,Z
  incfsz bigtick_dbl,f
  goto Bigtick_out
; here? bigtick has rolled over to zero and one minute has passed.
; reload bigtick and set a flag for the main counter
  movlw 0xFC  ;234375 = 0x039387
  movwf bigtick_dbl ;0 - 0x039387 = 0xFC6C79
  movlw 0x6C
  movwf bigtick_hi
  movlw 0x79
  movwf bigtick_lo
  bsf flags,1  ;notify Keep_time
; done working, start restoring
  swapf safe_s,w ;fetch status, reswap nibbles
  movwf STATUS  ;restore status
  swapf safe_w,f ;swap nibbles in preparation
  swapf safe_w,w ;for the swap restoration of w
  bcf INTCON,2 ;clear interrupt flag before return
  retfie   ;return from interrupt
; ignore high bit. set=LED off, clear=LED on, bit0=bottom LED, bit6=top LED
  addwf PCL,f
  dt 0xC1,0xBE,0xBE,0xBE,0xC1 ;"O"
  dt 0xFF,0xDE,0x80,0xFE,0xFF ;"1"
  dt 0xDE,0xBC,0xBA,0xB6,0xCE ;"2"
  dt 0xBD,0xBE,0xAE,0x96,0xB9 ;"3"
  dt 0xF3,0xEB,0xDB,0x80,0xFB ;"4"
  dt 0x8D,0xAE,0xAE,0xAE,0xB1 ;"5"
  dt 0xE1,0xD6,0xB6,0xB6,0xF9 ;"6"
  dt 0xBF,0xB8,0xB7,0xAF,0x9F ;"7"
  dt 0xC9,0xB6,0xB6,0xB6,0xC9 ;"8"
  dt 0xCF,0xB6,0xB6,0xB5,0xC3 ;"9"
  dt 0xFF,0xC9,0xC9,0xFF,0xFF ;":"
; clear important bits of ram
Ram_init movlw 0x07
  movwf keys
  movlw 0x12  ;why do clocks always start
  movwf hours  ;at 12:00 ?
  clrf minutes
  clrf dot_index
  clrf digit_index
  movlw 0xFC
  movwf bigtick_dbl
  retlw 0
; unused pins I am setting to be outputs
Port_init movlw 0x00  ;all output, b7=unused
  tris PORTB  ;on port b attached to LEDs
  movlw b'00010111' ;port a has 5 pins. I need 4 inputs
     ;b0=minutes, b1=10mins, b2=hours
     ;b3=unused, b4=rotation index
  tris PORTA  ;on port a
  retlw 0
; get timer-based interrupts going
Timer_init bcf INTCON,2 ;clear TMR0 int flag
  bsf INTCON,7 ;enable global interrupts
  bsf INTCON,5 ;enable TMR0 int
  clrf TMR0  ;clear timer
  clrwdt   ;why is this needed? just do it..
  movlw b'11011000' ;set up timer. prescaler(bit3)bypassed 
  option   ;send w to option. generate warning.
  clrf TMR0  ;start timer
  retlw 0
; test for index in rotation and store period in period_dup
Check_index movf PORTA,w  ;get the state of port a
  xorwf flags,w  ;compare with saved state
  andlw b'00010000' ;only interested in bit 4
  btfsc STATUS,Z ;test for edge
  retlw 0  ;not an edge, same as last
  xorwf flags,f  ;save for next time
  btfsc flags,4  ;test for falling edge
  retlw 0  ;must have been a rising edge
  movf period_count,w ;make a working copy
  movwf period_dup ;called period dup
  clrf period_count ;a fresh start for next rotation
  clrf digit_index ;set to first digit
  clrf dot_index ;first column
; calculate a period that does not dither or jitter
; period will not be changed unless new period is really different
  movf period_calc,w
  subwf period_dup,w ;find difference
  btfss STATUS,C ;carry flag set means no borrow
  goto Calc_period_neg ;must be other way
  sublw 2  ;allowable deviation = 3
  btfss STATUS,C ;borrow won't skip
  incf period_calc ;new value much larger than calc
  retlw 0
Calc_period_neg addlw 2  ;allowable deviation = 3
  btfss STATUS,C ;carry will skip
  decf period_calc ;no carry means it must be changed
  retlw 0
; change LED pattern based on state of digit_index and dot_index
Display_now movlw 0x05
  xorwf dot_index,w ;test for end of digit
  movlw 0xFF  ;pattern for blank column
  btfsc STATUS,Z
  goto D_lookup_3 ;it needs a blank
  bcf STATUS,C ;clear carry before a rotate
  rlf digit_index,w ;double the index because each
  addwf PCL,f  ;takes two instructions
D_10hr  swapf hours,w
  goto D_lookup ;what a great rush of power
D_1hr  movf hours,w  ;I feel when modifying
  goto D_lookup ;the program counter
D_colon  movlw 0x0A
  goto D_lookup
D_10min  swapf minutes,w
  goto D_lookup
D_1min  movf minutes,w
  goto D_lookup
D_nothing retlw 0
D_lookup andlw b'00001111' ;strip off hi bits
  movwf scratch  ;multiply this by 5 for lookup
  addwf scratch,f ;table base position
  addwf scratch,f ;is this cheating?
  addwf scratch,f ;I think not.
  addwf scratch,f ;I think it is conserving energy!
  btfss STATUS,Z ;test for zero
  goto D_lookup_2 ;not a zero
  movf digit_index,f ;this is just to test/set flag
  movlw 0xFF  ;this makes a blank LED pattern
  btfsc STATUS,Z ;test if it is 10 hrs digit
  goto D_lookup_3 ;it's a leading zero
D_lookup_2 movf dot_index,w ;get column
  addwf scratch,w ;add it to digit base
  call Char_tbl ;get the dot pattern for this column
D_lookup_3 movwf PORTB  ;send it to the LEDs
  movlw 0x0C  ;overhead value sub from period
  subwf period_calc,w ;compensate for overhead and set
  call Delay  ;width of digits with this delay
  incf dot_index,f ;increment to the next column
  movlw 0x06  ;6 columns is a digit plus space
  xorwf dot_index,w ;next digit test
  btfss STATUS,Z
  retlw 0  ;not a new digit
  clrf dot_index ;new digit time
  incf digit_index,f
  retlw 0  ;Display_now done.
; a short delay routine
Delay  movwf tick
Delay_loop decfsz tick,f
  goto Delay_loop ;w is not damaged, so Delay can
  return   ;be recalled without reloading
; test for keypress and call time adjust if needed
Check_keys movf PORTA,w  ;get port "a"
  xorwf keys,w  ;compare with previous
  andlw b'00000111' ;only care about button pins
  btfsc STATUS,Z ;zero set=no buttons
  retlw 0  ;return
  xorwf keys,f  ;store key value
  movlw 0x64  ;a fairly long delay will
  movwf scratch  ;prevent key bounces
Key_delay movlw 0xFF
  call Delay
  decfsz scratch
  goto Key_delay
  btfss keys,2  ;test "minutes" button
  goto Inc_mins
  btfss keys,1  ;test "tens" button
  goto Inc_tens
  btfss keys,0  ;test "hours" button
  goto Inc_hours
  retlw 0  ;must be a glitch. yeah, right!
; increment ten minutes
Inc_tens movlw 0x0A
  movwf scratch  ;scratch has ten
Inc_tens_loop call Inc_mins
  decfsz scratch
  goto Inc_tens_loop ;another minute added
  retlw 0
; increment one hour
Inc_hours movlw 0x12
  xorwf hours,w
  btfsc STATUS,Z
  goto Inc_hours_12
  movlw 0x07  ;this part gets a little sloppy
  addwf hours,w
  movlw 0x07
  btfss STATUS,DC
  movlw 1
  addwf hours,f
  retlw 0
Inc_hours_12 movlw 0x01
  movwf hours
  retlw 0
; increment the time based on flags,1 as sent by interrupt routine
; Inc_mins loop also used by time-setting routine
Keep_time btfss flags,1  ;the minutes flag
  retlw 0  ;not this time
  bcf flags,1  ;clear the minutes flag
Inc_mins movlw 0x07  ;start incrementing time
  addwf minutes,w ;add 7 minutes into w
  btfsc STATUS,DC ;did adding 7 cause digit carry?
  goto Sixty_mins ;then test for an hour change
  incf minutes  ;otherwise add 1 for real
  retlw 0  ;and go back
Sixty_mins movwf minutes  ;save the minutes
  movlw 0x60  ;test for 60
  xorwf minutes,w ;are minutes at 60?
  btfss STATUS,Z
  retlw 0  ;no? go back
  clrf minutes  ;otherwise zero minutes
  goto Inc_hours ;and increment hours
; End of subroutines
; Program starts here
Start  call Ram_init ;set variables to nice values
  call Port_init ;set port directions
  call Timer_init ;start timer based interrupt
; Done initializing, start the endless loop.
Circle     ;begin the big loop
; detect falling edge on PORTA,4 to determine rotary index
; calculate rotation period and store in period_dup
; compare with working period(period_calc) and adjust if way different
  call Check_index
; check display state and change if needed
  call Display_now
; check keyboard and adjust time
  call Check_keys
; check minute flag and increment time if a minute has passed
  call Keep_time
; gentlemen, that's a clock, keep it rolling
  goto Circle  ;you heard the man, get going!
; end of file

The hex code ready to load into a PIC16C84A or 16F84 chip.


No comments:

Post a Comment

Contact Us

Name : Unni Menon Email : Facebook Page: Twitt...