Interrupt-Driven Serial I/O

Purpose

To familiarize you with the use of interrupts to switch the attention of the central processor between tasks:

Due dates

You must hand in your answers to the questions at the beginning of your laboratory session on Thursday, October 23. This part of the assignment is worth 20 points.

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.

Background

In an earlier assignment, you used the UART to communicate with a Tera Term window. Your general strategy was to busy-wait for a device by polling a status register as described in Figure 12.6 of the text. The CPU concentrated all of its attention on either writing characters to the UART or reading them from the UART. This strategy makes two-way communication difficult because the communicating parties must explicitly ``turn the link around''.

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.

Interrupts

Section 11.4 of the text describes how the MC68000 processes interrupts. The MB5 has only a few external devices capable of generating interrupts, so the designer chose to allocate one device to each priority level and use autovectoring (example 11.5). Table 11.5 shows the autovector addresses. Notice that all of these addresses are in the ROM portion of the MB5's address space. It is therefore not possible for a program to set interrupt handler locations into these locations.

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:

The buffer is set empty at the beginning of the communication, and thereafter the producer inserts bytes into the buffer and the consumer extracts bytes from the buffer. Insertions and extractions can occur in any order, the only restrictions being that insertions into a full buffer are ignored and extractions from an empty buffer result in undefined values.

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.

Device control blocks

The MB5 has two identical UARTS. Because the UARTS are identical, we can carry out a specific operation on either UART with the same routine. Each UART has a certain amount of ``state information'', however, that must be made available to the routine controlling it. We therefore associate each UART with a device control block holding information unique to that UART. Common routines can then be given the address of the device control block, so that they can access information that they need about the device.

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:

The interrface specifications for these operators can be found in file comint.h.

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.

Task

Click here for a zip file containing a C-coded driver file, interface specifications, and a partially-coded UART module. Add code to file comint.src to complete the module:
  1. Implement the interface specifications for the C-callable routines SpaceAvailable, SendCharacter, CharactersAvailable, and RecvCharacter.

  2. Implement handlers for UART interrupts. The entry point names for the handlers are the third arguments of the UART macro. File comint.src gives these arguments as u1h and u2h respectively; feel free to change these symbols if you like.

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:
  1. (5 points) Explain why the symbols daddr, xaddr, etc. are defined with set directives rather than simply appearing in the location fields of the directives that allocate the corresponding storage.
  2. (2 points) Why is the Open flag important? Briefly explain what might happen if it were omitted. Does the interface specification preclude problems of this sort?
  3. (5 points) Explain why the code first disables all of the UART interrupts.
  4. (3 points) Explain why only the RDAE bit of IER is set after initializing the UART.
  5. (5 points) Explain the effect of the six instructions beginning at the comment Allow at least level-5 interrupts.

Demonstration

You must demonstrate your program to your TA on one of the machines in the Microlab.
Instructor Revision 1.42 (2007/08/27 00:15:43)