.include "tn45def.inc" ; Receives keystrokes from PS/2 keyboard and send them to the 595. As long as ; that number is not collected, we buffer the scan code received from ps/2. As ; soon as that number is collected we put the next number in the buffer. If the ; buffer is empty, we do nothing (the 595 already had its SRCLR pin triggered ; and shows 0). ; ; PS/2 is a bidirectional protocol, but in this program, we only care about ; receiving keystrokes. We don't send anything to the keyboard. ; ; The PS/2 keyboard has two data wires: Clock and Data. It is the keyboard that ; drives the clock with about 30-50 us between each clock. ; ; We wire the Clock to INT0 (PB2) and make it trigger an interrupt on the ; falling edge (the edge, in the PS/2 protocol, when data is set). ; ; Data is sent by the keyboard in 11-bit frames. 1 start bit (0), 8 data bits, ; one parity bit, one stop bit (1). ; ; Parity bit is set if number of bits in data bits is even. Unset otherwise. ; ; *** Receiving a data frame *** ; ; In idle mode, R18 is zero. When INT0 is triggered, it is increased and R17 is ; loaded with 0x80. We do this because we're going to right shift our data in ; (byte is sent LSB first). When the carry flag is set, we'll know we're ; finished. When that happens, we increase R18 again. We're waiting for parity ; bit. When we get it, we check parity and increase R18 again. We're waiting ; for stop bit. After we receive stop bit, we reset R18 to 0. ; ; On error, we ignore and reset our counters. ; *** Buffering scan codes *** ; ; The buffer starts at SRAM and stops at 0x100. It leaves space for the stack ; and makes overflow check easy. Also, we don't need a very big buffer. In this ; address space, Z chasing Y. When Y == Z, the buffer is empty. When 0x100 is ; reached, we go back to SRAM_START. ; ; Whenever a new scan code is received, we place it in Y and increase it. ; Whenever we send a scan code to the 595 (which can't be done when Z == Y ; because Z points to an invalid value), we send the value of Z and increase. ; *** Sending to the 595 *** ; ; Whenever a scan code is read from the 595, CE goes low and triggers a PCINT ; on PB4. When we get it, we clear the GPIOR0/1 flag to indicate that we're ; ready to send a new scan code to the 595. ; ; Because that CE flip/flop is real fast (375ns), it requires us to run at 8MHz. ; ; During the PCINT, we also trigger RCLK once because CE is also wired to SRCLR ; and we want the z80 to be able to know that the device has nothing to give ; (has a value of zero) rather than having to second guess (is this value, which ; is the same as the one that was read before, a new value or not?). With that ; "quick zero-in" scheme, there's no ambiguity: no scan code can be ready twice ; because it's replaced by a 0 as soon as it's read, until it can be filled with ; the next char in the buffer. ; *** 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. ; 1 - When set, indicate that the 595 holds a value that hasn't been read ; by the z80 yet. ; ; 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 595 ; 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 ; ; *** Constants *** ; .equ CLK = PINB2 .equ DATA = PINB1 .equ SRCLK = PINB3 .equ CE = PINB4 .equ RCLK = PINB0 ; init value for TCNT0 so that overflow occurs in 100us .equ TIMER_INITVAL = 0x100-100 rjmp main rjmp hdlINT0 rjmp hdlPCINT ; 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 ; Only PB4 is hooked to PCINT and we don't bother checking the value of the PB4 ; pin: things go too fast for this. ; no SREG fiddling because no SREG-modifying instruction hdlPCINT: ; SRCLR has been triggered. Let's trigger RCLK too. sbi PORTB, RCLK cbi PORTB, RCLK cbi GPIOR0, 1 ; 595 is now free reti main: ldi r16, low(RAMEND) out SPL, r16 ldi r16, high(RAMEND) out SPH, r16 ; Set clock prescaler to 1 (8MHz) ldi r16, (1< 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 next scan code in buffer to 595, MSB. sendTo595: sbic GPIOR0, 1 rjmp loop ; flag 1 set? 595 is "busy". Don't send. ; 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 sendTo595Loop: cbi PORTB, DATA sbrc r20, 7 ; if leftmost bit isn't cleared, set DATA high sbi PORTB, DATA ; toggle SRCLK cbi PORTB, SRCLK lsl r20 sbi PORTB, SRCLK dec r16 brne sendTo595Loop ; not zero yet? loop ; We're finished sending our data to the 595 and we're ready to go back ; to business as usual. However, timing is important here. The z80 is ; very fast and constantly hammers our 595 with polls. While this ; routine was running, it was getting zeroes, which is fine, but as soon ; as we trigger RCLK, the z80 is going to fetch that value. What we want ; to do is to enable back the interrupts as soon as RCLK is triggered ; so that the z80 doesn't have enough time to poll twice. If it did, we ; would return a double character. This is why RCLK triggering is the ; last operation. ; release PS/2 cbi DDRB, DATA ; Set GPIOR0/1 to "595 is busy" sbi GPIOR0, 1 ; toggle RCLK sbi PORTB, RCLK cbi PORTB, RCLK sei rjmp loop resetTimer: ldi r16, TIMER_INITVAL out TCNT0, r16 ldi r16, (1< r16 ; Wait for CLK to go low sbic PINB, CLK rjmp PC-1 ; 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 PC-1 ; Wait for CLK to go low sbic PINB, CLK rjmp PC-1 ; We can now release the DATA line cbi DDRB, DATA ; Wait for DATA to go low. That's our ACK sbic PINB, DATA rjmp PC-1 ; Wait for CLK to go low sbic PINB, CLK rjmp PC-1 ; We're finished! Enable INT0, reset timer, everything back to normal! rcall resetTimer clt ; also, make sure T isn't mistakely set. ldi r16, (1<