To assemble, load the AVR assembler with AVRA, then
"324 342 LOADR".
Receives keystrokes from PS/2 keyboard and send them to the
'164. On the PS/2 side, it works the same way as the controller
in the rc2014/ps2 recipe. However, in this case, what we have
on the other side isn't a z80 bus, it's the one of the two
controller ports of the SMS through a DB9 connector.
The PS/2 related code is copied from rc2014/ps2 without much
change. The only differences are that it pushes its data to a
'164 instead of a '595 and that it synchronizes with the SMS
with a SR latch, so we don't need PCINT. We can also afford to
run at 1MHz instead of 8. cont.
( ----- 021 )
Register Usage
GPIOR0 flags:
0 - when set, indicates that the DATA pin was high when we
received a bit through INT0. When we receive a bit, we set
flag T to indicate it.
R16: tmp stuff
R17: recv buffer. Whenever we receive a bit, we push it in
there.
R18: recv step:
- 0: idle
- 1: receiving data
- 2: awaiting parity bit
- 3: awaiting stop bit cont.
( ----- 022 )
R19: Register used for parity computations and tmp value in
some other places
R20: data being sent to the '164
Y: pointer to the memory location where the next scan code from
ps/2 will be written.
Z: pointer to the next scan code to push to the 595
( ----- 024 )
18 CONSTS $0060 SRAM_START $015f RAMEND $3d SPL $3e SPH
$11 GPIOR0 $35 MCUCR $33 TCCR0B $3b GIMSK
$38 TIFR $32 TCNT0 $16 PINB $17 DDRB $18 PORTB
2 CLK 1 DATA 3 CP 0 LQ 4 LR
$100 100 - CONSTANT TIMER_INITVAL
\ We need a lot of labels in this program...
5 VALUES L4 L5 L6 L7 L8
( ----- 025 )
FLBL, L1 \ main
FLBL, L2 \ hdlINT0
\ Read DATA and set GPIOR0/0 if high. Then, set flag T.
\ no SREG fiddling because no SREG-modifying instruction
' RJMP L2 TO, \ hdlINT0
PINB DATA SBIC,
GPIOR0 0 SBI,
SET,
RETI,
( ----- 026 )
' RJMP L1 TO, \ main
R16 RAMEND <<8 >>8 LDI, SPL R16 OUT,
R16 RAMEND >>8 LDI, SPH R16 OUT,
R18 CLR, GPIOR0 R18 OUT, \ init variables
R16 $02 ( ISC01 ) LDI, MCUCR R16 OUT, \ INT0, falling edge
R16 $40 ( INT0 ) LDI, GIMSK R16 OUT, \ Enable INT0
YH CLR, YL SRAM_START LDI, \ Setup buffer
ZH CLR, ZL SRAM_START LDI,
\ Setup timer. We use the timer to clear up "processbit"
\ registers after 100us without a clock. This allows us to start
\ the next frame in a fresh state. at 1MHZ, no prescaling is
\ necessary. Each TCNT0 tick is already 1us long.
R16 $01 ( CS00 ) LDI, \ no prescaler
TCCR0B R16 OUT,
DDRB CP SBI, PORTB LR CBI, DDRB LR SBI, SEI,
( ----- 027 )
LBL! L1 \ loop
FLBL, L2 \ BRTS processbit. flag T set? we have a bit to process
YL ZL CP, \ if YL == ZL, buf is empty
FLBL, L3 \ BRNE sendTo164. YL != ZL? buf has data
\ nothing to do. Before looping, let's check if our
\ communication timer overflowed.
R16 TIFR IN,
R16 1 ( TOV0 ) SBRC,
FLBL, L4 \ RJMP processbitReset, timer0 overflow? reset
\ Nothing to do for real.
' RJMP L1 LBL, \ loop
( ----- 028 )
\ Process the data bit received in INT0 handler.
' BRTS L2 TO, \ processbit
R19 GPIOR0 IN, \ backup GPIOR0 before we reset T
R19 $1 ANDI, \ only keep the first flag
GPIOR0 0 CBI,
CLT, \ ready to receive another bit
\ We've received a bit. reset timer
FLBL, L2 \ RCALL resetTimer
\ Which step are we at?
R18 TST, FLBL, L5 \ BREQ processbits0
R18 1 CPI, FLBL, L6 \ BREQ processbits1
R18 2 CPI, FLBL, L7 \ BREQ processbits2
( ----- 029 )
\ step 3: stop bit
R18 CLR, \ happens in all cases
\ DATA has to be set
R19 TST, \ was DATA set?
' BREQ L1 LBL, \ loop, not set? error, don't push to buf
\ push r17 to the buffer
Y+ R17 ST,
FLBL, L8 \ RCALL checkBoundsY
' RJMP L1 LBL, \ loop
( ----- 030 )
' BREQ L5 TO, \ processbits0
\ step 0 - start bit
\ DATA has to be cleared
R19 TST, \ was DATA set?
' BRNE L1 LBL, \ loop. set? error. no need to do anything. keep
\ r18 as-is.
\ DATA is cleared. prepare r17 and r18 for step 1
R18 INC,
R17 $80 LDI,
' RJMP L1 LBL, \ loop
( ----- 031 )
' BREQ L6 TO, \ processbits1
\ step 1 - receive bit
\ We're about to rotate the carry flag into r17. Let's set it
\ first depending on whether DATA is set.
CLC,
R19 0 SBRC, \ skip if DATA is cleared
SEC,
\ Carry flag is set
R17 ROR,
\ Good. now, are we finished rotating? If carry flag is set,
\ it means that we've rotated in 8 bits.
' BRCC L1 LBL, \ loop
\ We're finished, go to step 2
R18 INC,
' RJMP L1 LBL, \ loop
( ----- 032 )
' BREQ L7 TO, \ processbits2
\ step 2 - parity bit
R1 R19 MOV,
R19 R17 MOV,
FLBL, L5 \ RCALL checkParity
R1 R16 CP,
FLBL, L6 \ BRNE processBitError, r1 != r16? wrong parity
R18 INC,
' RJMP L1 LBL, \ loop
( ----- 033 )
' BRNE L6 TO, \ processBitError
R18 CLR,
R19 $fe LDI,
FLBL, L6 \ RCALL sendToPS2
' RJMP L1 LBL, \ loop
' RJMP L4 TO, \ processbitReset
R18 CLR,
FLBL, L4 \ RCALL resetTimer
' RJMP L1 LBL, \ loop
( ----- 034 )
' BRNE L3 TO, \ sendTo164
\ Send the value of r20 to the '164
PINB LQ SBIS, \ LQ is set? we can send the next byte
' RJMP L1 LBL, \ loop, even if we have something in the
\ buffer, we can't: the SMS hasn't read our
\ previous buffer yet.
\ We disable any interrupt handling during this routine.
\ Whatever it is, it has no meaning to us at this point in time
\ and processing it might mess things up.
CLI,
DDRB DATA SBI,
R20 Z+ LD,
FLBL, L3 \ RCALL checkBoundsZ
R16 R8 LDI,
( ----- 035 )
BEGIN,
PORTB DATA CBI,
R20 7 SBRC, \ if leftmost bit isn't cleared, set DATA high
PORTB DATA SBI,
\ toggle CP
PORTB CP CBI, R20 LSL, PORTB CP SBI,
R16 DEC,
' BRNE AGAIN?, \ not zero yet? loop
\ release PS/2
DDRB DATA CBI,
SEI,
\ Reset the latch to indicate that the next number is ready
PORTB LR SBI,
PORTB LR CBI,
' RJMP L1 LBL, \ loop
( ----- 036 )
' RCALL L2 TO, ' RCALL L4 TO, LBL! L2 \ resetTimer
R16 TIMER_INITVAL LDI,
TCNT0 R16 OUT,
R16 $02 ( TOV0 ) LDI,
TIFR R16 OUT,
RET,
( ----- 037 )
' RCALL L6 TO, \ sendToPS2
\ Send the value of r19 to the PS/2 keyboard
CLI,
\ First, indicate our request to send by holding both Clock low
\ for 100us, then pull Data low lines low for 100us.
PORTB CLK CBI,
DDRB CLK SBI,
' RCALL L2 LBL, \ resetTimer
\ Wait until the timer overflows
BEGIN, R16 TIFR IN, R16 1 ( TOV0 ) SBRS, AGAIN,
\ Good, 100us passed.
\ Pull Data low, that's our start bit.
PORTB DATA CBI,
DDRB DATA SBI,
( ----- 038 )
\ Now, let's release the clock. At the next raising edge, we'll
\ be expected to have set up our first bit (LSB). We set up
\ when CLK is low.
DDRB CLK CBI, \ Should be starting high now.
R16 8 LDI, \ We will do the next loop 8 times
R1 R19 MOV, \ Let's remember initial r19 for parity
BEGIN,
BEGIN, PINB CLK SBIC, AGAIN, \ Wait for CLK to go low
PORTB DATA CBI, \ set up DATA
R19 0 SBRC, \ skip if LSB is clear
PORTB DATA SBI,
R19 LSR,
\ Wait for CLK to go high
BEGIN, PINB CLK SBIS, AGAIN,
16 DEC,
' BRNE AGAIN?, \ not zero? loop
( ----- 039 )
\ Data was sent, CLK is high. Let's send parity
R19 R1 MOV, \ recall saved value
FLBL, L6 \ RCALL checkParity
BEGIN, PINB CLK SBIC, AGAIN, \ Wait for CLK to go low
\ set parity bit
PORTB DATA CBI,
R16 0 SBRC, \ parity bit in r16
PORTB DATA SBI,
BEGIN, PINB CLK SBIS, AGAIN, \ Wait for CLK to go high
BEGIN, PINB CLK SBIC, AGAIN, \ Wait for CLK to go low
\ We can now release the DATA line
DDRB DATA CBI,
\ Wait for DATA to go low, that's our ACK
BEGIN, PINB DATA SBIC, AGAIN,
BEGIN, PINB CLK SBIC, AGAIN, \ Wait for CLK to go low
( ----- 040 )
\ We're finished! Enable INT0, reset timer, everything back to
\ normal!
' RCALL L2 LBL, \ resetTimer
CLT, \ also, make sure T isn't mistakely set.
SEI,
RET,
( ----- 041 )
' RCALL L8 TO, \ checkBoundsY
\ Check that Y is within bounds, reset to SRAM_START if not.
YL TST,
IF, RET, ( not zero, nothing to do ) THEN,
\ YL is zero. Reset Z
YH CLR, YL SRAM_START <<8 >>8 LDI,
RET,
' RCALL L3 TO, \ checkBoundsZ
\ Check that Z is within bounds, reset to SRAM_START if not.
ZL TST,
IF, RET, ( not zero, nothing to do ) THEN,
\ ZL is zero. Reset Z
ZH CLR, ZL SRAM_START <<8 >>8 LDI,
RET,
( ----- 042 )
' RCALL L5 TO, ' RCALL L6 TO, \ checkParity
\ Counts the number of 1s in r19 and set r16 to 1 if there's an
\ even number of 1s, 0 if they're odd.
R16 1 LDI,
BEGIN,
R19 LSR,
' BRCC SKIP, R16 INC, ( carry set? we had a 1 ) TO,
R19 TST, \ is r19 zero yet?
' BRNE AGAIN?, \ no? loop
R16 $1 ANDI,
RET,
( ----- 045 )
\ A simple LED blinker on the Arduino Uno
\ To test the assembler mechanism. Requires ATMEGA328P.
DDRB 5 SBI, PORTB 5 CBI,
R16 $05 LDI, \ 1024 prescaler, CS00+CS02
TCCR0B R16 OUT,
R1 CLR, \ initialize overflow counter
BEGIN,
R16 TIFR0 IN,
R16 0 ( TOV0 ) SBRS, DUP AGAIN, \ no overflow
R16 $01 LDI, TIFR0 R16 OUT,
R1 INC,
PORTB 5 CBI,
R1 7 SBRS, PORTB 5 SBI, \ LED is on
AGAIN,
( ----- 050 )
\ Arduino SPI Spitter. See doc/hw/avr/spispit
103 CONSTANT BAUD_PRESCALE \ 9600 bauds at 16 MHz
R16 $80 LDI, R17 $04 LDI, CLKPR R16 STS, CLKPR R17 STS, \ x16
R16 BAUD_PRESCALE >>8 LDI, UBRR0H R16 STS,
R16 BAUD_PRESCALE <<8 >>8 LDI, UBRR0L R16 STS,
R16 $08 LDI, UCSR0B R16 STS, \ TXEN0
R16 CLR, PORTB R16 OUT,
R16 $2c LDI, DDRB R16 OUT, \ MOSI+SCK+SS/PB5+PB3+PB2
R16 $53 LDI, SPCR R16 OUT, \ SPE+MSTR+f_osc/128
ZH 0 LDI, ZL $ff LDI,
R1 Z+ LPM, \ number of 0x100 bytes blocks
( ----- 051 )
BEGIN, \ main loop
R16 Z+ LPM, SPDR R16 OUT,
BEGIN, R16 SPSR IN, R16 7 ( SPIF ) SBRS, AGAIN,
BEGIN, R16 UCSR0A LDS, R16 5 ( UDRE0 ) SBRS, AGAIN,
R16 SPDR IN, UDR0 R16 STS,
ZL TST, ' BRNE SKIP, R1 DEC, TO,
R1 TST, ' BRNE AGAIN?, \ end main
R16 $00 LDI, UCSR0B R16 STS, \ Disable UART
BEGIN, AGAIN, \ end program