Monday, January 19, 2015

Interrupts Part 2, Creating Interrupts

WHAT SHOULD INTERRUPTS DO?

The answer is, as little as possible that cannot be done logically in the background code.  To make your code run quickly and smoothly interrupts are necessary, but they should only do what absolutely has to be done by the Interrupt Service Routine.  This is mainly due to the fact that while in the ISR, interrupts are disabled and an interrupt cannot be called again until the program has exited the ISR.  Interrupts should not call functions or use delays.  Interrupts should also only use local variables if they are needed, they should never modify global variables within the ISR itself.

The subject of interrupts is very extensive.  Even on our elementary approach to PIC microcontroller programming we are taking two lessons to cover them, and we won't really even scratch the surface.  The reason behind this is mostly that there are many interrupt sources, and an endless list of reasons to use interrupts.  The true purpose of this text is to give you a basic understanding of programming PIC microcontrollers in C, and to give you a base of knowledge that allows you to write working programs.  To that end, my goal with these lessons on interrupts is to give you the basic principals of configuring and using them, not to make you an expert.  Once you have the basic knowledge, it will be easier for you to absorb a more thorough text from external sources.

This lesson will talk about getting into and out of interrupts quickly, clearing interrupt flags, creating our own flags, and polling interrupt sources.

THE INTERRUPT SERVICE ROUTINE

For Mid-Range PIC micros, there is only one interrupt vector which, as we talked about in the previous lesson is at address 0004h.  When any interrupt occurs in our source code, the program will always point to this vector.

In C we define the interrupt function using the function qualifier "interrupt".  An interrupt function must be declared as type void and cannot contain parameters.  We must also assign a name to the function as we do any other function.  A typical interrupt function name might be "isr".  With these rules in mind, an interrupt function declaration would typically look like: void interrupt isr(void)

When writing programs with interrupts we enable the "interrupt enable" bits for the interrupt sources we want to allow.  We also enable the "global interrupt enable" (GIE) bit which turns on interrupts.  When our program is running and an interrupt request is received, the Interrupt Flag bit of the corresponding interrupt source is set.  This interrupt flag bit verifies the source of the interrupt.  You should know that interrupt flags will get set when the right conditions exist regardless of the state of the interrupt enable bit.  The interrupt enable bit is used only to determine whether or not an interrupt flag going high will actually trigger an interrupt.  When entering into the ISR, the programmer should test the status of the interrupt enable bit and the interrupt flag bit before executing any instructions.  This is called "polling", and is a safeguard to prevent unwanted code from being executed.  It is also the method used to separate what code gets executed when our interrupt function responds to multiple interrupt sources.  More on this in a moment.

Consider the following scenario:  You are sitting in your chair reading a magazine when you think you hear someone call your name.  You stop reading for a moment and listen but don't hear anything else, so you determine that you must have been hearing things and you return to your reading.  Conversely, if someone had in fact called your name you would answer them and respond to their request before returning to your reading.  This is similar to polling the interrupt flag bit.

When program execution jumps to the interrupt vector, the Global Interrupt Enable (GIE) bit is cleared automatically, disabling further interrupts while in the ISR.  At the end of the ISR the GIE bit is reset.  This is done automatically by the compiler by placing a RETFIE instruction at the end of the ISR.  RETFIE is an Assembly language instruction which says "Return from interrupt with Interrupts Enabled".  Additionally when we enter the ISR, the contents of the Working and STATUS registers are saved so they can be restored at the end of the ISR.  This is done because interrupts can occur at any time, and will likely modify the values in these registers.  The instruction that gets executed first after exiting the ISR may depend on the values that were in these registers before the ISR occurred.  When exiting the interrupt, these registers are restored to their previous values.

WRITING AN INTERRUPT SERVICE ROUTINE

We will set up a new project here to demonstrate using an interrupt.  We will use two LEDs connected to a PIC12F629 microcontroller.  LED 1 is a red LED connected to GP4 and LED 2 is a blue LED connected to GP0.  The LEDs are connected to ground through 220Ohm current limiting resistors.  Additionally, we will connect a Normally Open momentary push button to the GP2/INT pin, which will be our external interrupt source.

