© Hubert Henry Ward 2020
H. H. WardIntermediate C Programming for the PIC Microcontrollerhttps://doi.org/10.1007/978-1-4842-6068-5_6

6. Interrupts

Hubert Henry Ward1 
(1)
Leigh, UK
 

In this chapter, you are going to look at interrupts. You will look at the importance of the fetch and execute cycle and the program counter.

Also, as stated in Chapter 4, you will use the compare aspect of the CCP module to create a square wave.

After reading this chapter, you will know what the program counter (PC) is and its importance. You will also learn what interrupts are, what they can be used for, and how to use them. You will appreciate what the compare aspect of the CCP module can do.

What Are Interrupts?

Interrupts are a very useful aspect of a microcontroller. Consider the situation where a microcontroller is monitoring a fire alarm system and other everyday tasks such as word processing or email. Hopefully the fire alarm will not go off. However, if it does, the microcontroller will stop your word processing and turn on the fire alarms and maybe some sprinklers and send out a call to the local fire station.

One way of making sure this will happen is for the program to examine the fire alarm switch to see if it has been pressed. The question is how often should it examine the switch: once every five minutes, once a minute, every second? This type of monitoring is termed “software polling.” However, 99% of the time it is a waste of effort, as hopefully, in this case, the fire alarm has not gone off. Also, there may be a delay in realizing the fire alarm switch was pressed because it was pressed between the polling of the switch. It’s not a very efficient method.

This is where interrupts can be used. This is a process whereby the microcontroller simply lets you carry on with mundane tasks but if an emergency happens, such as the fire alarm being pressed, the microcontroller will automatically interrupt the normal sequence of your program and force the controller to carry out the required action of the emergency. The monitoring of the fire alarm itself does not need any program instructions. What to do if the fire alarm goes off is written as a special type of subroutine called an interrupt service routine, or ISR.

This seems ideal since there is no wasted time polling the switch and there is no possibility of missing the emergency call. In reality, it is really a very efficient method of software polling, but, apart from writing to some SFRs, you don’t have to write it into your program; it happens automatically. Also, there is no chance of you missing a call.

To appreciate how this concept works, you need to take a brief look at the fetch and execute cycle of the microcontroller.

The Fetch and Execute Cycle

All microprocessor systems have to go through this type of cycle every time they carry out an instruction. The actual approach may vary but in essence it has to be something along these lines.

The Program Counter

You have to appreciate that all of the instructions for any program the micro is doing must be stored in the memory of the system, and the micro has to know where to go to in this memory to find the next instruction.

For the PIC to run a program, the micro must go to its program memory area to find the instructions of the program. All the memory locations in the PIC’s memory have their own address. How then does the micro know which address in memory it needs to go to so that it can get the instructions? A special register called the program counter (PC) does this job. With respect to the PIC18F4525, the PC is a 21-bit register that is made up of three 8-bit registers. When the PIC is first turned on, the housekeeping firmware loads the PC with the address of the first instruction in the program. This relates to the main loop that all C programs must have. From then on the PC is automatically incremented in the fetch and execute cycle, so that it is always pointing to the location in memory where the micro can get the next instruction in the program.

The fetch and execute cycle goes along following lines:
  1. 1.

    The micro examines the program counter to find out where it has to go to in the memory to find the next instruction. At the start of the program, this is the first instruction. The micro will then go to that address and get the instruction. This is the fetch part.

     
  2. 2.

    The next step, BEFORE the micro even looks at the instruction, is to increment the contents of the PC so that it is always pointing to the memory location of the next instruction. Note that because the instruction might be in two parts called the opcode and the operand, the information in the next memory location may not be an opcode; it may be the operand. However, this concept is not too important for this analysis.

     
  3. 3.
    This next step is the important step, and it may occur as I will describe it now or with some micros it may occur later in the cycle. However, it must occur in the fetch and execute cycle. The step is that the micro will check the interrupt flag. This is a flag that will be set if an event has occurred that demands the micro stops what it is doing, whatever it is, and deal with the emergency that triggered the interrupt flag. If this interrupt flag is set, the micro goes through the following steps:
    1. a.

      It stores the contents of the PC onto an area in memory called the stack.

       
    2. b.

      It then goes to a special memory location called the interrupt vector to find out where it has to go to carry out the sequence of events to deal with the emergency that triggered the interrupt flag.

       
    3. c.

      It must then carryout the instructions of the ISR (the interrupt service routine).

       
    4. d.

      Then, on returning from the ISR, it will reload the PC with the memory address it stored on the stack when it started the response to the interrupt in Step a.

       
    5. e.

      The micro can then complete the fetch and execute cycle.

       
     
  4. 4.

    If the flag is not set, the micro moves onto Step 5.

     
  5. 5.

    The next step in the cycle is to examine the instruction it has just retrieved from memory. If the micro needs more info before it can carry out the instruction, it looks at the contents of the PC to find out where in memory it must go and goes there to get the info. This means the cycle goes back to Step 1 and the micro goes through Steps 2 and 3 as before. This means it must check the interrupt flag again.

     
  6. 6.

    If the instruction does not need any more info, then the micro can carry out the instruction. This is the execute part of the cycle. Then the cycle starts back again at Step 1.

     

I am not guaranteeing that this is an exact description of the cycle. Indeed, the whole sequence is a complex procedure and it is not my intention to explain it in this book. However, it is correct in the essence of what must happen every time the micro gets an instruction or data from memory. It is important to realize that the micro must check this interrupt flag.

In this way, it is a kind of software polling except that the programmer does not have to write any program instruction to make this happen. It is done by what is known as the firmware of the PIC.

The benefit of using this approach is that you can never miss an interrupt call and you don’t have to waste time, and instructions, continually asking if an interrupt has occurred.

However, what you do have to understand is how to make the PIC make use of this interrupt flag. I will explain this in the next section.

The Sources of Interrupts

The PIC18F4525 has a number of different sources that can be used to trigger this interrupt flag.
  • There are external interrupt sources and the PIC18F4525 has three, which are
    • INT0 on Bit0 of PORTB

    • INT1 on Bit1 of PORTB

    • INT2 on Bit2 of PORTB

  • There is also the use of change on PORTB as an external interrupt; this uses Bits 7, 6, 5, and 4 of PORTB.

  • There is a wide range of internal interrupts, known as peripheral interrupts, which come from a range of peripheral devices, such as timers, the ADC, the UART, etc.

In general, there are three bits for each of the different interrupt sources that are used to control their operation. They are
  1. 1.

    The enable bit that simply enables the source to cause an interrupt.

     
  2. 2.

    The interrupt flag for that source. This is used to identify which source caused the interrupt.

     
  3. 3.

    The interrupt priority bit. This is used to signify if this interrupt can interrupt other interrupts that are already running.

     

There are 10 registers that are associated with interrupts and you will look at them as you go through an interrupt exercise.

The Process for a Simple Interrupt with No Priorities

