BigDumbDinosaur wrote:
BigDumbDinosaur wrote:
As I earlier posted, I slept on the PCB design for revsion 1 and decided I could do a better job of compacting the layout...I may make some further changes to the board layout, but I think this is substantially like what it will be when it is built.
I made some minor detail changes to some trace routing and have called it a done deal. The files for the below layout will be sent to the board house tomorrow.
While waiting for the new PCBs for POC V2.1 to arrive, I decided to put some cogitation time into the firmware for this contraption, especially the TIA-232 driver. POC V2.1 will use two NXP 28L92 DUARTs to act as a virtual QUART (vQUART), which creates an interesting programming challenge. The DUARTs are
$0100 apart in the I/O map, which means the chip registers that are routinely accessed by the driver are not contiguous as they are in the real QUART, in which all four channel register sets are uniformly spaced at
$08 intervals in the device's address space. This means the driver's approach to accessing the vQUART has to be different than accessing a real QUART.
Since POC V1.1 also has a 28L92 and is working hardware, I decided to use it as the guinea pig for testing my new-and-improved driver. The first step was to figure how to arrange for vQUART register access using a simple, zero-based channel index—channel A's index would be 0, channel B's would be 1, and so forth. It was soon patent that a direct page pointer table would work well in this application, the table arranged so some basic indexing could select the correct register in the correct DUART, as well as the location in RAM (the "circular FIFOs," aka "CFIFOs") into which incoming data would be stored and from which outgoing data would be retrieved.
¹A little thought suggested that the pointer table be organized into a "register table" for accessing the vQUART and a "CFIFO table" for accessing the CFIFOs. Here's what the register table looks like as implemented in POC V1.1's firmware:
Code:
02315 000E tiasr =tdsecct+s_tdcnt ;UART channel status
02316 0012 tiacr =tiasr+s_nxpchp ;UART channel command
02317 0016 tiafif =tiacr+s_nxpchp ;UART channel FIFO
02318 001A tiaisr =tiafif+s_nxpchp ;UART IRQ status
As there are two channels in POC V1.1's DUART, there are two sets of pointers associated with each of the above locations—the symbol
S_NXPCHP indirectly defines the number of channels. In POC V2.1, the vQUART will require that there be four sets of pointers associated with each of the above locations.
During POST, these pointers are loaded from a static data table that contains a list of DUART register absolute addresses. For example, location
TIASR points at
$D001, which is the status register for channel A of the DUART. Location
TIASR+2 points at
$D009, which is the status register for channel B of the DUART. In the same vein,
TIAFIF points at
$D003, which is the communications FIFO for channel A—the register through which incoming data is read and outgoing data is written.
TIAFIF+2 points at
$D00B, which is the communications FIFO for channel B.
The implementation in POC V2.1 will extend this pattern to account for the second DUART, which is wired to channels C and D. For example,
TIASR+4, which is the status register for channel C, will point at
$D101 and
TIASR+6, which is the status register for channel D, will point at
$D109.
The DUART only has one interrupt status register (DISR), which is wired to both channels, as well as to other features in the device, such as the counter/timer. Despite this, two pointers are used to access the DISR, which means
TIAISR and
TIAISR+2 both point at the DISR at
$D005. In the case of POC V2.1 and its twin DUARTs, there will be two DISRs and therefore
TIAISR+4 and
TIAISR+6 (channels C and D, respectively) will point at the second DUART's DISR, which will be accessible at
$D105. As will be seen, this little bit of redundancy makes for more efficient programming in the interrupt service routine part of the TIA-232 driver.
The CFIFO pointer table points to the CFIFOs, of which there are two per DUART channel, one for incoming data and the other for outgoing data. As the name suggests, each CFIFO is a "first-in, first-out" or queue data structure (compare that to a LIFO or "last-in, first-out" structure, which is usually a stack). Each CFIFO has two pointers associated with it, one pointing at where the next datum will be stored (called the "put" pointer) and the other pointing at where in the CFIFO from which the next datum will be retrieved (called the "get" pointer). Although the pointers are 16 bits, only the least significant byte (LSB) is manipulated as the CFIFO is accessed. The LSB simply wraps when the pointer is incremented while pointing at the far end of the CFIFO, which is why the CFIFO is "circular."
Here's what the CFIFO pointer table looks like in POC V1.1's firmware:
Code:
02319 001E tiagetrx =tiaisr+s_nxpchp ;RxD CFIFO 'get'
02320 0022 tiaputrx =tiagetrx+s_nxpchp ;RxD CFIFO 'put'
02321 0026 tiagettx =tiaputrx+s_nxpchp ;TxD CFIFO 'get'
02322 002A tiaputtx =tiagettx+s_nxpchp ;TxD CFIFO 'put'
As there are two channels in the DUART, there are two sets of pointers associated with each of the "get" and "put" arrays.
During POST, the CFIFO pointers are initialized with the base addresses of the CFIFOs to which they point. For example,
TIAGETRX will be initialized with
$CC00 and
TIAGETRX+2 will be initialized with
$CD00 (each CFIFO is 256 bytes in size in POC V1.1). POST will also initialize the "put" pointers to the same addresses as the corresponding "get" pointers. The "get" pointer is postincremented by the driver when a datum is fetched from the CFIFO. If prior to the fetch operation, the "get" pointer is the same as the "put" pointer the driver will interpret it to mean the CFIFO is empty.
When storing a datum into a CFIFO, the driver will preincrement the "put" pointer and then compare it to the "get" pointer. If the two are equal it is interpreted to mean the CFIFO is full. Code fragments will illustrate this process.
In addition to the above pointer tables, there is a one byte bit field in direct page that is used to track the status of the DUART transmitters:
Code:
02323 002E tiatxst =tiaputtx+s_nxpchp ;TxD status bit field
Each bit corresponds to a channel's transmitter, with a set bit indicating that the transmitter is disabled, the normal condition when no data is available for transmission. Bit 0 corresponds to channel A, bit 1 corresponds to channel B, etc. This bit field doesn't have to be in direct page, but of course access to it will be slightly faster than if it is in absolute memory.
In order to access any DUART register or CFIFO the 65C816's X-register is used to index into the appropriate pointer table. For example, to read the status register of DUART channel B:
Code:
lda #1 ;corresponds to channel B
asl a ;convert index to offset
tax ;.X is the main index register
lda (tiasr,x) ;read channel status
Similarly, to write a datum to DUART channel D's communications FIFO:
Code:
pha ;protect datum
lda #3 ;corresponds to channel D
asl a ;convert index to offset
tax
pla ;recover datum &...
sta (tiafif,x) ;write to output
This use of indexed indirect addressing is pervasive in the driver, as will be seen in some code samples.
The following code, which is executed when it has been determined that the DUART has interrupted, illustrates how incoming data flow from the DUART is put into the corresponding CFIFOs:
Code:
04184 ;DUART RECEIVER IRQ PROCESSING
04185 ;
04186 E2A5 A0 01 iirq0200 ldy #n_nxpch-1 ;starting channel index
04187 ;
04188 ;
04189 ; start of channel processing loop...
04190 ;
04191 E2A7 98 .0000010 tya ;copy channel index
04192 E2A8 0A asl a ;channel pointer offset
04193 E2A9 AA tax
04194 E2AA A1 1A lda (tiaisr,x) ;get channel IRQ status
04195 E2AC 39 2C F8 and nxprqtab,y ;RxD interrupting?
04196 E2AF F0 1C beq .0000030 ;no, skip this channel
04197 ;
04198 E2B1 A9 40 lda #nxpcresr ;clear an RxD...
04199 E2B3 81 12 sta (tiacr,x) ;overrun error (really bad kludge)
04200 ;
04201 E2B5 A1 0E .0000020 lda (tiasr,x) ;get channel status
04202 E2B7 89 01 bit #nxprxdr ;UART FIFO empty?
04203 E2B9 F0 12 beq .0000030 ;yes, done with channel
04204 ;
04205 E2BB A1 16 lda (tiafif,x) ;load datum from...
04206 E2BD EB xba ;channel & hold it
04208 E2BE B5 22 lda tiaputrx,x ;CFIFO 'put' pointer
04209 E2C0 1A inc a ;bump
04210 E2C1 D5 1E cmp tiagetrx,x ;CFIFO 'get' pointer
04211 E2C3 F0 F0 beq .0000020 ;no room in CFIFO, discard datum
04212 ;
04213 E2C5 EB xba ;expose datum &...
04215 E2C6 81 22 sta (tiaputrx,x) ;store in CFIFO
04216 E2C8 EB xba ;expose adjusted pointer &...
04218 E2C9 95 22 sta tiaputrx,x ;save
04219 E2CB 80 E8 bra .0000020 ;loop
04220 ;
04221 E2CD 88 .0000030 dey ;all channels serviced?
04222 E2CE 10 D7 bpl .0000010 ;no
04223 ;
04224 ; ...end of channel processing loop
As can be seen, the Y-register is the channel index. Each pass through the loop results in a pointer offset being generated from the current channel index—in theory, the algorithm is extensible to any number of channels.
The 28L92 has 16-deep receiver FIFOs and is configured to only interrupt if a FIFO is full, or if a datum is in a FIFO and has not been retrieved within 64 bit intervals after its arrival, which is a period of approximately 556 microseconds at 115.2Kbps. This "RxD watchdog" feature is intended to prevent the accumulation of "stale" data in the FIFO, such as might happen if the user only strikes one key. Once it has been determined that the channel's receiver has interrupted, the loop bounded by labels
.0000020 (lines 04201-04219, inclusive) repeatedly reads from the FIFO as long as the DUART indicates it has data.
Notice how starting at line 04205, the next datum is fetched from the DUART before the CFIFO pointers are tested to determine if there is room in the CFIFO—the datum will be discarded if there is no room. The alternative method would be to first test the CFIFO pointers and only if the CFIFO has room, read the DUART. The problem with doing so is once the DUART's FIFO has filled with data, subsequent data will be refused and RTS will be deasserted on the receipt of the next start bit. If the remote station fails to immediately respond to RTS an overrun error will occur, which must be cleared in order to resume reception. Hence the DUART is read regardless of whether or not the CFIFO has room. As a precaution, the channel is told to clear the overrun error flag (which sets a bit in the interrupt status register) each time the channel is serviced, an ugly kludge, since overrun errors should be reported to higher level functions.
A similar processing pattern is used to transmit data:
Code:
04228 ;DUART TRANSMITTER IRQ PROCESSING
04229 ;
04230 E2D0 A0 01 iirq0300 ldy #n_nxpch-1 ;starting channel index
04231 ;
04232 ;
04233 ; start of channel processing loop...
04234 ;
04235 E2D2 98 .0000010 tya ;copy channel index
04236 E2D3 0A asl a ;channel pointer offset
04237 E2D4 AA tax
04238 E2D5 A1 1A lda (tiaisr,x) ;get channel IRQ status
04239 E2D7 39 2E F8 and nxptqtab,y ;TxD interrupting?
04240 E2DA F0 1D beq .0000040 ;no, skip this channel
04241 ;
04242 E2DC B5 26 .0000020 lda tiagettx,x ;CFIFO 'get' pointer
04243 E2DE D5 2A cmp tiaputtx,x ;CFIFO 'put' pointer
04244 E2E0 F0 0E beq .0000030 ;nothing to transmit
04245 ;
04246 E2E2 A1 0E lda (tiasr,x) ;get channel status
04247 E2E4 89 04 bit #nxptxdr ;TxD FIFO full?
04248 E2E6 F0 11 beq .0000040 ;yes, done for now
04249 ;
04250 E2E8 A1 26 lda (tiagettx,x) ;read CFIFO &...
04251 E2EA 81 16 sta (tiafif,x) ;write to TxD FIFO
04252 E2EC F6 26 inc tiagettx,x ;bump 'get' pointer
04253 E2EE 80 EC bra .0000020 ;loop
04254 ;
04255 E2F0 A9 08 .0000030 lda #nxpcrtxd ;tell DUART to...
04256 E2F2 81 12 sta (tiacr,x) ;disable transmitter
04257 E2F4 B9 30 F8 lda tiatstab,y ;flag it...
04258 E2F7 04 2E tsb tiatxst ;as well
04259 ;
04260 E2F9 88 .0000040 dey ;all channels serviced?
04261 E2FA 10 D6 bpl .0000010 ;no
04262 ;
04263 ; ...end of channel processing loop
In all NXP 26xx and 28xx UARTs, the transmitter will interrupt as soon as it is enabled, the interrupt indicating that there is nothing to transmit. Upon entry to the above routine, the transmit CFIFO will be checked for data. If data is waiting it will be written to the transmitter as fast as the loop bounded by labels
.0000020 (lines 04242-04253, inclusive) can execute. As soon as a datum has been written to the transmitter the "transmitter empty" IRQ will be cleared. The transmitter has a 16-deep FIFO, so multiple data can be written following a single interrupt, which again improves throughput. Once the transmitter indicates its FIFO is full the loop bounded by
.0000020 will break and the MPU will move on to the next channel.
As soon as the transmitter has sent the last datum in its FIFO it will again interrupt. If the corresponding CFIFO is empty the transmitter must be disabled. Otherwise, the system will deadlock, since
/IRQ will be continuously held low due to the "transmitter empty" interrupt being asserted. Transmitter shutdown is handled at label
.0000030 (line 04255) in two steps. First a command is issued to the channel telling the DUART to disable the transmitter. The command immediately clears the transmitter interrupt, but doesn't actually disable the transmitter until the final datum in the FIFO has been serialized and transmitted. A flag is set in the transmitter status bit field to indicating that the transmitter has been disabled. The data table
TIASTAB (line 04257) is a set of masks that is used to select the appropriate bit in the status bit field that is to be manipulated. The
TSB instruction is handy for this sort of thing.
In the foreground part of the driver, each submission of a datum for transmission will result in the
TIATXST bit field being tested to see if the transmitter is disabled:
Code:
03759 E1D0 B9 30 F8 lda tiatstab,y ;transmitter status bit mask
03760 E1D3 14 2E trb tiatxst ;transmitter enabled?
03761 E1D5 D0 0C bne .enabtxd ;no, must enable it
In the above, the Y-register is again the channel index. Double duty is gotten out of the
TRB TIATXST instruction, as
TRB performs a logical AND of the accumulator and
TIATXST before it clears the flag bit in
TIATXST corresponding to the mask loaded into the accumulator. If the result of the logical AND is true then it means the transmitter has been disabled and code must be executed to restart it:
Code:
03782 ; enable transmitter...
03783 ;
03784 E1E3 A9 04 .enabtxd lda #nxpcrtxe
03785 E1E5 81 12 sta (tiacr,x)
03786 E1E7 80 EE bra .done
Upon restarting, the transmitter will immediately interrupt because no data will be in its FIFO. That interrupt will start transmission as described above.
All of this code has been tested on POC V1.1 and works as intended. I'm thinking I will be able to port it to POC V2.1's firmware, with the only changes being the number of channels being accessed. All I need are those PCBs...
———————————————————————————————————————————
¹If the discussion of FIFOs in the context of serial I/O processing is mystifying to you I suggest you check out Garth Wilson's serial I/O discussion on his interrupts article page—look here for the RS-232 stuff. Note that his page refers to the CFIFOs as "buffers," which they technically are not (no matter what Wikipedia says).