; Verbose clock
; ATMEGA88, 168, 328
;
; Display is a 24-digit 14-segment LED, driven by a 64-bit serial shift register
; connected to SPI port, loading leftmost digit first, high bit of high byte unused,
; send order is a,...,p,DP
;
; Digit select lines (to power transistors):
; Digit 0 = PC0
; Digit 1 = PC1
; Digit 2 = PC2
; Digit 3 = PD5
; Digit 4 = PD6
; Digit 5 = PD7
;
; PC3 = SELECT
; PC4 = UP
; PC5 = DOWN

 .include "m328pdef.inc"

;
;        a
;  -------------
;  |\    |    /|
; f| \g h|  j/ |b
;  |  \  |  /  |
;  |   \ | /   |
; p------ ------k
;  |   / | \   |
;  |  /  |  \  |
; e| /n m|  l\ |c
;  |/    |    \|  
;  -------------
;        d             * Decimal Point


#define LED_COMMON_ANODE 1
;#define ATMEGA48OR88 1

.def	REG_ZERO = r2 ; register always zero; saves a lot of clr instructions
.def	INTERRUPT_SREG = r3 ; Storage for Status flags during interrupt
.def	KEY_REPEAT_DELAY = r4 ; countdown for key repeat
.def	REFRESH_PHASE = r5 ; display refresh phase from 0 to 5 (0,2,4,6,8,10)
.def	SCRATCH0 = r16
.def	SCRATCH1 = r17
.def	SCRATCH2 = r18
.def	SCRATCH3 = r19
.def	SCRATCH4 = r20
.def	FLAG1_REGISTER = r21
; r26, r27, r28, r29, r30, r31 are used as pointers
.equ	FLAG1_TIME_CHANGED = 0
.equ	FLAG1_TIME_UPDATE = 1
.equ	FLAG1_BUTTON_SELECT_PRESSED = 2
.equ	FLAG1_BUTTON_UP_PRESSED = 3
.equ	FLAG1_BUTTON_DOWN_PRESSED = 4
.equ	FLAG1_BUTTON_HELD = 5
.equ	FLAG1_MODE_SET_HOURS = 6
.equ	FLAG1_MODE_SET_MINUTES = 7
; FLAG2 register is GPIOR0
.equ	FLAG2_SERIAL_DATA_PENDING = 0
.equ	FLAG2_SERIAL_DISPLAY_MODE = 1
.equ	FLAG2_SERIAL_LEFT_JUSTIFIED = 2
.equ	FLAG2_MODE_DISPLAY_OFF = 3
.equ	FLAG2_DISPLAY_OFF = 4
.equ	FLAG2_INTS_OK_1 = 5

; Beginning of code: interrupt vectors
; for the ATMEGA48/88/168/328P
.cseg
#ifdef	ATMEGA48OR88
	rjmp	V_RESET
	rjmp	V_INT0
	rjmp	V_INT1
	rjmp	V_PCINT0
	rjmp	V_PCINT1
	rjmp	V_PCINT2
	rjmp	V_WDT
	rjmp	V_TIM2_COMPA
	rjmp	V_TIM2_COMPB
	rjmp	V_TIM2_OVF
	rjmp	V_TIM1_CAPT
	rjmp	V_TIM1_COMPA
	rjmp	V_TIM1_COMPB
	rjmp	V_TIM1_OVF
	rjmp	V_TIM0_COMPA
	rjmp	V_TIM0_COMPB
	rjmp	V_TIM0_OVF
	rjmp	V_SPI_STC
	rjmp	V_USART0_RXC
	rjmp	V_USART0_UDRE
	rjmp	V_USART0_TXC
	rjmp	V_ADC
	rjmp	V_EE_READY
	rjmp	V_ANALOG_COMP
	rjmp	V_TWI
	rjmp	V_SPM_READY
#else
	jmp	V_RESET
	jmp	V_INT0
	jmp	V_INT1
	jmp	V_PCINT0
	jmp	V_PCINT1
	jmp	V_PCINT2
	jmp	V_WDT
	jmp	V_TIM2_COMPA
	jmp	V_TIM2_COMPB
	jmp	V_TIM2_OVF
	jmp	V_TIM1_CAPT
	jmp	V_TIM1_COMPA
	jmp	V_TIM1_COMPB
	jmp	V_TIM1_OVF
	jmp	V_TIM0_COMPA
	jmp	V_TIM0_COMPB
	jmp	V_TIM0_OVF
	jmp	V_SPI_STC
	jmp	V_USART0_RXC
	jmp	V_USART0_UDRE
	jmp	V_USART0_TXC
	jmp	V_ADC
	jmp	V_EE_READY
	jmp	V_ANALOG_COMP
	jmp	V_TWI
	jmp	V_SPM_READY
#endif
;
; !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
;
FONT_START:
FONT_SYMBOL_1:
;.db 0b0abcdefg,0bhjklmnpD ; Format example
 .db 0b00000000,0b00000000 ; space = 0
 .db 0b00100000,0b00000001 ; ! 
 .db 0b00100000,0b10000000 ; "
 .db 0b01001110,0b10001000 ; #
 .db 0b01011010,0b10101010 ; $ = 4
 .db 0b00010010,0b01000100 ; % 
 .db 0b01001001,0b01010100 ; &
 .db 0b00000000,0b01000000 ; '
 .db 0b00000000,0b01010000 ; ( = 8
 .db 0b00000001,0b00000100 ; )
 .db 0b00000001,0b11111110 ; *
 .db 0b00000000,0b10101010 ; +
 .db 0b00000000,0b00000100 ; , = 12
 .db 0b00000000,0b00100010 ; -
 .db 0b00000000,0b00000001 ; .
 .db 0b00000000,0b01000100 ; /

FONT_NUMERIC:
;.db 0b0abcdefg,0bhjklmnpD ; Format example
 .db 0b01111110,0b01000100 ; 0 = 16
 .db 0b00110000,0b00000000 ; 1
 .db 0b01101100,0b00100010 ; 2
 .db 0b01111000,0b00100010 ; 3
 .db 0b00110010,0b00100010 ; 4 = 20
 .db 0b01011010,0b00100010 ; 5
 .db 0b01011110,0b00100010 ; 6
 .db 0b01110000,0b00000000 ; 7
 .db 0b01111110,0b00100010 ; 8 = 24
 .db 0b01111010,0b00100010 ; 9

FONT_SYMBOL_2:
;.db 0b0abcdefg,0bhjklmnpD ; Format example
 .db 0b00000000,0b10001000 ; :
 .db 0b00000000,0b10000100 ; ;
 .db 0b00000000,0b01010000 ; < = 28
 .db 0b00001000,0b00100010 ; =
 .db 0b00000001,0b00000100 ; >
 .db 0b01100000,0b00101000 ; ?
 .db 0b01111110,0b00101000 ; @ = 32

