BigDumbDinosaur wrote:
I think I've licked the problems with getting the QUART to do what it's told...The next step will be to get all of that TIA-232 mish-mash working via interrupts.
Just about the time you think you have all your water pumps working and the swamp is starting to drain is when the 'gators show up and start snapping at your
glutei maximi.
The interrupt system of the 28C94 QUART is a very different different beast than that of the 28L92 DUART that I use in POC V1.1. On the surface, it appears that it should work in the same fashion, but it's not even close. It almost seems as though the interrupt arbitration system with which the 28C94 was endowed was added as an afterthought, much as security was added to Microsoft Windows as an afterthought. It's just plain ugly.
What is also very different is the quality of the respective data sheets. The 28L92's data sheet is reasonably well organized and lucid, with a good discussion of how events in the device cause interrupts. I had no difficulty in understanding the 'L92 data sheet and writing a device driver that has proved to be efficient and stable.
Not so with the 28C94's data sheet, especially the parts that discuss the interrupt features. The way that stuff is written makes it seem as though someone had a few too many beers as they were working on the documentation. The terminology is bizarre, e.g., interrupt "bids". Is this a discussion about computer hardware or an auction at Christies? The data sheet does a poor job of explaining the sequence in which things have to happen in order for an interrupt to be properly serviced, so much so that I am no closer right now to getting a driver working than I was a month ago. In other words, I've got this functional computer sitting here with no software to make it go.
The increase in the number of serial channels in POC V2 necessarily increases the complexity of the code, and I'm sure my problems are not solely because a drunken monkey with a typewriter is in charge of doing data sheets at NXP. So I've decided to take to heart the old maxim "in order to get to the future one must look to the past." So what I am going to do is test most of the interrupt part of the driver on POC V1.1. A key change from the driver that I originally wrote for V1.1 is the use of indexed indirect pointers to access the UART registers. Indirection wasn't necessary with V1.1 due to there only being two channels. With the QUART, it was clear that the driver would have to make better use of indirection in order to avoid having a big blob of inefficient code.
The "new and improved" driver makes use of indexed indirect addressing, that is
(<dp>,X), using direct page pointers that are set up during POST. This is a case of a simple tradeoff: I'm consuming more direct page locations so I can use smaller and tighter code in the interrupt service routine (ISR), which is where I need to gain execution efficiency. As one or two code fragments may be worth several paragraphs of explanation, below is the part of the ISR that processes incoming data flow.
In the comments, "circular FIFO" refers to where in RAM incoming or outgoing data are stored—the foreground part of the driver is in charge of pulling data from the receiver circular FIFO and giving it to an application looking for input. Similarly, a foreground routine is responsible for placing outgoing data into the transmitter circular FIFO.
"RxD" is shorthand for "receiver" and "RxD FIFO" refers to the FIFO in the UART that holds incoming data until the UART can be serviced. "TxD" is shorthand for "transmitter" and there is, of course, a "TxD FIFO." NXP UARTs are organized into one, two or four "blocks" (depending on the device) and each block has two independent communications channels (excepting the single-channel UARTs, such as the 28L91). An interrupt status register is present in each block and is where both channels' receive and transmit interrupt bits are accessed. Hence accessing UART registers involves both block-level and channel-level reads and writes.
Code:
;UART RECEIVER IRQ PROCESSING
;
iirq01 shortr
lda #n_nxpblk-1 ;start block number
;
;
; block processing loop...
;
.iirq100 pha ;save block number (0,1,2...)
asl
sta tiaiblk ;set block offset (0,2,4...)
lda #n_nxpch-1 ;start channel number
;
;
; channel processing loop...
;
.iirq110 pha ;save channel number (0 or 1)
clc
adc tiaiblk ;generate channel...
tay ;index (0,1,2,3...)
ldx tiaiblk ;get block offset
lda (tiaisr,x) ;read block IRQ status
and nxprqtab,y ;this channel's RxD interrupting?
beq .iirq130 ;no, skip this channel
;
tya ;copy channel index (0,1,2,3...)
asl ;now channel offset (0,2,4,6...)
tax ;X marks the spot!
;
;
; FIFO processing loop...
;
.iirq120 lda (tiasr,x) ;get channel status
bit #nxprxdr ;RxD FIFO empty?
beq .iirq130 ;yes, done with channel
;
lda (tiafif,x) ;load datum from...
tay ;channel & hold it
lda tiaputrx,x ;circular FIFO 'put' pointer
ina ;bump
cmp tiagetrx,x ;circular FIFO 'get' pointer
beq .iirq120 ;no room, discard datum
;
tya ;recover datum &...
sta (tiaputrx,x) ;store in circular FIFO
inc tiaputrx,x ;new circular FIFO 'put' pointer
bra .iirq120 ;get data more if available
;
; ...end of RxD FIFO processing loop
;
.iirq130 pla ;get current channel
dea ;all channels serviced?
bpl .iirq110 ;no, do next channel
;
; ...end of channel processing loop
;
pla ;get current block
dea ;all blocks serviced?
bpl .iirq100 ;no, do next block
;
; ...end of block processing loop
In the instruction
sta (tiaputrx,x), the pointers at
tiaputrx refer to the circular FIFOs into which incoming data are stored—the X-register selects which pointer to use according to the channel being processed. Only the least significant byte (LSB) of the active pointer is bumped after a datum has been stored, making for very succinct code. This arrangement, of course, assumes that each circular FIFO is 256 bytes in size. Simple masking makes it possible to use smaller circular FIFOs (128 bytes, for example, is what I will use in POC V2) or larger ones, although bigger isn't necessarily better.
Note that the serious work gets done in the FIFO processing loop. The 28L92 in POC V1.1 has 16-deep FIFOs, so up to 16 bytes can be retrieved and stored per interrupt. The code simply keeps checking a bit in the channel status register to see if data remains in the RxD FIFO. When the bit is cleared, the RxD FIFO is empty and the ISR can move on to something else.
The transmit code is very similar in principle. Only the pointers and data direction change.
Code:
;UART TRANSMITTER IRQ PROCESSING
;
lda #n_nxpblk-1 ;start block number
;
;
; block processing loop...
;
.iirq200 pha ;save block number
asl ;generate & save...
sta tiaiblk ;block index
lda #n_nxpch-1 ;start channel number
;
;
; channel processing loop...
;
.iirq210 pha ;save current channel number
clc
adc tiaiblk ;compute...
tay ;channel index
ldx tiaiblk ;get block index
lda (tiaisr,x) ;get block IRQ status
and nxptqtab,y ;TxD interrupting?
beq .iirq240 ;no, skip this channel
;
tya ;copy channel index
asl ;now a channel offset
tax ;0,2
;
;
; TxD FIFO processing loop...
;
.iirq220 lda tiagettx,x ;circular FIFO 'get' pointer
cmp tiaputtx,x ;circular FIFO 'put' pointer
beq .iirq230 ;circular FIFO is empty
;
lda (tiasr,x) ;get channel status
bit #nxptxdr ;TxD FIFO full?
beq .iirq240 ;yes, done for now
;
lda (tiagettx,x) ;read circular FIFO &...
sta (tiafif,x) ;write TxD FIFO
inc tiagettx,x ;new 'get' pointer
bra .iirq220
;
.iirq230 lda #nxpcrtxd ;tell UART to...
sta (tiacr,x) ;disable transmitter
lda tiatstab,y ;set a flag saying the...
tsb tiatxst ;transmitter is disabled
;
; ...end of TxD FIFO processing loop
;
.iirq240 pla ;get current channel
dea ;all channels serviced?
bpl .iirq210 ;no, do next channel
;
; ...end of channel processing loop
;
pla ;get current block
dea ;all blocks serviced?
bpl .iirq200 ;no, do next block
;
; ...end of block processing loop
The wrinkle with the transmit code is that when there is no data in the circular FIFO the transmitter must be taken off line in some fashion so it doesn't keep bugging the MPU with interrupts—the system will otherwise deadlock. In NXP UARTs, this can be accomplished by squelching the transmitter interrupt or disabling the transmitter itself. Of the two, the latter is more efficient, as it is a simple write to a channel register. As part of this feature, a bit field in direct page maintains a flag for each channel's transmitter status so an offline transmitter can be placed on line when new data is ready for transmission. The
TRB and
TSB instructions come in handy for manipulating this bit field.
It all works on paper!
———————————————————————————
EDIT: I tested the above code in POC V1.1 and it works without a hitch. It does not work in POC V2.0, which means I still lack full understanding of how the QUART generates interrupts.