; This is a copy of my seg7multiplex main program, translated for zasm.
; The output of zasm was verified against avra's.

; 7-segments multiplexer for an ATtiny45
;
; Register usage
; R0: Digit on AFF1 (rightmost, QH on the SR)
; R1: Digit on AFF2 (QG on the SR)
; R2: Digit on AFF3 (QF on the SR)
; R3: Digit on AFF4 (leftmost, QE on the SR)
; R5: always zero
; R6: generic tmp value
; R16: generic tmp value
; R18: value to send to the SR. cleared at every SENDSR call
;      in input mode, holds the input buffer
; R30: (low Z) current digit being refreshed. cycles from 0 to 3
;
; Flags on GPIOs
; GPIOR0 - bit 0: Whether we need to refresh the display
; GPIOR0 - bit 1: Set when INT_INT0 has received a new bit
; GPIOR0 - bit 2: The value of the new bit received
; GPIOR0 - bit 4: input mode enabled

; Notes on register usage
; R0 - R3: 4 low bits are for digit, 5th bit is for dot. other bits are unused.
;
; Notes on AFF1-4
; They are reversed (depending on how you see things...). They read right to
; left. That means that AFF1 is least significant, AFF4 is most.
;
; Input mode counter
; When in input mode, TIMER0_OVF, instead of setting the refresh flag, increases
; the counter. When it reaches 3, we timeout and consider input invalid.
;
; Input procedure
;
; Input starts at INT_INT0. What it does there is very simple: is sets up a flag
; telling it received something and conditionally sets another flag with the
; value of the received bit.
;
; While we do that, we have the input loop eagerly checking for that flag. When
; it triggers, it records the bit in R18. The way it does so is that it inits
; R18 at 1 (not 0), then for every bit, it left shifts R18, then adds the new
; bit. When the 6th bit of R18 is set, it means we have every bit we need, we
; can flush it into Z.

; Z points directly to R3, then R2, then R1, then R0. Because display refresh
; is disabled during input, it won't result in weird displays, and because
; partial numbers result in error display, then partial result won't lead to
; weird displays, just error displays.
;
; When input mode begins, we change Z to point to R3 (the first digit we
; receive) and we decrease the Z pointer after every digit we receive. When we
; receive the last bit of the last digit and that we see that R30 is 0, we know
; that the next (and last) digit is the checksum.

.inc "avr.h"
.inc "tn254585.h"
.inc "tn45.h"

; pins
.equ RCLK 0	; on PORTB
.equ SRCLK 3	; on PORTB
.equ SER_DP 4	; on PORTB
.equ INSER 1	; on PORTB

; Let's begin!

.org 0x0000
        RJMP    MAIN
	RJMP	INT_INT0
	RETI	; PCINT0
	RETI	; TIMER1_COMPA
	RETI	; TIMER1_OVF
	RJMP	INT_TIMER0_OVF

MAIN:
	LDI	R16, RAMEND&0xff
        OUT	SPL, R16
        LDI	R16, RAMEND}8
	OUT	SPH, R16

	SBI	DDRB, RCLK
	SBI	DDRB, SRCLK
	SBI	DDRB, SER_DP

	; we generally keep SER_DP high to avoid lighting DP
	SBI	PORTB, SER_DP

	; target delay: 600us. At 1Mhz, that's 75 ticks with a 1/8 prescaler.
	LDI	R16, 0x02	; CS01, 1/8 prescaler
	OUT	TCCR0B, R16
	LDI	R16, 0xb5	; TOP - 75 ticks
	OUT	TCNT0, R16

	; Enable TIMER0_OVF
	IN	R16, TIMSK
	ORI	R16, 0x02	; TOIE0
	OUT	TIMSK, R16

	; Generate interrupt on rising edge of INT0
	IN	R16, MCUCR
	ORI	R16, 0b00000011	; ISC00 + ISC01
	OUT	MCUCR, R16
	IN	R16, GIMSK
	ORI	R16, 0b01000000	; INT0
	OUT	GIMSK, R16

	; we never use indirect addresses above 0xff through Z and never use
	; R31 in other situations. We can set it once and forget about it.
	CLR	R31	; high Z

	; put 4321 in R2-5
	CLR	R30	; low Z
	LDI	R16, 0x04
	ST	Z+, R16		; 4
	DEC	R16
	ST	Z+, R16		; 3
	DEC	R16
	ST	Z+, R16		; 2
	DEC	R16
	ORI	R16, 0b00010000	; DP
	ST	Z, R16		; 1
	CLR	R30		; replace Z to 0

	SEI

