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:

  1. Initial PORTB read is done with the pin low. This sets input latch = 0, change latch = 0.
  2. Pin goes high. This sets input latch = 1, change latch = 0. Since input latch != change latch, RBIF is set.
  3. Vector to interrupt.
  4. MOVF PORTB,W      ; read PORTB to clear mismatch condition
  5. BCF INTCON,RBIF    ; clear RBIF
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:

  1. MOVF PORTB,W      ; read PORTB
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?
  1. MOVF PORTB,W      ; read and discard PORTB
  2. BCF INTCON,RBIF    ; clear RBIF
  3. MOVF PORTB,W      ; read PORTB
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:

  1. MOVF PORTB,W      ; read and discard PORTB
  2. BCF INTCON,RBIF    ; clear RBIF
  3. MOVF PORTB,W      ; read PORTB
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 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) and modern PIC18 families (PIC18FxxK40 and later).

Credits

Thanks to ocelot who first documented this issue.
Copyright © 2008 John Temples (pic at xargs dot com)