Exercise 2: interrupts

Main topics:
  • External interrupts
Components: ATmega32, 8x LED diode, 8x resistor \( 220 \Omega \) and 1x\( 10 k\Omega \) and  switch.

External interrupts

There are interrupts:
- external (generated by the external signals),
- internal (generated by internal peripheral modules).

When an interrupt occurs, a specific function called the interrupt handler is called. Interrupts are extremely useful for reacting quickly to events. Even if the main program performs long operations, when an external signal occurs, for example, it will suspend the operation, execute the interrupt handler and then return to execution of suspended operation (the main program). More details in the section: "Lecture: Interruptions and timers".

In this exercise, we focus on handling external interrupts. Internal interrupts will be discussed later. Our task is to create a program that, using external interrupts, will count the events reaching the microcontroller from the outside (i.e. edges of pulses), and then display the number of their occurrences in binary form using LEDs. The device generating the external events will be a switch.

The description of generating interrupt handling procedures can be found in the documentation of the interrupt.h header file included in the avr-libc documentation (https://labe.engined.eu/data/_uploaded/file/psm/materialy/Noty_katalogowe/avr-libc-user-manual-2.0.0.pdf , page: 190).

Each procedure takes the form of:

ISR ("interrupt vector name as defined in the documentation of the interrupt.h header file")
{
....... // commands executed in the procedure
}

There is a table with vector names on page 193 in the avr-libc documentation. In this exercise, we will be using external interrupt 0, so look up the vector name in the table. Note if the given vector name is for the ATmega32 microcontroller (last column). For ATmega32 microcontroller, external interrupt # 0 will have vector name: "INT0_vect".

There should be a set of quick operations in the interrupt handler. Longer operations should only be performed in the main program. If you want to change the value of a variable in the interrupt handler and in the main program, this variable must be of the volatile type (e.g. volatile uint8_t counter = 0;). What does type volatile mean? Means that the variable will not be optimized. By default, the optimization consists in the fact that at the beginning of the program, the value of the memory cell assigned to a given variable is retrieved and then this value is copied to the register. While the program is running, the main function operates on this register, updating the value of the variable. If there was an interrupt handler, we would read the variable value from the memory cell inside the procedure and write its new value there. The problem is that the main function will not read the new value from the memory cell because it only operates on the register. To avoid this problem, add the volatile prefix and force the program to run only on a memory location, not an additional register. Then it will not be a problem to change the value of the variable in the main program and in the interrupt handler.

You already know what the interrupt handler looks like. Time to think about how to configure the external interrupt.

First of all, see the ATmega32 documentation (
https://labe.engined.eu/data/_uploaded/file/psm/materialy/Noty_katalogowe/ATmega32.pdf
, from page 66) and take a special look at two registers: MCUCR (MCU Control Register) and GICR (General Interrupt Control Register). The MCUCR register is responsible, among other things, for determining what is to trigger the interrupt (low signal, falling edge, rising edge or maybe any change).

To set e.g. a falling edge, set the value 1 on the ISC01 bit and the value 0 on the ISC00 bit:


Source: ATmega32 documentation, page 67.

Looking at the MCUCR register, the default value for all bits is zero.
Source: ATmega32 documentation, page 66.

Therefore, we do not need to change the value of the ISC00 bit, but the value of the ISC01 bit should be changed from 0 to 1:

MCUCR|=(1<<ISC01);

The GICR register is responsible for determining which interrupts are to be active. Therefore, it must be set in it so that the interrupt number 0 is available. See page 67 of the ATmega32 documentation. For external interrupt No. 0 to be available, set the value 1 on bit INT0:

GICR|=(1<<INT0);

By default, interrupts are disabled. In order to enable the possibility of calling interrupts, the sei() macro should be called:

sei();

You already know everything necessary to perform the exercise. 

Goal:

Create a program that, using external interrupts, will count the events reaching the microcontroller from the outside (i.e. edges of pulses), and then display the number of their occurrences in binary form using LEDs.

Connection:  

1. Find the chip pin number associated with the first available external interrupt (INT0). Hint, analyze page 2 of the ATmega32 documentation.
2. Connect 8 LEDs to the pins of port C of the microcontroller (successive LEDS from PC0 to PC7), and the switch being the event generator to the lead found in step 1.

Realization:


1. In your program, enable and configure the first available external interrupt to respond to a trailing edge.

- determine what will trigger the interrupt (falling edge) - MCUCR register

- activate the external interrupt # 0 - GICR register

- enable interrupts - funcion sei()
2. In the interrupt handler, make counts of the value of appearing edges of the signal (use a global variable to store the number of events). Remember the volatile prefix.
3. In the main loop, display the number of counts as binary with 8 LEDs.

Try to complete the task yourself. When you're finished, watch the video below to compare the results: