Lab 6: Interrupt-Driven I/O
Description
Background
Preparation and Quiz (3 marks)
In Lab (2 marks)
Notes
Description
The purpose of this lab is to familiarize yourself with the use
of interrupts. Interrupts are a very powerful mechanism that can
be used to support multitasking, handling of exceptional conditions (i.e.,
unexpected results during calculation such as a division by zero),
emulation of unimplemented (in hardware) instructions,
single-step execution for debugging, and operating system calls/protection
among others.
In this lab, you will extend your solution to Lab 5 to
communicate with two new devices, the JTAG UART and timer, using interrupts,
while controlling the Car World
game. Specifically, you are to display the current speed of the car or
the current state of the sensors in the Monitor Program terminal,
using key presses to switch between these two values:
- Once every second, triggered by a timer interrupt,
print the current car speed or the current
sensor state to the Monitor Program terminal, using the
JTAG UART device (This is a second JTAG UART, not the same one used in Lab 5). Print
both values in hexadecimal. When printing the value, the new displayed value
should replace the old value in the terminal, rather than continually printing new values
one after the other.
-
When the user presses a key, detected using a JTAG UART read interrupt, change the
current display mode: 's' should cause the car speed to be displayed
each second, while 'r' should cause the car sensors to be displayed
each second. Pressing other keys should be ignored. Start your program in "sensors" mode.
-
You must use interrupts to read key presses from the JTAG UART and to be notified of
timer timeouts. You should not use
interrupts for writing to the terminal JTAG UART. You should not use
interrupts to control the Car World game.
-
To make things easier, do not communicate with the Car World UART inside
the interrupt handler. Rather, save the speed and the sensors state to a
dedicated memory location ("global variable") when processing Car World UART packets in your
main program loop. In the timer interrupt handler, display the saved slightly-stale value.
(Why: What happens if an interrupt occurs in the middle of sending a packet,
or while waiting for a response?)
Background
How to print to the terminal
The characters sent to the terminal JTAG UART will be displayed on the
terminal window. For example, sending
byte 0x34
to the terminal JTAG UART will display character
'4'. In the other direction, typing in the terminal window will send data to the
terminal JTAG UART (also using ASCII code).
The Monitor Program's terminal interprets certain character sequences ("escape
codes") as commands that control the terminal's output, such as clearing the window or moving the cursor.
These escape codes can be found on page 24 of
the Monitor
Program Tutorial. The table is reproduced
below. <ESC>
represents a character with value
27 (0x1b). For example, sending the four-character
sequence <ESC>[2K
(1b 5b 32 4b
) will erase an entire line.
How to Use Interrupts
Interrupts must be enabled in three places:
- Enable interrupt on the peripheral.
For example, to configure
the Timer to interrupt the processor when it times out, you need to set
the 0th bit of the Timer's Control Register to 1.
- Unmask the IRQ line corresponding to the peripheral on the
processor.
This is done using the processor's ienable
control register
(ctl3
). For example, the Timer
is associated with IRQ line 0, so you unmask this interrupt line by
setting bit 0 of ienable
to high.
- Enable interrupts globally in the processor.
This is
done by setting bit 0 high in the processor's status
(ctl0
) register.
Determining the Source of an Interrupt
- Find out what device caused the interrupt.
The ipending
(ctl4
) control register
tells you which IRQ line(s) are requesting an interrupt. For
example if bit 0 of ipending
is high you know that the Timer
is requesting an interrupt. Multiple interrupts may
happen simultaneously, therefore multiple bits of the
register ipending
can be 1 at the same time.
- Find out what event caused the interrupt.
Some
devices, such as the Timer, have only one event that causes an
interrupt, so the previous step is sufficient to determine the source of
the interrupt. Some devices can have multiple
events that cause interrupts, such as the JTAG UART with both read and write interrupts.
In this case
you may need to check the device's control register(s) to see
what event(s) actually caused the interrupt.
Returning from an Interrupt
When a device interrupt occurs, the processor jumps to the ISR. However, we
need to be able to return to the code that was executing before interrupt
happened. The processor saves the address of the instruction after
the instruction that was aborted in the
ea
(exception
address) register. Therefore you must adjust the saved
address in ea
so that the processor re-executes the
aborted instruction, by subtracting 4
from ea
(each instruction is 4 bytes), then executing
the eret
(exception return) instruction to return to
normal program execution.
How to debug interrupts
The debugger shows the state of the status, ienable, and ipending registers.
- To see whether interrupts are enabled, observe the status and ienable registers
in the debugger.
- To see whether an interrupt is currently being requested by a device, observe the ipending register
- Use a breakpoint at 0x20 to see whether the interrupt handler executes.
Where to Place your Interrupt Service Routine (ISR)
When a processor is interrupted, it causes the processor to jump to
location 0x00000020
. Your interrupt service routine must be located at this exact location. To do
this, place the ISR code in the .exceptions
section (rather than the usual ".text
" section).
The .exceptions
section always starts at location 0x20.
Use the code below as a template.
.section .exceptions, "ax"
myISR:
[interrupt service routine starts here at 0x20]
|
When compiling your code, you have to ensure that sections
.exceptions
(your ISR) and .text
(the rest of the code) are placed in non-overlapping memory regions.
To do this, change the "Linker Section Preset" to "Exceptions" when creating a new project
to change the starting locations of the various sections to suitable
values (.exceptions
to 0x20 and .text
to 0x200 by default).
Overlapping sections
result in an error similar to "section .exceptions loaded at [...] overlaps section .text loaded at [...]"
After compiling your code, verify that your ISR was actually
placed at address 0x00000020
. You can do this by
looking at the disassembly in the Monitor program. Typos are a common reason for the ISR to be placed incorrectly.
Links
Preparation and Quiz (3 marks)
-
How do you tell the JTAG UART to send read interrupts?
-
How do you tell the timer to send timeout interrupts?
-
How do you tell the processor to accept interrupts from both Timer1 and the terminal JTAG UART?
-
Write a line of code to adjust ea before returning from the interrupt handler so that the aborted instruction will be re-executed.
-
Which registers must you backup before overwriting inside an interrupt handler?
-
If you want to call a function implemented in C from inside your interrupt handler, which registers must you back up?
-
Write a simple test program to blink an LED with a period of 1 second using timer interrupts.
-
Write a simple test program to echo characters on the terminal JTAG UART using read interrupts.
-
Enhance your solution to Lab 5 to display speed and sensor state over the JTAG UART, as described above.
Use interrupts both to read from the terminal JTAG UART and to check the Timer for a timeout.
Be prepared to answer any questions about interrupts on Nios II.
In Lab (2 Marks)
-
Demonstrate your program. If your In Lab program doesn't work, you can demonstrate your simple test programs from the Preparation for 0.5 marks each.
-
Submit your code on blackboard. Put all .s files that you demonstrated to your TA in a zip file called code.zip.
Notes
-
Try to get interrupt initialization, triggering, and returning working using a simple main program (infinite loop?)
before combining this with your Lab 5 program.
Use the debugger to verify that interrupts are triggering and returning.
-
You are encouraged to reuse your solution to Lab 5. If done
right, this lab will require very little modification to your existing
code.
-
When trying to type into the Terminal, be sure that the Terminal
window is focused (by clicking on it). Otherwise, the key pressed will not
be captured by the terminal JTAG UART.