Fostering Friendships - Enhancing Social Bonds in the Classroom
The propeller
1. The Propeller, a mechanically scanned LED clock by Bob Blick.
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 a rotary transformer, or
using slip rings.
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. 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 handwired the circuit together. Use
an 18-pin socket for the 16C84 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 ohms. 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 a 47000uf 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,
2. this is a clock and should be accurate.
Step Three: Program the 16C84.
You'll need a programmer that will program a PIC16C84. 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)
OFF and oscillator to normal XT crystal.
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
losses. The clock may be working at this point, displaying 12:00. If
it
isn't. there was probably some voltage on the supercap 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 vibration.
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). Like this:
bcf STATUS,C
rrf period_calc,w
call Delay
January 25, 1997 Bob Blick
www.bobblick.com
copyright 1997 Bob Blick, all rights reserved
3. updated notes November 2, 2006
Note: PIC16C84 is now obsolete. You may use either a PIC16F84 or
PIC16F84A with no changes.
Note: Sharp VCR motor is now 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.
The Propeller Clock Parts List
Capacitors:
C1, C2 - 33pf ceramic
C3, C6 - 0.1uf ceramic
C4 - 47uf electrolytic
C5 - 47,000uf supercap(memory cap)
Diodes:
D1-D7 - light emitting diodes
D8-16 - 1N4001 general purpose 1amp rectifiers
Resistors:
R1 - 120 ohm DIP array or seven 120 ohm resistors
R2-R6 - 10k ohm
Misc:
J1 - three terminal Berg connector
SW1-SW3 - normally open pushbutton switches
U1 - PIC16C84, PIC16F84 or PIC16F84A
XTAL1 - 4MHz crystal
MOTOR - Sharp RMOTV1007GEZZ
Note: U1 to be programmed with mclock hex file
Code
;--------
; 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, www.gnu.org
; No warranties expredded or implied
; Bob Blick February 18, 2002
;--------
list p=16C84
radix hex
4. include "p16c84.inc"
;--------
; 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
bigtick_hi
bigtick_lo
keys ;key value
scratch ;scratch value
tick ;used by delay
endc
;--------
; Start of ROM
;--------
org 0x00 ;Start of code space
goto Start
;--------
; INTERRUPT SERVICE ROUTINE
;--------
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
clrwdt
;
; 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
5. 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
Bigtick_out
;--------
; 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
;--------
; CHARACTER LOOKUP TABLE
; ignore high bit. set=LED off, clear=LED on, bit0=bottom LED, bit6=top LED
;--------
Char_tbl
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 ;":"
Char_tbl_end
;--------
; SUBROUTINES STARTING HERE
;--------
; 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
;--------
6. ; 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
7. 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
8. 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?
9. 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
;--------
; end of file
;--------
Hex
:020000040000FA
:10000000F028FF3FFF3FFF3F8C00030E8D00640090
:100010008E0A03198E03980A0319970A0319960F7B
:100020001828FC3096006C309700793098009114B5
:100030000D0E83008C0E0C0E0B1109008207C134CB
:10004000BE34BE34BE34C134FF34DE348034FE34BA
:10005000FF34DE34BC34BA34B634CE34BD34BE34AE
:10006000AE349634B934F334EB34DB348034FB34BF