Project 5 Using Interrupts

Configuring Interrupts on Zynq

10415

Introduction

This project introduces interrupts. The interrupt mechanism – preempting normal processor operations to handle time-sensitive, critical, periodic, or other tasks – is a fundamental and indispensable part modern computing. The vast majority of embedded microprocessors that control products or processes use interrupts to conserve processor bandwidth.

This project uses interrupts to improve and add features to the previous UART project. Instead of polling the UART status bits to see when data is available, the ARM interrupt system is configured and used to alert the processor when new UART data is available.

This project also introduces the ARM’s global timer module. The global timer is an accurate timer that can be used to generate periodic interrupts. Periodic interrupts allow the processor to service regularly occurring events (like reading a sensor input at a given frequency) without having to keep track of time by counting processor cycles.

Finally, this project also introduces the use of GPIO devices (like pushbuttons) that are connected to the ARM using ZYNQ’s “multiplexed I/O” (MIO) system. Recall that some of the external pins on the ZYNQ package are connected to the ARM, and some are connected to the FPGA. So far, we have been using I/O devices connected to the FPGA (and these devices are made accessible by the Blackboard Standard Configuration file). To use devices connected to the ARM, the MIO system must first be configured.

Before you begin, you should:

  • Know how the UART system works;
  • Be able to find and apply information found in Blackboard’s Reference Manual and the ZYNQ TRM;
  • Be able to write programs in C;
  • Have working UART code from the previous project.

After you’re done, you should:

  • Understand interrupts and their use;
  • Understand how to configure ZYNQs GIC (Generic Interrupt Controller);
  • Be able to set up and use interrupts;
  • Be familiar with MIO system and Global Timer;
  • Be able to setup timing interrupts based on the Global Timer.

Background

Many important concepts are discussed in the topic documents for this project - you should read them all, and refer back to them as you proceed through this project.

Interrupts

The interrupt mechanism allows a processor to service a given event only when required, without wasting CPU cycles to keep checking status bits to see if/when service is needed. Consider the stopwatch/timer from an earlier project, and consider the timer being used in a more complex system like a microwave oven controller. An user could push the timer start button at any time – if the processor had to continuously read the button signal to see if a press had occurred, at a rate of perhaps 10 reads per second, every second, forever, a huge amount of processing time is wasted. A better solution is to let the processor deal with regular system tasks, and then interrupt the processor only when the button press occurs. When a button press interrupt occurs, the processor can divert from its current task to deal with the button press, and then return to its regular tasks.

Note that if the button is pressed once a minute (and that would be pretty frequent for a microwave oven), and the processor is running at almost 1GHz, then 60 billion instructions occur between button presses. Rounded to 10 decimal places, the interrupt system uses no bandwidth. In contrast, a polling system would use a measureable percentage of the bandwidth.

Interrupt systems have been a part of processors since the beginning. In order to let an external device (or an internal circuit, like a timer) alert the processor that service is needed, and in order to provide that service without “breaking” whatever processing was already in progress, several standard practices for writing interrupt service routines have evolved. This project presents the ARM interrupt system, and presents the software methods used to process/service interrupts.

Almost all of the IP peripherals built into ZYNQ’s processing system (PS) are capable of generating interrupts. The ARM processor includes only a single interrupt signal called “IRQ”, and all potential interrupt sources must assert this signal in order to interrupt the ARM. The ARM includes a Generic Interrupt Controller (GIC) circuit that receives all the potential interrupt signals, and based on certain programmable criteria, passes the highest priority pending interrupt source to the ARM. The GIC must be properly configured before it can be used, an a large portion of this project deals with properly configuring the GIC.

ARM Global timer

The ARM core includes a global timer module that is always available to the processor. The timer is based on a 64-bit counter can run up to 333Mhz (one half the processor’s clock). Once enabled, the counter will count continuously for almost 2000 years (2^64 is a big number!). Using programmable comparators, the counter can be configured to measure intervals with 3ns precision, and an interrupt can be generated at the end of any interval. You can read more about the timer module in the timer reference manual page.

MIO GPIO

ZYNQ’s multiplexed I/O (MIO) system connects ARM processor signals to ZYNQ’s external/physical pins. Only 54 physical pins are available to the processor, but the ZYNQ system has hundreds of signals that may want to use those pins (for example, ZYNQ’s USB, Ethernet, SPI, I2C, UART, CAN and other bus controllers, and many other peripherals as well, each with dozens of their own signals, are all eligible to connect to the external pins). When a circuit board (like the Blackboard) is designed, engineers must decide which peripherals get access to which pins, and the MIO system multiplexors must be programmed to make the desired connections (for the Blackboard, this happens in the Standard Configuration File). On the Blackboard, two pushbutton inputs and three LED outputs are connected to MIO pins, and the MIO module must be configured to use these pins (the physical connections are made for you in the Standard Configuration File, but you must still configure the MIO controller to pass those signals to the ARM).

