The Read-Modify-Write problem

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

)

open-collector (OC) buses

This gotcha is especially common with open-collector (OC) buses.

Ruben Jonsson [ruben at pp.sbbs.se] says

Be ware of interrupts that are manipulating bits in the same port as the OC output. I have got bitten by this a couple of times.

Say you have a serial TX on bit 0 port_A that is currently running and updating the serial stream. At the same time, You are accessing an I2C device with bit 1 port_A as OC data output/input outside the interrrupt routine. If you get an interrupt that updates the serial TX bit inbetween instruction 2 and 3 in the following code...

                    ;port_a bit 1 is an input at this stage.
mov w,#%11111100    ;prepare tris mask for data OC bit as output.
clrb port_A,1       ;make sure I2C data OC register is set low.
mov !RA,w           ;Set pin low by making the port bit an output.

...You could have been executed a read-modify-write instruction on port_A (setb, clrb...) that has in fact reset port_A to a one before it got set to an output.

This could be avoided.

1. Don't mix OC bits that are updated outside the interrupt routine with normal output bits that are updated inside the interrupt routine in the same port.

2. Use shadowregisters for the OC bits and reset these prior to exit from the interrupt routine if any portbit has been changed. Make sure they are updated before the real register is updated outside the interrupt routine though.

3. Disable interrupts when manipulating the OC bits (hard thing to do quick and simple on the SX).

4. Handle all the OC stuff inside the interrupt routine.

I guess there are more ways but I can't think of any right now...

Note that this also applies to normal bits that are manipulated in the following way:

Read the port
Clear bits that should be modified
Set bits that should be modified (to 1 or 0)
Write back byte to port

If an interrupt that is modifying a bit in the same port between instruction 1 and 4, the routine outside the interrupt is reseting these bits to the level they had in instruction 1.

This is easy to avoid, though - Just set or clear the bits individually by setb or clrb. Takes more instructions but are atomic...

Please *do* use setb or clrb instructions on the "shadow register", which is normal RAM bits. Please *don't* use them on ports.

See also:

Questions:

Comments:

Code: