You may recall I couldn't seem to get interrupt-driven serial I/O working with POC V
2.0 and
decided to shelve V2.0 and take off in another direction. This wasn't to say that I hadn't continued to think about why V2.0 was being so obdurate. A few months ago, I had finally figured out the mysteries of the 28C94's interrupt "bidding" system, which is inadequately documented in the QUART's data sheet. What was helpful was reading the data sheet for the SC28L194, which is also a QUART with interrupt bidding. The description is more lucid and helped me "connect the dots."
As I have a significant hardware bug in V2.1 that is doing a good job of being elusive, I decided to set that unit aside for a while (usually a period of "inattention" results in me figuring out the problem) and see if I could get interrupt-driven serial I/O (SIO) working on POC V2.0. I was confident that I finally understood the QUART's interrupt bidding setup well enough to make it work. Yet I still couldn't get things going. As soon as a write to the console channel (channel A in the QUART) occurred,
/IRQ would go low and stay there, and the machine would crash. Clearly the MPU wasn't able to service the IRQ, and I knew to a certainty at this point that the hardware itself was working as designed. It had to be something else that I was overlooking. The solution was to be found by examining the SIO driver code for POC V1.1, which had been used as a test bed to prove the theory behind my "vectored" driver that could be scaled to as many SIO channels as could be built into a system.
The vectored driver consists of a set of foreground routines for sending and receiving datums, and a interrupt handler for driving the UART. As there are multiple channels to process, there is a set of direct (zero) page pointers that point to the relevant chip registers and the circular FIFOs (CFIFOs) maintained in RAM for temporarily holding incoming and outgoing datums. Registers and CFIFOs are selected by passing a zero-based channel index to the driver code. Ultimately,
(<dp>,X) addressing is employed to read and write registers and CFIFOs, in which the value in
.X is the channel index times two. It's typical 6502 programming.
Code:
lda <chan> ;zero-based channel number
asl A ;double for offset
tax
lda (siosr,x) ;get channel status
...do some processing based upon channel status...
sta (sioio,x) ;write to transmitter
...etc...
This technique was tested on V1.1 and V2.1, and worked without a hitch. It should have worked on POC V2.0 as well, but all sorts of bizarre things would happen when SIO was attempted.
Within the firmware is a set of tables that is used to populate the direct page pointers during POST. I don't know why, but my attention got focused on the the source code used to produce those tables and in it, I found an error that resulted in some data being transposed. Cheerfully assuming that fixing that error would fix V2.0, I copied the table code I had used in V1.1's firmware over to V2.0's source file. Following assembly, burning a ROM and flipping on the power, I was greeted with the following display on the console screen.
Attachment:
File comment: POC V2.0 POST Screen Display
pocv2.0_post_display.gif [ 3.62 MiB | Viewed 2015 times ]
The above proved that interrupt-driven SIO was working. Also, I have a 100 Hz jiffy IRQ running for timekeeping, which was verified by putting some gratuitous time delays into the code and determining if the delay period was about right.
So it looks as though I'm back on POC V2.0 for a while.
Incidentally, here is the code that generates the tables used to populate the driver's direct page pointers.
Code:
;SERIAL I/O DRIVER DIRECT PAGE POINTER SETUP TABLE
;
sioiptab =*
;
;
; QUART register pointers...
;
.i .set 0 ;status
.rept n_nxpchn ;N_NXPCHN is the number of channels
.word io_quart+nx4sr+.i
.i .= .i+s_nxpcsp ;S_NXPCSP is channel "spacing" in bytes
.endr
;
.i .set 0 ;command
.rept n_nxpchn
.word io_quart+nx4cr+.i
.i .= .i+s_nxpcsp
.endr
;
.i .set 0 ;RHR/THR
.rept n_nxpchn
.word io_quart+nx4sio+.i
.i .= .i+s_nxpcsp
.endr
;
.i .set 0 ;interrupt
.rept n_nxpcbk
.rept n_nxpcpb
.word io_quart+nx4isr+.i
.endr
.i .= .i+s_nxpcbk
.endr
;
;
; circular FIFO (CFIFO) pointers...
;
.addr .set siocfifo ;receive CFIFO base address
.rept n_siobix ;N_SIOBIX is number of indices per CFIFO
.i .set 0
.rept n_nxpchn
.word .addr+.i
.i .=.i+s_siofif ;S_SIOFIF is size of a CFIFO in bytes
.endr
.endr
;
.addr .set siocfifo+.i ;transmit CFIFO base address
.rept n_siobix
.i .set 0
.rept n_nxpchn
.word .addr+.i
.i .=.i+s_siofif
.endr
.endr
;
sioipend =*
The above is "self-configuring," in that the tables are generated according to the number of serial I/O channels defined by the symbol
N_NXPCHN. In POC V1.1's firmware,
N_NXPCHN is 2, since there is one DUART. In POC V2.1's firmware,
N_NXPCHN is 4, owing to the presence of two DUARTs. As the 28C94 QUART can be logically considered to be two DUARTs in one package, the principle holds—it will work for any number of channels, as long as the number of channels is always a multiple of 2.
There are several assumptions designed into the above that if violated will break the code. The first assumption and the one where my error was located, is that the order of the direct page pointers exactly matches the order in which the pointer tables are generated. A simple loop is used to mass-copy the above tables to direct page, which is why the tables and the direct page pointers have to match in sequence. I had the status and command tables reversed in the above, which meant when the interrupt handler tried to read channel status to determine the source of the interrupt it was instead doing a read of the channel's command register. A read of command is marked "reserved" in the 28C94 data sheet, which means there is no telling how the QUART might react and also no telling what was being read by the MPU. Needless to say, the interrupt wasn't being properly serviced.
The other assumptions are the CFIFOs are contiguous in memory, have a size equal to that of a 6502 page or equal to an integral submultiple of a 6502 page size, and that the CFIFO base address (defined by
SIOCFIFO) is on a page boundary. These assumptionss simplify the management of the CFIFO pointers and also make it possible for the size of each CFIFO to be smaller than 256 bytes—I use a CFIFO size of 128, which has proved to be more than adequate.
So now that I have working SIO on POC V2.0 I can "flesh out" the rest of the firmware and make some progress. An interesting exercise will be in writing the memory test routines that will scan the total RAM in the system and report how much was found.