;Amy's nixie tube clock firmware
;(a)2013 reboots@g-cipher.net -- http://reboots.g-cipher.net
;This software is free as in thought, do what thou wilt.

;Compiles with Gerd's AVR assembler (gavrasm):
;http://www.avr-asm-tutorial.net/gavrasm/index_en.html

;This firmware programs a Supertex HV5622 high-voltage shift register
;to display the current 12-hour time on four nixie tubes, and represents
;the current second in binary on 6 LEDs--more or less. In actuality the
;display counts from 4 to 63, so all 6 LEDs are on for the last second
;of the minute. Time is kept in 1/2 second increments, based on an
;external DS32KHz TCXO. Routines are available for both little- and
;big-endian shifting, to allow for reversing the tube connections.

;The current time display is stored in four bytes of SRAM to keep
;registers free. It happens that there were four registers left, so
;this code could be simplified by using those instead.

;Western 12-hour time is deceptively complex to count: the clock must
;start at 12:00; roll over to 1:00, blanking the first digit; count to
;10:00 and re-enable the first digit, but only for 10:00-12:59. 24-hour
;time is simpler, but isn't implemented here. AM/PM is not tracked.

.include "m48def.inc"	;includes the ATmega48 definitions file
.def	temp	= R17	;temporary register
.def	temp1	= R18	;another temporary register
.def	disp	= R19	;LED data register
.def	delay	= R20	;delay register
.def	halfsec	= R21	;half-seconds
.def	min1_l	= R22	;single minutes low byte
.def	min1_h	= R23	;single minutes high byte
.def	min10	= R24	;tens of minutes
.def	hr1_l	= R25	;single hours low byte
.def	hr1_h	= R26	;single hours high byte
.def	hr10	= R27	;tens of hours

.dseg

TIME:		.BYTE 4 ;current time value stored here

.cseg

; Wiring from ATmega48 to HV5622
; PORTD,7 (11) = _Blanking (33)
; PORTB,0 (12) = Data In   (32)	Data input
; PORTB,1 (13) = _LE       (31)	Latch enable, data copied to latch on high
; PORTB,2 (14) = CLK       (28)	Clock, register shifts data on falling edge

;_POL (27) wired high

.equ	HV		=0x05	;PORTB HV5622 control
.equ	HVdat		=0	;HV5622 data
.equ	HVle		=1	;HV5622 latch enable
.equ	HVclk		=2	;HV5622 clock

.equ	delaynum	=32	;adjust global delay time here

.org 0x0000	  ;places the following code from address 0x0000
rjmp	reset	  ;take a relative jump to the RESET Label
rjmp	button_m  ;INT0 service routine
rjmp	button_h  ;INT1 service routine
reti  ; PCINT0
reti  ; PCINT1
reti  ; PCINT2
reti  ; watchdog
rjmp	count	   ; TIMER2 compare A
reti  ; TIMER2 compare B
reti  ; TIMER2 overflow

reset:
        ldi     temp,high(RAMEND)
        out     SPH,temp
        ldi     temp,low(RAMEND)
        out     SPL,temp

        ser     temp
        out     DDRB,temp	;PORTB all outputs
        out     DDRC,temp	;PORTC all outputs
        out     DDRD,temp	;PORTD all outputs

	;Set up external interrupts

        sbi     PORTD,2		;INT0
	sbi	PORTD,3		;INT1

        ldi     temp,0b00000000	; ISC11,10,01,00: set INT1,INT0 low trigger
        sts     EICRA,temp
        ldi     temp,0b00000011	; INT1,INT0: enable external interrupts
        out     EIMSK,temp

	;Set up the timer

        ldi     temp,0b00000010 ;WGM21: enable CTC mode (mode 2)
	sts	TCCR2A,temp

	ldi	temp,0b01111111	;set output compare value to 127 (1/2 0-255)
	sts	OCR2A,temp

	ldi	temp,0b00000101	;CS22:0: Set TIMER2 clock divisor
				;	to 32,768/128 = 256hz
	sts	TCCR2B,temp

	ldi	temp,0b00000010 ;OCIE2A: enable compare A interrupt on TIMER2
	sts	TIMSK2,temp

	ldi	temp,0b01100000	;EXCLK: enable external clock input
				;AS2: async operation of TIMER2 (32,768kHz)
	sts	ASSR,temp
	
	clr	disp		;reset LED display register

