;; ======================================================================== ;; ;; An attempt at a more complex serial driver ;; ;; ;; ;; This driver requires a pool of RAM to hold TX and RX buffers, as well ;; ;; as associated pointers, etc. ;; ;; ;; ;; Requires "cart.mac" macros for declaring storage. ;; ;; ======================================================================== ;; ;; ------------------------------------------------------------------------ ;; ;; UART0 Registers ;; ;; ------------------------------------------------------------------------ ;; CC3U0 PROC @@dll EQU $0F00 ; Divisor Latch LSB @@dlm EQU $0F01 ; Divisor Latch MSB @@thr EQU $0F00 ; Transmit Holding Register @@rbr EQU $0F00 ; Receive Buffer Register @@fcr EQU $0F02 ; FIFO Control Registers @@lsr EQU $0F03 ; Line Status Register @@lcr EQU $0F04 ; Line Control Register @@ter EQU $0F05 ; Transmit Enable Register @@PCLK EQU 15000000/16 ENDP ;; ------------------------------------------------------------------------ ;; ;; U0FCR Fields ;; ;; ------------------------------------------------------------------------ ;; U0FCR PROC @@tx_fifo_rst EQU $0004 ; TX FIFO Reset @@rx_fifo_rst EQU $0002 ; RX FIFO Reset @@fifo_enable EQU $0001 ; FIFO enable ENDP ;; ------------------------------------------------------------------------ ;; ;; U0LCR Fields ;; ;; ------------------------------------------------------------------------ ;; U0LCR PROC @@dlab EQU $0080 ; Enable access to divisor latches @@break EQU $0040 ; Send break @@par_n EQU $0000 ; No parity @@par_o EQU $0008 ; Odd parity @@par_e EQU $0018 ; Even parity @@par_0 EQU $0028 ; '0' parity @@par_1 EQU $0038 ; '1' parity @@stop_1 EQU $0000 ; 1 stop bit @@stop_2 EQU $0004 ; 2 stop bits @@char_5 EQU $0000 ; 5 bit characters @@char_6 EQU $0001 ; 6 bit characters @@char_7 EQU $0002 ; 7 bit characters @@char_8 EQU $0003 ; 8 bit characters @@8n1 EQU @@char_8 OR @@par_n OR @@stop_1 @@7e1 EQU @@char_7 OR @@par_e OR @@stop_1 @@7o1 EQU @@char_7 OR @@par_o OR @@stop_1 ENDP ;; ------------------------------------------------------------------------ ;; ;; U0LSR Fields ;; ;; ------------------------------------------------------------------------ ;; U0LSR PROC @@rdr EQU $0001 ; Receive Data Ready @@oe EQU $0002 ; Overrun Error @@pe EQU $0004 ; Parity Error @@fe EQU $0008 ; Framing Error @@bi EQU $0010 ; Break Interrupt (we've received a break) @@thre EQU $0020 ; Transmit Holding Register Empty @@temt EQU $0040 ; Transmitter EMpTy @@rxfe EQU $0080 ; Error in RX FIFO ENDP ;; ------------------------------------------------------------------------ ;; ;; U0TER Field ;; ;; ------------------------------------------------------------------------ ;; U0TER PROC @@txen EQU $0080 ; Transmit enable ENDP ;; ------------------------------------------------------------------------ ;; ;; Baud rates ;; ;; ------------------------------------------------------------------------ ;; MACRO cc3_baud x DECLE %x% / 2, ((CC3U0.PCLK + (%x%/2)) / %x%) ENDM CC3_BAUD PROC cc3_baud 115200 cc3_baud 57600 cc3_baud 38400 cc3_baud 19200 cc3_baud 9600 cc3_baud 4800 cc3_baud 2400 cc3_baud 1200 cc3_baud 600 cc3_baud 300 cc3_baud 110 @@rates EQU ($ - CC3_BAUD) / 2 ENDP ;; ======================================================================== ;; ;; Transmit and receive queues ;; ;; ======================================================================== ;; IF (DEFINED CC3_RXQ_DEPTH) = 0 CC3_RXQ_DEPTH EQU 128 ; must be power of 2 ENDI IF (DEFINED CC3_TXQ_DEPTH) = 0 CC3_TXQ_DEPTH EQU 128 ; must be power of 2 ENDI WORDVAR CC3_RXQ_WR WORDVAR CC3_RXQ_RD WORDARRAY CC3_RXQ, CC3_RXQ_DEPTH WORDVAR CC3_TXQ_WR WORDVAR CC3_TXQ_RD BYTEARRAY CC3_TXQ, CC3_TXQ_DEPTH BYTEVAR CC3_RXBUSY BYTEVAR CC3_TXBUSY CC3_RXQ_END EQU CC3_RXQ + CC3_RXQ_DEPTH CC3_TXQ_END EQU CC3_TXQ + CC3_TXQ_DEPTH ;; ======================================================================== ;; ;; CC3_PUMP_RX -- Receive as much as we can. ;; ;; ======================================================================== ;; CC3_PUMP_RX PROC ;; -------------------------------------------------------- ;; ;; Lock: Protect against reentrancy. Allows PUMP_RX to ;; ;; get called w/in an interrupt and outside an interrupt. ;; ;; -------------------------------------------------------- ;; ;; Set our busy flag. MVII #$8E, R0 MVI CC3_RXBUSY, R1 ; interrupt here will let other copy run and return here: OK MVO R0, CC3_RXBUSY ; Attempt to acquire lock. ; It may already be acquired. TSTR R1 ; Leave: We were already busy. BNEQ @@busy ;; -------------------------------------------------------- ;; ;; Receive code. ;; ;; -------------------------------------------------------- ;; MVI CC3_RXQ_WR, R4 MVI CC3_RXQ_RD, R3 SUBR R4, R3 ADDI #CC3_RXQ_DEPTH, R3 ; Room remaining in rx q BEQ @@rx_skip ANDI #CC3_RXQ_DEPTH-1,R4 ; \_ R4 points into rx q ADDI #CC3_RXQ, R4 ; / PSHR R3 ; remember init words remaining ;; Receive as many bytes as we can @@rx_loop: MVI CC3U0.lsr, R0 ; \ SARC R0, 1 ; |- check rx data ready BNC @@rx_done ; / SWAP R0 ; \_ merge received data XOR CC3U0.rbr, R0 ; / with its flags MVO@ R0, R4 CMPI #CC3_RXQ_END, R4 BNEQ @@rxq_ok SUBI #CC3_RXQ_DEPTH, R4 @@rxq_ok DECR R3 BNEQ @@rx_loop @@rx_done: SUB@ SP, R3 ; final - init = -received MVI CC3_RXQ_WR, R4 ; \_ old WR pointer - (-received) SUBR R3, R4 ; / MVO R4, CC3_RXQ_WR ; Store queue write pointer @@rx_skip: ;; -------------------------------------------------------- ;; ;; Unlock: Safe for other instances of CC3_PUMP_RX now. ;; ;; -------------------------------------------------------- ;; CLRR R0 ; \_ CC3_PUMP_RX no longer busy. MVO R0, CC3_RXBUSY ; / Remove lock. @@busy: JR R5 ENDP ;; ======================================================================== ;; ;; CC3_PUMP_TX -- Transmit as much as we can. ;; ;; ======================================================================== ;; CC3_PUMP_TX PROC ;; -------------------------------------------------------- ;; ;; Lock: Protect against reentrancy. Allows PUMP_TX to ;; ;; get called w/in an interrupt and outside an interrupt. ;; ;; -------------------------------------------------------- ;; ;; Set our busy flag. MVII #$8E, R0 MVI CC3_TXBUSY, R1 ; interrupt here will let other copy run and return here: OK MVO R0, CC3_TXBUSY ; Attempt to acquire lock. ; It may already be acquired. TSTR R1 ; Leave: We were already busy. BNEQ @@busy ;; -------------------------------------------------------- ;; ;; Transmit code. ;; ;; -------------------------------------------------------- ;; MVI CC3_TXQ_RD, R1 B @@tx_first @@tx_loop: MOVR R1, R2 ; Copy receive queue pointer INCR R1 ; Increment queue read pointer ANDI #CC3_TXQ_DEPTH-1,R2 ; Circular addressing w/in queue ADDI #CC3_TXQ, R2 ; Index into transmit queue MVO R1, CC3_TXQ_RD ; \_ Atomically store read pointer MVI@ R2, R0 ; / and read byte to transmit MVO R0, CC3U0.thr ; Send the byte @@tx_first: CMP CC3_TXQ_WR, R1 ; Anything more to send? BEQ @@tx_done MVI CC3U0.lsr, R0 ANDI #U0LSR.thre SHR 1,R0; Can we send anything? BNEQ @@tx_loop ; Nope: Leave @@tx_done: ;; -------------------------------------------------------- ;; ;; Unlock: Safe for other instances of CC3_PUMP_TX now. ;; ;; -------------------------------------------------------- ;; CLRR R0 ; \_ CC3_PUMP_TX no longer busy. MVO R0, CC3_TXBUSY ; / Remove lock. @@busy: JR R5 ENDP ;; ======================================================================== ;; ;; CC3_XMIT -- Send byte in R0, non-blocking ;; ;; Returns C=1 if queue is full. ;; ;; ======================================================================== ;; CC3_XMIT PROC MVI CC3_TXQ_RD, R1 MVI CC3_TXQ_WR, R2 SUBR R2, R1 CMPI #-CC3_TXQ_DEPTH, R2 BEQ @@full MOVR R2, R1 INCR R1 ANDI #CC3_TXQ_DEPTH-1,R2 ADDI #CC3_TXQ, R2 ; The following two updates are atomic wrt. to interrupts MVO@ R0, R2 ; Insert byte into queue MVO R1, CC3_TXQ_WR ; Update write pointer @@success: CLRC JR R5 @@full: SETC JR R5 ENDP ;; ======================================================================== ;; ;; CC3_XMITBLK -- Send byte in R0, blocking. Keeps trying to transmit ;; ;; until it succeeds. Calls CC3_SERPUMP if queue is full. ;; ;; ======================================================================== ;; CC3_XMITBLK PROC PSHR R5 @@xmit_loop CALL CC3_XMIT BNC @@xmit_done PSHR R0 CALL CC3_PUMP_TX PULR R0 B @@xmit_loop @@xmit_done: PULR PC ENDP ;; ======================================================================== ;; ;; CC3_RECV -- Receive byte in R0. Status in R1. Non-blocking. ;; ;; Returns C=1 if no character ready. ;; ;; ======================================================================== ;; CC3_RECV PROC MVI CC3_RXQ_RD, R1 CMP CC3_RXQ_WR, R1 BEQ @@empty MOVR R1, R2 INCR R1 ANDI #CC3_RXQ_DEPTH-1, R2 ADDI #CC3_RXQ, R2 ; The following two instructions are atomic wrt to interrupts MVO R1, CC3_RXQ_RD ; Update read pointer MVI@ R2, R0 ; Get word from receive queue MOVR R0, R1 ; Status in R1 ANDI #$FF, R0 ; Data in R0 SWAP R1 ; ANDI #$47, R1 ; Leave only error stat in R1 @@success: CLRC JR R5 @@empty: SETC JR R5 ENDP ;; ======================================================================== ;; ;; CC3_RECVBLK -- Receive byte in R0, blocking. Keeps trying to receive ;; ;; until it succeeds. Calls CC3_SERPUMP unconditionally. ;; ;; ======================================================================== ;; CC3_RECVBLK PROC PSHR R5 ; MVI CC3_RXQ_WR, R1 ; SUB CC3_RXQ_RD, R1 ; SLL R1, 2 ; SLL R1, 1 ; ADDI #$87, R1 ; MVO R1, $2EE MVI CC3_RXQ_WR, R1 SUB CC3_RXQ_RD, R1 CMPI #CC3_RXQ_DEPTH/2, R1 BGT @@no_pump @@recv_loop CALL CC3_PUMP_RX @@no_pump CALL CC3_RECV BC @@recv_loop @@recv_done: PULR PC ENDP ;; ======================================================================== ;; ;; CC3_SERINIT -- Init / reset the serial port. Sets it to 19200 8-N-1. ;; ;; ======================================================================== ;; CC3_SERINIT PROC CLRR R0 MVO R0, CC3_TXQ_RD MVO R0, CC3_TXQ_WR NOP MVO R0, CC3_RXQ_RD MVO R0, CC3_RXQ_WR CLRR R0 MVO R0, CC3_RXBUSY MVO R0, CC3_TXBUSY MVII #19200/2, R0 ; B CC3_SETBAUD ; fallthru ENDP ;; ======================================================================== ;; ;; CC3_SETBAUD -- Set the baud rate for the serial port. ;; ;; R0 is desired baud rate divided by 2, up to 115200. ;; ;; C=1 if baud rate is unsupported. ;; ;; ======================================================================== ;; CC3_SETBAUD PROC MVII #CC3_BAUD, R4 MVII #CC3_BAUD.rates, R1 @@loop: CMP@ R4, R0 BEQ @@got_it INCR R4 DECR R1 BNEQ @@loop ;; Didn't find it. Report error with C=1 SETC JR R5 @@got_it: MVII #$80, R0 ;\_ enable divisor access MVO R0, CC3U0.lcr ;/ MVI@ R4, R1 ; get baud rate from table MVO R1, CC3U0.dll ;\ SWAP R1 ; |- Set desired baud rate MVO R1, CC3U0.dlm ;/ MVII #$03, R1 ;\_ 8-N-1 format MVO R1, CC3U0.lcr ;/ MVII #$07, R1 ;\_ Enable, reset FIFOs MVO R1, CC3U0.fcr ;/ MVO R0, CC3U0.ter ; Enable transmitter CLRC JR R5 ENDP