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.