The PIC18F4525 can apply two levels of interrupt priorities, which are simply high or low priorities. To start, you will look at using interrupts with no priorities. This implies that there is a way of enabling or disabling the priority interrupts. Indeed, there is and it is Bit7 of the RCON register. This is called the IPEN (interrupt priority enable) bit. If you reset this bit to a logic 0, you will have disabled the interrupt priority function of the PIC. This is what you will do now. You will look at using interrupt priorities later in this chapter.

The process of an interrupt is as follows:
  • A source will set the interrupt flag that is checked in the fetch and execute cycle.

  • The current contents of the PC are stored on the stack. This is so the micro can find out where it needs to go back to in the main program when the interrupt service routine has been completed.

  • The PC is then loaded with the address 0X0008 if the high-priority interrupts are being used or address 0X0018 if there are no high-priority interrupts being used. These two addresses are termed “interrupt vector addresses.” In this case, since you are not using priority interrupts, the PC is loaded with 0X0018.

  • The micro then goes to the interrupt vector address where it loads the PC with the address that is stored there. This will be the address of the ISR, which, in the download operation, the housekeeping firmware stores in that vector.

  • The micro can now go to this ISR where it polls the interrupt flags of the sources that are being used to determine which source caused the interrupt.

  • The micro then carries out the instructions of that particular interrupt after which it returns back from the interrupt.

  • In doing so, the PC is reloaded with the address from the stack and the micro can then carry on with the fetch and execute cycle.

To help explain how this works, let’s write a program that will use two of the external interrupt sources, those of INT0 on Bit0 of PORTB and INT2 on Bit2 of PORTB, to cause an interrupt.

It will also use a peripheral interrupt source that associated with timer 2, TMR2.

Setting Up the PIC to Respond to the Interrupts

Now you can now go about setting up the PIC to respond to the two external interrupts: INT0, which is the external interrupt connected to PORTB Bit0, and INT2, which is the external interrupt connected to PORTB Bit2 and the TMR2 peripheral interrupt.

The first control register you will look at is the INTCON register. The names of the individual bits of the INTCON are detailed in Table 6-1.
Table 6-1

The INTCON Register

Bit7

Bit6

Bit5

Bit4

Bit3

Bit2

Bit1

Bit0

GIE/GIEH

PEIE/GIEL

TMR0IE

INT0IE

RBIE

TMR0IF

INT0IF

RBIF1

The operation of the bits is described as follows:

Bit7, GIE/GIEH (global interrupt enable/global interrupt enable priority high)

This bit has two uses. The uses for this bit depend upon the setting of the IPEN bit, which is Bit7 of the RCON register. Don’t worry; it’s not as confusing as it looks. The IPEN bit is simply the interrupt priority enable bit. This is a function that can allow an interrupt to be interrupted by another interrupt that has been given a higher priority than the one currently running. The programmer can allow this to happen by enabling the function. This is done by setting this bit, the IPEN bit, to a logic 1. If, as you will be doing, you set this bit to a logic 0, you won’t enable any interrupts to have a higher priority.

When the IPEN bit is a logic 0, the action for Bit7 is
  • A logic 1 will enable all unmasked interrupts

  • A logic 0 will disable all interrupts.

When the IPEN bit is a logic 1, the action for bit 7 is
  • A logic 1 will enable all high-priority interrupts.

  • A logic 0 will disable all interrupts.

This means that if you are to use any interrupts, external or peripheral, no priority or use priority, this bit must be set to a logic 1. The term “global” means it can enable or disable external and or peripheral interrupts.

Bit6, PEIE/GIEL (peripheral interrupt enable/global interrupt low priority enable)

This bit also has two sets of actions which depend on the setting of the IPEN bit in the RCON register.

When the IPEN bit is a logic 0, the action for Bit6 is
  • A logic 1 will enable all unmasked peripheral interrupts.

  • A logic 0 will disable all peripheral interrupts.

When the IPEN bit is a logic 1, the action for Bit6 is
  • A logic 1 will enable all low priority interrupts.

  • A logic 0 will disable all the low priority peripheral interrupts.

Since this program is going to use one of the peripheral interrupts, this bit must set to a logic 1.

Bit 5, TMR0IE (timer0 overflow interrupt enable bit)

There is an interrupt flag associated with timer0. When timer0 gets to its maximum value, a further increment will cause it to roll over back to 0; this is termed “roll over.” When this happens, the TMR0IF, timer0 interrupt flag, will be set to tell the micro this has happened. To allow this action to instigate an interrupt, you have to set this Bit5 of the INTCON register.

A logic 1 in Bit5 of the INTCON register will enable this interrupt.

A logic 0 in Bit5 of the INTCON register will disable this interrupt.

However, even though the interrupt action is disabled, the TMR0IF, Bit 2 of this INTCON register, will be set the first time timer0 rolls over. But this action will not set the interrupt flag, which is checked within the fetch and execute cycle.

You will not be using this interrupt, so you set this to logic 0.

Bit 4, INT0IE (external INT0 enable bit)

A logic 1 will enable the INT0 interrupt.

A logic 0 will disable the INT0 interrupt.

Therefore, as you are using INT0, this bit must be set to a logic 1.

Bit 3, RBIE (PORTB change interrupt enable bit)

A logic 1 will enable this interrupt.

A logic 0 will disable this interrupt.

Note that this interrupt is activated when one of the inputs on Bit7, Bit6, Bit5, and Bit4 of PORTB change their logic state.

You will not be using this interrupt, so you set this to logic 0.

Bit 2, TMR0IF (the timer0 interrupt flag)

This is the actual flag that the TMR0 sets when the timer rolls over. Note this flag has to be cleared or reset (i.e. set back to a logic 0) by the software program.

A logic 1 indicates that timer0 has rolled over. This bit must be cleared in software so that the micro can set it again the next time timer0 rolls over.

The programmer can monitor this bit to detect when timer0 rolls over even if this action is not going to initiate an interrupt. Indeed, the programmer can monitor most interrupt flags in this fashion.

Bit 1, INT0IF (INT0 external interrupt flag)

This is the actual flag that the INT0 interrupt sets when the INT0 causes an interrupt. In the ISR, you can ask the micro to check this flag to see if it was INT0 that caused the interrupt.

A logic 1 indicates that an interrupt on INT0 has occurred. This bit must be cleared in software.

Bit 0, RBIF (PORTB change interrupt flag)

A logic 1 means at least one of Bit7 to Bit4 on PORTB has changed state. This bit must be cleared in software.

In the ISR, you can ask the micro to check this flag to see if it was a change of state on PORTB that caused the interrupt.

You should now be able to determine what logic you must write to each of these bits in the INTCON register.

As you are going to use an interrupt, be it external or peripheral, you will have to enable all unmasked interrupts and so you must set Bit7 to a logic 1. Note that the IPEN bit in the RCON register will be set to a logic 0 since you won’t be using any high-priority interrupts.

You will need to enable the peripheral interrupts because you are using the TMR2 peripheral interrupt. Therefore you must set Bit6 the PEN, peripheral enable bit, to a logic 1.

