Be prepared to discuss your work at the beginning of your laboratory session on Tuesday, October 28, and to demonstrate your program to your TA during that period. This part of the assignment is worth 25 points.
The reason that the communicating parties must have some explicit way of switching control is that when the CPU is concentrating all of its attention on (say) writing characters to a UART, it cannot recognize that the UART has received a character. Two-way communication is simplified if the interrupt I/O strategy (Figure 12.7 of the text) is used instead of the programmed I/O strategy (Figure 12.6). An interrupt can be used to signal that the UART has received a character, and the CPU can deal with that character before continuing with its writing task.
Each autovector location in the MB5 ROM contains a specific address in RAM. An interrupt will therefore transfer control to that address, which must contain a valid sequence of instructions. The MB5 designer chose to allocate 8 bytes to each of these instruction sequences. This provides space for a jmp instruction with an arbitrary address.
Figure 11.9, on page 360 of the text, gives a step-by-step description of the sequence of events involved in an interrupt. In the case of the MB5, the Vector Address placed into the program counter is the address of the 8 bytes in RAM corresponding to the interrupting device's priority level. File uart.ah specifies the MB5 RAM addresses for the two UARTs as U1INT and U2INT respectively. The EnableUART routine (file comint.src) places the jmp instruction into the 8-byte area for the specified UART.
A UART interrupt may signal any one of four different classes of condition, as discussed in the description of the UART interface. Thus an interrupt handler must decode the condition before dealing with it. All of the information needed to determine the cause of the interrupt can be found in the UART's Interrupt Identification Register (IIR). When that register is read, it is reset to describe the next pending interrupt. That means that it is not possible to re-read information from the IIR; the result of a read must not be destroyed until all of the information it carries has been noted.
Characters read by the UART originate from the external world, and arrive at random times. Characters transmitted by the UART, however, originate within the program. This means that the Receive data register full and Transmit data register empty interrupts should be treated differently.
The UART needs to be able to respond to an arrival of a charater from the outside world when that character appears. Thus the Receive data register full interrupt should be enabled at all times.
The Transmit data register empty interrupt effectively says ``I'm ready to transmit another character''. If the program has another character to transmit, then it should respond to that interrupt by writing the next character the the UART's Transmitter Holding Register (THR). This write clears the interrupt.
If there are no further characters to be transmitted, however, then the
program should disable the Transmit data register empty interrupt.
When the program generates another character for transmission, it can
re-enable the interrupt and the interrupt will occur immediately.
(In fact, the program should set the enable bit for this interrupt every
time it generates a character for transmission; it shouldn't try to see
whether or not the interrupt is already enabled.)
Buffering
The problem of reading characters from an external device and processing
them is an example of a producer-consumer problem:
The external device is producing characters at random times, and
the CPU program is consuming those characters when it is able to
do so.
An analogous producer-consumer problem involves two people communicating
via e-mail:
One person produces a message at a random time, and the other consumes it
when convenient.
Because the producer and the consumer are running independently, there is no guarantee that the consumer is ready to consume something at the time the producer produces it. Thus an implementation of a producer-consumer problem always involves a buffer between the producer and the consumer. (A buffer is simply an area of memory shared by the producer and the consumer.) When the producer produces something, it is placed in the buffer. When the consumer is ready to consume something, it looks in the buffer. In the case of e-mail, for example, your computer stores (``buffers'') your mail messages. When you are ready to read mail, you ask for the messages that have been stored.
One method to buffer data passed between producers and consumers is called circular buffering. A circular buffer implements a bounded queue, a data structure that behaves like the checkout line at the supermarket: the producer adds information to the end of the queue while the consumer removes information from the front. Unlike a normal supermarket checkout line, however, a bounded queue has a maximum number of elements that it can hold.
Each circular buffer provides five basic operations:
File cb256.src provides code to implement 256-byte circular buffers. The module exports the necessary operations on such buffers. A client of this module must include file cb256.ah, and then may declare any number of circular buffers by using the macro named MakeCB.
File comint.src uses a macro, UART, to create a device control block. This macro defines two circular buffers, SendBuf and RecvBuf. SendBuf is used to hold characters sent to the UART until they can be picked up by the transmitter ready interrupt handler. RecvBuf is used to hold characters delivered by the receiver data interrupt handler until they can be used by the remainder of the program. Both buffers are set empty by EnableUART, the routine that opens the communication.
The UART module defined by file comint.src provides an application program interface (API) specifying an array of two UARTS (identified by the integers 0 and 1) and six C-callable operations on those UARTS:
You should read the inplementations of EnableUART and DisableUART in file comint.src to get a feeling for how the device control block can be used. File driver.c illustrates calls to the API.
Debug your program using two Tera Term windows, one connected to COM1 and the other connected to COM2, as you did in an earlier assignment. Characters typed into one window should appear in the other window, and vice-versa. With this implementation, you should be able to type between the two windows in any sequence.
To really test your code, you must link two machines, X and Y:
Unplug the MB5 from the COM2 port of machine X and plug it into the COM2
port of machine Y.
Using the keyboard of machine X, type into the Tera Term window connected
to COM1.
At the same time, have your partner use the keyboard of machine Y to type
into the Tera Term window connected to COM2.
The two of you should be able to type simultaneously, without loss of
characters.
Questions
Answer the following questions about the interrupt enable/disable code in
comint.src:
| Instructor | Revision 1.42 (2007/08/27 00:15:43) |