Port manipulation made easy

Is it possible to have a setup like this,

byte led1 = 3;
byte led2 = 4;

digitalWrite( led1, led2,..ledN, HIGH);

Where both pins are HIGH at the same time without needing to actually write "PORTD = B00011000;"?

Also maybe have the same setup with pinMode(led1,led2,...ledN, OUTPUT/INPUT)?

Yes it can if you roll your own function. I would use an array.

int mypins[] = { 1,3,5,7,9,11 };

void multiDigitalWrite(int *pins, int size, int level)
{
  for (int i=0; i< size; i++) digitalWrite(pins[i], level);
}

multiPinMode is left as an exercise :wink:

yea I know that but, I meant without any homemade functions, just a straight forward, arduino built-in function.

but you can add it into a library with all your other favourite functions.

only thing to do is

#include "mylib.h"

Again, yes, that I know, but why not make it a premade function for everyone?
Also using the loop would not make it set the pins together as port manipulation would, there would be a delay inbetween them.

Also I know that I can just include the for loop in my sketch and be able to give that to someone without errors. But I was just wondering if it could already be premade.

Again, yes, that I know, but why not make it a premade function for everyone?

There are a zillion premade functions possible and should the core support them all?

DigitalWrite() exists as it simplifies the setting of a line very much. One line of code iso 50 or more. That's a clear added value imho.

Also using the loop would not make it set the pins together as port manipulation would, there would be a delay inbetween them.

So you want a function that sets the pins simultaneously with DPM. I missed that part.
That is only possible if the pins are on the same port e.g. PORTB
The function would already fail to do a simultaneous setting if the pins are on different ports. There will always be a (small) time difference .

(above is just my opinion, you an propose always new features @ GitHub - arduino/Arduino: Arduino IDE 1.x )

But I was just wondering if it could already be premade.

as said above, it could be made and would look something like below:

void multiDigitalWrite(int *pins, int size, int val)
{
  uint8_t shadowBits[4] = 0;
  for (int i=0; i< size; i++) 
  {
	uint8_t port = digitalPinToPort(pins[i]);
        shadowBits[port] | = digitalPinToBitMask(pins[i]);
  }

  for (int i=0; i<numberOfPorts; i++)
  {
     if (shadowBits[i] == 0) continue;  // next port

     out = portOutputRegister(ports[i]);  // use out[i] to remove this line... ?
     if (timer[i] != NOT_ON_TIMER) turnOffPWM(timer[i]);

     	if (val == LOW) {  // move this test outside the loop
		uint8_t oldSREG = SREG;
                cli();
		*out &= ~shadowBits;
		SREG = oldSREG;
	} else {
		uint8_t oldSREG = SREG;
                cli();
		*out |= shadowBits;
		SREG = oldSREG;
	}
   }
}

This is definitely not working code yet but it shows that the complexity is not too bad either compared to digitalWrite() which I used as inspiration. The function collects all the bits to set in shadowBits and set them at once. The line with continue is an optimization. There are some other points to optimize see comments.

Now it is your turn to finish it and make it work :wink:

@HazardsMind

Came across this thread again today and dived into it. Came up with the following code.
As I lent my logic analyser to a friend so I could not check it yet,

so warning, highly experimental code, use at own risk

Goal: this code is to merge the masks of the bits to set simultaneously.

to be included in wiring_digital.c

void multiDigitalWrite(uint8_t *pins, uint8_t size, uint8_t val)
{
  // pins_arduino.h/c
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  const int numberOfPorts = 12;
  uint8_t bits[numberOfPorts] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
  
#else // 328 - 168
  const int numberOfPorts = 5;  
  uint8_t bits[numberOfPorts] = { 0, 0, 0, 0, 0 };
  
#endif
  
  for (uint8_t i = 0; i < size; i++) 
  {
    uint8_t p = digitalPinToPort(pins[i]);
    if (p == NOT_A_PIN) continue;

    bits[p] | = digitalPinToBitMask(pins[i]);
	
    uint8_t timer = digitalPinToTimer(pins[i]);
    if (timer != NOT_ON_TIMER) turnOffPWM(timer);
  }

  volatile uint8_t *out;
  for (int p = 0; p < numberOfPorts; p++)
  {
    if (bits[p] == 0) continue;  // no bits => skip port

    out = portOutputRegister(p);

    uint8_t oldSREG = SREG;
    cli();    
    if (val == LOW) 
    {
      *out &= ~bits[p];
    } else {
      *out |= bits[p];
    }
    SREG = oldSREG;
   }
}

robtillaart:
Came across this thread again today and dived into it. Came up with the following code.
As I lent my logic analyser to a friend so I could not check it yet,

