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:
- 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.
- 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
)
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 portIf 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:
poster@phord.com
refers to
"
http://web.archive.org/web/20041120185406/http://home.clear.net.nz/pages/joecolquitt/sx_pins2.html
The broken link "as of 2005-11-09" can be found here:
"
Questions:
I'm trying to invert a port on a 16F628A. I'm trying the following:
COMF PORTA,F
Given that nothing has happened on the port just prior to this command, should it work? It just sends the port high regardless of what was on it.
I've also tried using a shadow register and an XOR instruction with all 1's which did the opposite (port went low). Any suggestions would be great!
Your concept of shadow registers...I like...
for writing...write to the shadow first (bcf...whatever)...then MOVE that
to the port..
how about a port read...I'm thinking you DONT want to alter the shadow
output pins
(ie...if there is a cap load...they said an output could as 0 instead of
1...
I'm thinking....BSF STATUS,RP0 ; bank1 COMF TRISA,W ; read the direction bits...(inverted...so 1 for output) ANDWF shaddow,F ; now we only have the output bits and their current status MOVF TRISA,W ; read the direction bits...(1 for input) BCF status,rp0 ; bank0 ANDWF PORTA,W ; now we only have the input bits bsf status,rp0 ; bank1 IORWF shaddowA,F ; now we have the ACTUAL input values, ; and the logical output values WE set in code.am I correct in thinking...this is the ONLY way to be 100% when reading a
port...that you dotn alter the output values???FYI....most of my outputs are driving 'direct drive triacs' to handle
110VAC light dimmers (shocking!)and I toggle one from output to input (hi-Z) to make a red/green
bi-polar led show yellow (or kinda orange!)Wanted to get your thoughts... (would this work all the time? Is it overkill? would these loads need this or can I assume that the outputs wont change)
Thank you very much
Pat O'toole - never met a PIC I didnt like !
It looks like it would work in many situations, but yes, this is overkill. You can already be 100% sure: Reading a port never alters any output value.
Whenever you want to read the pins of a port, you might as well just
read the port directly (with movf port,w
or btfss
port,input_bit
or andwf port,w
), rather than reading
some copy of those port pins you've stored in RAM somewhere. (The R-M-W problem
is that "writing" a port sometimes sets the output register to something
other than what most people expect. The work-around is to always use
movwf
to write to a port. MOVWF is the only instruction that
sets the port to what everyone expected).
Sometimes I connect one pin of a port to an open-collector bus. I always want to keep the output register for that bit "0", so I also set the corresponding bit in the shadow register to "0". (I transmit a "1" on that bit by setting the corresponding tristate register bit to "input". Then the external "pull-up" resistor pulls the line up to "1" -- unless some other chip on the bus pulls the line down to "0").
I *don't* want the current value of that bit when I read the port (which may be "1") to overwrite the "0" I have in the shadow register, or the "0" I have in the output register.
Comments:
Code:
You have to disable A/D & Comparator on 12F675 and it works OK. How to disable A/D & Comparator: banksel ANSEL ; Select Bank 1 clrf ANSEL ; ANSEL <- 0; Disable all A/D inputs banksel GPIO ; Select Bank 0 movlw b'00000111' movwf CMCON ; Comparator off