PICOS18 > Tutorial > Interruptions

PICos18 : Les interruptions

By using the PIC18 uC you probably expect to use the internal peripherals. Doing so it is important to considere the interrupts.
You will learn how works interrupts under PICos18 and how to write your own interrupt routines.


> int.c file introduction

Now we're going to connect the both buttons on the external interrupt pins of the PIC18 that let us treate the button rebounce only when one button is pressed.

First of all we're going to see what is the "int.c" file that takes in charge the interrupt management. The PIC18 manages 2 kind of interrupts : the low priority interrupts (IT_vector_low) and the high priority interrupts (IT_vector_high). But what for ... ?!

Actually you have to know the PICos18 kernel does some complex operation with task and that it does not permit to be interrupted during the process (for instance during the preemption of one new task on the current task). Then to be as simple as possible the kernel is non-interruptible. But we've seen the determinsm of PICos18 is 50µs (what is called the tatency time), so there is a kind of blackout when an IT cannot be detected.

To avoid a such problem we allow fast interrupts of the PIC18 which uses a hardware mecanism to save the elemtary registers (like WREG, STATUS, ...). Unfortunatly to use the fast mode interrupt of the PIC18 you need to write a very simple C code... If you need to write a more complex code that calls C functions for isntance you have to use slow interrupts... with this specific 50 µs blackout !

/**********************************************************************
 * Function you want to call when an IT occurs.
 **********************************************************************/
  extern void AddOneTick(void);
/*extern void MyOwnISR(void); */
  void InterruptVectorL(void);
  void InterruptVectorH(void);

/**********************************************************************
 * General interrupt vector. Do not modify.
 **********************************************************************/
#pragma code IT_vector_low=0x18
void Interrupt_low_vec(void)
{
   _asm goto InterruptVectorL  _endasm
}
#pragma code

#pragma code IT_vector_high=0x08
void Interrupt_high_vec(void)
{
   _asm goto InterruptVectorH  _endasm
}
#pragma code

/**********************************************************************
 * General ISR router. Complete the function core with the if or switch
 * case you need to jump to the function dedicated to the occuring IT.
 **********************************************************************/
#pragma	code _INTERRUPT_VECTORL = 0x003000
#pragma interruptlow InterruptVectorL save=section(".tmpdata"), PROD
void InterruptVectorL(void)
{
  EnterISR();

  if (INTCONbits.TMR0IF == 1)
    AddOneTick();
/*Here is the next interrupts you want to manage */
/*if (INTCONbits.RBIF == 1)                      */
/*  MyOwnISR();                                  */

  LeaveISR();
}
#pragma	code

/* BE CARREFULL : ONLY BSR, WREG AND STATUS REGISTERS ARE SAVED  */
/* DO NOT CALL ANY FUNCTION AND USE PLEASE VERY SIMPLE CODE LIKE */
/* VARIABLE OR FLAG SETTINGS. CHECK THE ASM CODE PRODUCED BY C18 */
/* IN THE LST FILE.                                              */
#pragma	code _INTERRUPT_VECTORH = 0x003300
#pragma interrupt InterruptVectorH
void InterruptVectorH(void)
{
  if (INTCONbits.INT0IF == 1)
    INTCONbits.INT0IF = 0;
}
#pragma	code

At the first sight the "int.c" file seams to be very difficult to read but in fact it is very easy to understand. The only 2 areas that need to be modified are the InterruptVectorL() and InterruptVectorH() functions. In almost 99% of cases the slow interrupt mode will be suitable for your applications then you will only need to modify the InterruptVectorL() function.

 

> The "fast interrupt" mode

Here is the "fast interrupt" function :

/* BE CARREFULL : ONLY BSR, WREG AND STATUS REGISTERS ARE SAVED  */
/* DO NOT CALL ANY FUNCTION AND USE PLEASE VERY SIMPLE CODE LIKE */
/* VARIABLE OR FLAG SETTINGS. CHECK THE ASM CODE PRODUCED BY C18 */
/* IN THE LST FILE.                                              */
#pragma	code _INTERRUPT_VECTORH = 0x003300
#pragma interrupt InterruptVectorH
void InterruptVectorH(void)
{
  if (INTCONbits.INT0IF == 1)
    INTCONbits.INT0IF = 0;
}
#pragma	code

The external interrupts INT0 (RB0 pin), INT1 (RB1 pin) and INT2 (RB2 pin) are by default in fast interrupt mode so when an interrupt occurs the current code processing is stopped and the InterruptVectorH() is executed.

The INT0 interrupt is not settable that means it always calls the InterruptVectorH() function.

At the opposite side the INT1 and INT2 can be set to call the InterruptVectorL() function.

To do it you need to modify the INTCON3 register and particulary the INT2IP and INT1IP bits.

If you wish to connect the 2 buttons on the INT1 and INT2 pins you will need to write the anti-rebounce code in the InterruptVectorH() function. But we've seen the fast interrupt code has to be very simple and that it is not allowed to call any function... The only one thing we can do is to increment or decrement a global variable...!

The reason is easy to understand : you cannot use a register of the PIC18 except the WREG, STATUS and BSR registers. If you hesitate between fast and slow interrupts have a look at the assembling listing generated by C18 to know if anyther register has been used. Because we don't need a precision higher than 50 µs to manage the buttons we choose the "slow interrupt" mode.

 

