CONFIDENTLY USING INTERRUPTS IN YOUR MICROCONTROLLER PROJECT
Microcontrollers signal that an interrupt condition has
been reached by setting a flag — a bit in one of the system
registers. For our timer interrupt, this flag is set when the
timer rolls over. If that interrupt is enabled, this will trigger
an actual interrupt. In most microcontrollers, you will need
to clear this flag by hand in your interrupt routine or you
will keep getting the same interrupt over and over. For our
Timer0 interrupt in the PIC16F84, this means clearing the
T0IF bit with a BCF instruction.
The next step is writing your actual interrupt service
routine. You already know a few things that need to appear
in the routine. At the beginning, you need to save the state
of important registers, clear the flag, and subtract your
interrupt cycle value from the timer. At the end of the
interrupt, you need to restore the register values you
saved and then have a return-from-interrupt instruction.
The return-from-interrupt instruction is always the last
instruction of your interrupt service routine.
Here are the tasks that you will need to do in each of
your interrupt service routines (See the interrupt service
routine in the sidebar for a specific example.):
1. Save the values of the system registers you use in the
2. Subtract from the timer the “timer increments per
NUTS & VOLTS
interrupt” value you calculated.
3. Clear the interrupt flag.
4. Write the body of your interrupt. In our example, this is the
PWM generation code.
5. Restore the system register values that you saved.
6. End your interrupt routine with the return-from-interrupt
The final step to getting your interrupt running is to set
the hardware bits that enable the interrupt and the timer.
Most microcontrollers have both a global interrupt enable
bit and bits that enable each individual type of interrupt.
When the global interrupt enable bit is not set, all interrupts
in the microcontroller are disabled. For an interrupt to be
enabled, both the global and the individual interrupt enable
bits must be set.
In the PIC16F84, you can set these bits directly using
the BSF instruction. The global interrupt enable bit is
named GIE and the Timer0 interrupt enable bit is named
T0IE. You also need to set up and enable the timer so that
it starts incrementing every instruction cycle. In my
example, I set the PSA bit to have no prescaler and set the
T0CS bit to start the timer incrementing. You only need to
set these values once, unless you want to turn the
interrupts on and off. I usually set them in an initialization
section at the beginning of my main body of code.
If you are writing your interrupt routines in a C compiler,
the compiler may take care of some of these details for
you. It will probably enable you to write the body of your
interrupt routine in a special interrupt function. From this
function, it will generate the assembly code and place the
routine at the correct interrupt vector address and also
save the state variables for you. Some microcontrollers
also automatically save the important registers for you
whenever an interrupt occurs and then restore them when
the interrupt is over. Refer to your microcontroller’s data
sheet to see if it saves any register values automatically.
One general rule to remember is to keep your
interrupt code short. This is a solid principle, but it doesn’t
mean that you can’t do any processing in your interrupt.
The thing you want to absolutely avoid is for the next
interrupt to occur when the last one is still running. In our
example, we generate an interrupt every 49 instruction
cycles. So, it’s clear to see that — if our interrupt takes
more than 49 instructions to run — the next interrupt
will want to run before the current one is over. This is a
problem and it means that the main body of your code will
never get to run.
The longer your interrupt routine, the less processing
time is left for your main code. Because of branches in
your code, the number of instruction cycles it takes to run
can vary from interrupt to interrupt. In practice,
I’ve found that interrupt routines can take up 50% of the
time between interrupts without causing problems. In our
example, this would mean an interrupt routine that runs in
24 instructions or less. Overall, shorter is usually better.