Port manipulation made easy

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