FONT_ALPHABET:
;.db 0b0abcdefg,0bhjklmnpD ; Format example
 .db 0b01110110,0b00100010 ; A
 .db 0b01111000,0b10101000 ; B
 .db 0b01001110,0b00000000 ; C
 .db 0b01111000,0b10001000 ; D = 36
 .db 0b01001110,0b00100010 ; E
 .db 0b01000110,0b00100010 ; F
 .db 0b01001110,0b00110000 ; G
 .db 0b00110110,0b00100010 ; H = 40
 .db 0b01001000,0b10001000 ; I
 .db 0b00111100,0b00000000 ; J
 .db 0b00000110,0b01010010 ; K
 .db 0b00001110,0b00000000 ; L = 44
 .db 0b00110111,0b01000000 ; M
 .db 0b00110111,0b00010000 ; N
 .db 0b01111110,0b00000000 ; O
 .db 0b01100110,0b00100010 ; P = 48
 .db 0b01111110,0b00010000 ; Q
 .db 0b01100110,0b00110010 ; R
 .db 0b01011010,0b00100010 ; S
 .db 0b01000000,0b10001000 ; T = 52
 .db 0b00111110,0b00000000 ; U
 .db 0b00000110,0b01000100 ; V
 .db 0b00110110,0b00010100 ; W
 .db 0b00000001,0b01010100 ; X = 56
 .db 0b00000001,0b01001000 ; Y
 .db 0b01001000,0b01000100 ; Z

; .db 0b00110001,0b00010000 ; V (backward)