> The "slow interrupt" mode

In the most of the cases the "slow interrupt" mode is suitable. The only 3 interrupts by default in the "fast interrupt" mode are INT1 et INT2. Because INT0 cannot be modified we'll use INT1 and INT2 by setting them in the "slow interrupt" mode :

To use INT1 and INT2 in "slow interrupt" mode modify the INTCON3 register as above in the init phase of the main file.

Then modify the int.c file to catch the INT1 and INT2 interrupts :

/**********************************************************************
 * Function you want to call when an IT occurs.
 **********************************************************************/
  extern void AddOneTick(void);
  extern void MyOwnISR(void);
  void InterruptVectorL(void);
  void InterruptVectorH(void);

...

/**********************************************************************
 * General ISR router. Complete the function core with the if or switch
 * case you need to jump to the function dedicated to the occuring IT.
 **********************************************************************/
#pragma	code _INTERRUPT_VECTORL = 0x003000
#pragma interruptlow InterruptVectorL save=section(".tmpdata"), PROD
void InterruptVectorL(void)
{
  EnterISR();

  if (INTCONbits.TMR0IF == 1)
    AddOneTick();
/*Here is the next interrupts you want to manage */
  if (INTCON3bits.INT1IF || INTCON3bits.INT2IF)
    MyOwnISR();                                  

  LeaveISR();
}
#pragma	code

We need now to add the "MyOwnISR" function in any C file, in one a the both idle tasks connected to the button processing. For instance we add the function in the "tsk_task2.c" file :

void MyOwnISR(void)
{
  if (INTCON3bits.INT1IF)
  {
    INTCON3bits.INT1IF = 0;
    /* ... */
  }
  if (INTCON3bits.INT2IF)
  {
    INTCON3bits.INT2IF = 0;
    /* ... */
  }
}

Do not forget to declare the function at the beginning of the file :

#include "define.h"

void MyOwnISR(void);

/**********************************************************************
 * Definition dedicated to the local functions.
 **********************************************************************/
#define ALARM_TSK0      0
You can now rebuild the project (F10).

 

> Idle tasks management

At this moment the "MyOwnISR" function cannot send the information a button has been pressed. Moreover the idle tasks continue to run in an infinite loop. The we need to put to sleep the idle tasks and to wake them up if a button is pressed. To do so we modify the both tasks to be a BASIC tasks :

TASK(TASK2) 
{
  unsigned int i;

  for (i = 0; i < 10000; i++)
    Nop();
  if (PORTBbits.RB1 == 1)
    hour++;

  TerminateTask();
}

As you can notice the task number 2 don't have any more the "while(1)" loop and is finished with the "TerminateTask()" call.
Here is BASIC task features :

The goal is to set the task in SUSPENDED state by default then to activate it with the "ActivateTask()" function call.

Change the "taskdesc.c" file to set task 2 and 3 at the SUSPENDED state by default :

/**********************************************************************
 * -----------------------------  task 2 ------------------------------
 **********************************************************************/
rom_desc_tsk rom_desc_task2 = {
	TASK2_PRIO,                       /* prioinit from 0 to 15       */
	stack2,                           /* stack address (16 bits)     */
	TASK2,                            /* start address (16 bits)     */
	SUSPENDED,                        /* state at init phase         */
	TASK2_ID,                         /* id_tsk from 1 to 15         */
	sizeof(stack2)                    /* stack size    (16 bits)     */
};
/**********************************************************************
 * -----------------------------  task 3 ------------------------------
 **********************************************************************/
rom_desc_tsk rom_desc_task3 = {
	TASK3_PRIO,                       /* prioinit from 0 to 15       */
	stack3,                           /* stack address (16 bits)     */
	TASK3,                            /* start address (16 bits)     */
	SUSPENDED,                        /* state at init phase         */
	TASK3_ID,                         /* id_tsk from 1 to 15         */
	sizeof(stack2)                    /* stack size    (16 bits)     */
};

Modify the task 3 to be a BASIC task :

TASK(TASK3) 
{
  unsigned int i;

  for (i = 0; i < 10000; i++)
    Nop();
  if (PORTBbits.RB2 == 1)
    min++;

  TerminateTask();
}

Then modify the "MyOwnISR" function to activate the task dedictaed to the button management :

void MyOwnISR(void)
{
  TaskStateType State;

  if (INTCON3bits.INT1IF)
  {
    INTCON3bits.INT1IF = 0;
    GetTaskState(TASK2_ID, &State);
    if (State == SUSPENDED) 
      ActivateTask(TASK2_ID);
  }
  if (INTCON3bits.INT2IF)
  {
    INTCON3bits.INT2IF = 0;
    GetTaskState(TASK3_ID, &State);
    if (State == SUSPENDED) 
      ActivateTask(TASK3_ID);
  }
}

Rebuild (F10) and start the simulation (F9)

Put a breakpoint in the task 3 and another one in the "MyOwnISR" function. Start the simulation and stop it anytime (F5). Modify INTCON3 to set the interrupt (put for instance 0x1A to set the INT2 interrupt) : you will see the pointer stops in "MyOwnISR" then in the task 3.

If you continue to run the simulation you will see the pointer does not pass through the task 3 any more before the next interrupt : the task has been suspended.