Upon applying power, the microcontroller will turn on the blue LED and wait for a button press.  The blue LED shows us that things are on and working.  A push of the button initiates an interrupt which turns off the blue LED and flashes the red LED on/off 20 times to simulate a "busy" indicator.  So that we can see these flashes, we will include 50 millisecond delays for each on and off flash.  Figure 1 shows our project schematic.

Figure 1.
The external interrupt program is shown in figure 2.  The red error lines and exclamation points can be ignored as this source file is not linked to a specific microcontroller device in the IDE which generates these errors when referencing the GPIO register.

Figure 2.
This program begins like any other with comments, the configuration word, and the inclusion of xc.h.  In the Global Variables section on line 23 I have defined a bit-sized variable named "request".  Interrupt flags are a single bit that is either set (1) or clear (0).  This bit variable will act as a flag bit so that we can get in and out of the ISR as quickly as possible but still do what we need to do with the interrupt.

The GPIO register is initialized as clear, and the TRISIO register is setup for the button input on GP2.

Our LED masks are defined for GP4 and GP0.

TRUE and FALSE bit variations are defined for easier reading of the code.

The Oscillator frequency is defined.

We then define our LED on and off functions.  I have used an alternative method for turning off the LEDs here to demonstrate different ways to accomplish the same task.  Rather than our usual exclusive OR (XOR) bitwise operator, we are now bitwise ANDing GPIOimg with the inverse of the masks.  The inverse of the mask is to swap the state of the bits turning ones into zeroes, and zeroes into ones.  The blue LED mask is 00000001, so the inverse would be 11111110.  ANDing GPIOimg with this inverse results in:

GPIOimg:  00000001
~mask:      11111110
result:       00000000

The result is then copied as usual to the GPIO register turning off the LED.

After our I/O functions is our section for the Interrupt beginning on line 64.  Our interrupt function is declared using void interrupt isr(void).

Like all other functions, the interrupt instructions are contained in curly brackets.  Line 67 shows where we poll the status of the interrupt specific enable and flag bits.  We test both bits in the same line by using the logical AND operator, which says if the enable bit AND the flag bit are true the result is true.  Don't confuse the bitwise AND (&) with the logical AND (&&).  If the result here is true, then the instructions within the curly brackets get executed.  If not, they get skipped.

Within the interrupt function, we only perform two instructions.  First we set our own "request" flag bit (line 69), and then we clear the Interrupt Flag bit (line 70).  If we don't clear the interrupt flag bit, we will never exit the ISR and will be stuck in an endless loop there.  It is 100% up to the programmer to clear the flag bits.  Clearing the flag bit also allows the interrupt to occur again.

Line 73 begins the main program section.  TRISIO, GPIOimg, and GPIO are all initialized by copying the initialization values to the appropriate registers.

Our own request flag is cleared at startup on line 79, since we don't want the program instructions associated with this flag to execute until an interrupt actually occurs.

Line 80 clears the INT interrupt flag at startup.

Line 81 enables GP2/INT interrupts.

Line 82 enables global interrupts.  Remember that even if we have the INT interrupt enabled, an interrupt will not occur unless global interrupts are also enabled.

Now the program turns on the Blue LED on line 83 before we enter into our endless while(1) loop.

The while(1) loop sits and waits for the request bit to be set.  When we press our button, the input signal on GP2 sets the INT interrupt flag bit (INTF) and the program goes to the ISR.  In the ISR, the request bit is set and INTF is cleared and program flow returns to the while(1) loop where it left off.  Now the request bit will return a true result, and the instructions in the if function will be executed.

Using the bit variable "request" here moves all of these instructions outside the ISR so that we can get into the ISR and back out of it as quickly as possible while still getting the result we want to.  In this case, to turn off the blue LED, flash the red LED 20 times with 50 millisecond delays, and turning the blue LED back on.

Those instructions are all contained in the if function.  The 20 flash count is achieved by using the local variable "count", and will decrement once each pass until count is no longer greater than zero.

