;
; 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