DerTrueForce wrote:
I'm not using handshaking at all. Neither the display nor the keyboard I am using have the capability, to my knowledge. Each get only +5v, GND, and a signal line, so it isn't possible to do handshaking, certainly for the keyboard, and for the display, there is little point, to my mind.
Have you verified that the DUART setup is not expecting flow control input?
Quote:
I'm not using interrupts at all yet. I don't enable any interrupt sources that I am aware of, and I set the interrupt disable flag at the start, and never clear it (unless Tali Forth does it). I'm using non-blocking routines to read and write, but in all cases so far, they are used inside a loop that terminates once they do send or receive.
POC's firmware uses non-blocking code for reception—a return with carry set tells the caller no datum was available. Transmission in POC V1 is always blocking so the caller doesn't have to loop waiting for an open spot in the transmit circular queue (TxQ). The transmit primitive in the BIOS executes
WAI (
WAIt) instructions until space appears in the TxQ.
POC V2's firmware will give the caller the option of an immediate return (with carry set) or a block if TxQ is full.
Quote:
Maybe I need to make Tali's kernel preserve the status register, because my I/O routines do change the carry flag, and the ones in the original source don't.
It sounds as though you need to study Tali's source code to see if machine state immediately after an I/O primitive call is important. Most operating environments usually expect a primitive to return some type of status information as to whether the call succeeded or failed. So it could be Tali is expecting status to be returned to it after calling the output primitive and is puking because the status it's getting is not making sense.
As for driving the transmitter, whether by PIO or IRQs, the basic algorithm is the same, which is to keep stuffing datums into the THR (transmitter holding register, aka FIFO) until the channel status says THR is full. Bit 2 (
TxRDY) of the channel's status register (SR) will be cleared when THR is full and will be set as soon as a datum has been serialized and completely shifted out. As the MPU can write datums into THR many times faster than the UART can process them, transmission from the MPU's perspective is handled in very short bursts of activity, followed by long periods of inactivity.
Bit 3 (
TxEMT) of SR will be set when THR has been emptied and will be cleared as soon as a datum is written to THR. In a PIO setup, as you are running, you can safely ignore
TxEMT and only pay attention to
TxRDY. Monitoring the status of
TxEMT becomes important if using IRQs, as the setting of
TxEMT would cause the UART to interrupt the MPU.
Further down is a flow chart I developed that illustrates a transmission processing interrupt service routine (ISR). In the flow chart, TxD is an abstraction of the UART's transmitter (the MPU only knows about a transmit register, not the internal details of the transmitter itself), and THR is as previously described.
TxQ, which I briefly mentioned above as a circular queue, bears some explanation (also, see Garth's
website page on which he explains RS-232 reception using a circular queue—it works on the same principle as transmission). The serial I/O (SIO) primitives consist of foreground and background code subsections. "Background" refers to the DUART's ISR and "foreground" refers to code that acts as an interface between user programs and the SIO driver itself. Only the foreground is accessible to the user.
When a user program wants to transmit, it loads a datum into the accumulator and calls the transmit foreground function:
Code:
lda #datum ;datum to transmit
jsr putcha ;send it out on channel A
The above is for POC V1 and V1.1. In POC V2, a software interrupt is used instead of a subroutine call:
Code:
lda #datum ;datum to transmit
clc ;block until datum is accepted
cop _putsioa ;transmit on channel A
Within the foreground, the datum will be stored in TxQ, the circular queue, which is illustrated below.
Attachment:
File comment: 256 Byte Circular Queue
circular_queue_small.gif [ 21.15 KiB | Viewed 4132 times ]
Following the queuing of the datum, the transmitter will be checked to see if it is running. If the transmitter is not running it will be enabled, which will cause an immediate
TxEMT interrupt, and background processing will commence. If the transmitter is already running background processing will likewise be running. Note that the foreground's interaction with the UART is limited to enabling the transmitter if it has been disabled—the foreground does not otherwise "touch" hardware.
The background will respond to
TxEMT IRQs by checking the state of THR (i.e., testing the
TxRDY bit in SR, indicating full or not full) and if not full, reading the oldest datum from TxQ and writing it to TxD. This process will repeat until THR has been filled or TxQ has been emptied. If THR has been filled the read-write loop in the ISR will terminate and will not be run again until another
TxEMT IRQ occurs. If TxQ has been emptied the transmitter will be shut down, which will prevent the MPU from being hammered with
TxEMT IRQs that it won't be able to service; a flag will be set somewhere so the foreground will know the transmitter has been disabled.
Attachment:
File comment: TxD Interrupt Service Routine Flowchart
txd_isr_flow_small.gif [ 35.92 KiB | Viewed 4132 times ]
Use of IRQs to drive I/O hardware tends to be considerably more efficient than PIO, as the MPU doesn't constantly get stalled waiting on the UART. It can be said an IRQ driver reconciles the processing speed of the MPU (very fast) with the processing speed of the UART (not so fast). This especially becomes the case when the serial interface is running at a high speed, in which case flow control will likely in use. In such a scenario, serial I/O will frequently block until the receiving device can catch up. If transmission is via PIO, the MPU will be busy-waited each time the receiving device tells the transmitter to stop. In contrast, the UART would not tie up the MPU in an IRQ handler, since the UART wouldn't be interrupting if it couldn't accept more data for transmission.