
;; Binary Clock w/ WWVB - Copyright 2010 by Michael Kohn
;; Email: mike to mikekohn.net
;;   Web: http://www.mikekohn.net/
;;
;; Binary clock that sets time from the WWVB radio broadcast

.include "m168def.inc"

; Help pages:
; http://en.wikipedia.org/wiki/WWVB

; 50000 cycles per interrupt
; 160 interrupts per second
; 32 cycles = 0.2 seconds
; 80 cycles = 0.5 seconds
; 128 cycles = 0.8 seconds

; r0  = 0
; r1  = 1
; r2  = last hour + 1 minute
; r3  = last minute + 1 minute
; r7  = saved status register
; r14 = temp in function
; r15 = 255
; r16 = noise gate! :(
; r21 = interrupt counter <60 ticks = seconds light on
; r22 = off counter
; r23 = seconds counter
; r26 = minute
; r27 = hour
; r28 = minute holding
; r29 = hour holding
; r30 = marker flag
; r31 = marker count
;

; note: CLKSEL 0010
;       8MHz

.cseg

.org 0x000
  rjmp start
.org 0x016
  rjmp the_great_interrupt
.org 0x01a
  rjmp the_great_interrupt

;; FIXME - erase this padding.. it's dumb
.org 0x020

start:
  ;; I'm busy.  Don't interrupt me!
  cli

  ;; Set up constants registers
  eor r0, r0   ; r0 = constant 0
  eor r1, r1   ; r1 = constant 1
  inc r1
  eor r15, r15 ; r15 = constant 255
  dec r15

  ;; set up working registers
  ;clr r16
  clr r21
  clr r22
  clr r23
  clr r26 ; minute
  clr r27 ; hour
  clr r28 ; minute holding
  clr r29 ; hour holding
  clr r30 ; marker flag
  ldi r31, 10 ; marker count

  ;; Set up stack ptr
  ldi r17, RAMEND>>8
  out SPH, r17
  ldi r17, RAMEND&255
  out SPL, r17

  ;; Set up ports
  ldi r17, 63
  out DDRB, r17
  out PORTB, r0
  ldi r17, 31
  out DDRC, r17
  out PORTC, r0
  ldi r17, (1<<PD2)|(1<<PD3)|(1<<PD5)|(1<<PD6)
  out DDRD, r17
  ;;ldi r17, (1<<PD4)
  out PORTD, r0

  ;; ZOMG DEBUG
  sbi PORTD, PD0
  sbi PORTD, PD1

  ;; Set up TIMER1
  ;lds r17, PRR
  ;andi r17, 255 ^ (1<<PRTIM1)
  ;sts PRR, r17                   ; turn of power management bit on TIM1

  ldi r17, (49999>>8)
  sts OCR1AH, r17
  ldi r17, (49999&0xff)            ; compare to x interrupts a second
  sts OCR1AL, r17

  ldi r17, (1<<OCIE1A)
  sts TIMSK1, r17                ; enable interrupt comare A 
  sts TCCR1C, r0
  sts TCCR1A, r0                 ; normal counting (0xffff is top, count up)
  ldi r17, (1<<CS10)|(1<<WGM12)  ; CTC OCR1A
  sts TCCR1B, r17                ; prescale = 1 from clock source

  ;; Set up ADC
  ;out PORTC, r0
  ;out DDRC, r0
  ;;ldi r17, (1<<MUX0)
  ;sts ADMUX, r0  ; use ADC0 and Aref
  ;ldi r17, (1<<ADEN)|(1<<ADATE)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
  ;sts ADCSRA, r17
  ;ldi r17, (1<<ADC5D)|(1<<ADC4D)|(1<<ADC3D)|(1<<ADC2D)|(1<<ADC1D)|(1<<ADC0D)
  ;sts DIDR0, r17

  ; Fine, I can be interrupted now
  sei

main:
  rjmp main

; The Great Interrupt Routine!
the_great_interrupt:
  ; save status register
  ;in r7, SREG

  inc r21                  ; interrupt_counter++
  cpi r21, 159             ; if (interrupt_counter==159)
  brne not_160_yet         ; {
  clr r21                  ;   interrupt_counter=0;
  inc r23                  ;   second=second+1
  cpi r23, 60              ;   if (second==60)
  brne not_160_yet         ;   {
  clr r23                  ;     second=0;
  inc r26                  ;     minute++;
  cpi r26, 60              ;     if (minute==60)
  brne not_160_yet         ;     {
  clr r26                  ;       minute=0;
  inc r27                  ;       hour++;
  cpi r27, 24              ;       if (hour==24) hour=0;
  brne not_160_yet         ;     }
  clr r27                  ; } }

not_160_yet:
  out PORTB, r26           ; PORTB=minute;
  out PORTC, r27           ; PORTC=hour;

  cpi r21, 80              ; if (interrupt_count<80)
  brsh turn_off            ; {
  sbi PORTD, PD2           ;   PORTD.PD2=1;
  rjmp test_radio          ; }

turn_off:                  ;   else
  cbi PORTD, PD2           ; { PORTD.PD2=0; }

test_radio:
  sbic PIND, PD4           ; if (radio signal low)
  rjmp up_state            ; {
  ;; Count the downs
  cbi PORTD, PD3           ;   PORTD.PD3=0;
  inc r22                  ;   down_count++;
  reti                     ; }

up_state:                  ; // radio signal high
  sbi PORTD, PD3           ; PORTD.PD3=1;
  tst r22                  ; if (down_count==0) return;
  breq escape

  cpi r22, 40              ; if (down_count<40) goto got_zero;
  brlo got_zero

  cpi r22, 88              ; if (down_couint<88) goto got_one;
  brlo got_one

  cpi r22, 136             ; if (down_count<136) goto got_marker;
  brlo got_marker

  cbi PORTD, PD5           ; PORTD.PD5=0;
  cbi PORTD, PD6           ; PORTD.PD6=0;   // error condition

escape:
  clr r22                  ; down_count=0;
  ;out SREG, r7
  reti

; r28 = minute
; r29 = hour
; r30 = marker flag
; r31 = marker count

got_marker:
  sbi PORTD, PD5
  sbi PORTD, PD6

  tst r30                  ; if (marker_flag!=1)
  brne set_clock           ; {
  clr r22                  ;    down_count=0;
  inc r31                  ;    marker_count++;
  ser r30                  ;    marker_flag=true;
  reti                     ; }

got_one:
  sbi PORTD, PD5
  cbi PORTD, PD6

  cpi r31, 1
  brne not_bit_bang_minute_one
  sec
  rol r28
  rjmp exit_one

not_bit_bang_minute_one:
  cpi r31, 2
  brne exit_one
  sec
  rol r29

exit_one:
  clr r22               ; down_count=0
  clr r30               ; marker_flag=false
  reti

got_zero:
  cbi PORTD, PD5
  sbi PORTD, PD6

  cpi r31, 1
  brne not_bit_bang_minute_zero
  clc
  rol r28
  rjmp exit_zero

not_bit_bang_minute_zero:
  cpi r31, 2
  brne exit_zero
  clc
  rol r29

exit_zero:
  clr r22               ; down_count=0
  clr r30               ; marker_flag=false
  reti

set_clock:
  mov r17, r28             ; // let's calculate minutes
  andi r28, 15
  andi r17, (128|64|32)
  swap r17
  add r28, r17             ; // BCD to normal (top part * 10 + bottom)
  add r28, r17
  add r28, r17
  add r28, r17
  add r28, r17

  mov r17, r29             ; // let's calculate hours
  andi r29, 15
  andi r17, (64|32)
  swap r17
  add r29, r17             ; BCD to normal (top part * 10 + bottom)
  add r29, r17
  add r29, r17
  add r29, r17
  add r29, r17

  clr r10                  ; update flag

  cp r28, r2               ; if r29:r28 == r3:r2 then the read was good
  brne bad_radio_read      ; so set the clock
  cp r29, r3
  brne bad_radio_read

  mov r10, r15

bad_radio_read:            ; r3:r2 = r29:r28 + 1 minute
  inc r28                  ; if the read was correct, this should be the next
  cpi r28, 60              ; time value
  brne r28_not_60
  clr r28
  inc r29
  cpi r29, 24
  brne r29_not_24
  clr r29

r28_not_60:
r29_not_24:
  mov r2, r28
  mov r3, r29

  tst r10
  brne update_clock

exit_marker:
  clr r22                  ; down_count=0;
  ldi r31, 1               ; marker_count = 1
  ser r30                  ; marker_flag=true;
  reti

update_clock:
  clr r21                  ; interrupt_counter=0;  // for flashing LED
  ldi r23, 1               ; seconds=1;
  mov r26, r28             ; r27:26 = last time read
  mov r27, r29
  out PORTB, r26           ; PORTB=r26
  out PORTC, r27           ; PORTC=r27
  rjmp exit_marker

signature:
.db "Binary Clock w/ WWVB - Copyright 2010 - Michael Kohn - Version 1.00"