You are not using the TMR0 interrupt, so you can set Bit5 to a logic 0.

You are going to use the INT0 interrupt, therefore Bit4 must be set to a logic 1.

You are not going to the use the change on PORTB interrupt, therefore Bit3 can be set to a logic 0.

Bits 2, 1, and 0 are actual flags that if they are used, the micro will set. Therefore at this present time, set them to a logic 0.

This means that the 8-bit number for the INTCON register will be as shown in Table 6-2.
Table 6-2

The Data to Be Written to INTCON Register

Bit7

Bit6

Bit5

Bit4

Bit3

Bit2

Bit1

Bit0

GIE/GIEH

PEIE/GIEL

TMR0IE

INT0IE

RBIE

TMR0IF

INT0IF

RBIF1

1

1

0

1

0

0

0

0

The instruction to do this is

INTCON = 0b11010000;

This enables all global interrupts and peripheral interrupts. It also enables the INT0 interrupt.

You now need to enable the other two interrupts, the INT2 and TMR2 match interrupts. The control register that controls the INT2 external interrupt is INTCON3. INTCON1 and INTCON2 control other interrupts that you are not using. Therefore, these two control registers can be left at their default settings of zero.

The names of the bits of the INTCON3 8-bit register are shown in Table 6-3.
Table 6-3

The TNTCON3 Register

Bit7

Bit6

Bit5

Bit4

Bit3

Bit2

Bit1

Bit0

INT2IP

INT1IP

Not Used

INT2IE

INT1IE

Not Used

INT2IF

INT1IF

As you are not using any interrupt priority, Bits 7 and 6 can be set to logic 0.

As you are using the external interrupt INT2, Bit4 must be set to a logic 1 since this is INT2IE (interrupt 2 interrupt enable bit).

As you are not using INT1, Bit3 must be set to a logic 0.

Note that Bits 1 and 0 are flags or signals to the program that the appropriate interrupt has occurred. Therefore, at this point in time, they will be set to a logic 0.

Therefore, the 8-bit number that has to be written to this control register is 0b00010000. This is done with the following instruction:

INTCON3 = 0b00010000;

Just as a point of interest, I’ll explain why we use the term “flag.” This is because in the United States and some other countries, people sometimes have boxes at their garden gate where they get mail. To signify to the mailman that there is mail in the box that needs posting, the owner flips up a flag-shaped lever on the side of the can. This is seen as a logic 1. The mailman will get the post and flip the flag down, a logic 0. If there is no outgoing mail, this flag will be left down. Hence we use the term flag to represent a signal source.

The PIE1 Register

This is the peripheral interrupt enable 1 register. It is an 8-bit register that allows us to enable some of the peripheral interrupts; the TMR2 interrupt is one of them. There are so many peripheral interrupts that the PIC18F4525 has two PIE registers. The names of the bits of the PIE1 are shown in Table 6-4.
Table 6-4

The PIE1 Register

Bit7

Bit6

Bit5

Bit4

Bit3

Bit2

Bit1

Bit0

PSPIE1

ADIE

RCIE

TXIE

SSPIE

CCP1IE

TMR2IE

TMR1IE

The 8 bits control the PIC in the following manner:

Bit7, PSPIE (parallel slave port read/write interrupt enable bit)

1 = Enables the PSP read/write interrupt

0 = Disables the PSP read/write interrupt

Bit6, ADIE (A/D converter interrupt enable bit)

1 = Enables the A/D interrupt

0 = Disables the A/D interrupt

Bit5, RCIE (EUSART receive interrupt enable bit)

1 = Enables the EUSART receive interrupt

0 = Disables the EUSART receive interrupt

Bit4, TXIE (EUSART transmit interrupt enable bit)

1 = Enables the EUSART transmit interrupt

0 = Disables the EUSART transmit interrupt

Bit3, SSPIE (master synchronous serial port interrupt enable bit)

1 = Enables the MSSP interrupt

0 = Disables the MSSP interrupt

Bit2, CCP1IE (CCP1 interrupt enable bit)

1 = Enables the CCP1 interrupt

0 = Disables the CCP1 interrupt

Bit1, TMR2IE (TMR2 to PR2 match interrupt enable bit)

1 = Enables the TMR2 to PR2 match interrupt

0 = Disables the TMR2 to PR2 match interrupt

Bit0, TMR1IE (TMR1 overflow interrupt enable bit)

1 = Enables the TMR1 overflow interrupt

0 = Disables the TMR1 overflow interrupt

Since you want to use the TMR2 interrupt, you must set Bit1 to a logic 1. All of the other bits can be set to a logic 0.

Therefore, the data to be written to the PEI1 control register is 0b00000010. This is done with the following instruction:

PEI1 = 0b00000010;

The Algorithm for the Interrupt Test Program

The main section of the program will either turn off all LEDs on PORTC if Bit4 of PORTB is momentarily pressed or turn on LEDs 1 and 2 of PORTC if Bit5 of PORTB is momentarily pressed.

The three interrupts will force the PIC to do the following:
  • Light up the first four LEDs connected to Bits 0, 1, 2, and 3 of PORTC. This will happen if the INT0 external interrupt is activated. This will happen when Bit0 of PORTB is momentarily pressed.

  • Light up the three LEDs connected to Bits 2, 3, and 5 of PORTC. This will happen if the INT2 external interrupt is activated. This will happen when Bit2 of PORTB is momentarily pressed.

  • The LED connected to Bit0 of PORTD will light up the first time the value in timer2 matches the value in the PR2 register.

  • The next time the value in timer2 matches the value in the PR2 register, the LED will turn off.

  • From then on, the LED will continue to turn on and turn off when the value in timer 2 matches the value in the PR2 register. This is the action of the TMR2 match interrupt . Every time the value in timer2 matches the value in the PR2 register, the TMR2 match interrupt will occur.

Compiler Software Version Issue

There is an issue I have come across when using interrupts. There seems to be a problem of which compiler version to use with interrupts. I have noticed that the later versions of the XC8 compiler seem to have a problem with keyword Interrupt. However, I don’t have this problem when I use version 1.35. Therefore, to ensure the program compiles correctly, you should select version 1.35 when asked to select the compiler in the project creation process. If you have already created your project, you can change the compiler program by holding the right mouse button on the name of the project in the project tree. A flyout menu will appear, so select the properties option that appears at the bottom of the menu. You will see the window shown in Figure 6-1. You can now change the complier software to the earlier version 1.35, as I have done. The program will now compile correctly.
../images/497005_1_En_6_Chapter/497005_1_En_6_Fig1_HTML.jpg
Figure 6-1

The Project Properties window

This is a very strange issue. I have tried to find a solution on the Internet but I have found nothing that makes sense of this. What is even stranger is that if you now change the compiler back to the version 2.10, the program will compile correctly. I think this is another example of change is not always for the best. I do prefer using the XC8 compiler version 1.35 for my projects.

