avra: add LD/ST

This commit is contained in:
Virgil Dupras 2019-12-22 21:50:20 -05:00
parent 51e500e8da
commit 98ca338aba
5 changed files with 473 additions and 6 deletions

View File

@ -31,8 +31,11 @@ instrNames:
.equ I_BRBS 16
.db "BRBS", 0
.db "BRBC", 0
.equ I_LD 18
.db "LD", 0
.db "ST", 0
; Rd(5) + Rr(5) (from here, instrTbl8)
.equ I_ADC 18
.equ I_ADC 20
.db "ADC", 0
.db "ADD", 0
.db "AND", 0
@ -97,7 +100,7 @@ instrNames:
.db "TST", 0
.db "WDR", 0
.db "XCH", 0
.equ I_ANDI 82
.equ I_ANDI 84
.db "ANDI", 0
.db "CBR", 0
.db "CPI", 0
@ -106,10 +109,10 @@ instrNames:
.db "SBCI", 0
.db "SBR", 0
.db "SUBI", 0
.equ I_RCALL 90
.equ I_RCALL 92
.db "RCALL", 0
.db "RJMP", 0
.equ I_CBI 92
.equ I_CBI 94
.db "CBI", 0
.db "SBI", 0
.db "SBIC", 0
@ -118,7 +121,7 @@ instrNames:
; ZASM limitation: CALL and JMP constants are 22-bit. In ZASM, we limit
; ourselves to 16-bit. Supporting 22-bit would incur a prohibitive complexity
; cost. As they say, 64K words ought to be enough for anybody.
.equ I_CALL 96
.equ I_CALL 98
.db "CALL", 0
.db "JMP", 0
.db 0xff
@ -280,8 +283,11 @@ parseInstruction:
ld bc, 0
ld e, a ; Let's keep that instrID somewhere safe
; First, let's fetch our table row
cp I_ADC
cp I_LD
jp c, .BR ; BR is special, no table row
jp z, .LD ; LD is special
cp I_ADC
jp c, .ST ; ST is special
; *** Step 2: parse arguments
sub I_ADC ; Adjust index for table
@ -448,6 +454,40 @@ parseInstruction:
; bit in H, k in L.
jr .spitBR2
.LD:
ld h, 'R'
ld l, 'z'
call _parseArgs
ret nz
ld d, 0b10000000
jr .LDST
.ST:
ld h, 'z'
ld l, 'R'
call _parseArgs
ret nz
ld d, 0b10000010
call .swapHL
; continue to .LDST
.LDST:
; Rd in H, Z in L, base upcode in D
call .placeRd
; We're spitting LSB first, so let's compose it.
ld a, l
and 0b00001111
or c
call ioPutB
; Now, MSB's bit 4 is L's bit 4. How convenient!
ld a, l
and 0b00010000
or d
or b
; MSB composed!
call ioPutB
cp a ; ensure Z
ret
; local routines
; place number in H in BC at position .......d dddd....
; BC is assumed to be 0
@ -502,6 +542,9 @@ parseInstruction:
; 'D' - A double-length number which will fill whole HL.
; 'R' - an r5 value: r0-r31
; 'r' - an r4 value: r16-r31
; 'z' - an indirect register (X, Y or Z), with our without post-inc/pre-dec
; indicator. This will result in a 5-bit number, from which we can place
; bits 3:0 to upcode's 3:0 and bit 4 at upcode's 12 in LD and ST.
;
; All arguments accept expressions, even 'r' ones: in 'r' args, we start by
; looking if the arg starts with 'r' or 'R'. If yes, it's a simple 'rXX' value,
@ -579,6 +622,8 @@ _parseArgs:
jr z, _readK8
cp 'D'
jr z, _readDouble
cp 'z'
jp z, _readz
ret ; something's wrong
_readBit:
@ -708,4 +753,63 @@ _readExpr:
pop ix
ret
; Parse one of the following: X, Y, Z, X+, Y+, Z+, -X, -Y, -Z.
; For each of those values, return a 5-bit value than can then be interleaved
; with LD or ST upcodes.
_readz:
call strlen
cp 3
jp nc, unsetZ ; string too long
; Let's load first char in A and second in A'. This will free HL
ld a, (hl)
ex af, af'
inc hl
ld a, (hl) ; Good, HL is now free
ld hl, .tblStraight
or a
jr z, .parseXYZ ; Second char null? We have a single char
; Maybe +
cp '+'
jr nz, .skip
; We have a +
ld hl, .tblInc
jr .parseXYZ
.skip:
; Maybe a -
ex af, af'
cp '-'
ret nz ; we have nothing
; We have a -
ld hl, .tblDec
; continue to .parseXYZ
.parseXYZ:
; We have X, Y or Z in A'
ex af, af'
call upcase
; Now, let's place HL
cp 'X'
jr z, .fetch
inc hl
cp 'Y'
jr z, .fetch
inc hl
cp 'Z'
ret nz ; error
.fetch:
ld a, (hl)
; Z already set from earlier cp
ret
.tblStraight:
.db 0b11100 ; X
.db 0b01000 ; Y
.db 0b00000 ; Z
.tblInc:
.db 0b11101 ; X+
.db 0b11001 ; Y+
.db 0b10001 ; Z+
.tblDec:
.db 0b11110 ; -X
.db 0b11010 ; -Y
.db 0b10010 ; -Z

View File

@ -0,0 +1,343 @@
; 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

Binary file not shown.

View File

@ -0,0 +1,18 @@
ld r0, X
ld r1, Y
ld r2, Z
ld r3, X+
ld r4, Y+
ld r5, Z+
ld r6, -X
ld r7, -Y
ld r8, -Z
st X, r9
st Y, r10
st Z, r11
st X+, r12
st Y+, r13
st Z+, r14
st -X, r15
st -Y, r16
st -Z, r17

View File

@ -0,0 +1,2 @@
<0C>€ €=<3D>I<EFBFBD>Q<EFBFBD>n<EFBFBD>z<EFBFBD><EFBFBD>ś¨°ÍŮáţ