Writing to pins on a port without affecting other pins on that port

A procedure called "Read-Modify-Write".

What it involves is entirely in the name. You read. Then you modify. Then you write.

Read:

//Read in the value of the output register
tempVariable = [output register]

Modify:

//set all bits you want to modify to be 0.
tempVariable &= [some mask];
//or in the values of the bits with those bits you want unchanged set to 0
tempVariable |= [new value of bits];

Write:

//Write the new value back to the output register
[output register] = tempVariable;

The key is basically to end up with the values of bits you want to be unchanged to be written back to the output register along with the new values of bits you do want to change.

To determine what the output register is for your device, you should refer to its datasheet.


We can't just write to the register directly because it will affect the bits we don't want to change as well. So we need a sequence of operations that will change only the bits we want to. This is where bitwise operators come in.

There are several bitwise operators, but the important two are & (and) and | (or). Bitwise and anything with a 0 and it sets that bit to be 0, bitwise and anything with 1 and it stays the same. Bitwise or anything with a 1 and it sets that bit to be a 1, bitwise or anything with 0 and it stays the same. These two operators allow us to make the required changes because we now have a way to set just some bits to 0 and a way to set just some bits to 1.

The new value you want to write will require some bits setting to 0 and some bits setting to 1. We can achieve this by doing a bitwise and followed by a bitwise or. The and is used to set all bits we want to change to be 0 to allow us to then do the or which sets only the bits we want to be 1 to be 1.

An example will help. Say you want to modify the lower 5 bits to a value of 0b01011 but leave the upper 3 bits unchanged. Lets also say the current value is 0b10111101. So we follow the procedure:

Step 1, mask:

Current: 0b101 11101
Bitmask: 0b111 00000 <- remember a 1 means don't change, a 0 means clear.
Result : 0b101 00000

Step 2, modify:

Masked : 0b101 00000
New Val: 0b000 01011 <- remember a 1 means set to 1, a 0 means unchanged
Result : 0b101 01011

And there you have it - notice the upper 3 bits were unchanged in both operations, whilst the lower bits were updated to match the new value.


To bring up a point mentioned in the comments and the other answer, that this should indeed be done on the output register which was the original intention of my answer. It seems there is some confusion in assuming that by port I was referring to the PORTx registers in PICs - in fact the output register on some devices is the LATx register. Some PICs don't have a LATx register. On AVRs for example PORTx is the output register. The datasheet for your device will tell you what the output register is.

Furthermore, the technique can be used to modify variables as well as registers, and can be used when modifying the registers for things other than just I/O ports - you can modify things like control registers for serial peripherals and such as well.

Because of the differences in naming of registers and the fact that the process is a very universal approach, in the above I had tried to be generic as the same thing applies to not just PICs but any microcontroller - in fact pretty much anything that requires some bits of a register to be modified but not others.


Generally in the PIC18 architecture you should never use read-modify-write commands such as

PORTA |= 0x3F; // set bits 0 to 5

Rather, use

LATA |= 0x3F; // set bits 0 to 5

or

LATA &= ~0x80; // clear bit 7

The reason is that the PORTA |= xx instruction first reads the bit levels at the pins, modifies them, and then writes the result to the port latch.

The LATA instruction reads the bits in the port latch, modifies them, then writes the result to port latch.

If, for some reason (such as loading or propagation delays), the port pins are not at the correct and valid logic levels, the read-modify-write instruction can inadvertently modify bits that you did not intend to modify. If you are swapping pins from input to output to simulate open drain pins, a similar problem arises for pins that are temporarily inputs- the output latch of a different pin than you are intentionally modifying changes, and then when you switch the TRIS register back to 0 to turn on the simulated open drain, the latch state for that bit has been altered.

For older PICs that don't have LATx, if you have to use RMW, you can maintain a shadow register manually, modify it and then transfer the result to the port register.

A bit more detail regarding what I wrote above, from your compiler supplier here.