You don't need a logic analyzer to test this.
Just compile it and look at the assembler output.
That will tell the full story.

--- bill

True,
but my assembly skills are not that good to use it as proof the C code is right.
and yes a logic analyser is no proof either,

or I can compile it, add LED's etc ,
or use a 2nd Arduino to test drive #1, generate a random array, send it to #1, read back the pins, etc
or ...
still all testing is never proof.

robtillaart:
True,
but my assembly skills are not that good to use it as proof the C code is right.
and yes a logic analyser is no proof either,

An analyzer can do timings, but the issue then becomes, what should the time be?
It is a useful tool (I use one all the time for optimizing algorithms in embedded code)
but for low level stuff like port manipulations, the only way to know what is happening
is to look at the actual assembly code because for low level port manipulation it is all about
the instructions generated. The C code often has to be tailored to generate the proper
instructions and register accesses.
Time to start taking a look at the compiler output. It's not that difficult particularly
since it does not require the ability to write assembly but merely to look to see if
the assembly generated is efficient.
It does require knowledge of the particular instruction set and register modes, but
at least the AVR has a pretty simple instruction set that is easy to understand.
Download the AVR instruction set document from Atmel and refer
to that as you look at the assembly output.


In the bigger picture for this, If the goal/desire is to provide a generic
way to atomically set multiple pin outputs for all pin combinations,
the main problem is that this is technically not possible.

Now if the desire is to simply have a function to set multiple output pins with a single
function call where the outputs are not all set at the same, well, that one is definitely possible
and the rest of this post can be ignored.


For certain situations, multiple pins/bits can be set atomically, but
with the AVR, it cannot do this with a single AVR instruction
because the AVR registers simply do not provide the functionality needed
if it is less than 8 bits.
The AVR uses too simple of a model to update its port bits.
The AVR can:

  • read all 8 bits
  • set all 8 bits
  • set an individual bit (but not on all ports of all AVRs and then only when the register and bit is known at compile time)
  • clear an individual bit (but not on all ports of all AVRs and then only when the register and bit is known at compile time)
    Because of that, it is technically impossible to provide a way to generically set and clear
    multiple bits at the same time with a single instruction.

While operations like:
PORTD = B00011000;

will set bit3 and 4 at the same time, it also stomps on bits 0,1,2,5,6 and 7

Other micro-controllers have the ability to set or clear bits by using set and clear registers
vs using bit set and bit clear instructions.
The advantage to this is that bits can be set/cleared atomically without disturbing other
bits without having to mask interrupts since the register itself handles the atomicity.
When using set/clear registers, not only can you do set/clear operations when using
run time data (which you can't do on the AVR),
but in many processors you can set/clear multiple bits at the same time.

For example, on the PIC and ARM you could set bits 3 and 4 like above using the bit set register
by simply writing those bits combined as a mask to the register
and that single instruction would atomically set those 2 bits.

But even with bit set/clear registers that support multiple bits, it still is not technically possible to offer
a fully generic multi-pin operation function that is guaranteed to be atomic for all the pins and their values.
In some cases the all the bits associated with the pins may not be in the same register.
In these cases, it takes writing to multiple registers to update all the bits.
There is no solution to this problem.

Also, there is the case where not all the pins want/need to be the same value.
Suppose you need to have a condition
where two pins are changing and both need to change at the same time but are not both 0 or 1.
i.e. not eventually ending up in the final state, but both must change to the new values
at EXACTLY the same time without having interim values.
This also creates an issue.

There is a solution for this in but not one that updates the register with a single instruction.
It also can't work in the general case, since it requires that the port bits for the pins be in
the same output port.
For this situation, there is no choice but to read the port into a working variable,
update the bits of the working variable to the desired value
then write the working variable out to the port. (obviously masking interrupts appropriately).

So while there are solutions that can be done in certain situations, depending
how the pins map to output ports and bits, there is way to offer a generic solution that
can atomically update multiple pins.

--- bill

Thanks for the elaborate answer, you have a point. I have generated assembly before (objdump.exe) and I can read it - to certain level.
Think I best compare it with the normal digitalWrite asm code to see if it does what I think it should do.

In the bigger picture for this, If the goal/desire is to provide a generic
way to atomically set multiple pin outputs for all pin combinations,
the main problem is that this is technically not possible.....

I know, but the idea of the code is to set at least the pins that are on the same port simultaneously as that IS possible. Bits[p] works as a mask for multiple bits simultaneously by means of the *out &= bits[p]; . For every port I have a separate mask. When all masks are set correctly, the PORTs are set as fast as possible after each other (even faster than posted version I think). The logic analyser would me to determine the time delta between 'setting' two PORTs. When time permits I'll dive into it further.

Thanks again,