FONT_SYMBOL_3:
;.db 0b0abcdefg,0bhjklmnpD ; Format example
 .db 0b01001110,0b00000000 ; [
 .db 0b00000001,0b00010000 ; \ = 60
 .db 0b01111000,0b00000000 ; ]
 .db 0b00000000,0b00010100 ; ^
 .db 0b00001000,0b00000000 ; _ 
 .db 0b00000001,0b00000000 ; ` = 64

FONT_SYMBOL_4:
;.db 0b0abcdefg,0bhjklmnpD ; Format example
 .db 0b00000000,0b01010010 ; {
 .db 0b00000000,0b10001000 ; |
 .db 0b00000001,0b00100100 ; }
 .db 0b00000001,0b01000000 ; ~ = 68
 .db 0b01110110,0b00100011 ; A.
 .db 0b01100110,0b00100011 ; P.
 .db 0b00110111,0b01000001 ; M.

NUMBER_TEXT_MAP: ; offsets, 1-20
 .db 0,3,6,11,15,19,22,27,32,36,39,45,51,55,59,19,22,62,32,66,72,78,83,88,  92,  96,  98,  100
;    1 2 3 4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 30 40 50 TEEN PAST A.M. P.M. HALF
 .db 104,     111,    118,       127,         138,            152,          164
;    EXACTLY  QUARTER SET HOURS  SET MINUTES  SET BRIGHTNESS  TIME NOT SET  DISPLAY OFF

; Each line must be an even number of bytes, otherwise you get an extra 0 byte
TEXT_STRINGS:
 .db 47,46,37+128, 52,55,47+128, 52,40,50,37,37+128, 38,47,53,50+128, 38,41,54,37+128, 51,41,56+128
;    ONE           TWO           THREE               FOUR             FIVE             SIX
 .db 51,37,54,37,46+128, 37,41,39,40,52+128, 46,41,46,37+128
;    SEVEN                EIGHT              NINE
 .db 52,37,46+128, 37,44,37,54,37,46+128, 52,55,37,44,54,37+128, 52,40,41,50+128, 38,47,53,50+128, 38,41,38+128
;    TEN           ELEVEN                 TWELVE                 THIR             FOUR             FIF
 .db 37,41,39,40+128, 52,55,37,46,52,57+128, 52,40,41,50,52,57+128, 38,47,50,52,57+128, 38,41,38,52,57+128
;    EIGH             TWENTY                 THIRTY                 FORTY               FIFTY
 .db 52,37,37,46+128, 48,33,51,52+128, 69,71+128, 70,71+128, 40,33,44,38+128
;    TEEN             PAST             A.M.       P.M.       HALF
 .db 37,56,33,35,52,44,57+128, 49,53,33,50,52,37,50+128
;    EXACTLY                   QUARTER
 .db 51,37,52,00,40,47,53,50,51+128, 51,37,52,00,45,41,46,53,52,37,51+128 
;    SET HOURS                       SET MINUTES
 .db 51,37,52,00,34,50,41,39,40,52,46,37,51,51+128, 52,41,45,37,00,46,47,52,00,51,37,52+128
;    SET BRIGHTNESS                                 TIME NOT SET
 .db 36,41,51,48,44,33,57,0,47,38,38+128
;    DISPLAY OFF

; A=33 B=34 C=35 D=36 E=37 F=38 G=39 H=40 I=41 J=42 K=43 L=44 M=45
; N=46 O=47 P=48 Q=49 R=50 S=51 T=52 U=53 V=54 W=55 X=56 Y=57 Z=58

.equ NTM_OFFSET_TEEN = 24 ; these are 1-based not 0-based
.equ NTM_OFFSET_PAST = 25
.equ NTM_OFFSET_AM = 26
.equ NTM_OFFSET_PM = 27
.equ NTM_OFFSET_HALF = 28
.equ NTM_OFFSET_EXACTLY = 29
.equ NTM_OFFSET_QUARTER = 30
.equ NTM_OFFSET_SET_HOURS = 31
.equ NTM_OFFSET_SET_MINUTES = 32
.equ NTM_OFFSET_SET_BRIGHTNESS = 33
.equ NTM_OFFSET_TIME_NOT_SET = 34
.equ NTM_OFFSET_DISPLAY_OFF = 35
.equ FA_OFFSET_DASH = 13
.equ FA_OFFSET_BLANK = 00

.equ BAUD_DIVISOR = 51 ; 9600 baud at 8 MHz
.equ BRIGHTNESS_DEFAULT = 250

; These are the hardware initialization values which will be written into I/O registers
; at startup. Format is address, value. All registers which are accessible by in/out
; must have 0x20 added to provide the correct data space address.
CONFIGURATION_TABLE:
; Stack pointer
	.db	SPH+0x20,high(RAMEND)
	.db	SPL+0x20,low(RAMEND)
; Registers
	.db	GPIOR0+0x20,0 ; FLAGS2
	.db	GPIOR1+0x20,low(SERIAL_INPUT_BUFFER) ; serial read pointer
	.db	GPIOR2+0x20,low(SERIAL_INPUT_BUFFER) ; serial write pointer
	.db	OCR0A+0x20,BRIGHTNESS_DEFAULT ; default brightness value
; Timer0
	.db	TIMSK0,(1<<TOIE0)|(1<<OCIE0A) ; enable overflow and compare interrupts
	.db	TCCR0B+0x20,(1<<CS01)|(1<<CS00) ; Clock / 64, enable periodic interrupt
; SPI port
	.db	DDRB+0x20,(1<<PORTB2)|(1<<PORTB3)|(1<<PORTB5) ; SS, SCK, and MOSI to output
	.db	SPCR+0x20,(1<<SPE)|(1<<MSTR) ; Enable SPI, Master, set clock rate fck/4
	.db	SPDR+20,0 ; send a byte so SPIF flag is set
; Setup UART for 8,N,1, async, RXC interrupt
	.db	UBRR0H,high(BAUD_DIVISOR)
	.db	UBRR0L,low(BAUD_DIVISOR)
	.db	UCSR0B,(1<<RXCIE0)+(1<<RXEN0)+(1<<TXEN0) ; enable TX, RX, and RX interrupt
; Data direction registers
	.db	DDRC+0x20,(1<<PORTC0)|(1<<PORTC1)|(1<<PORTC2)
	.db	DDRD+0x20,(1<<PORTD1)|(1<<PORTD5)|(1<<PORTD6)|(1<<PORTD7)
; Digit select outputs
#ifdef	LED_COMMON_ANODE
	.db	PORTC+0x20,(1<<PORTC0)|(1<<PORTC1)|(1<<PORTC2)|(1<<PORTC3)|(1<<PORTC4)|(1<<PORTC5)
	.db	PORTD+0x20,(1<<PORTD2)|(1<<PORTD3)|(1<<PORTD4)|(1<<PORTD5)|(1<<PORTD6)|(1<<PORTD7)
#else
	.db	PORTC+0x20,(1<<PORTC3)|(1<<PORTC4)|(1<<PORTC5)
	.db	PORTD+0x20,(1<<PORTD2)|(1<<PORTD3)|(1<<PORTD4)
#endif
END_CONFIGURATION_TABLE:

V_INT0:
V_INT1:
V_PCINT0:
V_PCINT1:
V_PCINT2:
V_WDT:
V_TIM2_COMPA:
V_TIM2_COMPB:
V_TIM2_OVF:
V_TIM1_CAPT:
V_TIM1_COMPA:
V_TIM1_COMPB:
V_TIM1_OVF:
V_TIM0_COMPB:
V_SPI_STC:
V_USART0_UDRE:
V_USART0_TXC:
V_ADC:
V_EE_READY:
V_ANALOG_COMP:
V_TWI:
V_SPM_READY:
 
V_RESET:
	wdr	; so hardware configuration does not watchdog reset
	ldi	SCRATCH0,(1<<WDCE)+(1<<WDE) ; enable watchdog timer
	ldi	SCRATCH1,(1<<WDP1)+(1<<WDP2)+(1<<WDE) ; for about one second
	sts	WDTCSR,SCRATCH0
	sts	WDTCSR,SCRATCH1
;
	clr	REG_ZERO
	clr	FLAG1_REGISTER
	clr	REFRESH_PHASE ; start refresh phase at 0
; Configure the hardware from the table
	ldi	ZH,high(CONFIGURATION_TABLE<<1)
	ldi	ZL,low(CONFIGURATION_TABLE<<1)
	clr	YH
CONFIGURE_LOOP:
	lpm	YL,Z+ ; address
	lpm	SCRATCH0,Z+ ; data
	st	Y,SCRATCH0
	cpi	ZL,low(END_CONFIGURATION_TABLE<<1)
	brlo	CONFIGURE_LOOP
; Clear memory
	ldi	ZH,high(SRAM_START)
	ldi	ZL,low(SRAM_START)
CLRMEMLOOP:
	st	Z+,REG_ZERO
	cpi	ZL,low(SRAM_START+128)
	brlo	CLRMEMLOOP
;
; Put up the TIME NOT SET message
;
	ldi	SCRATCH3,NTM_OFFSET_TIME_NOT_SET
	rcall	WRITE_WORD_INIT
	rcall	UPDATE_DISPLAY_CENTERED
;
	sei ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; INTERRUPTS ENABLED
;
; Main program begins here
MAIN_LOOP:
	ldi	SCRATCH0,(1<<SE)
	out	SMCR,SCRATCH0
	sleep ; wait for an interrupt
; Time update if enabled and minute changed
	sbrs	FLAG1_REGISTER,FLAG1_TIME_UPDATE
	rjmp	MAIN_NO_TIME_UPDATE
	sbrc	FLAG1_REGISTER,FLAG1_TIME_CHANGED	
	rcall	UPDATE_TIME_DISPLAY
	cbr	FLAG1_REGISTER,1<<FLAG1_TIME_CHANGED
MAIN_NO_TIME_UPDATE:
; Process buttons if any pressed
	mov	SCRATCH0,FLAG1_REGISTER
	andi	SCRATCH0,(1<<FLAG1_BUTTON_SELECT_PRESSED)|(1<<FLAG1_BUTTON_UP_PRESSED)|(1<<FLAG1_BUTTON_DOWN_PRESSED)
	brne	MAIN_BUTTONS_PRESSED
MAIN_BUTTONS_DONE:
; Process serial characters if any received
	sbic	GPIOR0,FLAG2_SERIAL_DATA_PENDING
	rcall	MAIN_PROCESS_SERIAL
;
	rjmp	MAIN_LOOP	
;
MAIN_BUTTONS_PRESSED:
	cbi	GPIOR0,FLAG2_DISPLAY_OFF ; unblank display
	ldi	ZH,high(TIME_SECONDS)
	ldi	ZL,low(TIME_SECONDS) 
	ldi	SCRATCH0,10 ; mode reset timeout
	std	Z+3,SCRATCH0 ; start the reset clock (TIMEOUT_SETMODE)
	ldd	SCRATCH0,Z+2 ; hours
	ldd	SCRATCH1,Z+1 ; minutes
	sbrs	FLAG1_REGISTER,FLAG1_BUTTON_UP_PRESSED
	rjmp	MAIN_NOT_UP_BUTTON
;
; up button
	sbrs	FLAG1_REGISTER,FLAG1_MODE_SET_HOURS
	rjmp	MAIN_NOT_HOURS_UP
	inc	SCRATCH0
	rjmp	MAIN_TIME_SETTING_CHANGED
MAIN_NOT_HOURS_UP:
;
	sbrs	FLAG1_REGISTER,FLAG1_MODE_SET_MINUTES
	rjmp	MAIN_NOT_MINUTES_UP
	inc	SCRATCH1
	std	Z+0,REG_ZERO ; clear seconds on minutes change
	rjmp	MAIN_TIME_SETTING_CHANGED
MAIN_NOT_MINUTES_UP:
;
	sbis	GPIOR0,FLAG2_MODE_DISPLAY_OFF
	rjmp	MAIN_UP_NOT_DISPLAY_OFF
MAIN_UP_DISPLAY_OFF:
	sbi	GPIOR0,FLAG2_DISPLAY_OFF
	rjmp	MAIN_BRIGHTNESS_SETTING_CHANGED
MAIN_UP_NOT_DISPLAY_OFF:
;
; not time setting mode, so change brightness
	in	SCRATCH2,OCR0A
	inc	SCRATCH2
	brne	MAIN_BRUP_SKIP
	dec	SCRATCH2
MAIN_BRUP_SKIP:
	out	OCR0A,SCRATCH2
	rjmp	MAIN_BRIGHTNESS_SETTING_CHANGED
;
MAIN_NOT_UP_BUTTON:
	sbrs	FLAG1_REGISTER,FLAG1_BUTTON_DOWN_PRESSED
	rjmp	MAIN_NOT_DOWN_BUTTON
;
; down button
	sbrs	FLAG1_REGISTER,FLAG1_MODE_SET_HOURS
	rjmp	MAIN_NOT_HOURS_DOWN
	dec	SCRATCH0
	rjmp	MAIN_TIME_SETTING_CHANGED
MAIN_NOT_HOURS_DOWN:
;
	sbrs	FLAG1_REGISTER,FLAG1_MODE_SET_MINUTES
	rjmp	MAIN_NOT_MINUTES_DOWN
	dec	SCRATCH1
	std	Z+0,REG_ZERO ; clear seconds on minutes change
	rjmp	MAIN_TIME_SETTING_CHANGED
MAIN_NOT_MINUTES_DOWN:
;
	sbic	GPIOR0,FLAG2_MODE_DISPLAY_OFF
	rjmp	MAIN_UP_DISPLAY_OFF
;
; not time setting mode, so change brightness
	in	SCRATCH2,OCR0A
	subi	SCRATCH2,1
	brcc	MAIN_BRDN_SKIP
	inc	SCRATCH2
MAIN_BRDN_SKIP:
	out	OCR0A,SCRATCH2
	rjmp	MAIN_BRIGHTNESS_SETTING_CHANGED
MAIN_NOT_DOWN_BUTTON:
;
; mode selection
	sbrs	FLAG1_REGISTER,FLAG1_BUTTON_SELECT_PRESSED
	rjmp	MAIN_NOT_SELECT_BUTTON
	cbr	FLAG1_REGISTER,(1<<FLAG1_TIME_UPDATE)|(1<<FLAG1_BUTTON_SELECT_PRESSED)
;
	sbrs	FLAG1_REGISTER,FLAG1_MODE_SET_HOURS
	rjmp	MAIN_NOT_SELECT_HOURS
	cbr	FLAG1_REGISTER,1<<FLAG1_MODE_SET_HOURS
	sbr	FLAG1_REGISTER,1<<FLAG1_MODE_SET_MINUTES
	ldi	SCRATCH3,NTM_OFFSET_SET_MINUTES
	rjmp	MAIN_SELECT_WRITE_AND_OUT
MAIN_NOT_SELECT_HOURS:
;
	sbrs	FLAG1_REGISTER,FLAG1_MODE_SET_MINUTES
	rjmp	MAIN_NOT_SELECT_MINUTES
	cbr	FLAG1_REGISTER,1<<FLAG1_MODE_SET_MINUTES
	sbi	GPIOR0,FLAG2_MODE_DISPLAY_OFF
	ldi	SCRATCH3,NTM_OFFSET_DISPLAY_OFF
	rjmp	MAIN_SELECT_WRITE_AND_OUT
MAIN_NOT_SELECT_MINUTES:
;
	sbis	GPIOR0,FLAG2_MODE_DISPLAY_OFF
	rjmp	MAIN_NOT_SELECT_DISPLAY_OFF
	cbi	GPIOR0,FLAG2_MODE_DISPLAY_OFF
	ldi	SCRATCH3,NTM_OFFSET_SET_BRIGHTNESS
	rjmp	MAIN_SELECT_WRITE_AND_OUT
MAIN_NOT_SELECT_DISPLAY_OFF:
;
	sbr	FLAG1_REGISTER,1<<FLAG1_MODE_SET_HOURS
	ldi	SCRATCH3,NTM_OFFSET_SET_HOURS
MAIN_SELECT_WRITE_AND_OUT:
	rcall	WRITE_WORD_INIT
	rcall	UPDATE_DISPLAY_CENTERED
MAIN_NOT_SELECT_BUTTON:
	rjmp	MAIN_BUTTONS_DONE

MAIN_TIME_SETTING_CHANGED:
	cpi	SCRATCH0,0xfe ; hours under?
	brlo	MAIN_NOT_HOURS_UNDER
	ldi	SCRATCH0,23
MAIN_NOT_HOURS_UNDER:
	cpi	SCRATCH0,24 ; hours over?
	brlo	MAIN_NOT_HOURS_OVER
	ldi	SCRATCH0,0 ; clear to zero
MAIN_NOT_HOURS_OVER:
	cpi	SCRATCH1,0xfe ; minutes under?
	brlo	MAIN_NOT_MINUTES_UNDER
	ldi	SCRATCH1,59 ; wrap to 59
MAIN_NOT_MINUTES_UNDER:
	cpi	SCRATCH1,60 ; minutes over?
	brlo	MAIN_NOT_MINUTES_OVER
	ldi	SCRATCH1,0 ; clear to zero
MAIN_NOT_MINUTES_OVER:
	std	Z+2,SCRATCH0 ; hours
	std	Z+1,SCRATCH1 ; minutes
MAIN_BRIGHTNESS_SETTING_CHANGED:
	cbr	FLAG1_REGISTER,(1<<FLAG1_BUTTON_UP_PRESSED)|(1<<FLAG1_BUTTON_DOWN_PRESSED)
	sbr	FLAG1_REGISTER,(1<<FLAG1_TIME_UPDATE)|(1<<FLAG1_TIME_CHANGED) ; cause time update
	rjmp	MAIN_BUTTONS_DONE
;
MPS_OUT:
	ret


;
MAIN_PROCESS_SERIAL:
	cbi	GPIOR0,FLAG2_SERIAL_DATA_PENDING
	ldi	ZH,high(SERIAL_INPUT_BUFFER)
	in	ZL,GPIOR1 ; next character pointer
	in	SCRATCH4,GPIOR2 ; write pointer, one past last character
	sub	SCRATCH4,ZL
	andi	SCRATCH4,0b1111 ; SCRATCH4 = number of characters in buffer
;
MPS_LOOP:
	subi	SCRATCH4,1 ; at least one character left?
	brcs	MPS_OUT ; quit if not
	rcall	MPS_GET_NEXT_CHAR ; get character
;
	cpi	SCRATCH0,27 ; ESC
	breq	MPS_ESCAPE
;
	cpi	SCRATCH0,13 ; CR = update display
	breq	MPS_CR_UPDATE
;
	cpi	SCRATCH0,'~'+1
	brsh	MPS_SAVE_POS_CONT
	cpi	SCRATCH0,'z'+1
	brsh	MPS_SYM4
	cpi	SCRATCH0,'a'
	brsh	MPS_LOWERCASE_LETTER
	cpi	SCRATCH0,' '
	brsh	MPS_UPPERCASE_OR_SYM
	rjmp	MPS_SAVE_POS_CONT
;
MPS_SYM4:
	subi	SCRATCH0,'{'-65
	rjmp	MPS_OUTCHAR
;
MPS_LOWERCASE_LETTER:
	subi	SCRATCH0,('a'-'A')+32
	rjmp	MPS_OUTCHAR
;
MPS_UPPERCASE_OR_SYM:
	subi	SCRATCH0,32
;	rjmp	MPS_OUTCHAR
;
MPS_OUTCHAR:
	sbic	GPIOR0,FLAG2_SERIAL_DISPLAY_MODE
	rjmp	MPS_OUTCHAR_READY
	sbi	GPIOR0,FLAG2_SERIAL_DISPLAY_MODE
	cbr	FLAG1_REGISTER,1<<FLAG1_TIME_UPDATE
	ldi	XH,high(TEXT_BUFFER)
	ldi	XL,low(TEXT_BUFFER)
MPS_OUTCHAR_READY:
	cpi	XL,low(TEXT_BUFFER)+24 ; same page assumption
	brsh	MPS_SAVE_POS_CONT ; do not overrun buffer
	st	X+,SCRATCH0
;	rjmp	MPS_SAVE_POS_CONT

MPS_SAVE_POS_CONT:
	out	GPIOR1,ZL ; next character pointer
	rjmp	MPS_LOOP
;
MPS_CR_UPDATE:
	out	GPIOR1,ZL ; next character pointer
	sbic	GPIOR0,FLAG2_SERIAL_LEFT_JUSTIFIED
	rcall	UPDATE_DISPLAY_LEFT_JUSTIFIED
	sbis	GPIOR0,FLAG2_SERIAL_LEFT_JUSTIFIED
	rcall	UPDATE_DISPLAY_CENTERED
	rjmp	MAIN_PROCESS_SERIAL ; since registers have been trashed
;
; Escape code handlers are placed before and after the dispatcher
; so that relative branches will reach.
;
MPS_ESCAPE_A:
	sbr	FLAG1_REGISTER,(1<<FLAG1_TIME_UPDATE)|(1<<FLAG1_TIME_CHANGED) ; cause time update
	rjmp	MPS_SAVE_POS_CONT
;
MPS_ESCAPE_B:
	sbi	GPIOR0,FLAG2_SERIAL_LEFT_JUSTIFIED
	rjmp	MPS_SAVE_POS_CONT
;
MPS_ESCAPE_C:
	cbi	GPIOR0,FLAG2_SERIAL_LEFT_JUSTIFIED
	rjmp	MPS_SAVE_POS_CONT
;
MPS_ESCAPE:
	subi	SCRATCH4,1 ; another character available?
	brcs	MPS_OUT ; if not, wait for the next one
	rcall	MPS_GET_NEXT_CHAR ; get the escape code
	cpi	SCRATCH0,'A'
	brlo	MPS_SAVE_POS_CONT ; invalid escape code
	breq	MPS_ESCAPE_A ; esc A - return to clock mode
	cpi	SCRATCH0,'C'
	brlo	MPS_ESCAPE_B ; esc B - disable centering
	breq	MPS_ESCAPE_C ; esc C - enable centering
	cpi	SCRATCH0,'E'
	brlo	MPS_ESCAPE_D ; esc D - read clock
	breq	MPS_ESCAPE_E ; esc E - set clock
	cpi	SCRATCH0,'G'
	brlo	MPS_ESCAPE_F ; esc F - read blanking and brightness
	breq	MPS_ESCAPE_G ; esc G - set blanking and brightness
	cpi	SCRATCH0,'H'
	breq	MPS_ESCAPE_H ; esc H - ping/version
	cpi	SCRATCH0,'a' ; esc a-x custom characters
	brlo	MPS_NOT_LC_AX
	cpi	SCRATCH0,'x'+1
	brlo	MPS_ESCAPE_LC_AX
MPS_NOT_LC_AX:
	rjmp	MPS_SAVE_POS_CONT ; invalid escape code
;
MPS_ESCAPE_D:
	ldi	YH,high(TIME_SECONDS)
	ldi	YL,low(TIME_SECONDS)
	cli ; to avoid race condition
	ld	SCRATCH2,Y+
	push	SCRATCH2
	ld	SCRATCH2,Y+
	push	SCRATCH2
	ld	SCRATCH2,Y+ ; get them all at once to avoid race with time increment
	sei ; interrupts enabled again
	rcall	MPS_PRINT_NUMBER_2DIGIT
	pop	SCRATCH2
	rcall	MPS_PRINT_NUMBER_2DIGIT
	pop	SCRATCH2
	rcall	MPS_PRINT_NUMBER_2DIGIT
	rjmp	MPS_SAVE_POS_CONT
;
MPS_ESCAPE_E:
	subi	SCRATCH4,6 ; need six character value
	brcs	MPS_OUT2 ; exit if not available
	rcall	MPS_PARSE_TWO_DIGITS
	push	SCRATCH1
	rcall	MPS_PARSE_TWO_DIGITS
	push	SCRATCH1
	rcall	MPS_PARSE_TWO_DIGITS
	ldi	YH,high(TIME_SECONDS)
	ldi	YL,low(TIME_SECONDS)
	cli ; to avoid race condition
	st	Y+,SCRATCH1
	pop	SCRATCH1
	st	Y+,SCRATCH1
	pop	SCRATCH1
	st	Y+,SCRATCH1
	sei ; interrupts enabled again
	sbr	FLAG1_REGISTER,1<<FLAG1_TIME_CHANGED
	rjmp	MPS_SAVE_POS_CONT
;
MPS_ESCAPE_F:
	ldi	SCRATCH1,'0'
	sbis	GPIOR0,FLAG2_DISPLAY_OFF
	inc	SCRATCH1 ; send '0' if display off, '1' if display on
	rcall	MPS_SEND_CHARACTER
	in	SCRATCH2,OCR0A
	rcall	MPS_PRINT_NUMBER_3DIGIT
	rjmp	MPS_SAVE_POS_CONT
;
MPS_ESCAPE_G:
	subi	SCRATCH4,4 ; need four character value (0-1 blanking plus 3 digit brightness)
	brcs	MPS_OUT2 ; exit if not available
	rcall	MPS_GET_NEXT_CHAR ; get 0-1 blanking value
	sbrs	SCRATCH0,0 ; low bit = 0 for ascii "0"
	sbi	GPIOR0,FLAG2_DISPLAY_OFF ; display off
	sbrc	SCRATCH0,0 ; low bit = 1 for ascii "1"
	cbi	GPIOR0,FLAG2_DISPLAY_OFF ; display on
;
	clr	SCRATCH1
	rcall	MPS_GET_NEXT_CHAR
	subi	SCRATCH0,'0'
MPS_EG_L1:
	subi	SCRATCH0,1
	brcs	MPS_EG_L1O
	subi	SCRATCH1,-100
	rjmp	MPS_EG_L1
;
MPS_EG_L1O:
	rcall	MPS_PARSE_TWO_DIGITS_NO_CLEAR
	out	OCR0A,SCRATCH1
	rjmp	MPS_SAVE_POS_CONT
;
MPS_ESCAPE_H:
	ldi	SCRATCH1,'A' ; current version is A, new functionality would increase this
	rcall	MPS_SEND_CHARACTER
	rjmp	MPS_SAVE_POS_CONT
;
MPS_ESCAPE_LC_AX:
	subi	SCRATCH4,2 ; need two character value
	brcs	MPS_OUT2 ; exit if not available
	ldi	YH,high(DISPLAY_BUFFER)
	ldi	YL,low(DISPLAY_BUFFER)
	subi	SCRATCH0,'a'
	lsl	SCRATCH0 ; double for two byte characters
	add	YL,SCRATCH0 ; same page assumption
	rcall	MPS_GET_NEXT_CHAR
#ifdef	LED_COMMON_ANODE
	com	SCRATCH0
#endif
	st	Y+,SCRATCH0
	rcall	MPS_GET_NEXT_CHAR
#ifdef	LED_COMMON_ANODE
	com	SCRATCH0
#endif
	st	Y+,SCRATCH0
	rjmp	MPS_SAVE_POS_CONT
;
MPS_GET_NEXT_CHAR: ; put here so branches reach MPS_OUT2
	ld	SCRATCH0,Z+ ; get first pending character
	cpi	ZL,low(SERIAL_INPUT_BUFFER+16)
	brlo	MAIN_NOT_WRAP_1
	ldi	ZL,low(SERIAL_INPUT_BUFFER)
MAIN_NOT_WRAP_1:
MPS_OUT2:
	ret
;
MPS_PRINT_NUMBER_3DIGIT: ; SCRATCH2
	ldi	SCRATCH1,'0'
MPS_PN3_LOOP:
	subi	SCRATCH2,100
	brcs	MPS_PN3_OUT
	inc	SCRATCH1
	rjmp	MPS_PN3_LOOP
MPS_PN3_OUT:
	subi	SCRATCH2,-100
	rcall	MPS_SEND_CHARACTER
;
MPS_PRINT_NUMBER_2DIGIT:
	ldi	SCRATCH1,'0'
MPS_PN2_LOOP:
	subi	SCRATCH2,10
	brcs	MPS_PN2_OUT
	inc	SCRATCH1
	rjmp	MPS_PN2_LOOP
MPS_PN2_OUT:
	subi	SCRATCH2,(-10)-'0'
	rcall	MPS_SEND_CHARACTER
;
MPS_PN1_OUT:
	mov	SCRATCH1,SCRATCH2
;	rcall	MPS_SEND_CHARACTER
;	ret
;	
MPS_SEND_CHARACTER:
	lds	SCRATCH3,UCSR0A
	sbrs	SCRATCH3,UDRE0
	rjmp	MPS_SEND_CHARACTER
	sts	UDR0,SCRATCH1
	ret
;
MPS_PARSE_TWO_DIGITS:
	clr	SCRATCH1
MPS_PARSE_TWO_DIGITS_NO_CLEAR:
	rcall	MPS_GET_NEXT_CHAR
	subi	SCRATCH0,'0'
	lsl	SCRATCH0
	add	SCRATCH1,SCRATCH0
	lsl	SCRATCH0
	lsl	SCRATCH0
	add	SCRATCH1,SCRATCH0
	rcall	MPS_GET_NEXT_CHAR
	subi	SCRATCH0,'0'
	add	SCRATCH1,SCRATCH0
	ret


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Write the time to the display
; minutes-past-hours-minutes-ampm-exactly
UPDATE_TIME_DISPLAY:
	ldi	XH,high(TEXT_BUFFER)
	ldi	XL,low(TEXT_BUFFER)
	ldi	SCRATCH4,FA_OFFSET_BLANK ; SCRATCH4 is not used in WRITE_WORD
	lds	SCRATCH0,TIME_MINUTES
;
	cpi	SCRATCH0,0
	breq	WTM_HOUR
;
	cpi	SCRATCH0,15
	brne	WTM_NOT_QUARTER
	ldi	SCRATCH3,NTM_OFFSET_QUARTER
	rcall	WRITE_WORD
	rjmp	WTM_PAST
;
WTM_NOT_QUARTER:
	cpi	SCRATCH0,30
	brne	WTM_NOT_HALF
	ldi	SCRATCH3,NTM_OFFSET_HALF
	rcall	WRITE_WORD
	rjmp	WTM_PAST
;
WTM_NOT_HALF:
	cpi	SCRATCH0,11
	brsh	WTM_HOUR
	rcall	WRITE_NUMBER ; minutes
WTM_PAST:
	st	X+,SCRATCH4 ; space
	ldi	SCRATCH3,NTM_OFFSET_PAST
	rcall	WRITE_WORD
	st	X+,SCRATCH4 ; space
;
WTM_HOUR:
	lds	SCRATCH0,TIME_HOURS
	cpi	SCRATCH0,13
	brlo	WTM_HOUR_BELOW_13
	subi	SCRATCH0,12
WTM_HOUR_BELOW_13:
	cpi	SCRATCH0,0
	brne	WTM_NOT_MIDNIGHT
	ldi	SCRATCH0,12
WTM_NOT_MIDNIGHT:
	rcall	WRITE_NUMBER ; hour
	st	X+,SCRATCH4 ; space
;
	lds	SCRATCH0,TIME_MINUTES
	cpi	SCRATCH0,11
	brlo	WTM_SKIP_MINUTES
	cpi	SCRATCH0,15
	breq	WTM_SKIP_MINUTES
	cpi	SCRATCH0,30
	breq	WTM_SKIP_MINUTES
;
	rcall	WRITE_NUMBER ; minutes
	st	X+,SCRATCH4 ; space
;
WTM_SKIP_MINUTES:
	lds	SCRATCH0,TIME_HOURS
	ldi	SCRATCH3,NTM_OFFSET_PM
	cpi	SCRATCH0,12
	brsh	WTM_HOUR_PM
	ldi	SCRATCH3,NTM_OFFSET_AM
WTM_HOUR_PM:
	rcall	WRITE_WORD
;
	lds	SCRATCH0,TIME_MINUTES
	cpi	SCRATCH0,0
	brne	WTM_NOT_EXACTLY
;
	st	X+,SCRATCH4 ; space
	ldi	SCRATCH3,NTM_OFFSET_EXACTLY
	rcall	WRITE_WORD
;
WTM_NOT_EXACTLY:
	rcall	UPDATE_DISPLAY_CENTERED
	ret
	
	
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; SCRATCH0 = number to write (1-59)
; XH:XL = pointer into TEXT_BUFFER
; Uses Z pointer
WRITE_NUMBER:
	cpi	SCRATCH0,20
	brsh	WN_TWENTYORMORE
	mov	SCRATCH3,SCRATCH0
	rcall	WRITE_WORD ; copy out the word
	cpi	SCRATCH0,13 ; append "TEEN" if higher or equal
	brlo	WM_OUT
	ldi	SCRATCH3,NTM_OFFSET_TEEN
	rcall	WRITE_WORD
WM_OUT:
	ret

WN_TWENTYORMORE:
	ldi	SCRATCH3,20 ; offset of "20"
	subi	SCRATCH0,20
WM_M20_LOOP:
	cpi	SCRATCH0,10 ; still more than 10?
	brlo	WM_M20_OUT
	subi	SCRATCH0,10
	inc	SCRATCH3 ; step to next one
	rjmp	WM_M20_LOOP
WM_M20_OUT:
	rcall	WRITE_WORD ; copy out word TWENTY - FIFTY
	tst	SCRATCH0
	breq	WM_OUT ; done if zero
	ldi	SCRATCH1,FA_OFFSET_DASH
	st	X+,SCRATCH1 ; append the dash
	rjmp	WRITE_NUMBER ; tail recurse to write the units (ONE-NINE)
	
; SCRATCH3 = number text map offset to write
; XH:XL = pointer into text buffer
WRITE_WORD_INIT:
	ldi	XH,high(TEXT_BUFFER)
	ldi	XL,low(TEXT_BUFFER)
WRITE_WORD:
	ldi	ZH,high((NUMBER_TEXT_MAP<<1)-1)
	ldi	ZL,low((NUMBER_TEXT_MAP<<1)-1) ; first item is 1
	add	ZL,SCRATCH3
	adc	ZH,REG_ZERO
	lpm	SCRATCH3,Z ; get offset from table
	ldi	ZH,high(TEXT_STRINGS<<1)
	ldi	ZL,low(TEXT_STRINGS<<1)
	add	ZL,SCRATCH3
	adc	ZH,REG_ZERO
WW_LOOP:
	lpm	SCRATCH1,Z+
	mov	SCRATCH2,SCRATCH1
	andi	SCRATCH1,127 ; strip high bit
	st	X+,SCRATCH1 ; save to output buffer
	sbrs	SCRATCH2,7 ; end?
	rjmp	WW_LOOP
	ret
	

; Read text buffer, look up patterns in ROM, and write patterns into display buffer
; Assumes XH:XL points one after last character to display
; TEXT_BUFFER -> DISPLAY_BUFFER
UPDATE_DISPLAY_LEFT_JUSTIFIED:
	clr	SCRATCH0
	mov	SCRATCH1,XL ; end marker
	rjmp	UPDATE_DISPLAY_COMMON
;
UPDATE_DISPLAY_CENTERED:
; Calculate number of characters to indent
	mov	SCRATCH1,XL ; end marker
	ldi	SCRATCH0,low(TEXT_BUFFER+24) ; pointer to end of text buffer
	sub	SCRATCH0,SCRATCH1 ; SCRATCH0 = number of unused characters
	lsr	SCRATCH0 ; halve it = number to leave blank on left site
; Load registers
UPDATE_DISPLAY_COMMON:
	cbi	GPIOR0,FLAG2_SERIAL_DISPLAY_MODE
	ldi	YH,high(DISPLAY_BUFFER)
	ldi	YL,low(DISPLAY_BUFFER)
	ldi	ZH,high(FONT_START<<1)
#ifdef	LED_COMMON_ANODE
	ldi	SCRATCH2,0xff ; blank digit
#else
	ldi	SCRATCH2,0x00 ; blank digit
#endif	
; Clear first n characters to center the text
UDC_LOOP1:
	tst	SCRATCH0	
	breq	UDC_OUT1
	st	Y+,SCRATCH2 
	st	Y+,SCRATCH2 ; blank
	dec	SCRATCH0
	rjmp	UDC_LOOP1
UDC_OUT1:
; Decode text to patterns and write it onto the display
	ldi	XL,low(TEXT_BUFFER)
UDC_LOOP2:
	cp	XL,SCRATCH1
	brsh	UDC_OUT2 ; done
	ld	ZL,X+ ; get character
	lsl	ZL ; double address and put decimal point in C flag
	clr	SCRATCH3 ; must be a better way
	adc	SCRATCH3,REG_ZERO ; C flag into bit 0
	subi	ZL,0-low(FONT_START<<1) ; same page assumption (barely!)
	lpm	SCRATCH0,Z+
#ifdef	LED_COMMON_ANODE
	com	SCRATCH0
#endif
	st	Y+,SCRATCH0
	lpm	SCRATCH0,Z+
	or	SCRATCH0,SCRATCH3 ; merge in decimal point
#ifdef	LED_COMMON_ANODE
	com	SCRATCH0
#endif
	st	Y+,SCRATCH0
	rjmp	UDC_LOOP2
UDC_OUT2:
; Blank the rightmost digits	
UDC_LOOP3:
	cpi	YL,low(DISPLAY_BUFFER+48)
	brsh	UDC_OUT3 ; done
	st	Y+,SCRATCH2
	rjmp	UDC_LOOP3
UDC_OUT3:
	ret
	



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Brightness control interrupt, turn off display
; These instructions do not affect flags
; OCR0A register controls duty cycle and therefore brightness
V_TIM0_COMPA:
#ifdef	LED_COMMON_ANODE
	sbi	PORTC,PORTC0
	sbi	PORTC,PORTC1
	sbi	PORTC,PORTC2
	sbi	PORTD,PORTD5
	sbi	PORTD,PORTD6
	sbi	PORTD,PORTD7
#else
	cbi	PORTC,PORTC0
	cbi	PORTC,PORTC1
	cbi	PORTC,PORTC2
	cbi	PORTD,PORTD5
	cbi	PORTD,PORTD6
	cbi	PORTD,PORTD7
#endif
	sbi	GPIOR0,FLAG2_INTS_OK_1
	reti

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Main periodic interrupt, refresh display, increment time, check buttons
V_TIM0_OVF:
TIMER_INTERRUPT:
	in	INTERRUPT_SREG,SREG
	push	SCRATCH0
	push	SCRATCH1
	push	SCRATCH2
	push	ZL
	push	ZH
;
	sbic	GPIOR0,FLAG2_INTS_OK_1
	wdr
	cbi	GPIOR0,FLAG2_INTS_OK_1
;
	sbic	GPIOR0,FLAG2_DISPLAY_OFF ; if display is blank...
	rjmp	TI_KEY_SCAN ; proceed to key scan
;
; Send the display data to the shift register
	ldi	ZH,high(DISPLAY_BUFFER)
	ldi	ZL,low(DISPLAY_BUFFER)
	add	ZL,REFRESH_PHASE ; same page assumption
TI_REFRESH_COMMON_ITER:
	ld	SCRATCH2,Z+ ; get high byte of digit
	out	SPDR,SCRATCH2 ; send high byte
	ld	SCRATCH2,Z ; get low byte of digit
	adiw	ZH:ZL,11 ; step to high byte of next digit
TI_REF_WAITSPI0:
	in	SCRATCH1,SPSR
	sbrs	SCRATCH1,SPIF
	rjmp	TI_REF_WAITSPI0
	out	SPDR,SCRATCH2 ; send low byte
TI_REF_WAITSPI1:
	in	SCRATCH1,SPSR
	sbrs	SCRATCH1,SPIF
	rjmp	TI_REF_WAITSPI1
	cpi	ZL,(low(DISPLAY_BUFFER)+48) ; same page assumption
	brlo	TI_REFRESH_COMMON_ITER ; done if past end of buffer, else iterate
;
; Turn on the digits
	ldi	ZH,high(TI_REFRESH_PHASE)
	ldi	ZL,low(TI_REFRESH_PHASE)
	add	ZL,REFRESH_PHASE ; dangerous same page assumption
	ijmp	; Z

TI_REFRESH_PHASE:
#ifdef	LED_COMMON_ANODE
	cbi	PORTC,PORTC0
	rjmp	TI_REFRESH_PHASE_OUT
	cbi	PORTC,PORTC1
	rjmp	TI_REFRESH_PHASE_OUT
	cbi	PORTC,PORTC2
	rjmp	TI_REFRESH_PHASE_OUT
	cbi	PORTD,PORTD5
	rjmp	TI_REFRESH_PHASE_OUT
	cbi	PORTD,PORTD6
	rjmp	TI_REFRESH_PHASE_OUT
	cbi	PORTD,PORTD7
	ldi	SCRATCH0,0xfe
	mov	REFRESH_PHASE,SCRATCH0 ; rolls over to 0 when it gets +2 below
#else
	sbi	PORTC,PORTC0
	rjmp	TI_REFRESH_PHASE_OUT
	sbi	PORTC,PORTC1
	rjmp	TI_REFRESH_PHASE_OUT
	sbi	PORTC,PORTC2
	rjmp	TI_REFRESH_PHASE_OUT
	sbi	PORTD,PORTD5
	rjmp	TI_REFRESH_PHASE_OUT
	sbi	PORTD,PORTD6
	rjmp	TI_REFRESH_PHASE_OUT
	sbi	PORTD,PORTD7
	ldi	SCRATCH0,0xfe
	mov	REFRESH_PHASE,SCRATCH0 ; rolls over to 0 when it gets +2 below
#endif
TI_REFRESH_PHASE_OUT:
	inc	REFRESH_PHASE
	inc	REFRESH_PHASE ; note flags used below
;
; Scan the keys (every sixth interrupt)
	brne	TI_NO_KEY_SCAN ; this skips on all except phase 0
;
TI_KEY_SCAN:
	ldi	SCRATCH1,1 ; fast repeat interval
	sbrc	FLAG1_REGISTER,FLAG1_MODE_SET_HOURS
	ldi	SCRATCH1,20 ; slow repeat interval
	sbrc	FLAG1_REGISTER,FLAG1_MODE_SET_MINUTES
	ldi	SCRATCH1,20 ; slow repeat interval
	sbrs	FLAG1_REGISTER,FLAG1_BUTTON_HELD
	ldi	SCRATCH1,128 ; repeat delay
;
	in	SCRATCH0,PINC
	com	SCRATCH0
	andi	SCRATCH0,(1<<PORTC3)|(1<<PORTC4)|(1<<PORTC5)
	breq	TI_KEY_SCAN_NONE_DOWN
	sbrs	FLAG1_REGISTER,FLAG1_BUTTON_HELD
	rjmp	TI_BUTTON_NOT_HELD
; button held
	dec	KEY_REPEAT_DELAY
	brne	TI_KEY_SCAN_OUT
TI_BUTTON_NOT_HELD:
	mov	KEY_REPEAT_DELAY,SCRATCH1
	sbrc	SCRATCH0,PORTC3
	sbr	FLAG1_REGISTER,(1<<FLAG1_BUTTON_HELD)|(1<<FLAG1_BUTTON_SELECT_PRESSED)
	sbrc	SCRATCH0,PORTC4
	sbr	FLAG1_REGISTER,(1<<FLAG1_BUTTON_HELD)|(1<<FLAG1_BUTTON_UP_PRESSED)
	sbrc	SCRATCH0,PORTC5
	sbr	FLAG1_REGISTER,(1<<FLAG1_BUTTON_HELD)|(1<<FLAG1_BUTTON_DOWN_PRESSED)
	rjmp	TI_KEY_SCAN_OUT
TI_KEY_SCAN_NONE_DOWN:
	cbr	FLAG1_REGISTER,1<<FLAG1_BUTTON_HELD
TI_KEY_SCAN_OUT:
	
TI_NO_KEY_SCAN:
;
; Increment the time
	ldi	ZH,high(TIME_SUBSEC_L)
	ldi	ZL,low(TIME_SUBSEC_L) 
;
	ldd	SCRATCH0,Z+0 ; subsec_l
	add	SCRATCH0,REG_ZERO
	ldd	SCRATCH1,Z+1 ; subsec_m
	ldi	SCRATCH2,64 ; add 16384 counts
	adc	SCRATCH1,SCRATCH2
	ldd	SCRATCH2,Z+2 ; subsec_h
	adc	SCRATCH2,REG_ZERO
;
	std	Z+0,SCRATCH0
	std	Z+1,SCRATCH1
	std	Z+2,SCRATCH2
;
	subi	SCRATCH0,0
	sbci	SCRATCH1,18
	sbci	SCRATCH2,122 ; subtracting 8,000,000
	brlo	TI_INCTIME_OUT
;
; Subseconds rolled over, save it again and increment seconds
	std	Z+0,SCRATCH0
	std	Z+1,SCRATCH1
	std	Z+2,SCRATCH2
;
	ldd	SCRATCH0,Z+3 ; seconds
	inc	SCRATCH0
	cpi	SCRATCH0,60
	brlo	TI_INCTIME_SAVE_SECONDS
	clr	SCRATCH0
;
	ldd	SCRATCH1,Z+4 ; minutes
	inc	SCRATCH1
	cpi	SCRATCH1,60
	brlo	TI_INCTIME_SAVE_MINUTES
	clr	SCRATCH1
;
	ldd	SCRATCH2,Z+5 ; hours
	inc	SCRATCH2
	cpi	SCRATCH2,24
	brlo	TI_INCTIME_SAVE_HOURS
	clr	SCRATCH2
;
TI_INCTIME_SAVE_HOURS:
	std	Z+5,SCRATCH2
TI_INCTIME_SAVE_MINUTES:
	std	Z+4,SCRATCH1
	sbr	FLAG1_REGISTER,(1<<FLAG1_TIME_CHANGED)
TI_INCTIME_SAVE_SECONDS:
	std	Z+3,SCRATCH0
;
	ldd	SCRATCH0,Z+6 ; TIMEOUT_SETMODE (setting mode reset)
	subi	SCRATCH0,1
	brlo	TI_INCTIME_OUT ; underflow, was zero, leave it there
	std	Z+6,SCRATCH0 ; save decremented value
	brne	TI_INCTIME_OUT ; just turned over to zero?
	cbr	FLAG1_REGISTER,(1<<FLAG1_MODE_SET_HOURS)|(1<<FLAG1_MODE_SET_MINUTES) ; back to brightness mode
	cbi	GPIOR0,FLAG2_MODE_DISPLAY_OFF
	sbr	FLAG1_REGISTER,(1<<FLAG1_TIME_CHANGED)|(1<<FLAG1_TIME_UPDATE) ; re-display the time ; TODO what about data mode?
TI_INCTIME_OUT:
;
	pop	ZH
	pop	ZL
	pop	SCRATCH2
	pop	SCRATCH1
	pop	SCRATCH0
	out	SREG,INTERRUPT_SREG
	reti

; Fetch the incoming character into the circular input buffer
V_USART0_RXC:
	in	INTERRUPT_SREG,SREG
	push	ZH
	push	ZL
	push	SCRATCH0
	ldi	ZH,high(SERIAL_INPUT_BUFFER)
	in	ZL,GPIOR2 ; write pointer
	lds	SCRATCH0,UDR0
	st	Z+,SCRATCH0
	cpi	ZL,low(SERIAL_INPUT_BUFFER)+16
	brlo	V_USART_RXC_NOWRAP
	ldi	ZL,low(SERIAL_INPUT_BUFFER)
V_USART_RXC_NOWRAP:
	out	GPIOR2,ZL
	sbi	GPIOR0,FLAG2_SERIAL_DATA_PENDING
	pop	SCRATCH0
	pop	ZL
	pop	ZH
	out	SREG,INTERRUPT_SREG
	reti


.dseg
.org 0x0100
DISPLAY_BUFFER: .byte 48 ; bitmap of display
TEXT_BUFFER: .byte 24; buffer for preparing display text
SERIAL_INPUT_BUFFER: .byte 16
TIME_SUBSEC_L: .byte 1
TIME_SUBSEC_M: .byte 1
TIME_SUBSEC_H: .byte 1
TIME_SECONDS: .byte 1
TIME_MINUTES: .byte 1
TIME_HOURS: .byte 1
TIMEOUT_SETMODE: .byte 1

; EOF