LOOP:
	RCALL	INPT_CHK	; verify that we shouldn't enter input mode
	SBIC	GPIOR0, 0	; refesh flag cleared? skip next
	RCALL	RDISP
        RJMP    LOOP

; ***** DISPLAY *****

; refresh display with current number
RDISP:
	; First things first: setup the timer for the next time
	LDI	R16, 0xb5	; TOP - 75 ticks
	OUT	TCNT0, R16
	CBI	GPIOR0, 0	; Also, clear the refresh flag

	; Let's begin with the display selector. We select one display at once
	; (not ready for multi-display refresh operations yet). Let's decode our
	; binary value from R30 into R16.
	MOV	R6, R30
	INC	R6		; we need values 1-4, not 0-3
	LDI	R16, 0x01
RDISP1:
	DEC	R6
	BREQ	RDISP2		; == 0? we're finished
	LSL	R16
	RJMP	RDISP1

	; select a digit to display
	; we do so in a clever way: our registers just happen to be in SRAM
	; locations 0x00, 0x01, 0x02 and 0x03. Handy eh!
RDISP2:
	LD	R18, Z+		; Indirect load of Z into R18 then increment
	CPI	R30, 4
	BRCS	RDISP3		; lower than 4 ? don't reset
	CLR	R30		; not lower than 4? reset

	; in the next step, we're going to join R18 and R16 together, but
	; before we do, we have one thing to process: R18's 5th bit. If it's
	; high, it means that DP is highlighted. We have to store this
	; information in R6 and use it later. Also, we have to clear the higher
	; bits of R18.
RDISP3:
	SBRC	R18, 4		; 5th bit cleared? skip next
	INC	R6		; if set, then set R6 as well
	ANDI	R18, 0xf	; clear higher bits

	; Now we have our display selector in R16 and our digit to display in
	; R18. We want it all in R18.
	SWAP	R18		; digit goes in high "nibble"
	OR	R18, R16

	; While we send value to the shift register, SER_DP will change.
	; Because we want to avoid falsely lighting DP, we need to disable
	; output (disable OE) while that happens. This is why we set RCLK,
	; which is wired to OE too, HIGH (OE disabled) at the beginning of
	; the SR operation.
	;
	; Because RCLK was low before, this triggers a "buffer clock" on
	; the SR, but it doesn't matter because the value that was there
	; before has just been invalidated.
	SBI	PORTB, RCLK	; high
	RCALL	SENDSR
	; Flush out the buffer with RCLK
	CBI	PORTB, RCLK	; OE enabled, but SR buffer isn't flushed
	NOP
	SBI	PORTB, RCLK	; SR buffer flushed, OE disabled
	NOP
	CBI	PORTB, RCLK	; OE enabled

	; We're finished! Oh no wait, one last thing: should we highlight DP?
	; If we should, then we should keep SER_DP low rather than high for this
	; SR round.
	SBI	PORTB, SER_DP	; SER_DP generally kept high
	SBRC	R6, 0		; R6 is cleared? skip DP set
	CBI	PORTB, SER_DP	; SER_DP low highlight DP

	RET			; finished for real this time!

; send R18 to shift register.
; We send highest bits first so that QH is the MSB and QA is the LSB
; low bits (QD - QA) control display's power
; high bits (QH - QE) select the glyph
SENDSR:
	LDI	R16, 8		; we will loop 8 times
	CBI	PORTB, SER_DP	; low
	SBRC	R18, 7	; if latest bit isn't cleared, set SER_DP high
	SBI	PORTB, SER_DP	; high
	RCALL	TOGCP
	LSL	R18		; shift our data left
	DEC	R16
	BRNE	SENDSR+2	; not zero yet? loop! (+2 to avoid reset)
	RET

