;
; Brad and Lawrence's PIC Alice 2 interface chip.  The PIC
; is responsible for keyboard, serial, and clock interfaces.
;
; An internal queue keeps command-data sequences for the CPU.
; The command bytes are the constants pic_???_cmd.
; The timer command has no data byte, and the serial and keyboard
; bytes are followed by one byte of data.
;
; June 21st, 1997
;
        maclib 'p65.inc'

        device  pic16c65,xt_osc,wdt_off,pwrt_off,protect_off

w_temp          equ     020h
status_temp     equ     021h

;
; Keyboard I/O constants
;
init_stop_count equ     2       ; # of stop bits per byte (+ 1)
stop_count      equ     022h    ; storage for stop bits left
init_data_bits  equ     10      ; start bits + data bits (+ 1)
data_bits       equ     023h    ; storage for kbd data shifts left
                                ; (1st bit shifted in gets pushed off end)
kbd_byte        equ     024h    ; storage for keyboard byte

;
; Serial I/O constants
;
baud_rate_code  equ     00fh    ; 19200 baud at 20 MHz, BRGH=0, 15 decimal

;
; Alice I/O queue constants
;
outq_tail       equ     025h    ; output queue tail pointer
outq_head       equ     026h    ; output queue head pointer (actually
                                ; head+1)
outq_length     equ     027h    ; length in bytes of current output queue
                                ; (head - tail)
outq_byte_pop   equ     028h    ; byte popped off output queue
outq_byte_push  equ     029h    ; byte to push on output queue
outq_storage    equ     040h    ; first byte of output queue storage
outq_size       equ     040h    ; number of bytes in output queue
outq_wrap_mask  equ     03fh    ; mask for wrapping queue pointers

;
; Queue command constants
;
pic_non_cmd     equ     000h    ; no command yet (not used on the pic)
pic_ser_cmd     equ     001h    ; serial byte
pic_kbd_cmd     equ     002h    ; keyboard byte
pic_tim_cmd     equ     003h    ; timer (no byte)

;
; Timer1 constants
;
ten_hz_low      equ     0dch    ; low byte of 10 hz counter start
ten_hz_high     equ     00bh    ; high byte of 10 hz counter start
                                ; together these make 0xbdc, which means
                                ; upward-counting tmr1 does 62500 ticks,
                                ; and at 5 Hz (clk/4) with 1:8 scaling,
                                ; that's 10 overflows per second.

reset
        org     0               ; on reset
        goto    start

intsvc
        org     4               ; on interrupt
        goto    interrupt

start
        bcf     rp0             ; bank 0

        bcf     pspif           ; clear PSP interrupt flag

                ; Serial interface
        bcf     portc,6         ; clear USART transmit pin
        bcf     portc,7         ; clear USART receive pin
        bcf     rcif            ; clear USART receive interrupt flag

                ; keyboard interface 
        movlw   init_stop_count
        movwf   stop_count      ; set up # of stop bits in kbd data
        movlw   init_data_bits
        movwf   data_bits       ; set up # of actual shifts to make

        bsf     rp0             ; bank 1

                ; Alice 2 bus interface
        bcf     trisa,0         ; bit 0 port A is output
        bcf     trisa,1         ; bit 1 port A is output

                ; Alice 2 bus interface
        bsf     trise0          ; /RD is input
        bsf     trise1          ; /WR is input
        bsf     trise2          ; /CS is input

                ; Serial interface
        ; don't need to set trisc, default is 0xff (input)
        bsf     trisc, 6        ; transmit is output
        bsf     trisc, 7        ; receive is input

                ; Keyboard interface
        ; don't need to set trisb, default is 0xff (input)
        bsf     intedg          ; RB0/INT triggers on low-to-high

                ; Alice 2 bus interface
        bsf     pspie           ; enable PSP interrupts
        bsf     pspmode         ; enable PSP mode

                ; Serial interface
        movlw   baud_rate_code  ; default baud rate
        movwf   spbrg           ; set baud rate generator 
        bcf     sync            ; asynchronous serial
        bsf     txen            ; enable USART transmitter
        bcf     rp0             ; bank 0
        bsf     spen            ; enable USART receiver
        bsf     rp0             ; bank 1
        bsf     rcie            ; enable USART receive interrupt
        bcf     rp0             ; bank 0
        bsf     cren            ; enable USART receive next byte

                ; Alice 2 bus interface
        bsf     porta,0         ; don't interrupt CPU on IRQ 0
        bcf     porta,1         ; clear debug pin

                ; init Alice output queue
        movlw   outq_storage
        movwf   outq_head
        movwf   outq_tail
        clrf    outq_length

                ; init timer 1
        bsf     t1ckps1
        bsf     t1ckps0         ; set tmr1 prescaler to 8
        movlw   ten_hz_low
        movwf   tmr1l
        movlw   ten_hz_high
        movwf   tmr1h
        bsf     tmr1on

                ; Serial and Alice 2 bus interface
        bsf     rp0             ; bank 1
        bsf     peie            ; enable peripheral interrupts

                ; Timer interrupt
        bsf     tmr1ie          ; enable tmr1 interrupt

                ; Keyboard interface
        bcf     intf            ; clear RB0/INT interrupt flag
        bsf     inte            ; enable RB0/INT interrupt

        bsf     gie             ; enable all interrupts

        bcf     rp0             ; bank 0

loop
        goto    loop

