# Lecture 10 – Programming with interrupts (Digital Systems)

## Interrupts

A better approach in the `primes` program is to control the UART using an interrupt. We can configure the hardware of the UART specially so that the normal flow of execution of the program is interrupted when `UART_TXDRDY` becomes true, and at that point (between two successive instructions of the program), a special subroutine

```uart_handler()
```

is called. We can arrange that `serial_putc(ch)` does not usually wait until it can output the character `ch`, but instead stores it in an array `txbuf`, and the interrupt handler retrieves characters from the array and sends them to the UART.

We'll set up `txbuf` as a circular buffer, with two indices `bufin` and `bufout`, and (redundantly) a counter `bufcnt` that tells how many characters are stored. If `bufin < bufout`, then the part of the array that is occupied wraps around from `txbuf[NBUF-1]` to `txbuf[0]`. We can declare the variables we need like this:

```#define NBUF 64

static volatile int bufcnt = 0;
static int bufin = 0;
static int bufout = 0;
static volatile char txbuf[NBUF];

static volatile int txidle;
```

Note the use of the `volatile` keyword for variables that are shared between the main program and the interrupt handler.

A circular buffer

Let's write the interrupt handler first:

```void uart_handler(void) {
if (UART_TXDRDY) {
UART_TXDRDY = 0;
if (bufcnt == 0)
txidle = 1;
else {
UART_TXD = txbuf[bufout];
bufcnt--;
bufout = (bufout+1)%NBUF;
}
}
}
```
• The interrupt handler begins by checking why it has been called: `UART_TXDRDY = 1` is one reason, but there may be others.
• If there is nothing to transmit, the handler sets the flag `txidle`.
• Otherwise, it fetches a character from the buffer and starts the UART transmitting it.
• Making `NBUF` a power of 2 will make the modulo operation `%NBUF` cheaper on a machine with no divide instruction.

Here is the corresponding code for `serial_putc`:

```void serial_putc(char ch) {
while (bufcnt == NBUF) pause();

intr_disable();
if (txidle) {
UART_TXD = ch;
txidle = 0;
} else {
txbuf[bufin] = ch;
bufcnt++;
bufin = (bufin+1)%NBUF;
}
intr_enable();
}
```

Because an interrupt may come at any time, we must be quite careful.

• Variables such as `bufcnt` that are mentioned in both the main program and the interrupt handler are marked `volatile`. This prevents the compiler from looking at a loop such as
```while (bufcnt == NBUF) pause();
```
and assuming that `bufcnt` doesn't change in the loop, and so does not need loading from memory in each iteration. On the contrary, `bufcnt` will eventually be reduced by the interrupt handler, and then the loop should terminate. This won't happen if the loop continues to look at a stale copy of the value in a register.
• We know `bufcnt++` will be implemented by code like this:
```ldr r0, =bufcnt
ldr r1, [r0]
<-- Here!
str r1, [r0]
```
If an interrupt happens "here", then the effect of the statement `bufcnt--` in the interrupt handler will be lost, because when the interrupt handler returns, it is an old value of `bufcnt` that is incremented. The simple solution is to disable interrupts throughout this function by putting `intr_disable()` at the start and `intr_enable()` at the end.
• If the buffer is full when we want to add a character to it, then we must wait until at least one character has been removed from the buffer and sent to the UART. The idiom for doing this is the call `pause()`, implemented by a special instruction `wfe` (wait-for-event): this halts the processor until an interrupt occurs, saving power. It's possible that, even if this task is held up because the buffer is full, there are other tasks that could continue to run, and we might need them to run in order (for example) to continue updating the display. For that, we will need to introduce an operating system that allows multiple processes that run independently.

For interrupts to work, we must enable them when the program starts. Here's some code to add to `serial_init` that does this.

```void serial_init(void) {
...

// Interrupt for transmit only
UART_INTENSET = BIT(UART_INT_TXDRDY);
enable_irq(UART_IRQ);
txidle = 1;
}
```

There are three bits of hardware that play a part: the UART itself must be told to interrupt when `TXDRDY` is set (and not in this case when, e.g., `RXDRDY` is set because an input character has arrived). Then the chip's "Nested Vectored Interrupt Controller" must be set up, assigning an arbitrary priority to the UART interrupt, and setting a bit that allows the UART to interrupt the processor. Finally, the processor itself must be in a state where it accepts interrupts; this is the default, but it can be modified with the operations `intr_disable()` and `intr_enable()` used above.

## Updated results

We can run the modified program again to see whether the delays in the output have disappeared. The program starts in much the same way.

Primes with interrupts

The LED is on for a period about two seconds shorter than before. When we look at prime 458, the gaps has disappeared, as have all other gaps in the output.

No more gaps

If we look at the end of the run, there is a surprise: the output goes on after the main program has finished, as the interrupt handler continues to send characters until the buffer is empty.

Cheating at the end

Even though it cheats in this way, the program still runs a bit more quickly, because the time to find the primes is now entirely overlapped with the printing process.

(Universal Asynchronous Receiver/Transmitter). A peripheral interface that is able to send and receive characters serially, commonly used in the past for communication between a computer and an attached terminal. It is commonly used in duplex mode, with the transmitter of one device connected to the receiver of the other with one wire, and the receiver of the one connected to transmitter of the other with a different wire. The asynchronous part of the name refers to the fact that the transmitter and receiver on each wire do not share a common clock, but rely instead on the signalling protocol and precise timing to achieve synchronisation.