The complete code for this program that tests the operation of these three interrupts is shown in Listing 6-1.
 1. /*This is simple program to investigate interrupts
 2. It uses three interrupt sources which are
 3. INT0 and INT2 two external interrupts
 4. TMR2 match interrupt an internal peripheral interrupt
 5. INT0 and INT2 will effect the display on PORTC
 6. The TMR2 match will effect bit0 on PORTD
 7. The program is written for the PIC 18F4525
 8. Written by H H Ward Dated 07/06/15
 9. */
10. #include <xc.h>
11. #include <conFigInternalOscNoWDTNoLVP.h>
12. #include <PICSetUp.h>
13. void interrupt ISR ()
14. {
15. if (INTCONbits.INT0IF == 1)
16. {
17. INTCONbits.INT0IF= 0;
18. PORTC = 0x0F;
19. }
20. else if(INTCON3bits.INT2IF == 1)
21. {
22. INTCON3bits.INT2IF= 0;
23. PORTC = 0x2C;
24. }
25. else if (PIR1bits.TMR2IF == 1)
26. {
27. PIR1bits.TMR2IF = 0;
28. if (PORTDbits.RD0 == 0) PORTDbits.RD0 = 1;
29. else PORTDbits.RD0 = 0;
30. }
31. }
32. main ()
33. {
34. initialise ();
35. TRISBbits.RB0 = 1;
36. TRISBbits.RB2 = 1;
37. TRISBbits.RB4 = 1;
38. TRISBbits.RB5 = 1;
39. TRISD = 0;
40. INTCON = 0b11010000;
41. INTCON2 = 0b0000000;
42. INTCON3 = 0b00010000;
43. PIE1 = 0b00000010;
44. RCONbits.IPEN = 0;
45. OSCCON = 0b00010100;
46. T2CON = 0b00000111;
47. PR2 = 250;
48. PORTC = 0;
49. while (1)
50. {
51. if (PORTBbits.RB4 == 1) PORTC = 0;
52. else if (PORTBbits.RB5 == 1)  PORTC = 6;
53. }
54. }
Listing 6-1

The Code for the Three Interrupt Sources

The Analysis of Listing 6-1

Lines 1 to 9 are simply my comments for this program.
Line 10 #include <xc.h>
This is the main include file.
Line 11 #include <conFigInternalOscNoWDTNoLVP.h>
This is the header file you created before.
Line 12 #include <PICSetUp.h>
This is the header file you created before. However, there are certain aspects you will have to change. They are changed later in the program.
Line 13 void interrupt ISR ()

This is the first step in creating a special subroutine. It is termed the interrupt service routine ISR. That is why you have the keyword interrupt after the keyword void. When the IDE loads the program down to the PIC, the firmware will load the interrupt vector address 0X0018 with the memory location of where the first instruction of this ISR can be found in the PIC’s program memory.

The keyword void means that the ISR will not be sending any data back to the main program.
Line 14 {
This is simply the opening curly bracket of the ISR.
Line 15 if (INTCONbits.INT0IF == 1)
There are three possible interrupt sources that may have set the interrupt flag that was checked during the fetch and execute cycle. The first thing you must do in the ISR is determine which interrupt source caused the interrupt. You do this by testing each of the interrupt flags that belong to the sources. This uses the if this, then do that, else do something else test. In this case, check the next interrupt flag. In line 15, you check to see if the INT0IF (INT0 interrupt flag, that is Bit1 of the INTCON register) has been set. If it has, this means that it was the external interrupt INT0 that caused the interrupt. If it isn’t set, then you must check the other interrupt flags.
Line 16 {
Since there is more than one instruction to carry out with this test, you have to envelope the instructions inside a set of curly brackets. This is the opening curly bracket for the instructions associated with INT0.
 Line 17 INTCONbits.INT0IF = 0;
This will turn the INT0 Interrupt Flag off. This must be the first thing you do or the INT0 interrupt will continue to cause an interrupt even though Bit0 of PORTB has gone back to a logic 0. Note you would have let go of the momentary switch connected to Bit0 of PORTB by now.
Line 18 PORTC = 0x0F;
This is what should happen if the INT0 interrupt is made to happen. The first four bits on PORTC should go to a logic 1 and the remaining bits go to a logic 0.
Line 19 }
This is the closing curly bracket of the first if, the test for the INT0 interrupt. If it was INT0 that caused the interrupt, then the micro will ignore all the other instructions in the ISR and return back to the main program from here.
Line 20 else if (INTCON3bits.INT2IF == 1)
This is the else part of the first if test. If the test on line 15 was untrue, then the micro will carry out this test. This tests to see if the INT2IF is set. This would mean that the INT2 caused the interrupt.
Line 21 {
Again, this test is a multiple-line instruction and so the instructions are enclosed in a set of curly brackets. This is the opening curly bracket for this test.
Line 22 INTCON3bits.INT2IF = 0;
Again, you must turn off the interrupt flag that caused the interrupt so that it doesn’t continually cause an interrupt. In this, case it was INT2 that caused the interrupt so set its flag, IN2IF, to a logic 0.
Line 23 PORTC = 0x2C;
This is what should happen if the INT2 interrupt is made to happen.
Line 24 }
This is the closing curly bracket of the second if, the test for the INT2 interrupt.
Line 25 else if (PIR1bits.TMR2IF == 1)
Here you are checking to see if it was the third source that caused the interrupt. This is the TMR2 interrupt, which is an internal peripheral interrupt. This does not require any action from the user of the program. This interrupt will happen automatically every time the value in timer2 matches the value stored in the PR2 register. This PR2 register is a special register that is used in conjunction with timer2.
Line 26 {
This is the opening curly bracket for this if test.
Line 27 PIR1bits.TMR2IF = 0;
Again, the first thing you must do is turn off the TMR2 interrupt flag.
Line 28 if (PORTDbits.RD0 == 0) PORTDbits.RD0 = 1;
Here you are creating an if test that tests to see if Bit0 of PORTD is at a logic 0. If it is a logic 0, then set it to a logic 1. Note that this is a single-line if test, so you don’t need the curly brackets.
Line 29 else PORTDbits.RD0 = 0;

This is what the PIC must carry out if the test on line 28 was untrue (i.e. if Bit0 of PORTD was actually at a logic 1, then reset the bit to logic 0). Note that because you are using the if this, then do this, else do that type of function with lines 28 and 29, if the test on line 28 was true, the micro would simply skip this else instruction on line 29. The micro would only carry out the instruction on line 29 if the test on line 28 was untrue.

This is the difference between this type of instruction and simply listing a series of if test after if test. With the if test after if test, the micro must look at each if test whether or not the pervious test was true. This is a very subtle difference, but an important one.

In this way, lines 28 and 29 will make the logic on Bit0 of PORTD alternate between logic 1 and logic 0 every time the TMR2 interrupt happens. In this way, you make an LED connected to this bit flash at a rate controlled by timer2 and the value stored in the PR2 register.
Line 30 }
This is the closing curly bracket of the third if test.
Line 31 }
This is the closing bracket of the ISR.
Line 32 void main ()
This is the creation of the main loop.
Line 33 {
This is the opening curly bracket of the main loop.
Line 34 initialise ();
This is a subroutine call to force the PIC to go to the subroutine called initialise. This subroutine is in the file you created in the PICSetup.h header file and it will set up the PIC as you normally want it.
Line 35 TRISBbits.RB0 = 1;

To understand this line, you must remember that in the PICSetup.h header file you made all the bits in PORTB outputs. However, in this program you will need Bits 0, 1, 4, and 5 to be inputs. Therefore, you must change those bits in the TRISB to a logic 1. This is what this instruction does to Bit0 in TRISB.

Line 36, 37, and 38 do the same but for Bits 1, 4, and 5.
Line 39 TRISD = 0;
Here you are setting all the bits in TRISD to a logic 0. This makes all the bits on PORTD outputs. You need this because you will connect at least one LED to Bit0 of PORTD. Again, you should recall that in the PICSetup.h header file you set PORTD to all inputs.
Line 40 INTCON = 0b11010000;
This turns on all global and peripheral interrupts and enables the INT0 interrupt.
Line 41 INTCON2 = 0b00000000;
You are not using any interrupts associated with this control register. Note that you don’t really need this instruction as it defaults to zero.
Line 42  INTCON3 = 0b00010000;
This enables the INT2 interrupt.
Line 43 PEI1 = 0b00000010;
This enables the TMR2 peripheral interrupt.
Line 44 RCONbits.IPEN = 0;
This disables the interrupt priority facility. You don’t really need this instruction as the bit defaults to logic 0 at start-up.
Line 45 OSCCON = 0b00010100;
This sets the oscillator frequency to 125kHz and makes it stable.
Line 46 T2CON = 0b00000111;

This turns timer2 on and sets the divide rate at 16. This means timer2 counts at the following frequency:

oscillator = 125kHz

The clock is a quarter of the oscillator (i.e. = 31.25kHz).

timer2 divides this by 16, therefore frequency = 1.953kHz.

This means one tick of timer2 takes 512μs.

Since the PR2 is set to 250, it takes 250x512μs before the LED on PORTD will come on and a further 250x512μs before it turns off. Therefore, the LED will flash on for 128ms and off for 128ms.

Note that Bit0 of the T2CON register could be set to a logic 0 and nothing would change. This is because the T2CON register doesn’t care what logic is on this bit when selecting the TMR2 Preset to 16. This is signified by the x on the datasheet.
Line 47 PR2 = 250;

This simply loads 250 into the PR2 register. This is the value timer2 must count up to before it sets the interrupt.

As an exercise, change the value in the PR2 to 125. What do you think will happen?
Line 48 PORTC = 0;
This simply sets all bits on PORTC to logic 0 and so turns everything connected to PORTC off.
Line 49  while (1)
This sets up a forever loop to make the micro carry out the instructions within the curly brackets forever.
Line 50 {
The opening curly bracket for the forever loop.
Line 51 if (PORTBbits.RB4 == 1) PORTC = 0;
This tests to see if the logic on Bit4 of PORTB has gone to a logic 1. If it has, then it sets all bits on PORTC to logic 0 (i.e. turns everything connected to PORTC off).
Line 52 else if (PORTBbits.RB5 == 1)  PORTC = 6;
If Bit4 of PORTB is not a logic 1, then test to see if Bit5 of PORTB has gone to a logic 1. If it has, load PORTC with the value 6. This will turn on Bits 1 and 2 of PORTC. Note, if the test on line 51 is true, then the micro will skip this instruction on line 52. The micro will only carry out this instruction if the test on line 51 is untrue.
Line 53 }
This is the closing bracket of the forever loop.
 Line 54 }

