;--------------------------------------------------------------------------- ; a simple isr for uart 16c550 - 65c816 ;--------------------------------------------------------------------------- ; ram equates ; spoutbuf .equ $015000 ; serial port output buffer spobufsiz .equ $1000 ; serial port output buffer size spinbuf .equ $016000 ; serial port input buffer spibufsiz .equ $2000 ; serial port input buffer size spxon .equ $11 ; serial port xon: clear pause spxoff .EQU $13 ; serial port xoff: set pause ; interrupt vector register ; ivec .equ $00fd37 ; read only ; uart 16C550 at $00fd20 - int0 ; uart_rxtx .equ $00fd20 ; DLAB=0 uart_ier .equ $00fd21 ; DLAB=0 uart_dll .equ $00fd20 ; divisor latch low, DLAB=1 uart_dlh .equ $00fd21 ; divisor latch high, DLAB=1 uart_iir .equ $00fd22 ; interrupt ident. reg., read only uart_fcr .equ $00fd22 ; FIFO ctrl reg., write only uart_lcr .equ $00fd23 ; line ctrl reg. uart_mcr .equ $00fd24 ; modem ctrl reg. uart_lsr .equ $00fd25 ; line status reg. uart_msr .equ $00fd26 ; modem status reg. uart_scp .equ $00fd27 ; scratchpad reg. ; serial port variables declared in page 0 ; spvstart .ds 0 ; start of var's used by serial port driver isptail .dw ; pointer to input buffer tail isphead .dw ; pointer to input buffer head osptail .dw ; pointer to output buffer tail osphead .dw ; pointer to output buffer head ispcnt .dw ; count of bytes in input buffer ospcnt .dw ; count of bytes in output buffer spmin .dw ; min. count for clear remote pause spmax .dw ; max. count for set remote pause spmode .db ; <7>: 0=no handshake, 1=handshake ; <6>: 0=software/1=hardware handshake ; <5>: uplink cable (rts -> dcd) ; <4>: 0=odd parity, 1=even parity ; <3>: 0=no parity, 1=parity as specified ; by bit <4> ; <2>: 2 stop bits instead of 1 stop bit ; <1:0> : baud rate ; 00 = 38,400 ; 01 = 57,600 ; 10 = 115,200 ; 11 = 230,400 spline .db ; copy of uart msr register ; <7>: dcd line status ; <6>: ri line status = 1 (not used) ; <5>: dsr line status ; <4>: cts line status ; <3>: dcd line changed ; <2>: ri line changed = 0 (not used) ; <1>: dsr line changed ; <0>: cts line changed sppause .db ; local/remote pause flags ; <7>: remote pause (sent an xoff or set rts=1) ; <6>: local pause (received an xoff or cts=1) spmask .db ; cts or dcd mask spout .db ; xon/xoff flag sptmp .db ; temp. byte sperr .db ; serial error ; <7>: any rx error ; <6>: input buffer empty ; <3>: brk error ; <2>: framing error ; <1>: parity error ; <0>: overrun error splst .db ; last received byte that raise error spvlen .equ $ - spvstart ;--------------------------------------------------------------------------- ; interrupt nr. 0 handler - uart 16c550 ;--------------------------------------------------------------------------- ; vectorized interrupt table ; vint: .dw int0sr, int1sr, int2sr, int3sr .dw int4sr, int5sr, int6sr, int7sr ; irq handler ; irqhdlr: c16 ; all register's 16 bits pha ; save c in stack phx ; save x in stack phy ; save y in stack phd ; save dpr in stack phb ; save dbr in stack phk ; set dbr = pbr = $00 plb lda #0 tcd ; set dpr = $0000 c8 ; switch processor to 8 bits ldx .abs.ivec ; read irq vector (x2) jsr (vint,x) ; exec interrupt routine c16 ; switch processor to 16 bits plb ; restore dbr pld ; restore dpr ply ; restore y plx ; restore x pla ; restore a rti ; restore p and return .longa off .longi off int0sr: lda .abs.uart_iir ; read interrupt identification reg. lsr a ; if bit <0> = 1 no interrupt bcs ?end ; no interrupt pending and #0000011b ; mask on priority code ; rx timeout interrupt and rx data available interrupt use the same routine ; asl a tax jsr (?tbl,x) ; jump to right routine bra int0sr ; check again iterrupt ?end: rts ?tbl: .dw uartmsr, uarttx, uartrx, uartlsr ; this interrupt is raised either when rhr is full or when rx fifo reach the ; programmed trigger level ; uartrx: xy16 ; switch x/y to 16 bit ldx isphead ; x = pointer to input buffer head ldy ispcnt ; y = count of bytes in input buffer ?rxl: lda .abs.uart_lsr ; read lsr for check rx error asl a ; thre bit (tx hold reg. empty) to bit 6 sta sptmp lsr a lsr a ; dr (data ready) bit in cf bcc ?done ; no more rx data available xba lda .abs.uart_rxtx ; get top fifo rx data xba ; a = error, b = rx data and #$0f ; mask error(s) bne ?err ; some error(s): set error reg. ; if software handshake is active we will filter xon/xoff characters, ; that set/reset local pause ; xba ; a = rx data bit spmode ; test for active handshake bpl ?chk ; no handshake: goto to check input queue bvs ?tst ; hardware handshake (check queue overflow) cmp #spxon ; received an xon control byte? bne ?xoff ; no, check if received an xoff lda #$40 ; xon clear local pause flag trb sppause ; bit<6> = 0 -> local pause off (resume tx) bra ?rxl ; discard received data ?xoff: cmp #spxoff ; received an xoff control byte? bne ?tst ; no, so check queue overflow lda #$40 ; xoff set local pause flag tsb sppause ; bit<6> = 1 -> local pause on (will stop tx) bra ?rxl ; discard received data ?tst: cpy spmax ; check input buff. for remote pause condition bcc ?str ; below guard limit: store data ; we ask the remote terminal to put itself in pause ; bit sppause ; remote pause is already on ? bmi ?chk ; yes, so check input buffer xba ; b = received data bit spmode ; test handshake type bvs ?rtsh ; bit 6=1 -> hardware handshake so set rts=1 ; we send now xoff if possible ; lda #spxoff sta spout ; xoff deffered until thr is empty bit sptmp ; bit 6 = 1 mean thr is empty bvc ?ei ; we can't send now xoff lda #spxoff sta .abs.uart_rxtx ; send now stz spout ?ei: lda #00000010b ; force enable tx interrupt tsb .abs.uart_ier bra ?srp ; set remote pause flag ; hardware handshake: we put rts line high for ask remote pause ; ?rtsh: lda #00000010b trb .abs.uart_mcr ; set rts = high ?srp: lda #$80 ; set remote pause tsb sppause ; bit 7=1 -> remote pause on xba ; a = received data ?chk: cpy #spibufsiz ; left room in input buffer? bcc ?str ; yes, store received byte lda #$40 ; set bit 6: rx overflow ?err: ora #$80 ; set error register sta sperr sec ; cf = error flag bra ?done ; discard received data & exit ?str: sta >spinbuf,x ; store received data iny ; update bytes count inx ; update rx head pointer am16 ; switch a/m to 16 bit txa and #(spibufsiz-1) ; circular queue tax am8 ; switch back a/m to 8 bit bra ?rxl ; get more rx data if available ?done: stx isphead ; update pointer to input buffer head sty ispcnt ; update count of bytes in input buffer c8 ; switch cpu to 8 bit bcs uarterr ; handle rx error rts ; this interrupt is raised when receive a byte with some errors ; after rx error, rx & lsr interrupt are disabled ; uartlsr: lda #0 xba ; b = 0 lda .abs.uart_lsr ; read lsr for check rx error & clear lsr int. lsr a bcc ?no ; no rx data available xba lda .abs.uart_rxtx ; get top fifo rx data xba ; a = error, b = rx data ?no: and #$0f ; mask error(s) ora #$80 ; set rx error bit sta sperr ; set status register uarterr: xba ; a = last received data with error sta splst lda #00000101b ; disable lsr & rx interrupt trb .abs.uart_ier lda #10000011b ; clear rx fifo sta .abs.uart_fcr rts ; this interrupt is raised when one of the modem lines change state ; here just dsr, cts and dcd are used: dsr signaling remote disconnession, ; cts (or dcd) for hardware handshake (put local tx in pause) ; uartmsr: lda .abs.uart_msr ; read msr register: clear interrupt sta spline ; update line status bit spmode bpl ?end ; no handshake: ignore line changed bvc ?end ; software handshake: ignore line changed ; cts line (std. control) or dcd line (uplink cable) are used from ; remote terminal for put in "pause" the local tx terminal ; and spmask ; check if dcd or cts changed status tax ; save result and #$0f ; transition on dcd or cts line? beq ?end ; no: exit txa ; check level ldx #$40 ; bit for set/reset local pause flag ldy #00000010b ; bit for enable/disable tx interrupt and #$f0 ; if dcd or cts changed state to "false" (level = 0) then set local pause ; beq ?slp ; if dcd or cts changed state to "true" (level = 1) then clear local pause ; txa trb sppause ; clear local pause bit tya ; re-enable tx interrupt tsb .abs.uart_ier rts ?slp: txa tsb sppause ; set local pause bit tya ; disable tx interrupt trb .abs.uart_ier ?end: rts ; this interrupt is raised either when thr is empty ; or when tx fifo is empty ; uarttx: lda #00000010b ; disable further tx interrupt trb .abs.uart_ier ldy #16 ; default: send 16 bytes ldx spout ; pending an xon/xoff sending? beq ?cnt ; no stz spout ; clear xon/xoff flag dey ; send xon/xoff + 15 bytes stx .abs.uart_rxtx ; send xon/xoff lda #$80 ; remote pause bit cpx #spxon beq ?clr ; xon: clear remote pause flag tsb sppause ; xoff: set remote pause flag bra ?cnt ?clr: trb sppause ?cnt: bit sppause ; local pause is set? bvs ?end ; yes: cannot send nothing (tx int. disabled) sty sptmp ; count of bytes to send xy16 ; switch x&y to 16 bit ldy ospcnt ; are here bytes to send? beq ?done ; no: exit with tx interrupt disabled ldx osptail ; x = output buffer tail ; now get next byte available from output buffer and send to tx fifo ; ?txl: lda >spoutbuf,x ; get next byte to send sta .abs.uart_rxtx ; send to tx fifo inx ; update tail pointer am16 ; switch a/m to 16 bit txa and #(spobufsiz-1) ; circular queue tax am8 ; switch a/m back to 8 bit dey ; update count beq ?upd ; nothing else to send dec sptmp bne ?txl ; loop: send more bytes to tx fifo ?upd: stx osptail ; update tail pointer sty ospcnt ; update output buffer count lda #00000010b ; re-enable tx interrupt tsb .abs.uart_ier ?done: c8 ; switch cpu back to 8 bit ?end: rts ;--------------------------------------------------------------------------- ; serial port user callable routines ;--------------------------------------------------------------------------- ; max. free room (in bytes) in input buffer before to set remote pause ; nguardl .equ $0200 ; hardware handshake nguardh .equ $0400 ; software handshake ; min. free room (in bytes) in input buffer for clear remote pause ; nfreel .equ $0800 ; hardware handshake nfreeh .equ $0200 ; software handshake .bankf8 ; open serial port ; ; in: a = mode ; <7>: 0=no handshake, 1=handshake ; <6>: 0=software/1=hardware handshake ; <5>: uplink cable (rts -> dcd) ; <4>: 0=odd parity, 1=even parity (ignored if bit <3> = 0) ; <3>: 0=no parity, 1=parity as specified by bit <4> ; <2>: 2 stop bits instead of 1 stop bit ; <1:0> : baud rate ; 00 = 38,400 ; 01 = 57,600 ; 10 = 115,200 ; 11 = 230,400 ; spopen: sei ; disable interrupt jsr spres ; reset serial port sta spmode ; save mode ldx #$80 ; set dlab = 1 in lcr stx .abs.uart_lcr ; ...for access dll/dlh regs. tay ; y = mode and #00000011b ; baud rate selection tax lda >?div,x ; select divisor sta .abs.uart_dll ; set low divisor latch stz .abs.uart_dlh ; set high divisor latch = 0 lda >?fcr,x sta .abs.uart_fcr tya ; a = mode and #00011100b ; mask on bits 4,3 & 2 (parity mode & stop) ora #00000011b ; 8 bits data word length, 2 or 1 stop bits sta .abs.uart_lcr ; setup ldx #$88 ; dcd line test tya ; a = mode asl a asl a ; nf = bit <5> bmi ?set ; uplink cable (rts -> dcd) ldx #$11 ; standard handshake (rts -> cts) ?set: stx spmask bit spmode c16 ; switch cpu to 16 bit ldx #nguardl ; set limits ldy #nfreel bvs ?lim ; hardware handshake limits ldx #nguardh ; hardware handshake limits ldy #nfreeh ?lim: sty spmin stx spmax sec lda #spibufsiz sbc spmax sta spmax c8 ; switch cpu back to 8 bit lda .abs.uart_msr ; get line status sta spline ; update line status lda #00000011b ; set rts = dtr = low sta .abs.uart_mcr ldx .abs.uart_rxtx ; clear any pending interrupt ldx .abs.uart_lsr ldx .abs.uart_iir lda #00001111b ; enable all uart interrupts sta .abs.uart_ier cli ; enable interrupt rts ; 38.400, 57.600, 115.200 230.400 ?div: .DB 6, 4, 2, 1 ; fifo trigger level (all baud rate = 14) ?fcr: .db $c7, $c7, $c7, $c7 ; close serial port ; spclose: sei jsr spres cli rts ; reset serial port ; spres: stz .abs.uart_lcr ; access to ier register stz .abs.uart_ier ; disable all uart interrupts stz .abs.uart_mcr ; rts = dtr = high stz .abs.uart_fcr ; disable fifo ldx .abs.uart_rxtx ; clear any pending interrupt ldx .abs.uart_lsr ldx .abs.uart_iir ldx #spvlen-1 ; clear d.p. var's used by serial driver ?clr: stz spvstart,x dex bpl ?clr ldx #10000111b ; enable fifo, reset rx/tx fifo, trigger = 8 stx .abs.uart_fcr rts ; get next available byte from input buffer ; ; exit: a = byte (if available) ; zf = 1 if no byte available ; cf = 1 if error (b = error code) ; spget: sei ; disable interrupt xy16c ; switch x/y to 16 bit & clear carry ldx ispcnt ; how much available bytes in input buffer? bne ?get ; input buffer is empty: we check now if an error is pending ; the isr disable further rx interrupt first time an error occur ; sperr hold error code, while splst hold last received byte ; lda sperr beq ?exit ; exit with cf = 0, zf = 1: no byte available sec ; error flag xba ; b = error code lda splst ; a = last received byte (with error) bra ?exit ; exit with cf = 1 ; we ignore any pending error until the input buffer is not empty ; now we get next available byte from input buffer ; ?get: dex ; update counter stx ispcnt ldx isptail ; get pointer to next available byte lda >spinbuf,x ; get next available byte inx ; update input buffer tail pointer cpx #spibufsiz ; buffer overflow? bcc ?upd ; no ldx #0 ; x = 0: circular queue ?upd: stx isptail bit spmode ; handshake is active? bpl ?done ; no ; if handshake is active and remote pause is set, we must check the ; number of character in input buffer for clear a remote pause (if any) ; bit sppause ; remote pause is set? bpl ?done ; no ldx ispcnt cpx spmin ; below the min. that clear remote pause? bcs ?done ; no: we cannot clear remote pause now xba ; b = received byte bit spmode bvc ?sh ; software handshake ; hardware handshake: we clear remote pause setting rts low ; that restart remote terminal tx ; lda #00000010b ; rts control bit tsb .abs.uart_mcr ; set rts = low bra ?crp ; clear remote pause flag ; software handshake: we send an 'xon' control byte ; that restart remote terminal tx ; ?sh: lda #spxon sta spout ; xon sending is deffered lda #00100000b ; the/fifo is empty? bit .abs.uart_lsr beq ?ei ; enable tx int., but keep remote pause set lda spout sta .abs.uart_rxtx ; send now xon stz spout ; clear remote pause flag and re-enable tx interrupt ; ?crp: lda #$80 trb sppause ; reset bit <7> ?ei: lda #00000010b ; tx interrupt enable bit tsb .abs.uart_ier ; enable tx interrupt xba ; a = received byte ; we finish here: return cf = 0 & zf = 0 & a = received byte ; ?done: rep #pzflag.or.pcflag ?exit: xy8 ; switch back x/y to 8 bit cli ; re-enable interrupt rts ; send byte to serial port output buffer ; ; entry: a = byte to send ; ; exit: a = sent byte ; b = line status ; cf = 1 if output buffer overflow ; vf = local pause bit status ; nf = remote pause bit status ; spput: sei ; disable interrupt xy16c ; switch x/y to 16 bit & clear carry ldx ospcnt ; count of bytes in output buffer cpx #spobufsiz ; output buffer is full? bcs ?done ; yes - exit with error flag inx ; update output buffer count stx ospcnt ldx osphead ; next available index in output room sta >spoutbuf,x ; store byte in output buffer inx ; bump pointer cpx #spobufsiz bcc ?upd ldx #0 ; circular queue ?upd: stx osphead ; update output buffer pointer clc ; no error ?done: xy8 ; switch back x/y to 8 bit xba ; b = sent byte lda #00000010b ; tx interrupt enable bit tsb .abs.uart_ier ; enable tx interrupt lda spline xba ; b = line status, a = sent byte bit sppause ; nf = remote pause, vf = local pause cli ; re-enable interrupt rts ; cf = 1 if output buffer overflow