Why build a clock that displays four letter words? The current time is everywhere; on your PC, smartphone, GPS, MP3 player, etc. Heck, you may even own a watch! Four letter words are pretty common as well.
-----So why then?
1st) I had this display that I bought from DealExtreme.Com. The only reason I got it was because it was so cheap.
2nd) I wanted to experiment with writing/reading data from an EEPROM with a microcontroller.
3rd) I wanted to experiment with controlling two devices on the I2C bus in one application.
But.... I wanted a fun project idea to make the effort seem somewhat worthwhile and settled on a Four Letter Word Clock.
If you you just want to see the results and are not interested in the build details, here is a short video demo.
The time is shown in 24 hour format on the left four 7-segments. Every second a different four letter word is shown on the right four 7-segments. The eight LEDs under the 7-segments progress from left to right as a way to display seconds. If you listen closely to the video and you can hear a relay that gives the clock a mechanical ticking sound.
The buttons under the LEDs are used to set hours (S1), minutes (S2), increase display brightness (S6), decrease display brightness (S7), and turn ON/OFF the mechanical ticking sound (S8) from the relay.
The major components of the build are (full schematic to follow):
- Eight x 7-Segment + 8 x Red/Green LED + 8 x Input Button Display Module
- 24LC256 EEPROM to store the 1,003 four letter words
- DS1307 Real Time Clock (RTC) for time keeping
- Small relay to provide a clock like, mechanical ticking sound
- PICAXE 18M2 microcontroller with custom code provides the brains
------
The display from DealExtreme.Com is pretty awesome for the price. It contains eight 7-segment LED displays, eight LEDs that can be red, green, or red/green, and eight button switches. The display has a solid, well built feel to it and was a bargain at $4.99. As a plus, you can control all these feature with only three I/O pins on a microcontroller. On the downside, it ships with no documentation (zero, zip, nada...) so plan on doing some web searching to understand it.
-----
The 24LC256 EEPROM, DS1307 RTC, and PICAXE 18M2 are easy to get from many web sources. I rescued the Teladyne 712-5 relay from a trash bound PCB. A good thing because a web search shows that relay at $28 (it's an RF spec relay!). No fear, you can leave the relay off or just use any cheap relay as it is not used to switch any current, just for the ticking sound.
-----
Now came the time to load the 24LC256 EEPROM with four letter words. So... to the internet for a quickie download of all the four letter English words (including all your favorite cuss words) in one tight ASCII text file. Unfortunately, 7-segment displays don't display letters like "K", "M", "V", "W", "X", and "Z" very readable. I wrote a short Python script to pull out the offenders, which also meant some of the more 'expressive' words where lost. After it was all done, there were 1,003 four letters words that easily fit into the 24LC256 EEPROM. A short (and separate) program was written to tell the PICAXE 18M2 to load these words into the 24LC256 EEPROM.
-----
The harder part was the code to drive the display. The lack of documentation made it pretty challenging. I always find the PICAXE forum helpful in these situations (special thanks to "mjy58"). After much coding/debugging, the problem was solved.
Controlling the two I2C devices (the 24LC256 EEPROM and the DS1307 RTC) from the PICAXE 18M2 was a bit easier than I expected after sorting through the addressing procedures.
----
Here is a short vid (time lapse) of the rig working on the AXE091 development board. In the vid you can see the eight red LEDs progress from left to right as the seconds tick by.
-----
After verifying the operation for a few days the whole mess was moved off the AXE091 development board and onto a strip board PCB. Installing the rig into a $3.49 metal project box from Radio Shack provided a clean finished product.
-----
Below is the build schematic (click to enlarge).
-----PICAXE source code:
#rem
*******************************
***** www.WhiskeyTangoHotel.Com *****
*******************************
Project Name: 4Letter Word Clock
Start Date: August 2012
Program Rev History:
*******************************
http://www.dealextreme.com/p/8x-digital-tube-8x-key-8x-double-color-led-module-81873
#endrem
;
;LKM1638 Input pin 3 (CLK) ---> 18M2 c.0 LEG 17
;LKM1638 Input pin 4 (DIO) ---> 18M2 c.1 LEG 18
;LKM1638 Input pin 5 (STB0) ---> 18M2 c.2 LEG 1
;24LC156 EERPOM WP (Write Protect) GND
;24LC156 EERPOM SDA ----> 18M2 b.1 LEG 7
'24LC156 EERPOM SCL ----> 18M2 b.4 LEG 10
#picaxe 18m2
#no_data 'do not read internal 18M2 EEPROM
dirsc = 010111 ;c0, c1, c2, c4 as output
symbol clock = c.0 ;Clock output pin
symbol dio = c.1 ;Data input output pin
symbol strobe = c.2 ;Strobe output pin
' s1 thru s8 are the tact swithes under the single RED/Green LEDs
symbol s1 = bit16 ;b2 'to set hours
symbol s2 = bit17 ;b2 ' to set minutes - both to set seconds
symbol s3 = bit18 ;b2
symbol s4 = bit19 ;b2
symbol s5 = bit20 ;b2
symbol s6 = bit21 ;b2
symbol s7 = bit22 ;b2
symbol s8 = bit23 ;b2 'toggle to turn on and off the ticker relay
symbol dataio = b0 ;w0 and bit 0 to bit 7
symbol pad = b1 ;w0 and bit 8 to bit 15
symbol iobuf = w0 ;b0, b1
symbol keys = b2 ;bit16 to bit 23
symbol fixaddr = b3 ;start address for DE display
symbol Segment4LEFT = b4 ;Rightmost 7 seg, LEFT Side
symbol Segment4RIGHT = b5 ;Rightmost 7 seg, RIGHT Side
symbol Segment2LEFT = b6 ;Leftmiddle 7 seg, LEFT Side
symbol Segment3LEFT = b7 ;Rightmiddle 7 seg, LEFT Side
symbol Segment2RIGHT = b8 ;Leftmiddle 7 seg, Right Side
symbol Segment3RIGHT = b9 ;Rightmiddle 7 seg, Right Side
symbol Segment1LEFT = b10 ;Leftmost 7 seg, LEFT Side
symbol Segment1RIGHT = b11 ;Leftmost 7 seg, Right Side
symbol char = b12
symbol bank = b13
symbol tmpry = b14
symbol dispbrit = b15
symbol autoaddr = b16
symbol readmode = b17
symbol tmpry2 = b18
symbol EEPROMChar = b19
'w10 (b20/21) = used to read var from EEPROM
symbol LEDTicker = b22
symbol seconds = b23 ' vars for RTC
symbol minutes = b24
symbol hours = b25
symbol blinky = b26 'for RTC 010000 would Enable output at 1Hz blink rate. 000000 is no blink
symbol junkread = b27 'used to read/write RTC day, month, year, date. Also as a temp var in time set adjust routines
fixaddr = $c0
dispbrit = $88 '$88 (136DEC) min bright. $8F (143DEC) max bright
autoaddr = $40
readmode = $42
init:
high strobe ;Ensure strobe is initially high
gosub clearchars ;Clear all characters
blinky = 010000 ' 010000 would Enable output at 1Hz blink rate, start w/ relay click ON.. 000000 is no blink.
' Set the time on the DS1307 RTC
i2cslave %11010000, i2cslow, i2cbyte ; set PICAXE as master and DS1307 slave address
pause 50
'\/ \/ \/ \/ Un_REM THESE LINES (BELOW) IF SETTING UP A NEW RTC \/ \/ \/ \/
#rem
' Set the RTC chip time
; write time and date e.g. to 11:59:00 on Thurs 25/12/03
'; would be "writei2c 0,($00, $59, $11, $03, $25, $12, $03, 010000)"
' readi2c 0, (b0,b1,b2,b3,b4,b5,b6,b7) reads back the data
let hours = $19 ; 01-12 Note all BCD format
let minutes = $11 ; 00-59 Note all BCD format
let seconds = $10 ; 00-59 Note all BCD format
; program does not use for we use seconds. Set manually in the write statement
' for SQ Wave out on RTC. Last val: 010000 would Enable output at 1Hz blink rate. 000000 is no blink
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 50
#endrem
'/\ /\ /\ /\ Un_REM THESE LINES (ABOVE) IF SETTING UP A NEW RTC /\ /\ /\ /\
;--------------------------------------------------------
'I have laid out the 8 segments in the display as:
'| Segment1LEFT | Segment2LEFT | Segment3LEFT | Segment4LEFT | Segment1RIGHT | Segment2RIGHT | Segment3RIGHT | Segment4RIGHT
;Segment Values 0-9 = ( 0 , 1, 2 , 3 , 4 , 5 , 6 , 7, 8 , 9,
' 10-19 = A , b , C , d , E , F , g, H, i, J,
' 20-29 = K, L, M, N, o, P, q, r, S, T,
' 30-35 = U, V, W, X, y, Z ,
' 36-44 = segA, segB, segC, segD, segE, segF, segG, dp, off)
'the 'gosub display' routine expect 8 values; SegmentxLEFT and SEGMENTxRIGHT coded as
'lookup values shown in the rem above.
main:
if s1 = 1 or s2 = 1 or s8 = 1 then 'setting the clock time or relay ticker
if s1 = 1 and s2 = 0 then 'setting hours
junkread = junkread + 1
if junkread > 23 then
junkread = 0
end if
lookup junkread, ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23), hours
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 10
endif ' s1 = 1, setting hours
if s2 = 1 and s1 = 0 then 'setting minutes
junkread = junkread + 1
if junkread > 59 then
junkread = 0
end if
lookup junkread, ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59), minutes
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 10
endif 's2 = 1, setting minute
if s1 = 1 and s2 = 1 then 'reset seconds to 00
seconds = $00
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 10
endif 'settin seconds to zero
if s8 = 1 then ' turn on/off the clicking relay
'read the RTC to dected the seconds for the write to RTC below keeps them accurate
i2cslave %11010000, i2cslow, i2cbyte ; set PICAXE as master and DS1307 slave address
readi2c 0,(seconds, minutes, hours, junkread, junkread, junkread, junkread, blinky)
pause 10
if blinky = 010000 then 'blinky from RTC is ON and clinking the relay. turn it OFF
blinky = 000000
else 'blinky from RTC is OFF and NOT clinking the relay. turn it ON
blinky = 010000
end if
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 500
end if
else ' not settign the clock, check for brightness adjust and run as normal; so read a new 4letter word
if s7 = 1 then 'increase brightness
dispbrit = 140 'other values cause random LED7 behavior
end if
if s6 = 1 then 'decrease brightness
dispbrit = 136 ' 136 is min bright
end if
sertxd (#dispbrit, 13,10)
'Get SegmentxLEFT values for clock by reading the RTC
i2cslave %11010000, i2cslow, i2cbyte ; set PICAXE as master and DS1307 slave address
readi2c 0,(seconds, minutes, hours, junkread, junkread, junkread, junkread, blinky)
pause 10
gosub ReadEEPROM ' read the four letter word. These are loaded into SegmentxRIGHT vars
gosub Ticker 'ticks thru the R/G LEDs to show seconds
endif
Segment1LEFT = hours & %11110000 / 16 'BCD so shift upper 4 bits to lower 4 bits
Segment2LEFT = hours & 001111
Segment3LEFT = minutes & %11110000 / 16 'BCD so shift upper 4 bits to lower 4 bits
Segment4LEFT = minutes & 001111
gosub display 'Put the SegmentxLEFT and SEGMENTxRIGHT characters onto the 7 seg displays.
gosub getkeys ;Read tact buttons
goto main
'-------------------------------------------------------
Ticker: 'ticks thru the R/G LEDs to show seconds by cycling through each LED address
junkread = seconds & %11110000
junkread = junkread / 16 * 10
seconds = seconds & 001111
seconds = junkread + seconds
lookup seconds, (1,1,1,1,1,1,1,1,3,3,3,3,3,3,3,3,5,5,5,5,5,5,5,5,7,7,7,7,7,7,7,7,9,9,9,9,9,9,9,9,11,11,11,11,11,11,11,11,13,13,13,13,13,13,13,13,15,15,15,15), dataio
dataio = dataio + fixaddr ;LEDs are at odd addresses 1 to 15
junkread = dataio 'used to turn off LED later in this sub
low strobe
gosub sendchar
LEDTicker = LEDTicker + 1
if LEDTicker = 2 then
LEDTicker = 1
end if
dataio = LEDTicker 'Light the LEDs. 1 = RED. 2 = GREEN. 3 = R/G
gosub sendchar
high strobe
'Turn off LED here
dataio = junkread
low strobe
gosub sendchar
dataio = 0 '0 turns off the currently selected LED
gosub sendchar
high strobe;
dataio = dispbrit ;Display control on, brightness level
low strobe ;Strobe low
gosub sendchar
high strobe ;Strobe high
return 'Ticker
ReadEEPROM:
'24LC256 EEPROM is loaded with 987 four letters words (3948 characters)
'Each character is an address from 0 to 3947
'readi2c addrs, (charvalue)
i2cslave %10100000, i2cslow, i2cword ; set PICAXE as master and DS1307 slave address
'Read and Translate the char read from the EEPROM for the lookup(.,...), dataio command.
'Read the EEPROM letter then subtract 87 from that ASCII value for the "lookupchar" sub. Examples:
'ASCII value for a = 97; Lookup in this program value is 10. So, 97 - 87 = 10
'ASCII value for j = 106; Lookup in this program value is 19. So, 106 - 87 = 19
'ASCII value for k = 122; Lookup in this program value is 35. So, 122 - 87 = 35
readi2c w10, (Segment1RIGHT)
Segment1RIGHT = Segment1RIGHT - 87
w10 = w10 + 1
readi2c w10, (Segment2RIGHT)
Segment2RIGHT = Segment2RIGHT - 87
w10 = w10 + 1
readi2c w10, (Segment3RIGHT)
Segment3RIGHT = Segment3RIGHT - 87
w10 = w10 + 1
readi2c w10, (Segment4RIGHT)
Segment4RIGHT = Segment4RIGHT - 87
w10 = w10 + 1
'check if "yurt" (the last possible word) is displayed?
if Segment1RIGHT = 34 AND Segment2RIGHT = 30 AND Segment3RIGHT = 27 AND Segment4RIGHT = 29 then 'yes. it is "yurt"
w10 = 0 'yurt' found, so go back to address 0 (the first 4letter word)
end if
pause 1000 'keep the secs LED on and slow down the words
return ' ReadEEPROM
;--------------------------------------------------------
display: ;Displays data on the 7 seg displays, using 2 blocks of 4 digits
bank = 0 ;LEFT Side: First block of digits
dataio = fixaddr + bank + 0 ;Set Leftmost 7 seg, LEFT Side write address
low strobe ;Strobe low
gosub sendchar
char = Segment1LEFT ;Leftmost 7 seg, LEFT Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
dataio = fixaddr + bank + 2 ;Set Leftmiddle 7 seg, LEFT Side write address
low strobe ;Strobe low
gosub sendchar
char = Segment2LEFT ;Leftmiddle 7 seg, LEFT Side
gosub lookupchar
gosub sendchar
high strobe ; End of data - Strobe high
dataio = fixaddr + bank + 4 ;Set Rightmiddle 7 seg, LEFT Side write address
low strobe ; Strobe low
gosub sendchar
char = Segment3LEFT ;Rightmiddle 7 seg, LEFT Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
dataio = fixaddr + bank + 6 ;Set Rightmost 7 seg, LEFT Side write address
low strobe ; Strobe low
gosub sendchar
char = Segment4LEFT ;Rightmost 7 seg, LEFT Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
'RIGHT BANK
bank = 8 ;RIGHT Side: Second block of 4 digits
dataio = fixaddr + bank + 0 ;Set Leftmost 7 seg, Right Side write address
low strobe ;Strobe low
gosub sendchar
char = Segment1RIGHT ;Leftmost 7 seg, Right Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
dataio = fixaddr + bank + 2 ;Set Leftmiddle 7 seg, Right Side write address
low strobe ;Strobe low
gosub sendchar
char = Segment2RIGHT ;Leftmiddle 7 seg, Right Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
dataio = fixaddr + bank + 4 ;Set Rightmiddle 7 seg, Right Side write address
low strobe ; Strobe low
gosub sendchar
char = Segment3RIGHT ;Rightmiddle 7 seg, Right Side
gosub lookupchar
gosub sendchar
high strobe ; End of data - Strobe high
dataio = fixaddr + bank + 6 ;Set Rightmost 7 seg, RIGHT Side write address
low strobe ; Strobe low
gosub sendchar
char = Segment4RIGHT ;Rightmost 7 seg, RIGHT Side
gosub lookupchar
gosub sendchar
'-----------------
'must refresh dispbrit each time
dataio = dispbrit ;Display brightness level. $88 (136DEC) min bright. $8F (143DEC) max bright
low strobe ; Strobe low
gosub sendchar
high strobe ; Strobe high
return 'display
;--------------------------------------------------------
clearchars: ;Clear LEDs and 7 seg displays. ALL LEDS OFF. Segs and LEDs
dataio = autoaddr ; Data mode auto increment
low strobe ; Strobe low
gosub sendchar
high strobe ; Strobe high
;
low strobe ; Strobe low
dataio = fixaddr ; Set start address
gosub sendchar
for tmpry = 1 to $0f ;$0F = 15, so loop runs 16 times. 7 LEDs and 7 seg displays
dataio = 0 ;Zero blanks the display
gosub sendchar
next
high strobe ;Strobe high, keep low to end of data
return
;--------------------------------------------------------
sendchar: ;Routine to send all characters to LKM1638 module serially
pad = $ff ;$FF = 255. Set counter
high clock ;Ensure clock is high for pulseout
do
pinc.1 = bit0 ;Make c.1 the value in bit0
iobuf = iobuf/2 ;Shift right
pulsout clock,1 '10us clock pulse
loop Until pad = 0 'excecute 256 times
return
;--------------------------------------------------------
getkeys: ;Reads the input tact buttons in and places them in bits16 to bits23
dataio = readmode ; Data mode read
low strobe
gosub sendchar
input c.1 ;set c.1 as input
high clock ;Ensure clock is high for pulseout
for tmpry = 1 to 16 ;Read in bits 0-15
bit0 = pinc.1 ;Make bit0 the value on c.1. Need to use c.1 as it is both in & out
iobuf = iobuf*2 ;Shift bit left
pulsout clock,1 ;10us clock pulse, read next bit
next
s6 = bit3 ;Move 1st word switch values out of buffer
s2 = bit7
s5 = bit11
s1 = bit15
for tmpry = 1 to 16 ;Read in bits 16-31
bit0 = pinc.1 ;Make bit0 the value on b.0. Need to use c.1 as it is both in & out
iobuf = iobuf*2 ;Shift bit left
pulsout clock,1 ;10us clock pulse, read next bit
next
s8 = bit3 ;Move 2nd word switch values out of buffer
s4 = bit7
s7 = bit11
s3 = bit15
output c.1 ;Return c.1 to output
high strobe
return
;--------------------------------------------------------
lookupchar: ;Looks up the code to display the digit in 'char' on the 7 seg display
;character 0-9 = ( 0 , 1, 2 , 3 , 4 , 5 , 6 , 7, 8 , 9,
' 10-19 = A , b , C , d , E , F , g, H, i, J,
' 20-29 = K, L, M, N, o, P, q, r, S, T,
' 30-35 = U, V, W, X, y, Z ,
' 36-44 = segA, segB, segC, segD, segE, segF, segG, dp, off)
lookup char,(63,6,91,79,102,109,125,7,127,111,119,124,57,94,121,113,111,118,16,30,118,56,21,84,92,115,103,80,109,120,62,28,42,118,110,91,1,2,4,8,16,32,64,128,0),dataio
return
;-----------------------------------------
-----
Link back: Hack A Day
-----