This is the closing bracket of the main loop.

When you run this program, you should see that an LED on Bit0 of PORTD will continually flash at a frequency of 3.9Hz. This frequency is controlled by the value in the PR2 register. You should appreciate that there is nothing in the main loop of the program that makes this happen, apart from loading the PR2 with the value 250. This happens purely because of the peripheral interrupt of TMR2 match with the PR2.

A simulation of the program is shown in Figure 6-2.
../images/497005_1_En_6_Chapter/497005_1_En_6_Fig2_HTML.jpg
Figure 6-2

Simulation of the program

Exercise 6.1

If Bit6 of the INTCON register was set to a logic 0, explain what would change and why it would change.

Explain how you could keep the logic at Bit6 of the INTCON register a logic 1, but still achieve the same result.

Using the Compare Function of the CCP Module

This function links into the use of interrupts. You will use it as the alternative method of producing a square wave, as mentioned in Chapter 4. In Chapter 4, you created the square wave using the PWM firmware of the CCP module in the PIC. This was because you were primarily creating square waves to use the PWM to set the speed of a DC motor. If all you want to do was produce a square wave, then perhaps this next method will be more efficient.

The principle behind this operation is that the compare function of the CCP compares the value of the CCPRX register with either the TMR1 or TMR3 registers. Note that these three registers can be used as simple 8-bit registers or 16-bit registers. To make them into 16-bit registers, they use two 8-bit registers cascaded together.

Exercise 6-2

Briefly explain the advantage of cascading two 8-bit registers together, like this, to make a 16-bit register.

This means that each of the registers has a low byte (the CCPRXL) and a high byte (the CCPRXH), both of which can be accessed separately.

You use the x because there is CCPR1 and CCPR2. Note that by writing CCPR1 and CCPR2 you are addressing the complete 16-bit register. Writing CCPR1L means you are only addressing the low byte 8-bit register.

With the compare operation the CCPRX is compared with either the TIMR1 of TIMER3. When the values in the two registers are the same, you can control what happens to the CCPX pin on the PIC. There are three possible actions that can happen when the comparison finds a match between the two registers:
  • The CCPX can be driven high.

  • The CCPX can be driven low.

  • The CCPX can toggle; that is if, it is high, it goes low, or if it is low, it goes high.

It is the third option that can be used to create a square wave on the CCPX pin (i.e. on the CCP1 or CCP2 pin).

It is Bit6 and Bit3 of the T3CON register, the control register for timer3, that allow the programmer to decide which timer register is compared with the value in the CCPRX register. The two bits control this selection, as shown in Table 6-5.
Table 6-5

The Control of the Clock Sources for the Capture/Compare Mode of the CCP Modules

Bit 6

Bit 3

Operation

0

0

Timer 1 is the capture/compare clock source for the CCP I/O

0

1

Timer 1 is the capture/compare clock source CCP1 I/O

Timer 3 is the capture/compare clock source CCP2 I/O

1

0

Timer 3 is the capture/compare clock source for the CCP I/O

1

1

Timer 3 is the capture/compare clock source for the CCP I/O

For this program, you will set both bits to logic 0. This sets timer1 as the clock source for both the CCP I/O.

To test this concept, the following program will be created.

