A Bug in the PIC12F675 Microcontroller GPIO Register with Bit-Set Instruction

Jonathan Scott, SMIEE, Daniel Rosborough, and Michael Cosgrove
Department of Engineering, The University of Waikato
.
scottj@waikato.ac.nz

18 January, 2007.

Abstract:A hardware "shortcoming" (bug) within the integrated circuitry of a common CMOS microcontroller has been discovered. The fault is easily demonstrated with a simple assembly program. The instruction sequence that gives rise to the error is produced by C-compilers used with this processor. A workaround is available but is not always trivial.

The problem is known and explained elsewhere: http://www.piclist.com/techref/readmodwrite.htm (A cached copy of that text is given at the bottom of this page.)

It appears that there is a hardware bug in MicroChip's PIC12F675 microcontroller. Spurious resets of a digital output occur when a BSF instruction is used to alter an adjacent bit on the GPIO port. The problem is demonstrated below on two adjacent bits on the peripheral interface adapter "GPIO", namely "GPIO4" (pin 3) and "GPIO5" (pin 2).

The problem is easily reproduced with a very short program. The only initialisation required after power-on reset is the assignment of two bits of the GPIO port as digital outputs. This is achieved by writing a zero to the appropriate bit of the tristate control register "TRISIO". Thereafter a loop of a few instructions will produce outputs on the pins that clearly manifest the failure.

Consider this snippet of assembly code:

asm("BCF 0x03, 0x05"); //select register bank 0
asm("BCF 0x05, 0x04"); //clear GPIO4, pin 3
asm("BCF 0x05, 0x05"); //clear GPIO5, pin 2
asm("NOP");
asm("BSF 0x05, 0x04"); //set GPIO4, pin 3
asm("BSF 0x05, 0x05"); //set GPIO5, pin 2
asm("NOP");
asm("NOP");
asm("NOP");
asm("BCF 0x05, 0x05"); //clear GPIO5, pin 2
asm("BCF 0x05, 0x04"); //clear GPIO4, pin 3
asm("NOP");
asm("BSF 0x05, 0x05"); //set GPIO5, pin 2
asm("BSF 0x05, 0x04"); //set GPIO4, pin 3
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");

This is a screen capture from an oscilloscope showing the bit stream that results from running the above code. The PIC12F675 was running on 5V, and was mounted on a PICkit2 development/demonstration board, with nothing connected but a pair of Tektronix probes. It was running with its 4MHz internal clock, thus executing one instruction per microsecond (actually slightly more than 1 microsecond in this instance), and the code loop took 21 instructions per iteration. The code was compiled using a HiTech C compiler. We have observed the fault on several different devices, some purchased in 2006 in New Zealand and some purchased in 2005 in California, both directly from the MicroChip online store, and in at least two different hardware platforms. One of the NZ-purchased devices were marked "12F675 I/P10H 0437". All devices tested were in 8-pin DIP packages.

The diagram below is the same data presented in a more informative way to assist explanation.


Upon execution of the "BSF 0x05, 0x04" instruction to set GPIO4 high, that output goes high, and GPIO5 remains low. Upon execution of the "BSF 0x05, 0x05" instruction to set GPIO5 high, GPIO4 goes low, although that output should not have changed. The power supply was observed to be clean.

As the remainder of the code shows, the reciprocal situation does not occur.

The problem may affect other parts of the IC. It is possible that other bits of the port could be affected by bit-writes to GPIO5, or that other bits of the port affect GPIO4 when they are written. We have not observed a similar error on another port. We have not conducted an exhaustive set of tests. It is also possible that other members of the MicroChip family of microcontrollers, in particular the PIC12F629, will prove to exhibit the same problem.

We can find no reference to this phenomenon in the documentation of the PIC12F6xx series, or the errata as available on the MicroChip web site. Since compilers routinely seem to use the BCF and BSF instructions both on data memory and special function registers including the GPIO port, we infer that there is no generally-available prohibition on the use of these instructions with any special function registers.

A safe workaround consists of limiting code to accessing the GPIO register using byte- rather than bit-instructions. This generally requires the program to store the state of the port somewhere in RAM. In C programs, assignment to a Boolean (for example HiTech's "bit" type or the "INT1" type of the CCS compiler) such as a port address yields assemby code using the BSF and BCF instructions. Since this can lead to the problem outlined in this letter, it must be avoided. This can be difficult for the unwary, as names from the MicroChip datasheets are usually #defined to appropriate declarations deep in a nested group of .h files. In the Hitech case for example, GPIO4 is defined as a "static volatile bit" type, and so on for all individual port pins.

We have subsequently come to understand the problem. The matter below is reproduced from http://www.piclist.com/techref/readmodwrite.htm. Thanks to Pete Griffiths for pointing this out to us!

Michael Rigby-Jones [mrjones_at_NORTELNETWORKS.COM] says:

When you perform any operation, apart from a MOV on a register ... the PIC {or SX} first reads the register, then it performs the operation on the number it has just read and finally it write the number back to the register. This is fine when dealing with normal registers and most special function registers. However, if you perform RMW (read modify write) operation on a port register (PORTA PORTB etc) [ed: or some timer / counter registers] then you are heading for trouble. Why? Because when the uP reads a port register, it reads the actual state of the pins, rather than the output latch. This can cause two problems:
  1. If the pin is an input, then the input pin state will be read, the operation performed on it, and the result sent to the output latch. This may not immediately cause problems, but if that pin is made into an output later on, the state of the output latch may have changed from the time it was deliberately set by the code.
  2. Now, if the pin is defined as an output, the output latch and the actual pin *ought* to be in the same state. In practice sometimes they aren't. If you are driving a capacitive load, the pin will take time to respond as it charges and discharges the capacitor. A common problem occurs when using the bit set (bsf) or bit clear (bcf) directly on a port.
        bsf portb, 0 -- don't do this!
        bsf portb, 1 -- don't do this!
        bcf portb, 1 -- don't do this!
    or
        setb portb.0 -- don't do this!
        setb portb.1 -- don't do this!
    

    Which *might* work. However, if pin B0 is loaded in any way, then it may not have time to respond to the first instruction before the second one is executed. The second instruction reads the port and sees that the pin B0 is low (because it hasn't got time to go high) and writes the low state back into the output latch. The result would be that B0 never gets set.

How do you avoid this issue? Well, it's bad practice to use RMW instructions directly on a port. So you use whats known as a shadow register. The shadow register is simply a ram location you reserve. All operations are performed on this register, and when you are finished, you copy it to the port register. It's a bit more trouble, and it can slow things down a tiny bit, but the effort is worth it for reliable operation.

Note that it's not only bsf and bcf that are RMW instructions, although they are the most common. Other examples are

    andwf   portb,f -- don't do this!
    iorwf portb,f -- don't do this!
    xorwf   portb,f -- don't do this!

(Instructions that merely *read* from a port are safe, as long as you stay aware of the fact that the value you read may be different from the value you just wrote:

    andwf portb, w ; this is fine
    iorwf portb, w ; this is fine

)