Manipulate multiple pins with PORTD

I have a circuit that uses D4-D7. I would like to set the state all D4-D7 in the same instruction without affecting D0-D3.

Example:

Current state is PORTD = 10010000

PORTD = 01000000 // Turn D6 on, Turn off D4 & D7

Another example:

Current state is PORTD = 01000000

PORTD = 10010000 // Turn D7 & D4 on, Turn off D6

The above instructions also have the side effect of acting on D0-3. I need whatever state D0-D3 are in to remain in that state when I set the state of D4-D7 simultaneously.

I think Bitwise operations are probably the answer, but after looking at it how they work, I'm still not sure how I can define both the on & off state of only specific bits within a byte, while also completely ignoring the other bits.

You can't. You'll at least have to (1) read PORTD, (2) apply a bitwise OR to it and (3) write back the result.
You can do it in one line of C code, but it will still be multiple instructions.

The question is of course: why does it have to be the same instruction? What is your application that apparently requires this kind of strict timing and are you sure you're using the right kind of hardware if you really have this requirement (which in all likelihood you don't, but you know, for sake of the argument...)

Yes but all the pins you are changing will change at exactly the same time, at step 3.

That is correct.

No problem ...

PORTD &= ~0b11110000; // clear bits 7-4, bits 3-0 unchanged
PORTD |=  0b11110000; //   set bits 7-4, bits 3-0 unchanged

PORTD &= ~0b00010000; // clear bit 4, remaining bits unchanged
PORTD |=  0b00010000; //   set bit 4, remaining bits unchanged

PORTD &= ~0b00100000; // clear bit 5, remaining bits unchanged
PORTD |=  0b00100000; //   set bit 5, remaining bits unchanged

PORTD &= ~0b01000000; // clear bit 6, remaining bits unchanged
PORTD |=  0b01000000; //   set bit 6, remaining bits unchanged

PORTD &= ~0b10000000; // clear bit 7, remaining bits unchanged
PORTD |=  0b10000000; //   set bit 7, remaining bits unchanged

Note: Be careful not to confuse digital pin names with port names ... refer to your pinout diagram.

None of these pins will be affected ...
image

On a Mega, PD0 to PD3 controls pins D21 to D18 respectively ...
image

Should the interrupts be turned off when using "PORTD &= ~0b11110000;" if the other bits are changed in a interrupt ? I think so.

No one mentions the toggle command ?

If you know the output state, then all 4 pins can be changed at the same time with the toggle command. Write 0b10010000 to PIND when PortD is an output to toggle D7 and D4 at the same time. No disabling of interrupts are needed, it is a single write.

This is from the ATmega328P datasheet:

1 Like

Nope. Its an 8-bit register. The write is atomic.

:thinking: So you are saying that it is a single magical write without a read ?

It's a read, followed by the AND in registers, followed by a write. Overall, not atomic at all. If it were a single bit being updated, you could get away with assuming it was atomic, as long as you're dealing with constant values for the port and bit.