The Algorithm for the Compare Function

  • The main program will simply make an LED connected to Bit0 of PORTB flash at 0.5 seconds on and 0.5 seconds off (i.e. flash with a frequency of 1Hz).

  • The CCP module will compare the low byte of the CCPR1 register with the low byte of the timer1 register and, when they match, it will toggle the output of the CCP1 bit on PORTC. This will produce a square wave output on the CCP1 output. The frequency will be controlled by the value stored in the CCPR1 register.

  • The frequency of square wave is controlled as follows:
    • The CCP1 output toggles from low to high when the value in the timer1 register equals the value stored in the CCPR1 register. At the same time, the value in timer1 will be reset to 0.

    • The CCP1 output will then toggle from high to low when the value in timer1 next equals the value in the CCPR1 register.

    • The process will repeat and the CCP1 output will toggle from low to high when the timer1 equals the CCPR1 register.

  • This means the square wave will have a 50/50 duty cycle and the periodic time will be twice the time taken for the value in the timer1 register to equal the value in the CCPR1 register.

  • To appreciate how long it will take for this to happen, you have to understand the setting of the timer1 control register T1CON.

  • This will be loaded with the value 0b00110001 in line 30 of the program. This makes the timer1 register an 8-bit register with a divide rate of 8. (I made it an 8-bit register simply because my PROTEUS only supports 8-bit registers.)

  • Knowing that the clock runs at a quarter of the oscillator and the oscillator was set to 8Mhz in the PICSetup.h header file, the clock will run at 2Mhz.

  • As you are using a divide rate of 8, then timer1 will count at a frequency of 250khz (i.e. 2,000,000 divided by 8).

  • This means one count will take 4μs as this is the periodic time for a frequency of 250kHz.

  • You will load the CCPR1 register with 250 and so the mark time and the space time will be 250 x 4μs = 1ms. This means that the periodic time for the square wave will be 2ms, which gives a frequency of 500Hz.

  • The production of this 500Hz square wave is all done with the use of the CCP1 interrupt. This interrupt is triggered when the value in the CCPR1 register is matched by the value in the timer1 or timer3 register. You are using the timer1 register. This means you need to enable the CCP1 interrupt.

The program to test this process is shown in Listing 6-2.
 1. /* File:   variableFreqProg.c
 2. Author: H. H. Ward
 3. Using only 8 bit register therefore use TMR1L, CCPR1L
 4. Created on 01 February 2019, 16:36
 5. */
 6. #include <xc.h>
 7. #include <conFigInternalOscNoWDTNoLVP.h>
 8. #include <PICSetUp.h>
 9. unsigned char n;
10. void interrupt ISR ()
11. {
12. PIR1bits.CCP1IF = 0;
13. if (TMR1L >= CCPR1L) TMR1L = 0;
14. }
15. void delay (unsigned char t)
16. {
17. for (n = 0; n < t; n ++)
18. {
19. TMR0 = 0;
20. while (TMR0 < 255);
21. }
22. }
23. void main ()
24. {
25. initialise ();
26. INTCON = 0XC0;
27. PIE1 = 0X04;
28. TRISC = 0x00;
29. PORTC = 0;
30. T1CON = 0b00110001;
31. TMR0 = 0;
32. CCP1CON = 0b00000010;
33. CCPR1L = 250;
34. while (1)
35. {
36. PORTBbits.RB0 =(PORTBbits.RB0 ^ 1);
37. delay (15);
38. }
39. }
Listing 6-2

The CCP1 Interrupt Program

The Analysis Of Listing 6-2

I hope lines 1 to 9 need no analysis because you have used them before.
Line 10 void interrupt ISR ()
This is creating the interrupt service routine for this program.
Line 11 {
Simply the opening curly bracket for the ISR.
Line 12 PIR1bits.CCP1IF = 0;
This clears the CCP1 interrupt flag. You need to do this now to stop the CCP1 from continually interrupting the program.
Line 13 if (TMR1L >= CCPR1L) TMR1L = 0;

This line checks to see if the value in the TMR1L register is equal to or greater than the value on the CCPR1L register. If it is, then reset the value in the timer1 register back to 0.

The important thing to notice about this is that you are only using the low byte of the timer1 and the CCPR1 registers, hence the reference to TMR1L and CCPR1L. Also, you know the two registers will be equal to each other because that occurrence has caused the interrupt in the first place. However, you cannot ask if the two registers are equal, or use

if (TMR1L = CCPR1L) TMR1L = 0;

This is because by the time the micro gets to this instruction, the value in timer1 will be greater than the value in the CCPR1L register.

One last thing I should mention is that you have no need to ask this question at all, because the match must have happened already since the interrupt has occurred. This means you could replace this instruction with TMR1L = 0; and not use the if statement.

However, I wanted to show how and why you could use the >= the greater than or equal instruction. The program would work in just the same way. Try it and see.
Line 14 }

This is the closing curly bracket of the ISR.

Lines 15 to 22 create your variable delay subroutine from before.

The majority of the remaining lines you have used before. The new lines are
Line 26 INTCON = 0XC0;
This sets Bit7 and Bit6 of the INTCON register. This is to enable all of the global and peripheral interrupts. Note that the CCP1 interrupt is a peripheral interrupt.
Line 27 PIE1 = 0X04;
This sets Bit2 of the peripheral interrupt enable 1 register. This then enables the CCP1 interrupt.
Line 32 CCP1CON = 0b00000010;
This puts the CCP module into the compare mode. See Table 4-2 in Chapter 4.
Line 33 CPR1L = 250;
This loads the CCPR1L register with the value 250 in decimal. It is this value, linked with the frequency of timer1, that controls the mark and space and so the frequency of the square wave output on the CCP1 pin.
Line 36 PORTBbits.RB0 =(PORTBbits.RB0 ^ 1);

This is a new instruction. What it does is perform a logical EXOR operation with Bit0 of PORTB and the logic value 1. The ^ symbol simply stands for the EXOR logic instruction.

The truth table for the logical EXOR (i.e. Exclusive OR) is shown in Table 6-6.
Table 6-6

The Truth Table for the EXOR Function

B

A

F

0

0

0

0

1

1

1

0

1

1

1

0

This shows that the output F will be a logic 1 if A OR B are a logic 1. When the logic in both inputs are the same, the output F will be a logic 0. This is really a True OR function, as the output F will only be a logic 1 when either input A OR input B is a logic 1. The OR gate sets the output F to a logic 1 if A OR B is a logic 1, but also if A AND B are a logic 1. This is why the OR gate is sometimes called, or should be called, the inclusive OR since it includes the AND function. Note that EXOR stands for exclusive OR since it excludes the AND function.

What this means is that if Bit0 of PORTB is a logic 0, then when you EXOR it with a logic 1, the results is that bit0 goes to a logic 1. However, the next time you EXOR it with a logic 1, the two would be the same and Bit0 of PORTB is forced back to a logic 0.

This makes the logic at Bit0 of PORTB simply toggle on and off. It is a very neat way of toggling an output.
Line 37 delay (15);

This simply calls a 0.5 second delay between each EXOR operation.

