; 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