; 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. ; *** 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 ; 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 ; .inc "avr.h" .inc "tn254585.h" .inc "tn45.h" ; *** Constants *** .equ CLK 2 ; Port B .equ DATA 1 ; Port B .equ CP 3 ; Port B ; SR-Latch's Q pin .equ LQ 0 ; Port B ; SR-Latch's R pin .equ LR 4 ; Port B ; init value for TCNT0 so that overflow occurs in 100us .equ TIMER_INITVAL 0x100-100 ; *** Code *** rjmp main rjmp hdlINT0 ; Read DATA and set GPIOR0/0 if high. Then, set flag T. ; no SREG fiddling because no SREG-modifying instruction hdlINT0: sbic PINB, DATA ; DATA clear? skip next sbi GPIOR0, 0 set reti main: ldi r16, RAMEND&0xff out SPL, r16 ldi r16, RAMEND}8 out SPH, r16 ; init variables clr r18 out GPIOR0, r18 ; Setup int0 ; INT0, falling edge ldi r16, 0x02 ; ISC01 out MCUCR, r16 ; Enable INT0 ldi r16, 0x40 ; INT0 out GIMSK, r16 ; Setup buffer clr YH ldi YL, SRAM_START&0xff clr ZH ldi ZL, SRAM_START&0xff ; 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. ldi r16, 0x01 ; CS00 - no prescaler out TCCR0B, r16 ; init DDRB sbi DDRB, CP cbi PORTB, LR sbi DDRB, LR sei loop: brts processbit ; flag T set? we have a bit to process cp YL, ZL ; if YL == ZL, buffer is empty brne sendTo164 ; YL != ZL? our buffer has data ; nothing to do. Before looping, let's check if our communication timer ; overflowed. in r16, TIFR sbrc r16, 1 ; TOV0 rjmp processbitReset ; Timer0 overflow? reset processbit ; Nothing to do for real. rjmp loop ; Process the data bit received in INT0 handler. processbit: in r19, GPIOR0 ; backup GPIOR0 before we reset T andi r19, 0x1 ; only keep the first flag cbi GPIOR0, 0 clt ; ready to receive another bit ; We've received a bit. reset timer rcall resetTimer ; Which step are we at? tst r18 breq processbits0 cpi r18, 1 breq processbits1 cpi r18, 2 breq processbits2 ; step 3: stop bit clr r18 ; happens in all cases ; DATA has to be set tst r19 ; Was DATA set? breq loop ; not set? error, don't push to buffer ; push r17 to the buffer st Y+, r17 rcall checkBoundsY rjmp loop processbits0: ; step 0 - start bit ; DATA has to be cleared tst r19 ; Was DATA set? brne loop ; Set? error. no need to do anything. keep r18 ; as-is. ; DATA is cleared. prepare r17 and r18 for step 1 inc r18 ldi r17, 0x80 rjmp loop 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 sbrc r19, 0 ; skip if DATA cleared. sec ; Carry flag is set ror r17 ; Good. now, are we finished rotating? If carry flag is set, it means ; that we've rotated in 8 bits. brcc loop ; we haven't finished yet ; We're finished, go to step 2 inc r18 rjmp loop processbits2: ; step 2 - parity bit mov r1, r19 mov r19, r17 rcall checkParity ; --> r16 cp r1, r16 brne processbitError ; r1 != r16? wrong parity inc r18 rjmp loop processbitError: clr r18 ldi r19, 0xfe rcall sendToPS2 rjmp loop processbitReset: clr r18 rcall resetTimer rjmp loop ; Send the value of r20 to the '164 sendTo164: sbis PINB, LQ ; LQ is set? we can send the next byte rjmp 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 sbi DDRB, DATA ld r20, Z+ rcall checkBoundsZ ldi r16, 8 sendTo164Loop: cbi PORTB, DATA sbrc r20, 7 ; if leftmost bit isn't cleared, set DATA high sbi PORTB, DATA ; toggle CP cbi PORTB, CP lsl r20 sbi PORTB, CP dec r16 brne sendTo164Loop ; not zero yet? loop ; release PS/2 cbi DDRB, DATA sei ; Reset the latch to indicate that the next number is ready sbi PORTB, LR cbi PORTB, LR rjmp loop resetTimer: ldi r16, TIMER_INITVAL out TCNT0, r16 ldi r16, 0x02 ; TOV0 out TIFR, r16 ret ; Send the value of r19 to the PS/2 keyboard sendToPS2: cli ; First, indicate our request to send by holding both Clock low for ; 100us, then pull Data low ; lines low for 100us. cbi PORTB, CLK sbi DDRB, CLK rcall resetTimer ; Wait until the timer overflows in r16, TIFR sbrs r16, 1 ; TOV0 rjmp $-4 ; Good, 100us passed. ; Pull Data low, that's our start bit. cbi PORTB, DATA sbi DDRB, DATA ; 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. cbi DDRB, CLK ; Should be starting high now. ; We will do the next loop 8 times ldi r16, 8 ; Let's remember initial r19 for parity mov r1, r19 sendToPS2Loop: ; Wait for CLK to go low sbic PINB, CLK rjmp $-2 ; set up DATA cbi PORTB, DATA sbrc r19, 0 ; skip if LSB is clear sbi PORTB, DATA lsr r19 ; Wait for CLK to go high sbis PINB, CLK rjmp $-2 dec r16 brne sendToPS2Loop ; not zero? loop ; Data was sent, CLK is high. Let's send parity mov r19, r1 ; recall saved value rcall checkParity ; --> r16 ; Wait for CLK to go low sbic PINB, CLK rjmp $-2 ; set parity bit cbi PORTB, DATA sbrc r16, 0 ; parity bit in r16 sbi PORTB, DATA ; Wait for CLK to go high sbis PINB, CLK rjmp $-2 ; Wait for CLK to go low sbic PINB, CLK rjmp $-2 ; We can now release the DATA line cbi DDRB, DATA ; Wait for DATA to go low. That's our ACK sbic PINB, DATA rjmp $-2 ; Wait for CLK to go low sbic PINB, CLK rjmp $-2 ; We're finished! Enable INT0, reset timer, everything back to normal! rcall resetTimer clt ; also, make sure T isn't mistakely set. sei ret ; Check that Y is within bounds, reset to SRAM_START if not. checkBoundsY: tst YL breq $+4 ret ; not zero, nothing to do ; YL is zero. Reset Y clr YH ldi YL, SRAM_START&0xff ret ; Check that Z is within bounds, reset to SRAM_START if not. checkBoundsZ: tst ZL breq $+4 ret ; not zero, nothing to do ; ZL is zero. Reset Z clr ZH ldi ZL, SRAM_START&0xff ret ; 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. checkParity: ldi r16, 1 lsr r19 brcc $+4 ; Carry unset? skip next inc r16 ; Carry set? We had a 1 tst r19 ; is r19 zero yet? brne checkParity+2 ; no? loop and skip first LDI andi r16, 0x1 ; Sets Z accordingly ret