Requirements

1. Use interrupts to handle UART traffic

Instead of polling the UART module for new data, have the UART module generate interrupts whenever there’s data available to read. Configuring interrupts on the Zynq system is a multi-step process, so make sure you read all of the background documents to ensure you get all the steps correct.

Write an interrupt service routine that writes any characters in the UART receive FIFO to the transmit FIFO (i.e., echo any received characters). In your handler, make sure you check the interrupt ID and acknowledge the end of the interrupt.

After writing your UART interrupt handler, your main program needs to:

  1. Disable ARM interrupts (by writing to the CPSR);
  2. Register your interrupt handler;
  3. Configure the GIC for interrupts from the UART module, interrupt ID 82;
  4. Configure the UART module for interrupt generation;
  5. Enable ARM interrupts (again through the CPSR).

You can configure UART1 the same as in the previous lab. However, it will need additional configuration to generate interrupts (example code is provided). Configure the UART module to generate interrupts when the number of entries in the receive FIFO exceeds the programmable threshold value.

Once you have configured the interrupt system correctly, your main program can enter an endless loop, so that all UART activity is dealt with in the interrupt handler.

A basic structure for the program is given below. Read the background information and write your own functions to accomplish the necessary steps. The functions called in the sample structure are just suggesstions, you can write the functions with whatever parameters you prefer. You may want to break up the processes into even smaller steps - it’s your solution, and your code!.


#define UART1_INT_ID 82

void my_handler();


int main(void)
{
		
	//setup UART
	configure_uart1();


	//configure interrupt system below
	disable_ARM_interrupts();
	register_irq_handler(my_handler);
	configure_GIC();
	configure_uart1_interrupt();
	enable_arm_interrupts();


	//endless loop
	for(;;);
}


void my_handler()
{
	uint32_t id;

	//get interrupt id
	id = ICCIAR;


	if(id==UART_INT_ID)
	{
		//service UART itnerrupt here
	}


	//inform GIC that this interrupt has ended
	ICCEOIR = id;

}

When running your program,if you’ve configured everything properly, each character you send through the terminal will be sent back and displayed.

2. Use the GPIO Module with interrupts

Two of Blackboard’s pushbuttons and one RGB LED are connected to the Zynq’s General Purpose Input Output (GPIO) module through the MIO system. GPIO module input pins can be configured to generate interrupts based on input transistion events (edge or level based).

Write a program to configure the GPIO module to generate an interrupt when either of the MIO pushbuttons has a rising edge transition. (Note the Blackboard Standard Config file programs the MIO system to define GPIO pins 50 and 51 as inputs, so you do not need to program the MIO pin definitions as shown in the first Sample Code block in the refernce manual page). You will need to modify the necessary registers in the GIC, enabling interrupts from the GPIO module to reach the ARM core. The GPIO’s ID in the GIC is ID 52. After configuring the GPIO module and the interrupt system, have your main program enter an endless loop.

In your interrupt handler, check which button triggered the interrupt. Have one button turn on the RGB LED (all three diodes), and have the other button turn it off. Make sure you check the interrupt ID at the start of the interrupt handler, and make sure you clear the interrupt flags when you finish servicing the interrupt.

3. Use Global Timer Interrupts to create a software timer/counter, and send the timer value over the UART port

Create a counter that sends its value on over the UART port for display in the terminal window. The counter should increment every second and print its new value to the terminal. Use the Global Timer (GTC) as a time base. Configure the GTC to generate interrupts once per second. The relevant registers in the GIC will also need to be configured to pass Global Timer interrupts to the ARM. In the GIC, the GTC’s interrupt id is 27.

In the interrupt handler for the GTC, first send the current count value over UART, and then increment counter. Make sure your interrupt service routine clears any necessary interrupt flags.

HINT: You can use a global variable for the count, so it can be accessed during the service routine, and saved after the service routine. Make sure you denote it as a volatile type to inform the compiler that the variable can change at any time.

volatile int count; //defining a global volatile integer

4. Use global flags to reduce interrupt latency