;Load the digit registers for 12:00

	ldi    halfsec,0b00000111	; preload to 7
	ldi	min1_l,0b00000001	; XX:X0
	ldi	min1_h,0b00000000
	ldi	 min10,0b00000100	; XX:0X
	ldi	 hr1_l,0b00000100	; X2:XX
	ldi	 hr1_h,0b00000000	; XX:XX
	ldi	  hr10,0b00000100	; 1X:XX

;Load TIME for 12:00

	ldi	temp,0b00000100		; 1X:XX
	sts	TIME,temp
	ldi	temp,0b00000100		; X2:XX
	sts	TIME+1,temp
	ldi	temp,0b00000100		; XX:0X
	sts	TIME+2,temp
	ldi     temp,0b00000001		; XX:X0
	sts	TIME+3,temp

	rcall	UDHV	;load HV5622 with initial values
	sbi	PORTD,7	;enable the tube display
	sei		;set global interrupt flag
	
loop:		;Main program loop
	sleep
	rjmp	loop

count:		;1/2 second overflow ISR
	inc	halfsec
	cpi	halfsec,0b10000000	;reached 128 half-seconds?
	brne	cycle			;no? display value and return
	ldi	halfsec,0b00001000	;yes? reset to 8
	rcall	display			;output to LED display
	rjmp	add1min			;and add 1 minute
cycle:
	rcall	display			;output to LED display
	reti

add1min:
	sbrc	min1_h,1	;last digit numeral 9?	
	rjmp	add10min	;carry and reset
	lsl	min1_l		;else add 1 minute
	rol	min1_h		;carry to second byte if needed
	sts	TIME+3,min1_l	;store low byte to TIME byte 4
	lds	temp,TIME+2	;read next byte of TIME byte 3
	andi	temp,0b11111100	;don't mess with next numeral
	or	temp,min1_h	;but add relevant info
	sts	TIME+2,temp	;store high byte to TIME byte 3
	rcall	UDHV		;output to tube display
	reti

add10min:
        ldi     min1_l,0b00000001	;reset single minutes to numeral 0
	sts	TIME+3,min1_l	;store low byte to TIME byte 4
	clr	min1_h		;reset high byte
	sbrc	min10,7		;ten minute digit numeral 5?
	rjmp	addhr		;carry and reset
	lsl	min10		;else add ten minutes
	sts	TIME+2,min10	;store to TIME byte 3
	rcall	UDHV		;output to tube display
	reti

addhr:
	ldi	min10,0b00000100;reset tens of minutes to numeral 0
	sts	TIME+2,min10	;store to TIME byte3
	sbrc	hr10,2		;is tens of hours set?
	rjmp	ten2twelve	;jump to aux counter
	sbrc	hr1_h,1		;single hours digit numeral 9?
	rjmp	noon		;advance to 12:00
	lsl	hr1_l		;else add 1 hour
	rol	hr1_h		;carry to second byte if needed
	sts	TIME+1,hr1_l	;store low byte to TIME byte 2
	lds	temp,TIME	;load TIME byte 1
	andi	temp,0b11111100	;don't mess with tens of hours state
	or	temp,hr1_h	;but add single hours high byte
	sts	TIME,temp	;store back to TIME byte 1
	rcall	UDHV		;output to tube display
	reti

ten2twelve:
	sbrc	hr1_l,2		;single hours digit numeral 2?
	rjmp	oneoclock	;roll over to 1:00
	lsl	hr1_l		;else add one hour
	sts	TIME+1,hr1_l	;store low byte to TIME byte 2
	rcall	UDHV		;output to tube display
	reti