Just like clearing the INT flag bit, we will want to clear the request bit within the if function, otherwise we will never stop executing that function since request will evaluate true every time.  Clearing the request bit gets us out of the if function and back to waiting for another button press.

Using interrupts is simple once you understand the process.  In later lessons, we'll look at using more than one interrupt in a program.

Thursday, January 1, 2015

Interrupts Part 1, Introduction to Interrupts

WHAT IS AN INTERRUPT

Thus far in our lessons and program examples, we have been working under the mindset that our programs run in a defined sequence from one instruction to the next.  When we reach the end of the sequence of instructions, the program goes back to the beginning and starts again (Figure 1A).  What if we need a special piece of code to be executed no matter where in the program we are?  Some event occurs like a button is pressed, or a peripheral feature reaches a designated state?  We could write our code in such a way that continuously checks the status of the button or peripheral feature, which is a practice called "polling", but polling is redundant and needlessly uses up program memory.  This is where interrupts come in.

An interrupt is triggered by some event, whether internal or external, that interrupts normal program flow.  This triggering event is called an Interrupt Request (IRQ).  When an interrupt request occurs, the program halts normal program flow and jumps to a special piece of code called the Interrupt Service Routine (ISR).  The ISR contains whatever code you want executed when an IRQ is initiated.  After the interrupt service routine completes execution, the program jumps back to whatever point in the main program it was when the IRQ occurred (Figure 1B).  The interrupt request can happen at any time during normal program flow, not just at points where some status is polled.

Figure 1.
THE PROGRAM COUNTER, AND THE STACK

Figure 2 is taken from the PIC12F629 datasheet, and shows the device's program memory map and stack.  At the top of this map you can see the label PC<12:0>.  This is the Program Counter, and is 13 bits wide from bit 12 to bit 0.  The program counter holds the memory address of the current instruction being executed.  As the program runs through the instructions, the program counter automatically increments to the next instruction in line during execution of the current instruction.
Figure 2.

The PIC12F629 microcontroller is implemented with 1K of program memory space, which has a range of 000h to 03FFh (hexadecimal value).  In decimal form, this range extends from 0 to 1,023.  The program counter is used to point to an address within this range.

You will notice however, that beyond the On-chip Program Memory range, there is a grayed-out area labeled 0400h to 1FFFh (1,024 to 8,191).  With its 13-bit width, the program counter is capable of addressing from 000h to 1FFFh.  The PIC12F629 only utilizes the first 1K of this range, and the rest is "unimplemented".  You might ask why that is the case.  The answer simply, is so that Microchip (the maker of PIC MCUs) can limit the number of internal hardware designs across multiple MCUs.  Back to our Program Counter...

You might be asking, what does any of this have to do with interrupts?  Well, as you can see on the program memory map, the address 0004 holds the Interrupt Vector, or the location in the program memory where the program counter jumps to when an interrupt request occurs.  Below the Program Counter label on the map is the Stack.  When an instruction is executed that causes our program to break normal program flow, such as with an interrupt request, the current instruction address of the program counter is placed on the stack.  The program counter is then loaded with the address of the jump-to instruction (in the case of interrupts, address 0004).  The program now jumps to the new memory location to execute the instructions there.  When those instructions are complete, the address being held in the stack is returned to the program counter, and program flow returns to where it left off before the jump.
Figure 3.

Figure 3 provides a visual reference of this process.  As the program is running through normal program flow, the program counter is loaded with the next instruction address in line.  When an Interrupt Request occurs, the current address on the program counter gets pushed onto the stack.  The program counter is then loaded with the interrupt vector address so that the next instruction that gets executed is the one at the beginning of the Interrupt Service Routine.  At the end of the ISR, a return from interrupt instruction is given which recalls the memory address being held by the stack.  That address is returned to the program counter so that the next instruction executed is the one which would have been executed next, had the interrupt not happened.