These two instructions make the LED at Bit0 of PORTB oscillate on and off at a frequency of 1 Hz.

A simulation of the program to create the 500Hz square wave is shown in Figure 6-3.
../images/497005_1_En_6_Chapter/497005_1_En_6_Fig3_HTML.jpg
Figure 6-3

The square wave using the compare function of the CCP module

Using Priority Enabled Interrupts

Since the PIC can handle a large number of interrupts in one program, there may be a time when an interrupt is more important than the one that is running at present. In this case, the more important interrupt should be able to safely interrupt the current one and make the micro carry out its instructions of the ISR and then return to complete the instructions of the interrupt it overrode.

To enable this to happen, you can give interrupts a level of priority so that the PIC can say one interrupt is more important than another interrupt. With the PIC 18F4525, there are only two levels of priority: low priority and high priority. Other PICs may offer more levels of priority; indeed, the 32-bit PICs offer up to seven levels of priority.

The high-level interrupt can override a low-priority one but a low-priority interrupt cannot override a high-priority interrupt. Also, an interrupt cannot override an interrupt of the same priority. The PIC 18f4525 only has two levels of priority, which is not very extensive, but it is an attempt to provide a useful application.

To show you what you can do with the priority interrupts with the PIC 18F4525 I have written a program that incorporates a high and low level interrupt. It will use INT1 as the low priority interrupt and INT2 as the high priority interrupt. To keep the analysis simple, these will be the only interrupts used.

The Algorithm for the High/Low Priority Program

  • The first change is to set the IPEN bit, which is Bit7 of the RCON register. This is needed to enable the priority function of the interrupts.

  • Next, you need to set the priority level for the two interrupts you are going to use.

  • INT1 can be set to either low or high priority. This is controlled by the INT1IP (interrupt1 interrupt priority bit), which is Bit6 of the INTCON3 register. To set this to low priority, you simply set this bit to a logic 0.

  • INT2 can be set to either low or high priority. This is controlled by the INT2IP (interrupt2 interrupt priority bit), which is Bit7 of the INTCON3 register. To set the INT2 to high priority, this bit must be set to a logic 1.

  • You must enable these two interrupts and their interrupt enable pins are Bit4 for INT2 and Bit3 for INT1 of the INTCON3 register. These two bits must be set to a logic 1 to enable the two external interrupts.

  • This means that the data to be written to the INTCON3 register is 0b10011000. This is done using the instruction INTCON3 = 0b10011000;.

  • You also need to enable the global high priority interrupts, which means Bit7 of the INTCON register must be set to a logic 1.

  • As the IPEN bit is set to a logic 1 and you are using low-priority interrupts, you need to set Bit6 of the INTCON register, since now Bit6 is used to enable all low-priority interrupts.

  • This means that the data to be written to the INTCON register is 0b11000000. This is done using the instruction INTCON = 0b11000000;.

  • Since you are using both high- and low-priority interrupts, you need to write two interrupt service routines, one for the high priority and one for the low priority. Note that the PIC has two vector locations in the program memory area; this is shown in Figure 6-4. The high priority vector is at address 0008h and the low priority vector address is at 0018h. It is at these two addresses where the housekeeping software of the PIC stores the address of the two respective interrupt service routines. When the PIC is called to go to the ISR, it goes to the respective vector address to find the address it needs to load into the program counter for the respective ISR. These vector locations are shown in the program memory map in Figure 6-4.

../images/497005_1_En_6_Chapter/497005_1_En_6_Fig4_HTML.jpg
Figure 6-4

The user memory map for the PIC18f4525

Again, because of the uncertainty of the keyword interrupt, you should use the XC8 v1.35 compiler software when creating the project.

I downloaded this program to my prototype board and it worked perfectly.

The program for the high and low priority interrupts is shown in Listing 6-3.
 1. /*This is  simple program to investigate interrupt high an low priority
 2. It uses two external interrupt sources which are
 3. INT1 and INT2 two external interrupts
 4. INT1 and INT2 will effect the display on PORTB
 5. The main program simply makes an led on PORTD flash
 6. The program is written for the PIC 18F4525
 7. Written by H H Ward Dated 07/06/15
 8. */
 9. //some include files
10. #include <xc.h>
11. #include <conFigInternalOscNoWDTNoLVP.h>
12. //some variables
13. unsigned char n;
14. //some subroutines
15. void delay (unsigned char t)
16. {
17. for (n = 0; n < t; n ++)
18. {
19. TMR0 = 0;
20. while (TMR0 < 255);
21. }
22. }
23. void interrupt HP_int ()
24. {
25. if (INTCON3bits.INT2IF == 1)
26. {
27. INTCON3bits.INT2IF= 0;
28. PORTC = 0x0F;
29. while (!PORTBbits.RB4);
30. PORTC = 0;
31. }
32. }
33. void interrupt low_priority LP_int ()
34. {
35. INTCON3bits.INT1IF = 0;
36. PORTC = 0x2C;
37. while (!PORTBbits.RB5);
38. PORTC = 8;
39. }
40. void main ()
41. {
42. TRISB = 0xFF;
43. TRISC = 0;
44. TRISD = 0;
45. RCON = 0b10000000;
46. INTCON = 0b11000000;
47. INTCON3 = 0b10011000;
48. ADCON1 = 0x0F;
49. OSCCON = 0b01110100;
50. T0CON = 0b11000111;
51. PORTD = 0;
52. PORTC = 0x01;
53. while (1)
54. {
55. PORTDbits.RD0 = 1;
56. delay (30);
57. PORTDbits.RD0 = 0;
58. delay (30);
59. }
60. }
Listing 6-3

The Code For the High/Low Priority Interrupts

Analysis of Listing 6-3

Lines 1 to 9 are just some normal comments about the program. Lines 10 and 11 are the two major include files. I did not include the PICSetUp.h header file because I want to remind you about some important control registers.