(all assuming we're talking about an AVR (implied by "PORTD", right?) Other chips have other ways of updating parts of an IOPort.)

2 Likes

Just curious: Skipping the "AND" or "OR" registers, would this be a write-only and atomic operation? (where all bits are affected simultaneously)

PORTD = 0b11110000;

Anyways, I guess a solution would be to do this ...

noInterrupts();
PORTD |=  0b11110000; // set bits 7-4, bits 3-0 unchanged
interrupts();

It would be write-only, and so atomicity wouldn't matter.
It would be two instructions (LDI 0xF0 into register, OUT register to PORTD), and there could be an interrupt in between the two instructions, but there aren't any circumstances where PORTD would wind up with a different value than expected.

1 Like

This is what I was looking at, but consider this:

Current State: 10011010

PORTD &= ~0b11110000; // clear bits 7-4, bits 3-0 unchanged

Desired State: 00001010
Actual End State: 10010000

Is what we were looking for PORTD &= ~0b00001111;? With that, we get the following (I checked on a binary calculator, but please correct if I'm wrong):

Desired State: 00001010
Actual End State: 00001010


PORTD |= 0b11110000; // set bits 7-4, bits 3-0 unchanged

Actual End State: 11111010

I don't have a lot of experience with binary math, but I think the above works.

It appears you've missed the bitwise NOT operator.

PORTD &= ~0b11110000; // clear bits 7-4, bits 3-0 unchanged
                    ^

The tilde (~) inverts the following 0b value then ands it with PORTD.
So the operation performed is PORTD &= 0b00001111.

        10011010
and  00001111
'--------------------'
        00001010

Right. To set the upper 4 bits only to 0xC0, the safe and efficient sequence is like:

#include <util/atomic.h>

void setup() {}

void loop() {
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    uint8_t tempv = PORTD & ~0xF0;
    PORTD = tempv | 0xC0;
  }
}

Using a temporary variable where the AND and OR are done makes the code slightly fasted by only accessing PORTD 2 times instead of 4, and prevents the intermediate state (bits cleared but not set yet) from possibly causing problems.

ATOMIC_BLOCK shuts out interrupts around the code segment...

000002d8 <loop>:
 2d8:   9f b7           in      r25, 0x3f       ; read current interrupt status
 2da:   f8 94           cli                     ; shut off interrupts
 2dc:   8b b1           in      r24, 0x0b       ;  read portd
 2de:   80 7f           andi    r24, 0x0F       ; clear upper bits
 2e0:   80 6c           ori     r24, 0xC0       ; or in new value
 2e2:   8b b9           out     0x0b, r24       ; write portd
 2e4:   9f bf           out     0x3f, r25       ; restore previous interrupt state.

(Note that most non-AVR platforms do not support ATOMIC_BLOCK :frowning:
(even though they should: Add cores/util/atomic.h by WestfW · Pull Request #384 · arduino/ArduinoCore-samd · GitHub ))

2 Likes

Assembly produced by WOKWI, for the interested:

void setup() {
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
 1aa:	9f b7       	in	r25, 0x3f	; 63
    return 1;
}

static __inline__ uint8_t __iCliRetVal(void)
{
    cli();
 1ac:	f8 94       	cli
    uint8_t tempv = PORTD & ~0xF0;
 1ae:	8b b1       	in	r24, 0x0b	; 11
 1b0:	8f 70       	andi	r24, 0x0F	; 15
    PORTD = tempv | 0xC0;
 1b2:	80 6c       	ori	r24, 0xC0	; 192
 1b4:	8b b9       	out	0x0b, r24	; 11
    (void)__s;
}

static __inline__ void __iRestore(const  uint8_t *__s)
{
    SREG = *__s;
 1b6:	9f bf       	out	0x3f, r25	; 63
  }

  noInterrupts();
 1b8:	f8 94       	cli
  PORTD &= ~0b11110000; // clear bits 7-4, bits 3-0 unchanged
 1ba:	8b b1       	in	r24, 0x0b	; 11
 1bc:	8f 70       	andi	r24, 0x0F	; 15
 1be:	8b b9       	out	0x0b, r24	; 11
  PORTD |=  0b11000000; //   set bits 7 and 6, bits 5-0 unchanged
 1c0:	8b b1       	in	r24, 0x0b	; 11
 1c2:	80 6c       	ori	r24, 0xC0	; 192
 1c4:	8b b9       	out	0x0b, r24	; 11
  interrupts();
 1c6:	78 94       	sei

The .ino file:

#include <util/atomic.h>

void setup() {
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    uint8_t tempv = PORTD & ~0xF0;
    PORTD = tempv | 0xC0;
  }

  noInterrupts();
  PORTD &= ~0b11110000; // clear bits 7-4, bits 3-0 unchanged
  PORTD |=  0b11000000; //   set bits 7 and 6, bits 5-0 unchanged
  interrupts();
}

void loop() {
}

noInterrupts() uses two more bytes (16) than ATOMIC_BLOCK(ATOMIC_RESTORESTATE, by my count, and does not preserve the interrupt status.

WOKWI project link: atomic block demo.ino - Wokwi ESP32, STM32, Arduino Simulator

2 Likes

I've got an idea that is atomic and interrupt-safe. Use the special feature of PIND that toggles bits in PORTD where a 1 is written.

  // Set 5 and 7, clear 4 and 6.
  byte DesiredValue = 0b1010; 

  byte CurrentValue = PORTD;
  byte ChangedBits = CurrentValue ^ (DesiredValue << 4);
  ChangedBits &= 0b11110000;  // Only change 4 through 7
  PIND = ChangedBits; // Toggle the bits to the desired state.

For example, if PORTD is 0b11100111 the
DesiredValue = 0b1010;
ChangedBits will be 0b01000111 and then masked to 0b01000000.
Writing that to PIND will toggle bit 6 and result in the desired pattern of 0b1010. The bottom four bits will remain unchanged because no 1's were written there.

This can be reduced to:
PIND = (PORTD ^ (DesiredValue << 4)) & 0b11110000;

3 Likes

That's cool -- it relies on the assumption that this code is the only code controlling those bits, but that is a perfectly reasonable constraint.

1 Like

Oh my, I didn't know Wokwi would do that. Cool. It's "View Compiled Assembly Code" under the F1 menu:

Yeah, it's petty handy for a quick look 'under the hood'.

1 Like

The Raspberry Pi Pico SDK actually does this as well: https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_gpio/include/hardware/gpio.h#L480