6
\$\begingroup\$

I'm outputting a 6-bit number on PORTA (RA0-RA5), how do I write to those bits without messing up what is already on RA6 and RA7?

For example if I want to output 0x3F on bits RA0-RA5. If I use PORTA = 0x3F; that will output 00111111 effectively wiping what ever was already written to bits RA6 and RA7.

I'm coding in C with MikroC on a PIC18

\$\endgroup\$
  • 1
    \$\begingroup\$ Compound assignment operators are your friend: &=, |=, ^= are all operators you should be very familiar with in embedded development. \$\endgroup\$ – uint128_t Apr 22 '16 at 1:37
  • \$\begingroup\$ Read-Modify-Write. \$\endgroup\$ – Tom Carpenter Apr 22 '16 at 1:42
  • 1
    \$\begingroup\$ Use the single-bit instructions (BTFSS,BTFSC or whatever they are for your MCU). A good C compiler can generate them from the &= |= operators (gcc can, but doesn't support PIC as far as I know). Inspect the assembly output from your compiler to see what it does. (Ada programmers have it much easier, for MCUs that gcc can target - just define a packed array of booleans and you can set/clear them directly). \$\endgroup\$ – Brian Drummond Apr 22 '16 at 12:04
9
\$\begingroup\$

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.

\$\endgroup\$
  • 1
    \$\begingroup\$ And on architectures and compilers that support it, bitwise operations on ports can be converted to bit set/clear operations if they take less time or fewer instructions. \$\endgroup\$ – Ignacio Vazquez-Abrams Apr 22 '16 at 2:24
  • 2
    \$\begingroup\$ Whomever downvoted this, care to explain why? \$\endgroup\$ – Tom Carpenter Apr 22 '16 at 11:39
  • \$\begingroup\$ This does not work on ports. On every micro I've ever used (and I've been in this game for over 20 years) including the PIC, reading a port reads the current state of the port's pins, NOT the state of the outputs. This is a very important difference if some of those pins are inputs or open-collector outputs. Instead you need to keep a variable to store what you last wrote to the port, modify that variable, and then write that variable to the port. \$\endgroup\$ – Graham Apr 22 '16 at 12:12
  • \$\begingroup\$ @TomCarpenter I wasn't the original downvoter. But I've also downvoted your answer for the reason I've just given. \$\endgroup\$ – Graham Apr 22 '16 at 12:14
  • 1
    \$\begingroup\$ @Graham I will correct that. It's worth pointing out though that by port I mean the output register - on an AVR it is called PORTx. Whereas on PICs it is called LATx. \$\endgroup\$ – Tom Carpenter Apr 22 '16 at 12:17
9
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

To manipulate only the Least Significant Bit (LSB) of a register

Set

GPIO_DATA = GPIO_DATA | 0x01;

Clear

GPIO_DATA = GPIO_DATA & (~0x01);

Below is a set of notes that I keep handy that might be of use. Pay attention the notes in Red.

enter image description here

For detailed explanation I suggest you refer here.


References:

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.