oneoclock:
	ldi	hr1_l,0b00000010;reset single hours to numeral 1
	sts	TIME+1,hr1_l	;store low byte to TIME byte 2	
	clr	hr10		;blank tens of hours
	sts	TIME,hr10	;store to TIME byte 1
	rcall	UDHV		;output to tube display
	reti

noon:	;(or midnight)
	ldi	hr1_l,0b00000001;reset single hours to numeral 0
	sts	TIME+1,hr1_l	;store low byte to TIME byte 2
	clr	hr1_h		;reset high byte
	ldi	hr10,0b00000100	;set tens of hours to numeral 1
	sts	TIME,hr10	;store to TIME byte 1
	rcall	UDHV		;output to tube display
	reti

UDHV:		;Update high voltage shift register routine
		;Uncomment [LSB] to shift data counter-clockwise
		;Uncomment [MSB] to shift data clockwise
	lds	temp1,TIME+3	;get fourth byte of data [LSB]
;	lds	temp1,TIME	;get first byte of data	[MSB]
	ldi	temp,8		;load bit counter
rcall	UDHVloop		;shift out byte
	lds	temp1,TIME+2	;get third byte of data [LSB]
;	lds	temp1,TIME+1	;get second byte of data [MSB]
	ldi	temp,8		;bit counter
rcall	UDHVloop		;shift out byte
	lds	temp1,TIME+1	;get second byte of data [LSB]
;	lds	temp1,TIME+2	;get third byte of data [MSB]
	ldi	temp,8		;bit counter
rcall	UDHVloop		;shift out byte
	lds	temp1,TIME	;get first byte of data [LSB]
;	lds	temp1,TIME+3	;get fourth byte of data [MSB]
	ldi	temp,8		;bit counter
rcall	UDHVloop		;shift out byte
	cbi	HV,HVdat	
rcall	wait
	sbi	HV,HVle		;latch data
rcall	wait
	cbi	HV,HVle
	ret
	
UDHVloop:	;Shifts a byte to the HV5622
rcall	wait
	sbi	HV,HVclk
	cbi	HV,HVdat
	sbrc	temp1,0		;if LSB set then set data bit [LSB]
;	sbrc	temp1,7		;if MSB set then set data bit [MSB]
	sbi	HV,HVdat	
rcall	wait
	cbi	HV,HVclk	;transfer data to HV5622
	lsr	temp1		;next bit [LSB]
;	lsl	temp1		;next bit [MSB]
	dec	temp
	brne	UDHVloop
	ret

display:	;LED output routine
	mov	disp,halfsec
	ror	disp		;last bit used for half-seconds is hidden
	out	PORTC,disp	;output to LED display
	ret

button_m:	;Minute advance button
	cli			;disable interrupts
        ldi     halfsec,0b00000111         ;set half-seconds to zero
	rcall	wait
        sbis    PIND,0x02
	rjmp	button_m_finished
        rcall   wait
        sbis    PIND,0x02
        rjmp    button_m_finished
        rcall   wait
        sbis    PIND,0x02
        rjmp    button_m_finished
        rcall   wait
        sbis    PIND,0x02
        rjmp    button_m_finished
	rjmp	add1min	
button_m_finished:
        reti

button_h:	;Hour advance button
	cli			;disable interrupts
	rcall	wait		;Finely-tuned debouncing delay
        rcall   wait		;I was in a hurry and it worked
        rcall   wait		;Hardware is gone, I cannot perfect
        rcall   wait		;Surely there is a more elegant way.
        rcall   wait
        rcall   wait
        rcall   wait
        rcall   wait
        rcall   wait
	sbic	PIND,0x03
	rjmp	addhr
        reti

wait:		;Wait routine for shift register clock
	ldi	delay,delaynum
waiting:
	dec	delay
	brne	waiting
	ret