interrupt
        ; save W and STATUS
        movwf   w_temp          ; save W
        swapf   status, w       ; save status in W
        bcf     rp0             ; bank 0
        movwf   status_temp     ; save status

        btfsc   tmr1if          ; test timer 1 flag
        goto    timer_int       ; if timer int, then go handle timer

        btfss   pspif           ; test PSP interrupt flag
        goto    not_psp_intr    ; not PSP

        bsf     rp0             ; bank 1
        btfss   ibf             ; did we get a write?
        goto    psp_was_read    ; nope, jump to read

        ; we got a write
        ; send on to serial 
        bcf     rp0             ; bank 0
        movf    portd, w        ; get byte
        movwf   txreg

        goto    end_psp_interrupt
                                ; return

timer_int
        bcf     tmr1on          ; turn off temporarily
        movlw   ten_hz_low
        movwf   tmr1l
        movlw   ten_hz_high
        movwf   tmr1h
        bsf     tmr1on          ; reset and turn on

        movlw   pic_tim_cmd
        call    send_byte       ; alert Alice of timer interrupt
                ; fall through to endtimer_interrupt

endtimer_interrupt
        bcf     tmr1if          ; clear TMR1 interrupt flag
        goto    finish_interrupt

psp_was_read
        call    check_queue     ; send next byte if there is one
        goto    end_psp_interrupt

not_psp_intr    ; either RB0/INT or RC/USART
        btfss   intf            ; skip next if is definitely INT interrupt
        goto    serial_intr     ; it's a byte from the serial port

        decfsz  data_bits, 1    ; skip next if done with data bits
        goto    got_kbd_bit     ; have a good bit on port B
        decfsz  stop_count, 1   ; skip next if done with keyboard cycle
        goto    more_stops      ; not done yet, have stop bits

got_kbd_byte
        movlw   pic_kbd_cmd     ; put the kbd cmd into the queue
        call    send_byte       ; send data to CPU
        movf    kbd_byte, w     ; get kbd byte
        call    send_byte       ; send data to CPU

        movlw   init_stop_count
        movwf   stop_count      ; set up # of stop bits in
        movlw   init_data_bits
        movwf   data_bits       ; set up # of actual shifts

        goto    end_rb0_interrupt
                                ; done with sending byte

more_stops                      ; only stop bits left
        clrf    data_bits
        incf    data_bits       ; set remaining data_bits to 1
                                ; next loop through dec's data_bits, it's 0,
                                ; skips to decfsz stop_count
        goto    end_rb0_interrupt
                                ; finish with rb0/int interrupt

got_kbd_bit                     ; shift in a bit from keyboard

        bsf     c               ; set carry
        btfss   portb, 1        ; if kbd data bit is set, skip next
        bcf     c               ; clear carry
        rrf     kbd_byte, 1     ; rotate data bit into MSB of kbd_byte
        ; fall through to end_rb0_interrupt

end_rb0_interrupt
        bcf     rp0             ; bank 0
        bcf     intf            ; clear RB0/INT interrupt flag
        goto    finish_interrupt
                                ; pop processor context

end_psp_interrupt
        bcf     rp0             ; bank 0
        bcf     pspif           ; clear PSP interrupt flag
        goto    finish_interrupt

serial_intr
        bcf     rp0             ; bank 0
        movlw   pic_ser_cmd     ; put the serial cmd into the queue
        call    send_byte       ; send to CPU
        movf    rcreg, w        ; get USART in
        call    send_byte       ; send to CPU
        ; fall through into end_ser_interrupt

end_ser_interrupt
        bcf     rp0             ; bank 0
        bcf     rcif            ; clear USART receive interrupt
        ; fall through to finish_interrupt

finish_interrupt
        ; restore W and STATUS
        swapf   status_temp, w  ; put old status in W
        movwf   status          ; put into status
        swapf   w_temp, 1       ; swap w_temp in place
        swapf   w_temp, w       ; put old W into W

        retfie

send_byte
        ; put byte that's in w into the out queue
        ; assumes interrupts are disabled (called from int routine)

                ; push on queue
        bcf     rp0             ; bank 0

        movwf   outq_byte_push

        movlw   outq_size
        subwf   outq_length, w
        btfsc   c
        return                  ; if q length >= size (overflow), then
                                ; return
        movf    outq_head, w
        movwf   fsr
        movf    outq_byte_push, w
        movwf   indf            ; *head = outq_byte_push

        incf    outq_head
        movf    outq_head, w
        andlw   outq_wrap_mask
        addlw   outq_storage
        movwf   outq_head       ; head = storage + (head - storage + 1) % size
                                ; i.e. head++

        incf    outq_length     ; length ++

        call    check_queue

        return


check_queue
        ; if there is a byte in the queue, get it and send it on to the
        ; Alice 2 through port D
        ; assumes interrupts are disabled (called from int routine)

        bsf     rp0             ; bank 1
        btfss   obf             ; skip if byte already on portd
        goto    do_another
        bcf     rp0             ; bank 0
        return

        bsf     porta, 0        ; turn off interrupt to CPU

do_another
        bcf     rp0             ; bank 0
        movf    outq_length     ; move to itself
        btfsc   z               ; skip if not zero
        return                  ; if length == 0 return

        movf    outq_tail, w
        movwf   fsr
        movf    indf, w
        movwf   outq_byte_pop   ; outq_byte_pop = *tail

        movwf   portd           ; send on to Alice
        bcf     pspif           ; clear PSP interrupt flag
        bcf     porta, 0        ; turn on interrupt to CPU

        incf    outq_tail
        movf    outq_tail, w
        andlw   outq_wrap_mask
        addlw   outq_storage
        movwf   outq_tail       ; tail = storage + (tail - storage + 1) % size
                                ; i.e. tail++

        decf    outq_length     ; length --

        return