; toggle SRCLK, waiting 1us between pin changes
TOGCP:
	CBI	PORTB, SRCLK	; low
	NOP			; At 1Mhz, this is enough for 1us
	SBI	PORTB, SRCLK	; high
	RET

; ***** INPUT MODE *****

; check whether we should enter input mode and enter it if needed
INPT_CHK:
	SBIS	GPIOR0, 1	; did we just trigger INT_INT0?
	RET			; no? return
	; yes? continue in input mode

; Initialize input mode and start the loop
INPT_BEGIN:
	SBI	GPIOR0, 4	; enable input mode
	CBI	GPIOR0, 1	; The first trigger was an empty one

	; At 1/8 prescaler, a "full" counter overflow is 2048us. That sounds
	; about right for an input timeout. So we co the easy route and simply
	; clear TCNT0 whenever we want to reset the timer
	OUT	TCNT0, R5	; R5 == 0
	CBI	GPIOR0, 0	; clear refresh flag in case it was just set
	LDI	R30, 0x04	; make Z point on R3+1 (we use pre-decrement)
	LDI	R18, 0x01	; initialize input buffer

; loop in input mode. When in input mode, we don't refresh the display, we use
; all our processing power to process input.
INPT_LOOP:
	RCALL	INPT_READ

	; Check whether we've reached timeout
	SBIC	GPIOR0, 0	; refesh flag cleared? skip next
	RCALL	INPT_TIMEOUT

	SBIC	GPIOR0, 4	; input mode cleared? skip next, to INPT_END
	RJMP	INPT_LOOP	; not cleared? loop

INPT_END:
	; We received all our date or reached timeout. let's go back in normal
	; mode.
	CLR	R30		; Ensure Z isn't out of bounds
	SBI	GPIOR0, 0	; set refresh flag so we start refreshing now
	RET

; Read, if needed, the last received bit
INPT_READ:
	SBIS	GPIOR0, 1
	RET			; flag cleared? nothing to do

	; Flag is set, we have to read
	CBI	GPIOR0, 1	; unset flag
	LSL	R18
	SBIC	GPIOR0, 2	; data flag cleared? skip next
	INC	R18

	; Now, let's check if we have our 5 digits
	SBRC	R18, 5		; 6th bit cleared? nothing to do
	RCALL	INPT_PUSH

	OUT	TCNT0, R5	; clear timeout counter

	RET

; Push the digit currently in R18 in Z and reset R18.
INPT_PUSH:
	ANDI	R18, 0b00011111	; Remove 6th bit flag

	TST	R30		; is R30 zero?
	BREQ	INPT_CHECKSUM	; yes? it means we're at checksum phase.

	; Otherwise, its a regular digit push
	ST	-Z, R18
	LDI	R18, 0x01
	RET

INPT_CHECKSUM:
	CBI	GPIOR0, 4	; clear input mode, whether we error or not
	MOV	R16, R0
	ADD	R16, R1
	ADD	R16, R2
	ADD	R16, R3
	; only consider the first 5 bits of the checksum since we can't receive
	; more. Otherwise, we couldn't possibly validate a value like 9999
	ANDI	R16, 0b00011111
	CP	R16, R18
	BRNE	INPT_ERROR
	RET

INPT_TIMEOUT:
	CBI	GPIOR0, 4	; timeout reached, clear input flag
	; continue to INPT_ERROR

INPT_ERROR:
	LDI	R16, 0x0c	; some weird digit
	MOV	R0, R16
	MOV	R1, R16
	MOV	R2, R16
	MOV	R3, R16
	RET

; ***** INTERRUPTS *****

; Record received bit
; The main loop has to be fast enough to process that bit before we receive the
; next one!
; no SREG fiddling because no SREG-modifying instruction
INT_INT0:
	CBI	GPIOR0, 2	; clear received data
	SBIC	PINB, INSER	; INSER clear? skip next
	SBI	GPIOR0, 2	; INSER set? record this
	SBI	GPIOR0, 1	; indicate that we've received a bit
	RETI

; Set refresh flag whenever timer0 overflows
; no SREG fiddling because no SREG-modifying instruction
INT_TIMER0_OVF:
	SBI	GPIOR0, 0
	RETI