There are other events which cause the program counter to jump to another memory location, pushing an address onto the stack.  These instructions are noted in Figure 2 in Assembly language, which are CALL, RETURN, RETFIE, and RETLW.  You will notice also in Figure 2 that the stack is represented in eight different levels, from Stack Level 1, to Stack Level 8.  If normal program flow is jumped, and an address is pushed onto the stack, it goes to stack level 1.  If before returning to normal program flow, the program jumps again, another address is loaded into stack level 1, and the address that was at level 1 gets moved to level 2.  If we keep jumping program flow without returning from the jump first, the stack gradually fills up until all eight stack levels are holding an address.  If we jump one more time, the address at stack level 8 is lost forever, and cannot be recalled.  This event is called Stack Overflow, and it can be problematic.  If we are writing a program which causes the program counter to jump repeatedly, all the while pushing addresses onto the stack which causes an overflow, the program will not be able to return to where it originally started jumping from.  You will find as you grow in your programming that you have to be aware of how deep into the stack your programs are going.  Not to worry for now though!

Is knowing any of this necessary in order to write working programs?  No.  Is it necessary to know if you want to be a good programmer?  Yes!

INTERRUPT REQUEST SOURCES

Across their product line, Microchip PICs contain numerous interrupt request sources.  Not every PIC has every interrupt source though.  To learn what interrupts our specific PIC has, we must again reference the device datasheet, which contains a section dedicated to the device's interrupts.  For the PIC12F629, section 9.4 tells us we have six interrupt sources available.  These are External Interrupt on pin GP2, Timer 0 Overflow Interrupt, GPIO Port Change Interrupt, Comparator Interrupt, Timer 1 Overflow Interrupt, and EEPROM Data Write Interrupt.  A seventh interrupt source is available for the PIC12F675 which is identical to the PIC12F629 except that it includes an Analog to Digital Converter module.

Interrupts are controlled by Special Function Registers in the microcontroller.  These registers are the Interrupt Control register (INTCON), and the Peripheral Interrupt Register (PIR) (Figure 4).
Figure 4.
Some microcontrollers have more than one Peripheral Interrupt Register, which is why the PIC12F629 PIR is labeled as PIR1.  These registers contain Interrupt Enable bits to turn on interrupts, and also Interrupt Flag bits, which are used to show what interrupt specifically occurred.

The INTCON register contains:
GIE - Global Interrupt Enable bit
PEIE - Peripheral Interrupt Enable bit
T0IE - Timer0 Interrupt Enable bit
INTE - GP2/INT External Interrupt Enable bit
GPIE - GPIO Port Change Interrupt Enable bit
T0IF - Timer0 Overflow Interrupt Flag bit
INTF - GP2/INT External Interrupt Flag bit
GPIF - GPIO Port Change Interrupt Flag bit

The PIR1 register contains:
EEIF - EEPROM Write Operation Interrupt Flag bit
ADIF - A/D Converter Interrupt Flag bit (PIC12F675 only)
CMIF - Comparator Module Interrupt Flag bit
TMR1IF - Timer1 Overflow Interrupt Flag bit

To turn on interrupts and make them so they can be used, the enable bit for that interrupt must be set (made 1).  When the interrupt actually occurs, the corresponding Interrupt Flag bit is automatically set so that the program knows what caused the interrupt.  During the Interrupt Service Routine, the programmer must be sure to clear (make 0) the Interrupt Flag bit so that the program doesn't continue to jump back into the ISR after it has been executed.  The Global Interrupt Enable bit (GIE) is used to turn on/off all interrupts.  In order for a specific interrupt to be used, the specific interrupt enable bit and the global interrupt enable bit must be set.

In this lesson, we discovered what interrupts are and what they do to program flow.  We also got introduced to their control registers and the Enable and Flag bits.  Interrupts are very easy to use and extremely useful, it only takes a while to cover all of the information needed to understand how to use them.  In the next lesson, Interrupts Part 2, we will talk about how to set up an interrupt and how to write an Interrupt Service Routine.  Don't worry that we haven't talked about peripheral features such as Timers and the Comparator yet.  We are only getting used to understanding interrupts.  Remember, we build our knowledge one step at a time.