Modify your programs from requirements 1 and 3 so that the interrupt handlers only set software flags, instead of processing or moving data (this is a more tpyical approach that minimizes the time spent inside of the interrupt handler routine). Then, in your main program, check for these flags, and perform the needed operations there. The program behaviour should be the same as in requirements 1 and 3, but the structure should be different - only the flags (global variables) should be set in the interrupt handler, and then these flags should be checked in a loop running in main. When main detects a flag is set, it should perform whatever task is needed (like sending a count value over the UART and then incrementing the count value), and then the flags should be cleared. UART FIFO accesses should only be done in Main.

For your modification to requirement 3, the counter is only accessed in main, so it no longer needs to be a global variable. An example program structure is provided for the modification of requirement 1. The functions that are called will need to be written or replaced with code from your previous programs.


void isr();

volatile int uart_flag;


int main(void)
{
	uart_flag =0;

	disable_interrupts();
	register_handler(isr);

	configure_uart();
	configure_interrupts();


	enable_interrupts()

	
	while(1)
	{
		if(uart_flag==1)
		{
			//service uart interrupt here

			uart_flag =0; //flag serviced
		}
	}



	return 1;
}


void isr()
{
	int id;
	id = ICCIAR;	//get interrupt id

		if(id==UART1_ID)
		{
			//check receive trigger
			if(uart1_rxtrig())
				uart_flag=1;

			clear_uart1_flags();
		}
		
	ICCEOIR = id;

}

Write a program that toggles the GPIO-connected RGB LED on and off for a specified number of cycles. Receive the number of cycles as a character from 1-9 over UART (Make sure you convert from character to integer). Start when one of the GPIO-connnected buttons is pressed, and stop and reset the count when the other is pressed. Have the LED blink at 1Hz, changing it’s state every 1/2 second. Indicate in the terminal that the LED has started and stopped blinking. If the LED is currently blinking, ignore any terminal input (Make sure to discared the FIFO entries, though). Do all processing in interrupt handlers; Have your main program configure UART, button, and timer interrupts and then enter an endless loop.

Write your program so the timer only interrupts the CPU after the sequence has been started. After the blink sequence has been stopped, either through the stop button or reaching the end of the sequence, the processor should not be interrupted by the timer. Once another sequence has been started, timer interrupts may be continued.

Interrupts can be enabled/disabled in multiple ways. If all interrupts need to be disabled, the ARM’s CPSR or the GIC’s distributor can be modified. Specific interrupts can be disabled through GIC’s registers or at the source of the interrupt by configuring the module’s local registers.

Challenges

1. Create an Universal configure_GIC Function

Write a generic function for configuring relevant GIC registers to a specific interrupt ID. Your function will need to calculate which specific registers and fields within each register correspond to the given ID. You will also need to ensure that you only modify the bits you need, so you don’t inadvertantly modify the configuration of another interrupt. Read the Zynq’s technical refernce manual carefully and develop an algorithm that calculates the correct locations to modify.

void configure_GIC_ID(uint32_t ID, uint32_t priority, uint32_t sensitivity){
	// you code goes here
}

2. Measure the bounce time of a single button press.

All mechanical switches exhibit “switch bounce”. When the actuator on a mechanical switch (like a pushbutton) is used, the contact apparatus will make and break contact several times over a short time interval because of the mechanical elasticity in the materials used to make the switch contacts. Virtually all low-cost mechanical switches exhibit some amount of switch bounce. Depending on the size of the switch and the circuit around the switch, bounces may be relatively long and rail-to-rail, or relatively short and limited to a few 10’s of millivolts. Most small switches exhibit bounce behavior with perhaps one to ten make-break cycles over a period of several microseconds to several milliseconds. In previous observations, the pushbuttons on the Blackboard typically show one to several bounces with a period in the low microsecond range, and they are usually rail-to-rail (or nearly so).

You can configure the Global Timer module to measure the bounce time of a pushbutton. Because the clock for the timer module is one-half the CPU clock (333MHz), you can measure bounce time with a resolution of 3ns, which is more than fast enough to measure switch bounce. You can configure the GPIO Module to generate an interrupt on the rising and falling edge of the pushbutton signal, and then you can read the free-running global timer on both edges. You can subtract the rising-edge time from the falling-edge time, multiply by 5, and display the bounce period in nanoseconds on the seven-segment display (Note: mutliplying the edge-to-edge time difference by 3 converts clock cycles to nanoseconds, and then multiplying that by 2 converts the half-period bounce time to a full-period bounce time). To setup the Blackboard to make this measurement you need to reconfigure the GPIO Module’s Bank 1 interrupt type, polarity, and sensitivity registers.