I originally made my Word Clocks using PICAXE micro-controllers, some LED drivers, and plain, white, LEDs.

A while ago, I decided to upgrade them to colour LEDs; specifically the WS2812B type of LEDs which are serially addressable, and can be controlled directly from a micro-controller. I also changed the micro-controller to an Arduino. I originally built two of these clocks, and in the updated versions one is using an Arduino UNO, and the other an Arduino Nano – the same micro-controller, but in different formats.

The clock continuously, randomly, fades the colour of each word from one colour to another.

Word Clock in time mode

Word Clock in test mode

 

First thing I decided to have a go at designing and printing were some legs for my Word Clock.

I had already come across Wings3D when I downloaded KiCad for creating schemas and PCB layouts. I've never used Wings3D for designing anything, so this is going to be a learning excercise.

I worked out the design and measurements for the leg and went to design it in Wings3D. One thing I found alien was that WIngs3D has no concept of millimeters or inches or any term of measure, it just has units. The actual size of the object is just how you, or an external program, interprets those units. Not really sure what I was doing, I decided that 1 unit would be one centimeter – which actually turned out to be a stroke of genius later on.

Leg Wings3D

There are lots of extraneous verticies and edges in my model where I was playing around with Wings3D. I didn't really understand the principle extruding edges and faces out from an object, so this model is made from 4 shapes; one along the bottom, the upright and the two triangular supports. I also couldn't work out how to combine the objects in to one object; for example, the upright extends in to the bottom piece, I think this affected the way the final object was printed because you can see where the printer tried to print the faces that were imbedded within another object.

After completing my design, I exported the model to an stl file, and then imported that in to RelicatorG.

