Bug in the PORTB Interrupt on Change Feature
Most Microchip PIC16 and PIC18 microcontrollers support an interrupt on change
feature which can generate an interrupt when an input port (typically
bits 4-7 of PORTB) changes state. There is a race condition in the
implementation of this feature which can result in a pin change not
generating an interrupt.
In this diagram of PORTB taken from the PICmicro
Mid-Range MCU Family Reference Manual, the change detection logic
has been highlighted in red. The latch labeled Latch
is the
input latch; the unlabeled latch below it is the change latch. The
input latch is updated every Q1, and the change latch is updated every
Q3 when the port is read. The output of these two latches is XORed to
provide the change indication. The problem arises because the input
to both latches is the port pin, but the latches are updated at
different times in the instruction cycle. The result is that a pin
change during Q2 of an instruction that reads the port will not
generate an interrupt.
Here's an example of the timing that will cause the problem:
- Initial PORTB read is done with the pin low. This sets input
latch = 0, change latch = 0.
- Pin goes high. This sets input latch = 1, change latch = 0.
Since input latch != change latch, RBIF is set.
- Vector to interrupt.
-
MOVF PORTB,W ; read PORTB to clear mismatch condition
- Q1: Input latch is updated to 1.
- Q2: Read PORTB input latch. Pin goes low here.
- Q3: Change latch is updated to 0.
- Q4: W is written with the state of the input latch (1).
-
BCF INTCON,RBIF ; clear RBIF
- Q1: Input latch is updated to 0.
- Q2: INTCON is read.
- Q3: Processing.
- Q4: INTCON is written. Since the input latch and change latch are
both 0 at this point, RBIF is permitted to be cleared.
At this point, we have what we believe to be the current state of the
pin (high) in W and RBIF is clear. But the pin is actually low, and
we won't get another interrupt until the pin goes high again.
The fix is to add a third step: read the port a second time. The
value of the second read reflects the true state of the pin:
-
MOVF PORTB,W ; read PORTB
- Q1: Input latch is updated to 0.
- Q2: Read PORTB input latch.
- Q3: Change latch is updated to 0.
- Q4: W is written with the state of the input latch (0).
Now we have the correct state of PORTB in W, and the latches are in
the correct state. But now that this step has been added, what if we
get the pin change in Q2 of this second read instead of the first
read?
-
MOVF PORTB,W ; read and discard PORTB
- Q1: Input latch is updated to 1.
- Q2: Read PORTB input latch.
- Q3: Change latch is updated to 1.
- Q4: W is written with the state of the input latch (1).
-
BCF INTCON,RBIF ; clear RBIF
- Q1: Input latch is updated to 1 (again).
- Q2: INTCON is read.
- Q3: Processing. Change latch is not updated here since PORTB is
not being read.
- Q4: INTCON is written. Since the input latch and change latch are
both 1 at this point, RBIF is permitted to be cleared.
-
MOVF PORTB,W ; read PORTB
- Q1: Input latch is updated to 1 (again).
- Q2: Read PORTB input latch. Pin goes low here.
- Q3: Change latch is updated to 0. Since input latch != change
latch, RBIF is set.
- Q4: W is written with the state of the input latch (1).
Once again, W doesn't contain the value of the pin. But the important
difference here is that RBIF is still set. So when we return from the
interrupt handler, we'll immediately vector again to read the correct
pin state.
The final scenario is a pin change occurring in Q2 of both port
read instructions:
-
MOVF PORTB,W ; read and discard PORTB
- Q1: Input latch is updated to .1
- Q2: Read PORTB input latch. Pin goes low here.
- Q3: Change latch is updated to 0.
- Q4: W is written with the state of the input latch (1).
-
BCF INTCON,RBIF ; clear RBIF
- Q1: Input latch is updated to 0.
- Q2: INTCON is read.
- Q3: Processing. Change latch is not updated here since PORTB is
not being read.
- Q4: INTCON is written. Since the input latch and change latch are
both 0 at this point, RBIF is permitted to be cleared.
-
MOVF PORTB,W ; read PORTB
- Q1: Input latch is updated to 0 (again).
- Q2: Read PORTB input latch. Pin goes high here.
- Q3: Change latch is updated to 1. Since input latch != change
latch, RBIF is set.
- Q4: W is written with the state of the input latch (0).
Once again, W doesn't have the correct value, but RBIF is still set,
so everything takes care of itself.
Using PORTB outside an ISR
Both the PIC16 and PIC18 Family Reference Manuals warn, The
interrupt on change feature is recommended for wake-up on key
depression and operations where PORTB is only used for the interrupt
on change feature. Polling of PORTB is not recommended while using the
interrupt on change feature.
Other data sheets specifically warn
that interrupts can be lost by polling PORTB, and Microchip forum
users have reported cases of interrupts being lost.
Conclusion
This issue is only a problem if you have either
- A single pin that can change twice in a period approximately
equal to your RBIF interrupt handler's latency, or
- Two or more pins that can both change within a period approximately
equal to your RBIF interrupt handler's latency.
This issue has been corrected in the PIC30 core by using the output of
the input latch as the input to the change latch. It has also been
corrected in the enhanced midrange line (PIC16F1xxx).
Credits
Thanks to ocelot who first documented this issue.
Copyright © 2008 John Temples (pic at xargs dot com)