Lines 15 to 22 set up the usual variable delay subroutine that you have used before.
Line 23 void interrupt HP_int ()
This is how you set up the high-priority interrupt service routine.
Line 24 {
This is the normal opening curly bracket.
Line 25 if (INTCON3bits.INT2IF == 1)
There is no real need for this instruction since there is only one high-priority interrupt. I am only adding this instruction to show how you would accommodate the use of more than one high-priority interrupt.
Line 26 {
This is the normal opening curly bracket of the test in line 25. Again, this is not really needed in this program.
Line 27 INTCON3bits.INT2IF = 0;
This resets the INT2 interrupt flag back to a logic 0. This is very important here as with priority interrupts the global interrupt enable bit, Bit7 of the INTCON register, is not temporarily set to a logic 0, which would temporarily disable all interrupts. This means that if you left the INT2IF flag at a logic 1, the PIC would constantly trigger the interrupt service routine. Try commenting this instruction out by putting two forward slashes (//) in front of it and see what happens.
Line 28 PORTC = 0X0F;
This just lights up the first four bits on PORTC. This is just something for the PIC to do in this ISR.
Line 29 while (!PORTBbits.RB4);

This makes the PIC wait until the switch connected to Bit4 of PORTB goes to a logic 1. This is because while the logic on the bit is a logic 0, signified by the ! (or NOT) symbol, the PIC will do nothing.

This is just so that the PIC is trapped in this ISR, giving you time to try to interrupt it with INT1. However, since INT1 is a low-priority interrupt and INT2 is a high-priority one, INT1 should not be able to interrupt this ISR.
Line 30 PORTC = 0;
This turns everything connected to PORTC off. This is just to signify that you have completed the last instruction of this ISR for INT2 and the PIC can return to the main program or to the interrupt it may have interrupted.
Line 31 }
The closing bracket of the if test on line 25.
Line 32 }
The closing bracket of the ISR.
Line 33 void interrupt low_interrupt LP_int ()
This is how you set up the low-priority ISR.
Line 34 {
This is the opening curly bracket of the low-priority ISR.
Line 35 INTCON3bits.INT1IF = 0;
This resets the INT1F to logic 0 to prevent it continually triggering the interrupt. Note also that you have not had to test which interrupt triggered the ISR since there is only one low-priority interrupt.
Line 36 PORTC = 0x2C;
This turns on anything connected to Bits 5, 3, and 2 of PORTC. Just something for this ISR to do.
Line 37 while (!PORTBbits.RB4);
This gets the PIC to wait until the switch connected to Bit4 is switched to a logic 1. This is done to trap the PIC here, giving you time to try to interrupt the ISR with INT2. Since INT2 is of a higher priority than INT1, it should interrupt the ISR.
Line 38 PORTC = 8;
This will set Bit3 of PORTC to a logic 1. This is to let you know the PIC has carried out the last instruction of the low-priority ISR.
Line 39 }
The normal closing bracket of the low priority ISR.
Line 40 void main ()
This sets up the important main loop of the program.
Line 41 {

The opening curly bracket of the main loop.

I have now gone through some setup instructions that are really in the PICSetUp.h header file. I have done this to remind you that these instructions are needed for every program and that you may have to tailor them to the specific program.
Line 42 TRISB = 0XFF;
This sets all the bits in the special function register TRISB to a logic 1. This sets all bits on PORTB to inputs.
Line 43 TRISC = 0;
This simply sets all the bits in TRISC a logic 0. This then sets all the bits on PORTC as outputs.
Line 44 TRISD = 0;
This does the same but with TRISD and so sets all bits on PORTD as outputs.
Liane 45 RCON = 0b10000000;
This sets Bit7 of the RCON register to a logic 1. This is the IPEN bit that turns on the interrupt priority function of the PIC.
Line 46  INTCON = 0b11000000;
This sets Bit7 and Bit6 to a logic 1. As the IPEN bit is a logic 1, this enables all high and low priority interrupts.
Line 47  INTCON3 = 0b10011000;
This sets INT2 to high priority, Bit7 is a logic 1. It also sets INT1 to low priority, Bit6 is a logic 0. It also enables INT2 and INT1; Bit4 and Bit3 respectively are set to logic 1.
Line 48 ADCON1 = 0X0F;

This sets Bits 3, 2, 1, and 0 to a logic 1. This then makes all 13 bits that could be analog set to digital.

This is required because Bits 0, 1, 2, 3, and 4 of PORTB can be used as analog but you need them to be digital. You could have loaded the ADCON1 register with 0X07 but since you are not using the ADC you made them all digital.
Line 49 OSCCON = 0b01110100;
This sets the internal oscillator to produce a stable 8Mhz frequency.
Line 50 T0CON = 0b11000111;
This turns on timer0, sets it to an 8-bit register, and gives it a maximum divide rate. In this way timer0 counts at a frequency of 7812.5Hz.
Line 51  PORTC = 0X01;

This just sets bit 0 of PORTC to a logic 1.

The remaining lines of the program just set up a forever loop with Bit0 of PORTD simply flashing one second on and one second off.

Explanation of How the High/Low Priority Program Works

The main program just makes Bit0 of PORTD flash on and off. If you momentarily press the switch on Bit1 of PORTB, you will initiate the INT1 interrupt. The PIC will get the address of the low-priority ISR from the low-priority vector at location 0018h. The PIC will then go to the low-priority ISR where it will carry out the instruction to reset the INT1 interrupt flag. If it didn’t do this, the PIC would continually think the INT1 interrupt had been caused and you could not move on. This is because, unlike the situation with non-priority interrupts, the PIC does not turn off all interrupts by temporarily setting Bit7 of the INTCON register to a logic 0 whenever an interrupt is initiated. This is because with priority interrupts you need all interrupts to be still enabled so that a higher priority interrupt can override the current interrupt.

The PIC then carries out the instruction PORTC = 0X2C;. Bit0 of PORTD will stop flashing as long as the PIC is carrying out the instructions of the ISR for INT1. This is because the PIC will not be looking at the instructions in the main program.

The PIC then waits for the user to momentarily press the switch on Bit5 of PORTB. This is to give you time to activate INT2. If you do activate INT2, then, because it has a higher priority than INT1, the PIC will break out of the low-priority ISR and go to the high-priority ISR.

The first thing it does after resetting the INT2 interrupt flag, neglecting the test to see if it was the INT2 interrupt, is to carry out the instruction PORTC = 0X0F. This will overwrite what was in PORTC. This is so that you know the PIC is now running the high-priority ISR.

Now the PIC waits for you to momentarily press the switch on Bit4 of PORTB. This is to give you time to try to activate the INT1 interrupt. However, because INT1 is a low-priority interrupt, it cannot override the INT2 ISR.

Now when you send Bit4 momentarily to a logic 1, the PIC can move onto the next instruction in the high-priority ISR. This is where it loads PORTC with the value 0. This will set all of the bits on PORTC to a logic 0.

Now, if the INT2 had interrupted the low-priority interrupt INT1, the PIC would have gone back to the instruction on line 37 of if (!PORTBbits.RB5); where it is waiting for you to momentarily press the switch connected to Bit5. You should be able to appreciate that the PIC has gone back to this instruction because Bit0 of PORTD is still not flashing. If you now momentarily press the switch on Bit5 of PORTB, you should see Bit0 of PORTD start to flash again.

I hope this analysis helps you to understand how the high and low priority interrupts on the PIC18f4525 work. The principle is the same for most PICs. There is a simulation of the program shown in Figure 6-5.
../images/497005_1_En_6_Chapter/497005_1_En_6_Fig5_HTML.png
Figure 6-5

The high and low priority interrupts

Summary

In this chapter, you learned what interrupts within the microcontroller are and why you have them. You studied how to set them up and use them. You learned the difference between high and low priority interrupts and how to set up the priority interrupts and use them.

You had a brief look at the fetch and execute cycle that all micros must go through when carrying out an instruction. You also looked at the important special function register called the program counter.

I hope you have found this chapter useful. In the next chapter, you will look at the capture aspect of the CCP module and the use of the EEPROM in the PIC.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.119.206