(Note regarding ReplicatorG: when I installed it, it needs to point to a Pyton interpreter. I had installed the latest Python 3.4, but I couldn't get ReplicatorG to recognise it, so I installed the earlier Python 2.6, which solved that problem.)

   Leg in ReplicatorG

Once the file was loaded, the model appeared really tiny (smaller than in the image above). Doing some Googling, I discovered that the square grid in ReplicatorG represent centimeters, and the units used in Wings3D have been interpreted as millimeters; so I had designed my model as 5.4 units long (thinking 5.4 centimeters), and ReplicatorG has used that as millimeters – so my model was 5.4 millimeters long. A simple fix was to use the scale function to make the model 10 times bigger – and viola.

I didn't bother trying to play with the ReplicatorG settings and just left everything at the default and went straigt to printing the model.

Legs

It took an hour to print each leg. The first leg printed out no problem, but half way through the first attempt at the second leg ReplicatorG seemed to freeze and it stopped printing. So I had to kill ReplicatorG, cancel the print on the printer and start again. It printed fine on the second attempt.

LegsF LegsB
Pretty smart, if I say so myself.

Schema for the display of the time:-

Display Time Schema-sm

 

Schema for the display of the temperature:-

Display Temp Schema-sm

This is the code for the main 18M2+.

#PICAXE 18M2
#terminal off

; Project: Word Clock v2.
; File: Word Clock V2.bas
; Description:
;     Read time from DS1307 RTC and display on LED matrix, where
;     the LEDs are behind a template with an array of cut out
;     letters and the lit letters will spell out the time in
;     words. The time will only br accurate to within five
;     minutes. Time can also be displayed as numbers using 3x5
;     blocks of LEDs to display numbers.
;
; Copyright (C) 2014 Marc Symonds
; All rights reserved.
;
; This software may be used and redistributed, with or without
; modification, as long as it is understood that this software
; is provided as-is without any explicit or implied warranties
; of merchantablity or fitness of purpose.


#rem
DS1307 Pins

        +---+
    X1 X|1  |X Vcc
    X2 X|   |X SQW/OUT
+VBatt X|   |X I2C/SCL
    0V X|   |X I2C/SDA
        +---+
#endrem

#rem        
PICAXE18-M2 Pins

                               +---+
 DISPLAYF/TEMP - (Out/In) C.2 -|1  |- C.1 (In/Out) - Button - Mode
   LOWBRIGHT/TEMP - (Out) C.3 -|   |- C.0 (In/Out) - Button - Set
             SerIn - (In) C.4 -|   |- C.7 (In/Out) - Piezo
                     (In) C.5 X|   |X C.6 (In/Out) 
                           0V -|5  |- Vcc
   MAX7219/CLK - (Out/In) B.0 -|   |- B.7 (In/Out) - Button - Adjust
DS1307 - (Out/In/I2C SDA) B.1 -|   |- B.6 (In/Out) - Button - Brightness
  MAX7219/DATA - (Out/In) B.2 -|   |- B.5 (In/Out) - LDR
  MAX7219/LOAD - (Out/In) B.3 -|9  |X B.4 (In/Out/I2C SCL) - DS1307
                               +---+
#endrem

#rem
PICAXE-08M2 Pins

                                      +---+
                                  +V -|1  |- 0V
  DISPLAYF/TEMP - (In) Serial in C.5 -|   |- C.0 Serial out (Out/hserout/DAC) - MAX7219/CLK
Temp Sensor - (Touch/ADS/Out/In) C.4 -|   |- C.1 (In/Out/ADC/Touch/hserin/SRI/I2C SCL) - MAX7219/LOAD
           LOWBRIGHT/TEMP - (In) C.3 -|   |- C.2 (In/Out/ADC/Touch/PWM/Tune/SRQ/I2C SDA) - MAX7219/DATA
                                      +---+
#endrem

#rem
Memory:-

00 - 27 - Registers
30 - 36 - Top half buffer.
37 - 43 - Bottom half buffer.
44 - 50 - Top half buffer compare.
51 - 57 - Bottom half buffer compare.

Sending to the MAX7219 is slow, so we only send data where something has changed.


Digit/Segment layout on display (11x10):-

0A 0B 0C 0D 0E 0F 0G 0P 5A 5B 5C
1A 1B 1C 1D 1E 1F 1G 1P 5D 5E 5F
2A 2B 2C 2D 2E 2F 2G 2P 5G 5P 6A      First MAX7219
3A 3B 3C 3D 3E 3F 3G 3P 6B 6C 6D
4A 4B 4C 4D 4E 4F 4G 4P 6E 6F 6G
--------------------------------
0A 0B 0C 0D 0E 0F 0G 0P 5A 5B 5C
1A 1B 1C 1D 1E 1F 1G 1P 5D 5E 5F
2A 2B 2C 2D 2E 2F 2G 2P 5G 5P 6A      Second MAX7219
3A 3B 3C 3D 3E 3F 3G 3P 6B 6C 6D
4A 4B 4C 4D 4E 4F 4G 4P 6E 6F 6G

P = DP.

#endrem

symbol MAX7219_REGISTER = b0
symbol MAX7219_DATA_1 = b1
symbol DS1307_DATA = b0        ; Must use b0 as bitX is used to manipulate. MAX and DS not accessed at the same time, so can reuse variable.
;symbol vWVAR = w0

symbol MAX7219_DATA_2 = b2
symbol MAX7219_SEND_COUNT = b3
;symbol vWVAR = w1

symbol MAX7219_SEND_DATA = b4
;symbol vBVAR = b5
;symbol vWVAR = w2

;symbol vBVAR = b6
;symbol vBVAR = b7
;symbol vWVAR = w3

;symbol vBVAR = b8
;symbol vBVAR = b9
;symbol vWVAR = w4

;symbol MODE_BUTTON_COUNTER = b10
;symbol SET_BUTTON_COUNTER = b11
symbol DISP_TABLE_IDX = w5

;symbol BRIGHTNESS_BUTTON_COUNTER = b12
;symbol ADJUST_BUTTON_COUNTER = b13
;symbol vWVAR = w6

;symbol vBVAR = b14
;symbol vBVAR = b15
;symbol vWVAR = w7

symbol DISP_MEM = b16
symbol DISP_DATA = b17
;symbol vWVAR = w8

symbol DISP_MERGE = b18
symbol DISP_NUM_TO_SHOW = b19
;symbol vWVAR = w9

symbol v_SECONDS = b20
symbol v_MINUTES = b21
;symbol vWVAR = w10

symbol v_HOURS = b22
symbol v_SECONDS_PREV = b23
;symbol vWVAR = w11

symbol v_MINUTES_PREV = b24
symbol v_MODE = b25
;symbol vWVAR = w12

symbol v_BRIGHTNESS = b26
;symbol vBVAR = b27
;symbol vWVAR = w13


' MAX7219 Pins
symbol MAX7219_LOAD = B.2
symbol MAX7219_DIN = B.3
symbol MAX7219_CLK = B.0

; MAX7219 Registers
symbol MAX7219_NOOP = $00
symbol MAX7219_DIGIT0 = $01
symbol MAX7219_DIGIT1 = $02
symbol MAX7219_DIGIT2 = $03
symbol MAX7219_DIGIT3 = $04
symbol MAX7219_DIGIT4 = $05
symbol MAX7219_DIGIT5 = $06
symbol MAX7219_DIGIT6 = $07
symbol MAX7219_DIGIT7 = $08
symbol MAX7219_DECODE_MODE = $09
symbol MAX7219_INTENSITY = $0A
symbol MAX7219_SCAN_LIMIT = $0B
symbol MAX7219_SHUTDOWN = $0C
symbol MAX7219_TEST_MODE = $0F


; DS1307 I2C Address.
symbol DS1307_ADDRESS = %11010000

; DS1307 Registers.
symbol DS1307_SECONDS = $00
symbol DS1307_MINUTES = $01
symbol DS1307_HOURS = $02
symbol DS1307_WEEKDAY = $03
symbol DS1307_DAY = $04
symbol DS1307_MONTH = $05
symbol DS1307_YEAR = $06
symbol DS1307_CONTROL = $07
symbol DS1307_RAM = $08 ; to $3F

; DS1307 Bits (assumes b0 used to manipulate values)
symbol DS1307_SECONDS_CH = bit7 ; Clock Halt - high = halt, low = go

symbol DS1307_HOURS_1224 = bit6 ; high = 12 hour, low = 24 hour
symbol DS1307_HOURS_AMPM = bit5 ; high = PM, low = AM (in 12 hour mode)

symbol DS1307_CONTROL_OUT = bit7
symbol DS1307_CONTROL_SQWE = bit4
symbol DS1307_CONTROL_RS1 = bit1
symbol DS1307_CONTROL_RS0 = bit0


; We'll use the DS1307 RAM to store some values in case main power is lost.
symbol DS1307_USR_MODE = DS1307_RAM + $00 ; Display mode
symbol DS1307_USR_BRIGHTNESS = DS1307_RAM + $01 ; Brightness level


; Display data will be buffered in these memory loctations, before being sent to the display (MAX7219).
symbol MEM_DISPLAY_TOP = 30 ; Start of buffer for top half of display.
symbol MEM_DISPLAY_TOP_END = 36 ; End of buffer for top half of display.
symbol MEM_DISPLAY_BOTTOM = 37
symbol MEM_DISPLAY_BOTTOM_END = 43

symbol MEM_DISPLAY_TOP_CMP = 44 ; -> 50
symbol MEM_DISPLAY_BOTTOM_CMP = 51 ; -> 57

symbol MEM_BUT_MODE = 58
symbol MEM_BUT_SET = 59
symbol MEM_BUT_ADJUST = 60
symbol MEM_BUT_BRIGHTNESS = 61

symbol MEM_BRIGHT_CURR = 62
symbol MEM_BRIGHT_HIGHER = 63
symbol MEM_BRIGHT_COUNT = 64

; Pins used for buttons.
symbol BUT_MODE = C.1
symbol BUT_MODEP = pinC.1
symbol BUT_SET = C.0
symbol BUT_SETP = pinC.0
symbol BUT_ADJUST = B.7
symbol BUT_ADJUSTP = pinB.7
symbol BUT_BRIGHTNESS = B.6
symbol BUT_BRIGHTNESSP = pinB.6

; Pin used for piezo.
symbol PIEZO = C.7

;
symbol BRIGHTNESS_SENSOR = B.5
symbol TEMP_BRIGHTNESS = C.3
symbol TEMP_BRIGHTNESSP = pinC.3
symbol TEMP_FAREN = C.2
symbol TEMP_FARENP = pinC.2

; Data for displaying digits.

; 0 - 9 On left of display.
symbol TAB_NUM_LEFT = 0

table TAB_NUM_LEFT, (%01110000, %01010000, %01010000, %01010000, %01110000, 0, 0) ; 0
table               (%00100000, %01100000, %00100000, %00100000, %01110000, 0, 0) ; 1
table               (%01110000, %00010000, %01110000, %01000000, %01110000, 0, 0) ; 2
table               (%01110000, %00010000, %00110000, %00010000, %01110000, 0, 0) ; 3 
table               (%01010000, %01010000, %01110000, %00010000, %00010000, 0, 0) ; 4
table               (%01110000, %01000000, %01110000, %00010000, %01110000, 0, 0) ; 5
table               (%01110000, %01000000, %01110000, %01010000, %01110000, 0, 0) ; 6
table               (%01110000, %00010000, %00010000, %00010000, %00010000, 0, 0) ; 7
table               (%01110000, %01010000, %01110000, %01010000, %01110000, 0, 0) ; 8
table               (%01110000, %01010000, %01110000, %00010000, %01110000, 0, 0) ; 9

; 0 - 9 In middle of display.
symbol TAB_NUM_MIDDLE = 70

table TAB_NUM_MIDDLE, (%00000111, %00000101, %00000101, %00000101, %00000111, 0, 0)
table                 (%00000010, %00000110, %00000010, %00000010, %00000111, 0, 0)
table                 (%00000111, %00000001, %00000111, %00000100, %00000111, 0, 0)
table                 (%00000111, %00000001, %00000011, %00000001, %00000111, 0, 0)
table                 (%00000101, %00000101, %00000111, %00000001, %00000001, 0, 0)
table                 (%00000111, %00000100, %00000111, %00000001, %00000111, 0, 0)
table                 (%00000111, %00000100, %00000111, %00000101, %00000111, 0, 0)
table                 (%00000111, %00000001, %00000001, %00000001, %00000001, 0, 0)
table                 (%00000111, %00000101, %00000111, %00000101, %00000111, 0, 0)
table                 (%00000111, %00000101, %00000111, %00000001, %00000111, 0, 0)

; 0 - 9 On right of display.
symbol TAB_NUM_RIGHT = 140

table TAB_NUM_RIGHT, (0, 0, 0, 0, 0, %01111011, %01101111)
table                (0, 0, 0, 0, 0, %10101100, %00010111)
table                (0, 0, 0, 0, 0, %11110011, %01100111)
table                (0, 0, 0, 0, 0, %11110010, %01001111)
table                (0, 0, 0, 0, 0, %11011011, %01001001)
table                (0, 0, 0, 0, 0, %11111001, %01001111)
table                (0, 0, 0, 0, 0, %11111001, %01101111)
table                (0, 0, 0, 0, 0, %01110010, %01001001)
table                (0, 0, 0, 0, 0, %11111011, %01101111)
table                (0, 0, 0, 0, 0, %11111011, %01001111)

; H.
symbol TAB_WRD_Hr = 210

table TAB_WRD_Hr, (%01001000, %01001000, %01111000, %01001000, %01001000, 0, 0)

; M.
symbol TAB_WRD_Mi = 217

table TAB_WRD_Mi, (%01000100, %01101100, %01010100, %01000100, %01000100, 0, 0)


; Pixels to light to tell the time in words. These are the indexes for the table.
symbol TAB_TIM_ITIS = 224
symbol TAB_TIM_OCLOCK = 228
symbol TAB_TIM_FIVE = 232
symbol TAB_TIM_TEN = 236
symbol TAB_TIM_A_QUARTER = 240
symbol TAB_TIM_TWENTY = 244
symbol TAB_TIM_TWENTYFIVE = 248
symbol TAB_TIM_HALF = 252
symbol TAB_TIM_PAST = 256
symbol TAB_TIM_TO = 260
symbol TAB_TIM_ONE = 264
symbol TAB_TIM_TWO = 268
symbol TAB_TIM_THREE = 272
symbol TAB_TIM_FOUR = 276
symbol TAB_TIM_FIVEw = 280
symbol TAB_TIM_SIX = 284
symbol TAB_TIM_SEVEN = 288
symbol TAB_TIM_EIGHT = 292
symbol TAB_TIM_NINE = 296
symbol TAB_TIM_TENw = 300
symbol TAB_TIM_ELEVEN = 304
symbol TAB_TIM_TWELVE = 308

;                          Row  1st 8      Top End    Bottom End
table TAB_TIM_ITIS,       ($00, %01101100, %00000000, %00000000)

table TAB_TIM_OCLOCK,     ($09, %10000011, %00000000, %00000111)
table TAB_TIM_FIVE,       ($02, %10000001, %10000001, %00000000)
table TAB_TIM_TEN,        ($03, %10000011, %00000000, %00000000)
table TAB_TIM_A_QUARTER,  ($01, %11011111, %00001000, %00000000)
table TAB_TIM_TWENTY,     ($02, %01111110, %00000000, %00000000)
table TAB_TIM_TWENTYFIVE, ($02, %11111111, %10000001, %00000000)
table TAB_TIM_HALF,       ($03, %01111000, %00000000, %00000000)

table TAB_TIM_PAST,       ($04, %01111000, %00000000, %00000000)
table TAB_TIM_TO,         ($03, %00000000, %00000000, %00011000)

table TAB_TIM_ONE,        ($05, %01110000, %00000000, %00000000)
table TAB_TIM_TWO,        ($06, %00000000, %00001110, %00000000)
table TAB_TIM_THREE,      ($05, %10000001, %01110000, %00000000)
table TAB_TIM_FOUR,       ($06, %01111000, %00000000, %00000000)
table TAB_TIM_FIVEw,      ($06, %10000111, %00000000, %00000000)
table TAB_TIM_SIX,        ($05, %00001110, %00000000, %00000000)
table TAB_TIM_SEVEN,      ($08, %01111100, %00000000, %00000000)
table TAB_TIM_EIGHT,      ($07, %01111100, %00000000, %00000000)
table TAB_TIM_NINE,       ($04, %10000000, %00000000, %00000111)
table TAB_TIM_TENw,       ($09, %01110000, %00000000, %00000000)
table TAB_TIM_ELEVEN,     ($07, %10000011, %10000001, %01000000)
table TAB_TIM_TWELVE,     ($08, %10000011, %00000000, %00111000)


; ********************************************************************************
; *** Start

  setfreq m4

  ;let dirsB = %00001101
  ;let dirsC = %10001100

  output MAX7219_LOAD
  output MAX7219_DIN
  output MAX7219_CLK

  input BUT_MODE
  input BUT_SET
  input BUT_ADJUST
  input BUT_BRIGHTNESS

  input BRIGHTNESS_SENSOR

  output TEMP_BRIGHTNESS
  output TEMP_FAREN

  output PIEZO
  
  ; The 08M2 which is used to read the temperature is using its serial in pin
  ; as an input from this chip to indicate display mode. Therefore the 08M2
  ; uses the  disconnect  function so that the pin can be used as a normal
  ; input pin. The program in the 08M2 has a long pause when it starts up so
  ; that it can still be programmed.
  ; The delay below allows the 08M2 to get through it's own delay before we
  ; start up.

  pause 3200

  gosub MAX7219_Initialise
  gosub DS1307_Initialise

  ; Load saved settings from DS1307.
  setfreq m8
  hi2cin DS1307_USR_MODE, (v_MODE, v_BRIGHTNESS)
  setfreq m32

  ; Bit 7 is flag for temp display in F.

  let v_MODE = v_MODE & $03
  if v_MODE > 2 then
    let v_MODE = 0
  end if  

  ; Bit 4 is flag for auto brightness.
  let v_BRIGHTNESS = v_BRIGHTNESS & $1f
  if v_BRIGHTNESS > $10 then
    let v_BRIGHTNESS = $10
  end if

  gosub ResetDisplay

; *** Main loop

main:
  gosub SampleBrightness

  ; Read the current time from the DS1307.
  setfreq m8
  hi2cin DS1307_SECONDS, (v_SECONDS, v_MINUTES, v_HOURS)
  setfreq m32

;peek MEM_BRIGHT_CURR, b0
;gosub ShowNum

  if v_BRIGHTNESS > $0f then ; Auto brightness?
    peek MEM_BRIGHT_CURR, b0
    let b1 = v_BRIGHTNESS & $0f
    if b1 <> b0 then
      let v_BRIGHTNESS = $10 + b0
      gosub SetModeBrightness
    end if
  end if

  ; Display the time.
  let b0 = v_MODE & $7f
  on b0 gosub ShowTimeInWords, ShowHoursMinutes, ShowMinutesSeconds

  ; Check for button presses.
  ;BUTTON pin,downstate,delay,rate,bytevariable,targetstate,address

  peek MEM_BUT_MODE, b0
  button BUT_MODE, 1, 255, 0, b0, 1, SwitchModeMain
  poke MEM_BUT_MODE, b0

  peek MEM_BUT_BRIGHTNESS, b0
  button BUT_BRIGHTNESS, 1, 255, 0, b0, 1, SwitchBrightnessMain
  poke MEM_BUT_BRIGHTNESS, b0

  peek MEM_BUT_SET, b0
  button BUT_SET, 1, 255, 0, b0, 1, SetTime
  poke MEM_BUT_SET, b0
  
  pause 10
  goto main


; Make a beep.
KeySound:
  sound PIEZO, (20, 40)
  return
  

; Mode button pressed.
SwitchModeMain:
  poke MEM_BUT_MODE, b0

  gosub KeySound

  ; Hold for 1 second to set temp display.

  setfreq m8
  let b0 = 0
  do
    inc b0
    pause 10
  loop while b0 < 100 and BUT_MODEP = 1
  setfreq m32

  if b0 >= 100 then
    gosub KeySound
    gosub SwitchTempDisplayMode
  else
    gosub SwitchMode
  end if

  goto main


; Brightness button pressed.
SwitchBrightnessMain:
  poke MEM_BUT_BRIGHTNESS, b0

  gosub KeySound
  gosub SwitchBrightness
  goto main  

; Set button pressed.
SetTime:
  poke MEM_BUT_SET, b0

  gosub KeySound
  gosub ClearDisplay
  
; Set top half of display dim and bottom half bright.  
  let MAX7219_DATA_1 = $02
  let MAX7219_DATA_2 = $0f
  gosub MAX7219_SetIntensity

; Change the hours.
  let DISP_TABLE_IDX = TAB_WRD_HR
  gosub DisplayTop
  gosub SendToDisplay

  do
    pause 10
  loop while BUT_SETP = 1

  let b18 = 0
  
  let b15 = v_HOURS & $3f
  let b14 = $23 ; BCD
  gosub AdjustNumber
  if b17 = 0 then SetTimeEnd ; b17 = 0 means cancelled.
  let v_HOURS = v_HOURS & $C0 | b15

; Change the minutes.
  gosub ClearDisplayTop
  let DISP_TABLE_IDX = TAB_WRD_MI
  gosub DisplayTop
  
  let b15 = v_MINUTES
  let b14 = $59 ; BCD
  gosub AdjustNumber
  if b17 = 0 then SetTimeEnd ; b17 = 0 means cancelled.
  let v_MINUTES = b15
  
  if b18 = 0 then SetTimeEnd ; Nothing was changed, so don't update the DS1307.
  
; Save the new time to the DS1307. Set seconds to 0.
  setfreq m8
  hi2cout DS1307_SECONDS, (0, v_MINUTES, v_HOURS)
  setfreq m32
  
SetTimeEnd:
  gosub ResetDisplay
  goto main
  
AdjustNumber:
  gosub ClearDisplayBottom
  let DISP_NUM_TO_SHOW = b15

  gosub DisplayNumBottom
  gosub SendToDisplay

AdjustNumberButtons:
  ;peek MEM_BUT_ADJUST, b0
  ;button BUT_ADJUST, 1, 100, 60, b0, 1, AdjustNumberInc ; Adjust number
  ;poke MEM_BUT_ADJUST, b0
 if BUT_ADJUSTP = 1 then goto AdjustNumberInc

  peek MEM_BUT_SET, b0
  button BUT_SET, 1, 255, 0, b0, 1, AdjustNumberSave ; Save number and move to next
  poke MEM_BUT_SET, b0

  peek MEM_BUT_MODE, b0
  button BUT_MODE, 1, 255, 0, b0, 1, AdjustNumberCancel ; Cancel adjust
  poke MEM_BUT_MODE, b0

  pause 10
  goto AdjustNumberButtons

AdjustNumberInc:
 ; poke MEM_BUT_ADJUST, b0

  gosub KeySound
; Working with BCD numbers, so need to faff about a bit.
  let b0 = b15 & $0f
  if b0 = $09 then
    let b15 = b15 & $f0 + $10
  else
    inc b15
  end if
  
  if b15 > b14 then
    let b15 = $00
  end if
  
  let b18 = 1 ; Flag to indicate number has changed.
  
  goto AdjustNumber
  
AdjustNumberSave:
  poke MEM_BUT_SET, b0

  gosub KeySound

  do
    pause 10
  loop while BUT_SETP = 1
  
  let b17 = 1
  return
  
AdjustNumberCancel:
  poke MEM_BUT_MODE, b0

  gosub KeySound
  
  do
    pause 10
  loop while BUT_MODEP = 1

  let b17 = 0
  return
  

SwitchBrightness:
  let b0 = v_BRIGHTNESS & $10

  if b0 = 0 then
    if v_BRIGHTNESS = 0 then
      peek MEM_BRIGHT_CURR, b0
      let v_BRIGHTNESS = $10 | b0 ; Turn on auto brightness.
      pause 100
      gosub KeySound
    else
      dec V_BRIGHTNESS
    end if
  else
    let v_BRIGHTNESS = $0f ; Turn off auto brightness and set to max.
  end if

; Save the brightness to the DS1307.
  setfreq m8
  hi2cout DS1307_USR_BRIGHTNESS, (v_BRIGHTNESS)
  setfreq m32

  goto SetModeBrightness


SampleBrightness:
  peek MEM_BRIGHT_CURR, b0
  peek MEM_BRIGHT_HIGHER, b1
  readadc BRIGHTNESS_SENSOR, b2
  peek MEM_BRIGHT_COUNT, b3

  let b2 = b2 / 25 + 4

  if b2 < b0 then
    if b1 = 1 then
      poke MEM_BRIGHT_HIGHER, 0
      let b3 = 0
    else
      inc b3
      if b3 > 100 then
        dec b0
        poke MEM_BRIGHT_CURR, b0
        let b3 = 0
      end if
    end if
  elseif b2 > b0 then
    if b1 = 0 then
      poke MEM_BRIGHT_HIGHER, 1
      let b3 = 0
    else
      inc b3
      if b3 > 100 then
        inc b0
        poke MEM_BRIGHT_CURR, b0
        let b3 = 0
      end if
    end if
  else
    let b3 = 0
  end if

  poke MEM_BRIGHT_COUNT, b3

  return


; Save the mode to the DS1307.
SaveMode:
  setfreq m8
  hi2cout DS1307_USR_MODE, (v_MODE)
  setfreq m32

  return

SwitchMode:
; 0=Words, 1=Hours/Minutes, 2=Minutes/Seconds

  let b0 = v_MODE & $7f + 1
  if b0 > 2 then
    let b0 = 0
  end if
  let v_MODE = v_MODE & $80 + b0
  gosub SaveMode
 
; Force update next time round.
ResetDisplay:
  let v_MINUTES_PREV = $ff
  let v_SECONDS_PREV = $ff
  
; *** Fall-through.
SetupMode:
  gosub ClearDisplay
  gosub SendToDisplay
  gosub SetTempDisplayMode
; *** Fall-through.  
SetModeBrightness:
  let MAX7219_DATA_2 = v_BRIGHTNESS & $0f
  
  let b1 = v_MODE & $7f
  if b1 = 0 then
    let MAX7219_DATA_1 = MAX7219_DATA_2 ' Normal "Word" mode, same brightness on top and bottom.
  else
    let MAX7219_DATA_1 = MAX7219_DATA_2 / 3 ; "Minute" or "Second" mode, set top half to 1/3 brightness.
  end if

  if MAX7219_DATA_2 > 7 then
    high TEMP_BRIGHTNESS
  else
    low TEMP_BRIGHTNESS
  end if

  goto MAX7219_SetIntensity


SwitchTempDisplayMode:
  let b0 = v_MODE & $80
  if b0 = 0 then
    let v_MODE = v_MODE | $80
  else
    let v_MODE = v_MODE & $7f
  end if

  gosub SaveMode
; *** Fall-through.  
SetTempDisplayMode:
  let b0 = v_MODE & $80
  let b1 = TEMP_FARENP
  if b0 = 0 then
    if b1 = 1 then
      low TEMP_FAREN
    end if
  else
    if b1 = 0 then
      high TEMP_FAREN
    end if
  end if

  return


FromBCD:
  let b1 = b0 / 16 * 10
  let b0 = b0 & $0f + b1
  return


ShowTimeInWords:
  if v_MINUTES <> v_MINUTES_PREV then
    gosub ClearDisplay

    ; Convert hours from BCD time in to normal values to deal with easier.
    
    let b0 = v_HOURS & $3f ; Remove 24 hour flag.
    gosub FromBCD
    let v_HOURS = b0

    ; Clock is running in 24 hour mode, so convert hours to 12 hour.
      
    if v_HOURS = 0 then
      let v_HOURS = 12
    elseif v_HOURS > 12 then
      let v_HOURS = v_HOURS - 12
    end if

    ; IT IS    
    let DISP_TABLE_IDX = TAB_TIM_ITIS
    gosub DisplayWord
    
    ; Fuzzy time 58-02=0, 03-07=5, 08-12=10, 13-17=15, 18-22=20, 23-27=25, 28-32=30, 33-37=35, 38-42=40, 43-37=45, 48-52=50, 53-57=55
    ; Minutes
    if v_MINUTES <= $02 then
      let DISP_TABLE_IDX = TAB_TIM_OCLOCK
    elseif v_MINUTES <= $07 then
      let DISP_TABLE_IDX = TAB_TIM_FIVE
    elseif v_MINUTES <= $12 then
      let DISP_TABLE_IDX = TAB_TIM_TEN
    elseif v_MINUTES <= $17 then
      let DISP_TABLE_IDX = TAB_TIM_A_QUARTER
    elseif v_MINUTES <= $22 then
      let DISP_TABLE_IDX = TAB_TIM_TWENTY
    elseif v_MINUTES <= $27 then
      let DISP_TABLE_IDX = TAB_TIM_TWENTYFIVE
    elseif v_MINUTES <= $32 then
      let DISP_TABLE_IDX = TAB_TIM_HALF
    else
      if v_MINUTES <= $37 then
        let DISP_TABLE_IDX = TAB_TIM_TWENTYFIVE
      elseif v_MINUTES <= $42 then
        let DISP_TABLE_IDX = TAB_TIM_TWENTY
      elseif v_MINUTES <= $47 then
        let DISP_TABLE_IDX = TAB_TIM_A_QUARTER
      elseif v_MINUTES <= $52 then
        let DISP_TABLE_IDX = TAB_TIM_TEN
      elseif v_MINUTES <= $57 then
        let DISP_TABLE_IDX = TAB_TIM_FIVE
      else
        let DISP_TABLE_IDX = TAB_TIM_OCLOCK
      end if
      
      ; Gone past 30 minutes, so adjust hour to the hour we are coming up to.
      inc v_HOURS
      if v_HOURS > 12 then
        let v_HOURS = 1
      end if
    end if
    
    gosub DisplayWord
    
    ; Past or To. If not O'Clock.
    if v_MINUTES >= $03 and v_MINUTES < $58 then
      if v_MINUTES < $33 then
        let DISP_TABLE_IDX = TAB_TIM_PAST
      else
        let DISP_TABLE_IDX = TAB_TIM_TO
      end if
      
      gosub DisplayWord
    end if
      
    ; Hour.
    let DISP_TABLE_IDX = v_HOURS - 1 * 4 + TAB_TIM_ONE
    gosub DisplayWord
    
    gosub SendToDisplay
    
    let v_MINUTES_PREV = v_MINUTES
  end if

  return


; --------------------------------------------------------------------------------
; Display a word. Which LEDs to light are read from the table.

DisplayWord:
; DISP_TABLE_IDX = Index in table of word to display.

  symbol DW_LINE = b2
  symbol DW_MAIN_DATA = b3
  symbol DW_MAIN_MEM_IDX = b4
  symbol DW_OTHER_MEM_IDX = b6
  symbol DW_DATA = b5

  readtable DISP_TABLE_IDX, DW_LINE ; Line
  inc DISP_TABLE_IDX
  readtable DISP_TABLE_IDX, DW_MAIN_DATA ; Main data
  inc DISP_TABLE_IDX
  
  if DW_LINE > 4 then
    let DW_MAIN_MEM_IDX = DW_LINE - 5 + MEM_DISPLAY_BOTTOM
    let DW_OTHER_MEM_IDX = MEM_DISPLAY_BOTTOM_END - 1
  else
    let DW_MAIN_MEM_IDX = DW_LINE + MEM_DISPLAY_TOP
    let DW_OTHER_MEM_IDX = MEM_DISPLAY_TOP_END - 1
  end if
  
  peek DW_MAIN_MEM_IDX, DW_DATA
  let DW_DATA = DW_DATA or DW_MAIN_DATA
  poke DW_MAIN_MEM_IDX, DW_DATA
  
  readtable DISP_TABLE_IDX, DW_MAIN_DATA
  inc DISP_TABLE_IDX

  peek DW_OTHER_MEM_IDX, DW_DATA
  let DW_DATA = DW_DATA or DW_MAIN_DATA
  poke DW_OTHER_MEM_IDX, DW_DATA
  
  inc DW_OTHER_MEM_IDX
  
  readtable DISP_TABLE_IDX, DW_MAIN_DATA
  peek DW_OTHER_MEM_IDX, DW_DATA
  let DW_DATA = DW_DATA or DW_MAIN_DATA
  poke DW_OTHER_MEM_IDX, DW_DATA

  return


; --------------------------------------------------------------------------------
; Show the time in hours and minutes. Hours in top half, minutes in bottom half.

ShowHoursMinutes:
  if v_MINUTES <> v_MINUTES_PREV then
    gosub ClearDisplay

    ; NB. Time values are in BCD.

    let DISP_NUM_TO_SHOW = v_HOURS & $3f ; Take off the 24 Hour flag.
    gosub DisplayNumTop    

    let DISP_NUM_TO_SHOW = v_MINUTES
    gosub DisplayNumBottom

    gosub SendToDisplay
    
    let v_MINUTES_PREV = v_MINUTES
  end if
  
  return


; --------------------------------------------------------------------------------
; Show the time in minutes and seconds. Minutes in top half, seconds in bottom
; half.

ShowMinutesSeconds:
  if v_SECONDS <> v_SECONDS_PREV then
    gosub ClearDisplay
    
    ; NB. Time values are in BCD.
    
    let DISP_NUM_TO_SHOW = v_MINUTES
    gosub DisplayNumTop

    let DISP_NUM_TO_SHOW = v_SECONDS
    gosub DisplayNumBottom

    gosub SendToDisplay
    
    let v_SECONDS_PREV = v_SECONDS
  end if
  
  return
  
; ********************************************************************************
; *** Display Functions **********************************************************
; ********************************************************************************

; --------------------------------------------------------------------------------
; Send the data from the display buffer to the MAX7219's. It only sends data that
; has changed since the last time data was sent to the MAX7219's.

SendToDisplay:
  symbol STD_MEM_TOP = b5
  symbol STD_MEM_BOT = b6
  symbol STD_MEM_TOP_CMP = b7
  symbol STD_MEM_BOT_CMP = b8
  symbol STD_CMP_TOP_DATA = b3
  symbol STD_CMP_BOT_DATA = b4

  let MAX7219_REGISTER = MAX7219_DIGIT0
  let STD_MEM_TOP = MEM_DISPLAY_TOP
  let STD_MEM_TOP_CMP = MEM_DISPLAY_TOP_CMP
  let STD_MEM_BOT_CMP = MEM_DISPLAY_BOTTOM_CMP

  for STD_MEM_BOT = MEM_DISPLAY_BOTTOM to MEM_DISPLAY_BOTTOM_END
    peek STD_MEM_TOP, MAX7219_DATA_1 ; Read new display data.
    peek STD_MEM_BOT, MAX7219_DATA_2
  
    peek STD_MEM_TOP_CMP, STD_CMP_TOP_DATA ; Read last displayed data.
    peek STD_MEM_BOT_CMP, STD_CMP_BOT_DATA

;    if b1 = b3 and b2 <> b4 then
;      poke b8, b2
;      
;      gosub MAX7219_SendCmdAndDataTo2
;    elseif b1 <> b3 and b2 = b4 then
;      poke b7, b1
;      
;      gosub MAX7219_SendCmdAndDataTo1
;    elseif b1 <> b3 and b2 <> b4 then ; If changed, then send to MAX7219.
    if MAX7219_DATA_1 <> STD_CMP_TOP_DATA or MAX7219_DATA_2 <> STD_CMP_BOT_DATA then ; If changed, then send to MAX7219.
      ; Save new displayed data.
      
      poke STD_MEM_TOP_CMP, MAX7219_DATA_1
      poke STD_MEM_BOT_CMP, MAX7219_DATA_2
      
      gosub MAX7219_SendCmdAndSepData2Both
    endif
    
    inc MAX7219_REGISTER ; Next digit.
    inc STD_MEM_TOP
    inc STD_MEM_TOP_CMP
    inc STD_MEM_BOT_CMP
  next
  goto MAX7219_ShutdownOff


; --------------------------------------------------------------------------------
; Display 2 digit number in the top half of the display

DisplayNumTop:
  ; NB. Values should in BCD.

  let DISP_TABLE_IDX = DISP_NUM_TO_SHOW & 15
  gosub DisplayNum01Top
  let DISP_TABLE_IDX = DISP_NUM_TO_SHOW / 16
; *** Fall-through
; Display the "Tens" part of the number.
DisplayNum10Top:
; DISP_TABLE_IDX = Number to display.

  let DISP_TABLE_IDX = DISP_TABLE_IDX & $00FF * 7 + TAB_NUM_LEFT
  goto DisplayTopAdj:

; Display the "Unit" part of the number.
DisplayNum01Top:
; DISP_TABLE_IDX = Number to display.
  let DISP_TABLE_IDX = DISP_TABLE_IDX & $00FF * 7 + TAB_NUM_MIDDLE
  goto DisplayTopAdj:


; --------------------------------------------------------------------------------
; Display 2 digit number in the bottom half of the display

DisplayNumBottom:
  ; NB. Values should in BCD.

  let DISP_TABLE_IDX = DISP_NUM_TO_SHOW & 15
  gosub DisplayNum01Bottom
;return
  let DISP_TABLE_IDX = DISP_NUM_TO_SHOW / 16
; *** Fall-through.
; Display the "Tens" part of the number.
DisplayNum10Bottom:
; DISP_TABLE_IDX = Number to display.
  let DISP_TABLE_IDX = DISP_TABLE_IDX & $00FF * 7 + TAB_NUM_MIDDLE
  goto DisplayBottom

; Display the "Unit" part of the number.
DisplayNum01Bottom:
; DISP_TABLE_IDX = Number to display.
  let DISP_TABLE_IDX = DISP_TABLE_IDX & $00FF * 7 + TAB_NUM_RIGHT
  goto DisplayBottom

#rem
ShowNum:
  let DISP_NUM_TO_SHOW = b0
  gosub ClearDisplay
  gosub DisplayNum
  gosub SendToDisplay
    
  return

DisplayNum:
  let DISP_TABLE_IDX = DISP_NUM_TO_SHOW / 100 * 7 + TAB_NUM_LEFT
  gosub DisplayTop

  let b0=DISP_NUM_TO_SHOW / 100
  let DISP_TABLE_IDX = DISP_NUM_TO_SHOW % 100 / 10 * 7 + TAB_NUM_MIDDLE
  gosub DisplayTop

  let DISP_TABLE_IDX = DISP_NUM_TO_SHOW % 10 * 7 + TAB_NUM_RIGHT
  goto DisplayTop
#endrem

; --------------------------------------------------------------------------------
; Draw the "character" pointed to by DISP_TABLE_IDX in the top half of the
; display.

DisplayTop:
; DISP_TABLE_IDX = Start table address to read from. DISP_TABLE_IDX will be updated.
  
  for DISP_MEM = MEM_DISPLAY_TOP to MEM_DISPLAY_TOP_END
    readtable DISP_TABLE_IDX, DISP_DATA
    peek DISP_MEM, DISP_MERGE
    let DISP_DATA = DISP_DATA | DISP_MERGE
    poke DISP_MEM, DISP_DATA
    inc DISP_TABLE_IDX
  next

  return


; --------------------------------------------------------------------------------
; Draw the "character" pointed to by DISP_TABLE_IDX in the top half of the
; display adjusted one bit to the right. This is to offset the characters that
; will appear in the middle of the display.
DisplayTopAdj:
; DISP_TABLE_IDX = Start table address to read from. DISP_TABLE_IDX will be updated.

;((x & 1) * 128) + ((x & 254) / 2)
  for DISP_MEM = MEM_DISPLAY_TOP to MEM_DISPLAY_TOP_END
    readtable DISP_TABLE_IDX, DISP_DATA
  
    ; Adjust right one pixel.
    let DISP_MERGE = DISP_DATA & 1 * 128
    let DISP_DATA = DISP_DATA / 2 + DISP_MERGE
  
    peek DISP_MEM, DISP_MERGE
    let DISP_DATA = DISP_DATA | DISP_MERGE
    poke DISP_MEM, DISP_DATA
    inc DISP_TABLE_IDX
  next

  return


; --------------------------------------------------------------------------------
; Draw the "character" pointed to by DISP_TABLE_IDX in the bottom half of the
; display.

DisplayBottom:
; DISP_TABLE_IDX = Start table address to read from. DISP_TABLE_IDX will be updated.

  for DISP_MEM = MEM_DISPLAY_BOTTOM to MEM_DISPLAY_BOTTOM_END
    readtable DISP_TABLE_IDX, DISP_DATA
    peek DISP_MEM, DISP_MERGE
    let DISP_DATA = DISP_DATA | DISP_MERGE
    poke DISP_MEM, DISP_DATA
    inc DISP_TABLE_IDX
  next
  
  return


; --------------------------------------------------------------------------------
; Clear the display.

ClearDisplay:
  gosub ClearDisplayBottom
; *** Fall-through ***
ClearDisplayTop:
  let b13 = MEM_DISPLAY_TOP_CMP
  for DISP_MEM = MEM_DISPLAY_TOP to MEM_DISPLAY_TOP_END
     poke DISP_MEM, 0
     poke b13, 255
     inc b13
  next

  return

ClearDisplayBottom:
  let b13 = MEM_DISPLAY_BOTTOM_CMP
  for DISP_MEM = MEM_DISPLAY_BOTTOM to MEM_DISPLAY_BOTTOM_END
    poke DISP_MEM, 0
    poke b13, 255
    inc b13
  next
  
  return

; ********************************************************************************
; *** DS1307 Functions ***********************************************************
; ********************************************************************************

DS1307_Initialise:
  setfreq m8
  
  hi2csetup i2cmaster, DS1307_ADDRESS, i2cslow_8, i2cbyte

  gosub DS1307_24HourMode

  let b1 = 0
  gosub DS1307_SetClockHalt
  
  goto DS1307_SetSQWE_1Hz


; Set the CH (Clock Halt) bit in RTC.
DS1307_SetClockHalt:
; b1.0 = Clock Halth (CH) state

  hi2cin DS1307_SECONDS, (DS1307_DATA)
  if DS1307_SECONDS_CH <> bit8 then ;(bit8 = bit 0 of b1)
    let DS1307_SECONDS_CH = bit8
    hi2cout DS1307_SECONDS, (DS1307_DATA)
  end if

  return


DS1307_SetSQWE_1Hz:
  let DS1307_DATA = 0 ; Out = 0 and RS1/RS0 = 0.
  let DS1307_CONTROL_SQWE = 1 ; Set SQWE bit in DS1307_DATA (must be b0).
  hi2cout DS1307_CONTROL, (DS1307_DATA) 
  
  return


#rem
; Switch to 12 hour mode
DS1307_12HourMode:
  hi2cin DS1307_HOURS, (DS1307_DATA)
  if DS1307_HOURS_1224 = 0 then
    ; BCD to BIN
    let b1 = DS1307_DATA & $30 / 16 * 10
    let b1 = DS1307_DATA & $0f + b1
  
    let b2 = 1
    if b1 >= 12 then
      if b1 > 12 then
        let b1 = b1 - 12
      endif
      let b2 = 1
    else
      if b1 = 0 then
        let b1 = 12
      end if
      let b2 = 0
    endif
  
    ; BIN to BCD
    let DS1307_DATA = b1 / 10 * 16
    let DS1307_DATA = b1 % 10 + DS1307_DATA
  
    if b2 = 1 then
      let DS1307_HOURS_AMPM = 1 ' Set AM/PM but in DS1307_DATA (must be b0).
    endif
  
    let DS1307_HOURS_1224 = 1
    hi2cout DS1307_HOURS, (DS1307_DATA)
  end if

  return
#endrem

; Switch to 24 hour mode
DS1307_24HourMode:
  hi2cin DS1307_HOURS, (DS1307_DATA)
  if DS1307_HOURS_1224 = 1 then
    ; BCD to BIN
    let b1 = DS1307_DATA & $10 / 16 * 10
    let b1 = DS1307_DATA & $0f + b1

    if DS1307_HOURS_AMPM = 1 then
      if b1 < 12 then
        let b1 = b1 + 12
      end if
    elseif b1 = 12 then
      let b1 = 0
    end if

    ; BIN to BCD
    let DS1307_DATA = b1 / 10 * 16
    let DS1307_DATA = b1 % 10 + DS1307_DATA

    ;let DS1307_HOURS_1224 = 0 ; Bit will already be 0, so don't need to explicitly set it.
    hi2cout DS1307_HOURS, (DS1307_DATA)
  end if
  
  return

; ********************************************************************************
; *** MAX7219 Functions **********************************************************
; ********************************************************************************

MAX7219_Initialise:
  setfreq m32
  
  high MAX7219_LOAD
  low MAX7219_CLK
  low MAX7219_DIN

  pause 10

  let MAX7219_REGISTER = MAX7219_DECODE_MODE
  let MAX7219_DATA_1 = $00 ; Don't use decode mode for any data.
  gosub MAX7219_SendCmdAndDataToBoth

  let MAX7219_DATA_1 = $03
  let MAX7219_DATA_2 = $0c
  gosub MAX7219_SetIntensity

  let MAX7219_REGISTER = MAX7219_SCAN_LIMIT
  let MAX7219_DATA_1 = $06 ; Using 7 digits (0-6); not using the 8th digit.
  gosub MAX7219_SendCmdAndDataToBoth

  gosub MAX7219_ShutdownOff
  goto MAX7219_TestModeOff ; Return will be peformed by this function


MAX7219_ShutdownOff:
  let MAX7219_REGISTER = MAX7219_SHUTDOWN
  let MAX7219_DATA_1 = $01
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function

#rem
MAX7219_ShutdownOn:
  let MAX7219_REGISTER = MAX7219_SHUTDOWN
  let MAX7219_DATA_1 = $00
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function
#endrem

MAX7219_TestModeOn:
  let MAX7219_REGISTER = MAX7219_TEST_MODE
  let MAX7219_DATA_1 = $01
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function

  
MAX7219_TestModeOff:
  let MAX7219_REGISTER = MAX7219_TEST_MODE
  let MAX7219_DATA_1 = $00
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function


MAX7219_SetIntensity:
; MAX7219_DATA_1 = Intensity level for top half.
; MAX7219_DATA_2 = Intensirt level for bottom half.

  let MAX7219_REGISTER = MAX7219_INTENSITY
  goto MAX7219_SendCmdAndSepData2Both ; Return will be peformed by this function


MAX7219_SendCmdAndDataToBoth:
; MAX7219_REGISTER = Register
; MAX7219_DATA_1 = Data

  gosub MAX7219_SendCmdAndData ; This will send to the second MAX7219
  gosub MAX7219_SendCmdAndData ; This will send to the first MAX7219

  pulsout MAX7219_LOAD, 1

  return  


MAX7219_SendCmdAndSepData2Both:
; MAX7219_REGISTER = Register
; MAX7219_DATA_1 = Data for chip 1
; MAX7219_DATA_2 = Data for chip 2
  
  swap MAX7219_DATA_1, MAX7219_DATA_2 ; We have to send the data for chip 2 first, so swap the Data values
  gosub MAX7219_SendCmdAndData ; Send to the second MAX7219
  swap MAX7219_DATA_1, MAX7219_DATA_2 ; Swap the values back.
  gosub MAX7219_SendCmdAndData ; Send to the first MAX7219

  pulsout MAX7219_LOAD, 1

  return  


MAX7219_SendCmdAndDataTo1:
; MAX7219_REGISTER = Register.
; MAX7219_DATA_1 = Data for chip 1.

  gosub MAX7219_SendCmdAndData

  pulsout MAX7219_LOAD, 1

  return


MAX7219_SendCmdAndDataTo2:
; MAX7219_REGISTER = Register.
; MAX7219_DATA_2 = Data for chip 2.

  swap MAX7219_DATA_1, MAX7219_DATA_2
  gosub MAX7219_SendCmdAndData ; Send data for chip 2.
  
  swap MAX7219_REGISTER, MAX7219_DATA_1 ; Need to preserve MAX7219_REGISTER.
  
  let MAX7219_REGISTER = MAX7219_NOOP ; Send NOOP for chip 1.
  gosub MAX7219_SendCmdAndData
  
  swap MAX7219_REGISTER, MAX7219_DATA_1 ; Restore MAX7219_REGISTER..
  
  pulsout MAX7219_LOAD, 1

  return
  

MAX7219_SendCmdAndData:
; MAX7219_REGISTER = Register.
; MAX7219_DATA_1 = Data.
; b3 & b4 used as temp.

  let MAX7219_SEND_DATA = MAX7219_REGISTER
  gosub MAX7219_Send

  let MAX7219_SEND_DATA = MAX7219_DATA_1
; *** Fall-through is intended. ***
MAX7219_Send:
; b3 = Data to send

  for MAX7219_SEND_COUNT = 0 to 7
    if MAX7219_SEND_DATA > 127 then
      high MAX7219_DIN
    else
      low MAX7219_DIN
    end if

    pulsout MAX7219_CLK, 1    
    MAX7219_SEND_DATA = MAX7219_SEND_DATA * 2
  next

  low MAX7219_DIN

  return 

Changes from v1

  • Instead of creating a lattice from cardboard for each of the letter cells, I used 12mm MDF and used a large (16mm) countersink bit to create countersunk holes with a bevel around the LED, a bit like the reflector in a torch, and painted it white to reflect as much of the light as possible.
  • Use an LDR to allow automatic brightness adjustment based on the light level in the room.
  • Display the temperature – because why not.

I originaly thought of reading and displaying the temperature from the PICAXE 18M2+, however there wasn't a lot of program memory left, and additionally reading the temperature using the readtemp12 command actually stops the chip from doing anything else – like responding to button presses – for about ¾ of a second, so I decided instead to use a separate PICAXE 08M2 to read and display the time.

Light Reflector Array

Light reflector array drilled in to a piece of MDF. I drilled pilot holes first, then used large countersink to produce the beveled edge:-

Light reflector array

I wandered a bit in the lower left corner, so stuck on some cardboard to fill the holes along the edge.

This program is used on a PICAXE 08M2 to read the temperature from a DS18B20 temperature sensor and display it on a 3 digit 7 segment LED display. A MAX7219 LED driver is used to drive the display.

The main processor can also control this chip through 2 inputs; one to control the brightness level, and the other display the temperature in farenheit.

#picaxe 08m2
#no_data

; Project: Word Clock v2.
; File: ReadDisplayTemp.bas
; Description:
;     Read temperature from a DS18B20 and display on a three
;     digit 7 segment LED display using a MAX7219 LED display
;     driver.
;
; Copyright (C) 2014 Marc Symonds
; All rights reserved.
;
; This software may be used and redistributed, with or without
; modification, as long as it is understood that this software
; is provided as-is without any explicit or implied warranties
; of merchantablity or fitness of purpose.

    
; MAX7219 Pins
symbol MAX7219_LOAD = C.1
symbol MAX7219_CLK = C.0
symbol MAX7219_DIN = C.2

; MAX7219 Registers
symbol MAX7219_NOOP = $00
symbol MAX7219_DIGIT0 = $01
symbol MAX7219_DIGIT1 = $02
symbol MAX7219_DIGIT2 = $03
symbol MAX7219_DIGIT3 = $04
symbol MAX7219_DIGIT4 = $05
symbol MAX7219_DIGIT5 = $06
symbol MAX7219_DIGIT6 = $07
symbol MAX7219_DIGIT7 = $08
symbol MAX7219_DECODE_MODE = $09
symbol MAX7219_INTENSITY = $0A
symbol MAX7219_SCAN_LIMIT = $0B
symbol MAX7219_SHUTDOWN = $0C
symbol MAX7219_TEST_MODE = $0F

; DS18B20 Pins
symbol DS18B20_DQ = C.4

; Pins used by main processor to control display.
symbol DISPLAY_F = C.5 ; Display in Farenheit.
symbol DISPLAY_FP = pinC.5
symbol LOW_BRIGHTNESS = C.3 ; Display brightness high or low.
symbol LOW_BRIGHTNESSP = pinC.3


symbol P_Register = b0
symbol P_Data = b1
symbol V_Temp1 = b3
symbol V_Temp2 = b4

symbol V_Neg = b8
symbol V_TempFrac = b9
symbol V_TempWhole = b10
symbol V_Digit0 = b11
symbol V_Digit1 = b12
symbol V_Digit2 = b13

symbol V_Temperature = w3 ; b6 b7

symbol V_LowBrightness = b14

letThereBeLight:
  setfreq m4
  
  ; This long delay is to allow the chip to be more easily programmed later,
  ; because further down we "disconnect" the chip so that it stops looking for
  ; new downloads.

  pause 3000
  
  let V_LowBrightness = 0

  input DISPLAY_F
  input LOW_BRIGHTNESS
  
  gosub MAX7219_Initialise

  ; We are using C.5 (serial in) as an input (to display farenheit), so we need
  ; to stop the chip from scanning the serial in pin looking for a new download.
  ; Once the disconnect command has run, you have to do a hard-reset to download
  ; a new program; see the manual.
  
  disconnect

main:
  setfreq m4
  readtemp12 DS18B20_DQ, V_Temperature
  setfreq m8

  gosub displayTemp

  if LOW_BRIGHTNESSP <> V_LowBrightness then
    let V_LowBrightness = LOW_BRIGHTNESSP
    
    if V_LowBrightness = 0 then
      let P_Data = $05
    else
      let P_Data = $0E
    end if
    
    gosub MAX7219_SetIntensity
  end if
  
  ;pause 1000
  sleep 2
  goto main


; The 3 digit 7 segment display I'm using has some segments that don't work;
; notably the  e  segment of the first character, and the DP segments of the
; first and third characters.
; So the displaying of the temperature below is a bit more complicated than
; would normally be required, to work around the problems with the display.
  
displayTemp:
  let V_Neg = 0
  if V_Temperature > $7FFF then
    let V_Temperature = -V_Temperature
    let V_Neg = 1
  end if
  
  if DISPLAY_FP = 1 then
    ; Convert C to F. 115/64 = 1.7968 (1.8 ish), and 512 is 32 * 16 (temp from DS18B20 is in 16th's of a degree).
    let V_Temperature = V_Temperature * 115 / 64 + 512 
  end if
  
  let b9 = V_Temperature & 15 * 10 / 16 ; frac
  let b10= V_Temperature / 16 ; whole

  ; Work out what to display in each of the three digits.
  
  if V_Neg = 1 then
    let V_Digit0 = 10 ; minus sign.
    
    if b10 > 99 then ; Can't display anything below -99, so show EE. If it's below -99 we're probably dead anyway.
      let b12 = 11 ; E
      let b13 = 11 ; E
    elseif b10 > 9 then
      let b12 = b10 / 10
      let b13 = b10 % 10
    else
      let b12 = b10 | $80 ; Turn on DP
      let b13 = b9
    end if      
  else
    if b10 > 99 then
      let b11 = b10 / 100
      let b12 = b10 / 10 % 10
      let b13 = b10 % 10
    elseif b10 < 20 then
      if b10 > 9 then
        let b11 = b10 / 10
      else
        let b11 = 15 ; blank
      end if
      
      let b12 = b10 % 10 | $80 ; Turn on DP
      let b13 = b9
    else
      let b11 = 15
      let b12 = b10 / 10
      let b13 = b10 % 10
    end if
  end if

  ; Display it.
  
  let b0 = MAX7219_DIGIT0
  let b1 = b11
  gosub MAX7219_SendCmdAndDataTo1
  
  let b0 = MAX7219_DIGIT1
  let b1 = b12
  gosub MAX7219_SendCmdAndDataTo1

  let b0 = MAX7219_DIGIT2
  let b1 = b13
  gosub MAX7219_SendCmdAndDataTo1
  
  return

; ********************************************************************************
; *** MAX7219 Functions **********************************************************
; ********************************************************************************

MAX7219_Initialise:
  output MAX7219_LOAD
  output MAX7219_CLK
  output MAX7219_DIN
  
  low MAX7219_LOAD
  low MAX7219_CLK
  low MAX7219_DIN

  pause 10

  gosub MAX7219_TestModeOff
  gosub MAX7219_ShutdownOff

  let b0 = MAX7219_SCAN_LIMIT
  let b1 = $02 ; Using 3 digits.
  gosub MAX7219_SendCmdAndDataTo1

  let b0 = MAX7219_DECODE_MODE
  let b1 = $7 ; Use BCD for digits 0, 1 and 2.
  gosub MAX7219_SendCmdAndDataTo1

  let b1 = $0C ' Max brightness.
  gosub MAX7219_SetIntensity

  return


MAX7219_ShutdownOff:
  let b0 = MAX7219_SHUTDOWN
  let b1 = $01
  goto MAX7219_SendCmdAndDataTo1 ; Return will be peformed by this function

#rem
MAX7219_ShutdownOn:
  let b0 = MAX7219_SHUTDOWN
  let b1 = $00
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function
#endrem

MAX7219_TestModeOn:
  let b1 = $01
  goto MAX7219_TestModeSet ; Return will be peformed by this function  
MAX7219_TestModeOff:
  let b1 = $00
MAX7219_TestModeSet:
  let b0 = MAX7219_TEST_MODE
  goto MAX7219_SendCmdAndDataTo1 ; Return will be peformed by this function


MAX7219_SetIntensity:
; b1 = Intensity level for top half.
; b2 = Intensirt level for bottom half.

  let b0 = MAX7219_INTENSITY
; *** Fall-through ***


MAX7219_SendCmdAndDataTo1:
; b0 = Register.
; b1 = Data for chip 1.

  gosub MAX7219_SendCmdAndData

; For 7219 you only need to pulse the LOAD pin to load the data.
  
  pulsout MAX7219_LOAD, 1
  
  return  

MAX7219_SendCmdAndData:
; b0 = Register.
; b1 = Data.
; b3 & b4 used as temp.

  let b3 = b0
  gosub MAX7219_Send

  let b3 = b1
; *** Fall-through is intended. ***
MAX7219_Send:
; b3 = Data to send

  for b4 = 0 to 7
    if b3 > 127 then
      high MAX7219_DIN
    else
      low MAX7219_DIN
    end if

    pulsout MAX7219_CLK, 1    

    b3 = b3 * 2
  next

  low MAX7219_DIN

  return 


#rem
3 Digit/7 Segment display Pins

          1 1 1                      a
          2 1 0 9 8 7               ---
          | | | | | |             f|   |b
         +-----------+             | g |
         |           |              ---
         | 8. 8. 8.  |            e|   |c
         |           |             |   |
         +-----------+              ---  O 
          | | | | | |                d
          1 2 3 4 5 6

 1 = Digit 0 cathode
 2 = Segment e
 3 = Segment d
 4 = Digit 1 cathode
 5 = Segment c
 6 = DP
 7 = Digit 2 cathode
 8 = Segment b
 9 = Segment g
10 = Segment a
11 = Segment f
12 = NC

#endrem

I decided to have a go at making a Word Clock, which, as the name implies, shows the time as words. The face of the clock will contain a grid of letters, and will show the approximate time by highlighting appropriate words within the grid.

 

Overview

Processor

For the processor I will use a PICAXE 18M2+ as I've used PICAXE before. The chips are cheap (£2) and are easy to program using the free Programming Editor.

Clock

To maintain the time I will use a DS1307 RTC with an external resonator. A better option was the DS3232 which has an internal resonator, but that only appears to be available in a surface mount package, which I've found difficult to build in to a circuit. The DS1307 comes in a simple, fat finger friendly, 8 pin DIP.

LED Drivers

The clock face will contain a grid of 110 letters, 11 letters across by 10 down. Each letter will have a single LED to light it, thus requiring the ability to drive 110 LEDs.

I have gone for the MAX7219 drivers which are designed to drive up to 8 seven segment displays (plus decimal); i.e. a MAX7219 can drive a total of 64 LEDs. So I will need to use two of these.

Case

The clock will be housed in a deep, 10 inch square picture frame. The clock face will consist of a vinyl mask with the letters cut out.

 

Parts

DS1307 RTC IC http://www.techsupplies.co.uk £2.30 £3.50
32.768 kHz resonator http://www.techsupplies.co.uk £0.22
CR2032 battery holder http://www.techsupplies.co.uk £0.40
PICAXE-18M2 microcontroller http://www.techsupplies.co.uk £2.00
MAX7219CNG LED driver (x2) eBay (it_2002) £3.90 £1.35
5V AC-DC power supply eBay (megabrokers) £6.49  
3mm Ulra Bright White LED (x110) eBay (bright_components) £6.14 £0.99
10 inch square 40mm deep picture frame eBay (alanw7865 – Essex Gallery) £13.00 £3.00
SPST Momentary push switch (x4) eBay (ha5ia) £2.38  
2.1mm DC power panel mount socket eBay (donedeal1966) £0.28  
Royal Blue vellum paper eBay (lynneduncan) £0.10  
Resister 1k5 (x2)      
Resister 1k0 (x4)      
Resister 10k0 (x4)      
Resister 18k0 (x2)      
Resister 4k7 (x2)      
5mm standard red LED      
Capacitor 10uF Electrolytic (x2)      
Vinyl for clock face http://www.provinylcutz.co.uk/ £15.00  

 

The Build

I did some initial testing of the electronics using a breadboard.

 

Since I don't have the ability to create proper circuit boards, I will be using strip board. I drew a rough sketch of the circuit layout:-

Word Clock Circuit Layout

 

Starting to build the circuit:-

The leads next to the battery are to a socket which is used for programming the 18M2 micro controller.

Originally I placed a diode on the +V line as a way to protect the components if the power was connected incorrectly. However, I found that the ICs seemed to act erratically, so I assumed the diode was causing too much of a voltage drop resulting in the problem, so I took it out and everything worked OK.

 

The completed circuit:-

The MAX7219 ICs require a 0.33uF electrolytic capacitor on the power lines. Since there wasn't enough room on the top of the board, I soldered a capacitor on the underside of the board directly from the +V pin to the 0V pin.

The bundles of black and blue wires coming from the MAX7219 ICs are the leads for the LEDs.

The black wires between the resistors next to the 18M2 go to the 4 buttons.

 

Clock face

The clock face will be illuminated by an array of 110 white LED's; one for each letter. The layout of the letters are on a fixed grid; 11 letters across by 10 letters down. The layout I decided on have letters that are 13mm tall, and spaced 12mm (on centre) horizontally, and 20mm (on centre) vertically. The grid is centred within a 250mm (10 inch) square.

 

The vinyl template, still with it's backing paper:-

.

I copied the letter grid from a similar project I saw on MAKE: http://blog.makezine.com/projects/small-word-clock/

The vinyl template has a sticky side and needed to be stuck to a a piece of glass. I decided to stick it to a piece of clear Perspex that I had available. It turned out to be quite difficult to get the vinyl positioned correctly and to remove air bubbles, of which there are still a few. I used the "wet method" to position the vinyl, which involved spraying water on the back of the vinyl and the surface it was going to be fixed to. The water is, apparently, supposed to allow some movement of the vinyl when it is positioned, but I found that as soon as touched the Perspex it became fixed – The vinyl is quite thin, so I didn't want to pull on it too hard to try and lift it again.

I decided to use a piece of thin Perspex, since it's easy to cut and drill for mounting the LED's. Once the clock was built though, an issue became apparent where ambient light from an LED went out through the Perspex and up in to adjacent cells.

 

Thin perspex marked with positions of LED's:-

 

Drilled and LEDs inserted:-

A small drop of superglus was used to hold each LED in place. I also drew in the digit and segment relations from the MAX7219 ICs. One MAX7219 will drive the top 5 rows of the grid, and the other MAX7219 will drive the lower 5 rows.

 

LEDs wired up:-

To wire the grid of LEDs, I simply used the legs of the LED's and soldered them to next LED. For each "digit" driven by the MAX7219, I joined all of the cathodes together, and for each "segment" driven by the MAX7219 I joined all of the anodes remembering that one MAX7219 drives the first 5 rows and the other drives the bottom 5 rows. I then just ran wires from an appropriate point in the LED lines to the circuit board.

 

Testing the display:-

Testing the display by showing the time as digits. The first MAX7219, which is driving the top half of the display, is set to about one third brightness, while the second MAX7219 is set to full brightness.

 

The restrict the amount of light leakage from one letter to the next, I created a grid using cardboard strips. I simply cut a number of card strips the same width, created cuts within each strip at appropriate intervals and made the grid simply by slotting the pieces together:-

 

I glued small tabs to each corner of the completed cardboard grid, and then used those tabs to tape the grid to the back of the perspex with the vinyl mask. I also inserted a piece of blue vellum paper to provide some colour and diffuse the light from the LED's:-

 

Completed clock:-

 

PICAXE Code

Below is the code for the PICAXE:-

#PICAXE 18M2
#terminal off

; Copyright (c) 2013 Marc Symonds.
; Feel free to copy and modify as you wish.

#rem
DS1307 Pins

        +---+
    X1 -|1  |- Vcc
    X2 -|   |- SQW/OUT
+VBatt -|   |- I2C/SCL
    0V -|   |- I2C/SDA
        +---+
        
        
PICAXE18-M2 Pins
                               +---+
         Piezo - (Out/In) C.2 -|1  |- C.1 (In/Out) - Button - Mode
                    (Out) C.3 -|   |- C.0 (In/Out) - Button - Set
                     (In) C.4 -|   |- C.7 (In/Out) - Button - Adjust
                     (In) C.5 -|   |- C.6 (In/Out) - Button - Brightness
                          0V - |5  |- Vcc
                 (Out/In) B.0 -|   |- B.7 (In/Out) - MAX7219/DATA
DS1307 - (Out/In/I2C SDA) B.1 -|   |- B.6 (In/Out)
                 (Out/In) B.2 -|   |- B.5 (In/Out) - MAX7219/CLK
  MAX7219/LOAD - (Out/In) B.3 -|9  |- B.4 (In/Out/I2C SCL) - DS1307
                               +---+


Memory:-

00 - 27 - Registers
30 - 36 - Top half buffer.
37 - 43 - Bottom half buffer.
44 - 50 - Top half buffer compare.
51 - 57 - Bottom half buffer compare.

Sending to the MAX7219 is slow, so we only send data where something has changed.


Digit/Segment layout on display (11x10):-

0P 0A 0B 0C 0D 0E 0F 0G 6P 6A 6B
1P 1A 1B 1C 1D 1E 1F 1G 6C 6D 6E
2P 2A 2B 2C 2D 2E 2F 2G 6F 6G 7P      First MAX7219
3P 3A 3B 3C 3D 3E 3F 3G 7A 7B 7C
4P 4A 4B 4C 4D 4E 4F 4G 7D 7E 7F
--------------------------------
0P 0A 0B 0C 0D 0E 0F 0G 6P 6A 6B
1P 1A 1B 1C 1D 1E 1F 1G 6C 6D 6E
2P 2A 2B 2C 2D 2E 2F 2G 6F 6G 7P      Second MAX7219
3P 3A 3B 3C 3D 3E 3F 3G 7A 7B 7C
4P 4A 4B 4C 4D 4E 4F 4G 7D 7E 7F

P = DP.

#endrem


; MAX7219 Pins
symbol MAX7219_LOAD = B.3
symbol MAX7219_DIN = B.7
symbol MAX7219_CLK = B.5

; MAX7219 Registers
symbol MAX7219_NOOP = $00
symbol MAX7219_DIGIT0 = $01
symbol MAX7219_DIGIT1 = $02
symbol MAX7219_DIGIT2 = $03
symbol MAX7219_DIGIT3 = $04
symbol MAX7219_DIGIT4 = $05
symbol MAX7219_DIGIT5 = $06
symbol MAX7219_DIGIT6 = $07
symbol MAX7219_DIGIT7 = $08
symbol MAX7219_DECODE_MODE = $09
symbol MAX7219_INTENSITY = $0A
symbol MAX7219_SCAN_LIMIT = $0B
symbol MAX7219_SHUTDOWN = $0C
symbol MAX7219_TEST_MODE = $0F

; DS1307 Address
symbol DS1307_ADDRESS = %11010000

; DS1307 Registers
symbol DS1307_SECONDS = $00
symbol DS1307_MINUTES = $01
symbol DS1307_HOURS = $02
symbol DS1307_WEEKDAY = $03
symbol DS1307_DAY = $04
symbol DS1307_MONTH = $05
symbol DS1307_YEAR = $06
symbol DS1307_CONTROL = $07
symbol DS1307_RAM = $08 ; to $3F

; DS1307 Bits (assumes b0 used to manipulate values)
symbol DS1307_SECONDS_CH = bit7 ; Clock Halt - high = halt, low = go

symbol DS1307_HOURS_1224 = bit6 ; high = 12 hour, low = 24 hour
symbol DS1307_HOURS_AMPM = bit5 ; high = PM, low = AM (in 12 hour mode)

symbol DS1307_CONTROL_OUT = bit7
symbol DS1307_CONTROL_SQWE = bit4
symbol DS1307_CONTROL_RS1 = bit1
symbol DS1307_CONTROL_RS0 = bit0


; We'll use the DS1307 RAM to store some values in case main power is lost.
symbol DS1307_USR_MODE = DS1307_RAM + $00 ; Display mode
symbol DS1307_USR_BRIGHTNESS = DS1307_RAM + $01 ; Brightness level


; Display data will be buffered in these memory loctations, before being sent to the display (MAX7219).
symbol MEM_DISPLAY_TOP = 30 ' Start of buffer for top half of display.
symbol MEM_DISPLAY_TOP_END = 36 ' End of buffer for top half of display.
symbol MEM_DISPLAY_BOTTOM = 37
symbol MEM_DISPLAY_BOTTOM_END = 43

symbol MEM_DISPLAY_TOP_CMP = 44
symbol MEM_DISPLAY_BOTTOM_CMP = 51

; Pins used for buttons.
symbol BUT_MODE = C.1
symbol BUT_SET = C.0
symbol BUT_ADJUST = C.7
symbol BUT_BRIGHTNESS = C.6

symbol PIEZO = C.2

; Data for displaying digits.

; 0 - 9 On left of display.

symbol TAB_NUM_LEFT = 0

table TAB_NUM_LEFT, (%11100000, %10100000, %10100000, %10100000, %11100000, 0, 0)
table               (%01000000, %11000000, %01000000, %01000000, %11100000, 0, 0)
table               (%11100000, %00100000, %11100000, %10000000, %11100000, 0, 0)
table               (%11100000, %00100000, %01100000, %00100000, %11100000, 0, 0)
table               (%10100000, %10100000, %11100000, %00100000, %00100000, 0, 0)
table               (%11100000, %10000000, %11100000, %00100000, %11100000, 0, 0)
table               (%11100000, %10000000, %11100000, %10100000, %11100000, 0, 0)
table               (%11100000, %00100000, %00100000, %00100000, %00100000, 0, 0)
table               (%11100000, %10100000, %11100000, %10100000, %11100000, 0, 0)
table               (%11100000, %10100000, %11100000, %00100000, %11100000, 0, 0)

; 0 - 9 In middle of display.

symbol TAB_NUM_MIDDLE = 70

table TAB_NUM_MIDDLE, (%00001110, %00001010, %00001010, %00001010, %00001110, 0, 0)
table                 (%00000100, %00001100, %00000100, %00000100, %00001110, 0, 0)
table                 (%00001110, %00000010, %00001110, %00001000, %00001110, 0, 0)
table                 (%00001110, %00000010, %00000110, %00000010, %00001110, 0, 0)
table                 (%00001010, %00001010, %00001110, %00000010, %00000010, 0, 0)
table                 (%00001110, %00001000, %00001110, %00000010, %00001110, 0, 0)
table                 (%00001110, %00001000, %00001110, %00001010, %00001110, 0, 0)
table                 (%00001110, %00000010, %00000010, %00000010, %00000010, 0, 0)
table                 (%00001110, %00001010, %00001110, %00001010, %00001110, 0, 0)
table                 (%00001110, %00001010, %00001110, %00000010, %00001110, 0, 0)

; 0 - 9 On right of display.

symbol TAB_NUM_RIGHT = 140

table TAB_NUM_RIGHT, (0, 0, 0, 0, 0, %11110110, %11011110)
table                (0, 0, 0, 0, 0, %01011001, %00101110)
table                (0, 0, 0, 0, 0, %11100111, %11001110)
table                (0, 0, 0, 0, 0, %11100101, %10011110)
table                (0, 0, 0, 0, 0, %10110111, %10010010)
table                (0, 0, 0, 0, 0, %11110011, %10011110)
table                (0, 0, 0, 0, 0, %11110011, %11011110)
table                (0, 0, 0, 0, 0, %11100100, %10010010)
table                (0, 0, 0, 0, 0, %11110111, %11011110)
table                (0, 0, 0, 0, 0, %11110111, %10011110)

; hr.

symbol TAB_WRD_Hr = 210

table TAB_WRD_Hr, (%10000000, %11100010, %10010100, %10010100, %00000000, 0, 0)

; mi.

symbol TAB_WRD_Mi = 217

table TAB_WRD_Mi, (%01010000, %10101010, %10101010, %10001010, %00000000, 0, 0)


; Pixels to light to tell the time in words.

symbol TAB_TIM_ITIS = 224
symbol TAB_TIM_OCLOCK = 228
symbol TAB_TIM_FIVE = 232
symbol TAB_TIM_TEN = 236
symbol TAB_TIM_AQUARTER = 240
symbol TAB_TIM_TWENTY = 244
symbol TAB_TIM_TWENTYFIVE = 248
symbol TAB_TIM_HALF = 252
symbol TAB_TIM_PAST = 256
symbol TAB_TIM_TO = 260
symbol TAB_TIM_ONE = 264
symbol TAB_TIM_TWO = 268
symbol TAB_TIM_THREE = 272
symbol TAB_TIM_FOUR = 276
symbol TAB_TIM_FIVEw = 280
symbol TAB_TIM_SIX = 284
symbol TAB_TIM_SEVEN = 288
symbol TAB_TIM_EIGHT = 292
symbol TAB_TIM_NINE = 296
symbol TAB_TIM_TENw = 300
symbol TAB_TIM_ELEVEN = 304
symbol TAB_TIM_TWELVE = 308

;                          Row  1st 8      Top End    Bottom End
table TAB_TIM_ITIS,       ($00, %11011000, %00000000, %00000000)

table TAB_TIM_OCLOCK,     ($09, %00000111, %00000000, %00001110)
table TAB_TIM_FIVE,       ($02, %00000011, %00000011, %00000000)
table TAB_TIM_TEN,        ($03, %00000111, %00000000, %00000000)
table TAB_TIM_AQUARTER,   ($01, %10111111, %00010000, %00000000)
table TAB_TIM_TWENTY,     ($02, %11111100, %00000000, %00000000)
table TAB_TIM_TWENTYFIVE, ($02, %11111111, %00000011, %00000000)
table TAB_TIM_HALF,       ($03, %11110000, %00000000, %00000000)

table TAB_TIM_PAST,       ($04, %11110000, %00000000, %00000000)
table TAB_TIM_TO,         ($03, %00000000, %00000000, %00110000)

table TAB_TIM_ONE,        ($05, %11100000, %00000000, %00000000)
table TAB_TIM_TWO,        ($06, %00000000, %00011100, %00000000)
table TAB_TIM_THREE,      ($05, %00000011, %11100000, %00000000)
table TAB_TIM_FOUR,       ($06, %11110000, %00000000, %00000000)
table TAB_TIM_FIVEw,      ($06, %00001111, %00000000, %00000000)
table TAB_TIM_SIX,        ($05, %00011100, %00000000, %00000000)
table TAB_TIM_SEVEN,      ($08, %11111000, %00000000, %00000000)
table TAB_TIM_EIGHT,      ($07, %11111000, %00000000, %00000000)
table TAB_TIM_NINE,       ($04, %00000001, %00000000, %00001110)
table TAB_TIM_TENw,       ($09, %11100000, %00000000, %00000000)
table TAB_TIM_ELEVEN,     ($07, %00000111, %00000011, %10000000)
table TAB_TIM_TWELVE,     ($08, %00000111, %00000000, %01110000)


symbol v_SECONDS = b20
symbol v_MINUTES = b21
symbol v_HOURS = b22

symbol v_SECONDS_PREV = b23
symbol v_MINUTES_PREV = b24

symbol v_MODE = b25
symbol v_BRIGHTNESS = b26

; ********************************************************************************
; *** Start

  setfreq m4
  
  pause 500 ; Give other chips time to start up.

  let dirsB = %10101101
  let dirsC = %11000011

  gosub DS1307_Initialise
  gosub MAX7219_Initialise

  ; Load saved settings from DS1307.
  setfreq m8
  hi2cin DS1307_USR_MODE, (v_MODE, v_BRIGHTNESS)
  setfreq m32
  
  if v_MODE > 2 then let v_MODE = 0 end if
  let v_BRIGHTNESS = v_BRIGHTNESS & $0f

  gosub ResetDisplay

main:
  setfreq m8
  hi2cin DS1307_SECONDS, (v_SECONDS, v_MINUTES, v_HOURS)
  setfreq m32

  on v_MODE gosub ShowTimeInWords, ShowHoursMinutes, ShowMinutesSeconds

;BUTTON pin,downstate,delay,rate,bytevariable,targetstate,address
  button BUT_MODE, 1, 255, 0, b10, 1, SwitchModeMain
  button BUT_BRIGHTNESS, 1, 255, 0, b11, 1, SwitchBrightnessMain
  button BUT_SET, 1, 255, 0, b12, 1, SetTime
  
  pause 10
  goto main

KeySound:
  sound PIEZO, (20, 40)
  return
  

SwitchModeMain:
  gosub KeySound
  gosub SwitchMode
  goto main


SwitchBrightnessMain:
  gosub KeySound
  gosub SwitchBrightness
  goto main  


SetTime:
  gosub KeySound
  gosub ClearDisplay
  
; Set top half of display dim and bottom half bright.  
  let b1 = $03
  let b2 = $0f
  gosub MAX7219_SetIntensity

  let w0 = TAB_WRD_HR
  gosub DisplayTop
  
  do
    pause 10
    let b0 = BUT_SET
  loop  while b0 = 1
  
  let b15 = v_HOURS & $3f
  let b16 = $23
  gosub AdjustNumber
  if b17 = 0 then SetTimeEnd
  let v_HOURS = v_HOURS & $C0 | b15

  gosub ClearDisplayTop
  let w0 = TAB_WRD_MI
  gosub DisplayTop
  
  let b15 = v_MINUTES
  let b16 = $59
  gosub AdjustNumber
  if b17 = 0 then SetTimeEnd
  let v_MINUTES = b15
  
; Save the new time to the DS1307. Set seconds to 0.
  setfreq m8
  hi2cout DS1307_SECONDS, (0, v_MINUTES, v_HOURS)
  setfreq m32
  
SetTimeEnd:
  gosub ResetDisplay
  goto main
  
AdjustNumber:
  gosub ClearDisplayBottom
  let b5 = b15
  gosub DisplayNumBottom
  gosub SendToDisplay
AdjustNumberButtons:
  button BUT_ADJUST, 1, 100, 60, b13, 1, AdjustNumberInc ; Adjust number
  button BUT_SET, 1, 255, 0, b12, 1, AdjustNumberSave ; Save number and move to next
  button BUT_MODE, 1, 255, 0, b10, 1, AdjustNumberCancel ; Cancel adjust
  pause 10
  goto AdjustNumberButtons

AdjustNumberInc:
  gosub KeySound
; Working with BCD numbers, so need to faff about a bit.
  let b0 = b15 & $0f
  if b0 = $09 then
    let b15 = b15 & $f0 + $10
  else
    inc b15
  end if
  
  if b15 > b16 then
    let b15 = 0
  end if
  
  goto AdjustNumber
  
AdjustNumberSave:
  gosub KeySound
  do
    pause 10
    let b0 = BUT_SET
  loop while b0 = 1
  
  let b17 = 1
  return
  
AdjustNumberCancel:
  gosub KeySound
  do
    pause 10
    let b0 = BUT_MODE
  loop while b0 = 1
  
  let b17 = 0
  return
  

SwitchBrightness:
  if v_BRIGHTNESS = 0 then
    let v_BRIGHTNESS = $0f
  else
    dec v_BRIGHTNESS
  end if
  
; Save the brightness to the DS1307.
  setfreq m8
  hi2cout DS1307_USR_BRIGHTNESS, (v_BRIGHTNESS)
  setfreq m32

  goto SetModeBrightness


SwitchMode:
; 0=Words, 1=Hours/Minutes, 2=Minutes/Seconds

  inc v_MODE
  if v_MODE > 2 then
    let v_MODE = 0
  end if
  
; Save the mode to the DS1307.
  setfreq m8
  hi2cout DS1307_USR_MODE, (v_MODE)
  setfreq m32
  
; Force update next time round.
ResetDisplay:
  let v_MINUTES_PREV = $ff
  let v_SECONDS_PREV = $ff
  
; *** Fall-through.
SetupMode:
  gosub ClearDisplay
  gosub SendToDisplay
; *** Fall-through.  
SetModeBrightness:
  let b2 = v_BRIGHTNESS
  if v_MODE = 0 then
    let b1 = b2
  else
    let b1 = b2 / 3
  end if

  goto MAX7219_SetIntensity


FromBCD:
  let b1 = b0 / 16 * 10
  let b0 = b0 & $0f + b1
  return


ShowTimeInWords:
  if v_MINUTES <> v_MINUTES_PREV then
    gosub ClearDisplay

    ; Convert hours from BCD time in to normal values to deal with easier.
    
    let b0 = v_HOURS & $3f
    gosub FromBCD
    let v_HOURS = b0

    ; Clock is running in 24 hour mode, so convert hours to 12 hour.
      
    if v_HOURS = 0 then
      let v_HOURS = 12
    elseif v_HOURS > 12 then
      let v_HOURS = v_HOURS - 12
    end if

    ; IT IS    
    let w0 = TAB_TIM_ITIS
    gosub DisplayWord
    
    ; Fuzzy time 58-02=0, 03-07=5, 08-12=10, 13-17=15, 18-22=20, 23-27=25, 28-32=30, 33-37=35, 38-42=40, 43-37=45, 48-52=50, 53-57=55
    ; Minutes
    if v_MINUTES <= $02 then
      let w0 = TAB_TIM_OCLOCK
    elseif v_MINUTES <= $07 then
      let w0 = TAB_TIM_FIVE
    elseif v_MINUTES <= $12 then
      let w0 = TAB_TIM_TEN
    elseif v_MINUTES <= $17 then
      let w0 = TAB_TIM_AQUARTER
    elseif v_MINUTES <= $22 then
      let w0 = TAB_TIM_TWENTY
    elseif v_MINUTES <= $27 then
      let w0 = TAB_TIM_TWENTYFIVE
    elseif v_MINUTES <= $32 then
      let w0 = TAB_TIM_HALF
    else
      if v_MINUTES <= $37 then
        let w0 = TAB_TIM_TWENTYFIVE
      elseif v_MINUTES <= $42 then
        let w0 = TAB_TIM_TWENTY
      elseif v_MINUTES <= $47 then
        let w0 = TAB_TIM_AQUARTER
      elseif v_MINUTES <= $52 then
        let w0 = TAB_TIM_TEN
      elseif v_MINUTES <= $57 then
        let w0 = TAB_TIM_FIVE
      else
        let w0 = TAB_TIM_OCLOCK
      end if
      
      ; Gone past 30 minutes, so adjust hour to the hour we are coming up to.
      inc v_HOURS
      if v_HOURS > 12 then
        let v_HOURS = 1
      end if
    end if
    
    gosub DisplayWord
    
    ; Past or To. If not O'Clock.
    if v_MINUTES >= $03 and v_MINUTES < $58 then
      if v_MINUTES < $33 then
        let w0 = TAB_TIM_PAST
      else
        let w0 = TAB_TIM_TO
      end if
      
      gosub DisplayWord
    end if
      
    ; Hour.
    let w0 = v_HOURS - 1 * 4 + TAB_TIM_ONE
    gosub DisplayWord
    
    gosub SendToDisplay
    
    let v_MINUTES_PREV = v_MINUTES
  end if

  return


DisplayWord:
  readtable w0, b2 ; Line
  inc w0
  readtable w0, b3 ; Main data
  inc w0
  
  if b2 > 4 then
    let b4 = b2 - 5 + MEM_DISPLAY_BOTTOM
    let b6 = MEM_DISPLAY_BOTTOM_END - 1
  else
    let b4 = b2 + MEM_DISPLAY_TOP
    let b6 = MEM_DISPLAY_TOP_END - 1
  end if
  
  peek b4, b5
  let b5 = b5 or b3
  poke b4, b5
  
  readtable w0, b3
  inc w0

  peek b6, b5
  let b5 = b5 or b3
  poke b6, b5
  
  inc b6
  readtable w0, b3
  peek b6, b5
  let b5 = b5 or b3
  poke b6, b5

  return


ShowHoursMinutes:
  if v_MINUTES <> v_MINUTES_PREV then
    gosub ClearDisplay

    let b5 = v_HOURS & $3f
    gosub DisplayNumTop    

    let b5 = v_MINUTES
    gosub DisplayNumBottom

    gosub SendToDisplay
    
    let v_MINUTES_PREV = v_MINUTES
  end if
  
  return


ShowMinutesSeconds:
  if v_SECONDS <> v_SECONDS_PREV then
    gosub ClearDisplay
    
    let b5 = v_MINUTES
    gosub DisplayNumTop

    let b5 = v_SECONDS
    gosub DisplayNumBottom

    gosub SendToDisplay
    
    let v_SECONDS_PREV = v_SECONDS
  end if
  
  return
  
; ********************************************************************************
; *** Display Functions **********************************************************
; ********************************************************************************

SendToDisplay:
; Sending to the MAX7219 is slow, so we only send data where something has changed.

  let b0 = MAX7219_DIGIT0
  let b5 = MEM_DISPLAY_TOP
  let b7 = MEM_DISPLAY_TOP_CMP
  let b8 = MEM_DISPLAY_BOTTOM_CMP
  for b6 = MEM_DISPLAY_BOTTOM to MEM_DISPLAY_BOTTOM_END
    peek b5, b1 ' Read new display data.
    peek b6, b2
  
    peek b7, b3 ' Read last displayed data.
    peek b8, b4

;    if b1 = b3 and b2 <> b4 then
;      poke b8, b2
;      
;      gosub MAX7219_SendCmdAndDataTo2
;    elseif b1 <> b3 and b2 = b4 then
;      poke b7, b1
;      
;      gosub MAX7219_SendCmdAndDataTo1
;    elseif b1 <> b3 and b2 <> b4 then ' If changed, then send to MAX7219.
    if b1 <> b3 or b2 <> b4 then ' If changed, then send to MAX7219.
      poke b7, b1
      poke b8, b2
      
      gosub MAX7219_SendCmdAndSepData2Both
    endif
    
    inc b0
    inc b5
    inc b7
    inc b8
  next
  goto MAX7219_ShutdownOff


DisplayNumTop:
  let b0 = b5 & 15
  gosub DisplayNum01Top
  let b0 = b5 / 16
; *** Fall-through
DisplayNum10Top:
; b0 = Number to display.

  let b1 = 0
  let w0 = w0 * 7 + TAB_NUM_LEFT
  goto DisplayTopAdj:
  

DisplayNum01Top:
; b0 = Number to display.
  let b1 = 0
  let w0 = w0 * 7 + TAB_NUM_MIDDLE
  goto DisplayTopAdj:


DisplayNumBottom:
  let b0 = b5 & 15
  gosub DisplayNum01Bottom
  let b0 = b5 / 16
; *** Fall-through.
DisplayNum10Bottom:
; b0 = Number to display.
  let b1 = 0
  let w0 = w0 * 7 + TAB_NUM_MIDDLE
  goto DisplayBottom:


DisplayNum01Bottom:
; b0 = Number to display.
  let b1 = 0
  let w0 = w0 * 7 + TAB_NUM_RIGHT
  goto DisplayBottom:


DisplayTop:
; w0 = Start table address to read from. w0 will be updated.

  for b2 = MEM_DISPLAY_TOP to MEM_DISPLAY_TOP_END
    readtable w0, b3
    peek b2, b4
    let b3 = b3 | b4
    poke b2, b3
    let w0 = w0 + 1
  next

  return


; Display data adjusted right by one bit.
DisplayTopAdj:
; w0 = Start table address to read from. w0 will be updated.

  for b2 = MEM_DISPLAY_TOP to MEM_DISPLAY_TOP_END
    readtable w0, b3
    peek b2, b4
    let b3 = b3 / 2 | b4 ; Divide by 2 to adjust right.
    poke b2, b3
    let w0 = w0 + 1
  next

  return


DisplayBottom:
; w0 = Start table address to read from. w0 will be updated.

  for b2 = MEM_DISPLAY_BOTTOM to MEM_DISPLAY_BOTTOM_END
    readtable w0, b3
    peek b2, b4
    let b3 = b3 | b4
    poke b2, b3
    let w0 = w0 + 1
  next
  
  return


ClearDisplay:
  gosub ClearDisplayBottom
; *** Fall-through ***
ClearDisplayTop:
  for b0 = MEM_DISPLAY_TOP to MEM_DISPLAY_TOP_END
     poke b0, 0
  next
  
  return


ClearDisplayBottom:
  for b0 = MEM_DISPLAY_BOTTOM to MEM_DISPLAY_BOTTOM_END
    poke b0, 0
  next
  
  return

; ********************************************************************************
; *** DS1307 Functions ***********************************************************
; ********************************************************************************

DS1307_Initialise:
  setfreq m8
  
  hi2csetup i2cmaster, DS1307_ADDRESS, i2cslow_8, i2cbyte

  gosub DS1307_24HourMode

  let b1 = 0
  gosub DS1307_SetClockHalt
  
  goto DS1307_SetSQWE_1Hz


; Set the CH (Clock Halt) bit in RTC.
DS1307_SetClockHalt:
; b1.0 = Clock Halth (CH) state

  hi2cin DS1307_SECONDS, (b0)
  if DS1307_SECONDS_CH <> bit8 then ;(bit8 = bit 0 of b1)
    let DS1307_SECONDS_CH = bit8
    hi2cout DS1307_SECONDS, (b0)
  end if

  return


DS1307_SetSQWE_1Hz:
  let b0 = 0 ; Out = 0 and RS1/RS0 = 0
  let DS1307_CONTROL_SQWE = 1
  hi2cout DS1307_CONTROL, (b0) 
  
  return


#rem
; Switch to 12 hour mode
DS1307_12HourMode:
  hi2cin DS1307_HOURS, (b0)
  if DS1307_HOURS_1224 = 0 then
    ; BCD to BIN
    let b1 = b0 & $30 / 16 * 10
    let b1 = b0 & $0f + b1
  
    let b2 = 1
    if b1 >= 12 then
      if b1 > 12 then
        let b1 = b1 - 12
      endif
      let b2 = 1
    else
      if b1 = 0 then
        let b1 = 12
      end if
      let b2 = 0
    endif
  
    ; BIN to BCD
    let b0 = b1 / 10 * 16
    let b0 = b1 % 10 + b0
  
    if b2 = 1 then
      let DS1307_HOURS_AMPM = 1
    endif
  
    let DS1307_HOURS_1224 = 1
    hi2cout DS1307_HOURS, (b0)
  end if
  
  return
#endrem

; Switch to 24 hour mode
DS1307_24HourMode:
  hi2cin DS1307_HOURS, (b0)
  if DS1307_HOURS_1224 = 1 then
    ; BCD to BIN
    let b1 = b0 & $10 / 16 * 10
    let b1 = b0 & $0f + b1

    if DS1307_HOURS_AMPM = 1 then
      if b1 < 12 then
        let b1 = b1 + 12
      end if
    elseif b1 = 12 then
      let b1 = 0
    end if

    ; BIN to BCD
    let b0 = b1 / 10 * 16
    let b0 = b1 % 10 + b0

    ;let DS1307_HOURS_1224 = 0 ; Bit will already be 0, so don't need to explicitly set it.
    hi2cout DS1307_HOURS, (b0)
  end if
  
  return

; ********************************************************************************
; *** MAX7219 Functions **********************************************************
; ********************************************************************************

MAX7219_Initialise:
  setfreq m32
  
  high MAX7219_LOAD
  low MAX7219_CLK
  low MAX7219_DIN

  pause 10

  let b0 = MAX7219_DECODE_MODE
  let b1 = $00 ; Don't use decode mode for any data.
  gosub MAX7219_SendCmdAndDataToBoth

  let b1 = $03
  let b2 = $0c
  gosub MAX7219_SetIntensity

  let b0 = MAX7219_SCAN_LIMIT
  let b1 = $06 ; Using 7 digits (0-6); not using the 8th digit.
  gosub MAX7219_SendCmdAndDataToBoth

  gosub MAX7219_ShutdownOff
  goto MAX7219_TestModeOff ; Return will be peformed by this function


MAX7219_ShutdownOff:
  let b0 = MAX7219_SHUTDOWN
  let b1 = $01
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function

#rem
MAX7219_ShutdownOn:
  let b0 = MAX7219_SHUTDOWN
  let b1 = $00
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function
#endrem

MAX7219_TestModeOn:
  b0 = MAX7219_TEST_MODE
  b1 = $01
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function

  
MAX7219_TestModeOff:
  b0 = MAX7219_TEST_MODE
  b1 = $00
  goto MAX7219_SendCmdAndDataToBoth ; Return will be peformed by this function


MAX7219_SetIntensity:
; b1 = Intensity level for top half.
; b2 = Intensity level for bottom half.

  let b0 = MAX7219_INTENSITY
  goto MAX7219_SendCmdAndSepData2Both ; Return will be peformed by this function


MAX7219_SendCmdAndDataToBoth:
; b0 = Register
; b1 = Data

  low MAX7219_LOAD
  
  gosub MAX7219_SendCmdAndData ; This will send to the second MAX7219
;  pause 1
  gosub MAX7219_SendCmdAndData ; This will send to the first MAX7219

  high MAX7219_LOAD
  
  return  


MAX7219_SendCmdAndSepData2Both:
; b0 = Register
; b1 = Data for chip 1
; b2 = Data for chip 2

  low MAX7219_LOAD
  
  swap b2, b1 ; We have to send the data for chip 2 first, so swap the Data values
  gosub MAX7219_SendCmdAndData ; Send to the second MAX7219
  swap b2, b1 ; Swap the values back.
  gosub MAX7219_SendCmdAndData ; Send to the first MAX7219

  high MAX7219_LOAD
  
  return  


MAX7219_SendCmdAndDataTo1:
; b0 = Register.
; b1 = Data for chip 1.

  low MAX7219_LOAD

  gosub MAX7219_SendCmdAndData
  
  high MAX7219_LOAD
  
  return


MAX7219_SendCmdAndDataTo2:
; b0 = Register.
; b2 = Data for chip 2.

  low MAX7219_LOAD

  swap b1, b2
  gosub MAX7219_SendCmdAndData
  swap b0, b1 ; Need to preserve b0.
  let b0 = MAX7219_NOOP
  gosub MAX7219_SendCmdAndData
  swap b0, b1 ; Restore b0.
  
  high MAX7219_LOAD
  
  return
  

MAX7219_SendCmdAndData:
; b0 = Register.
; b1 = Data.
; b3 & b4 used as temp.

  let b3 = b0
  gosub MAX7219_Send

  let b3 = b1
; *** Fall-through is intended. ***
MAX7219_Send:
; b3 = Data to send

  for b4 = 0 to 7
    if b3 > 127 then
      high MAX7219_DIN
    else
      low MAX7219_DIN
    end if

    pulsout MAX7219_CLK, 1    
    b3 = b3 * 2
  next

  return