SAM3X8E (Arduino Due) Pin IO registers

If you have a read of section 31 of the Datasheet, available from here, things may come a little clearer for you.

Here's a summary of what I know:

PIO stands for Parallel Input/Output and offers the functionality to read and write multiple register ports at a time. Where the datasheet mentions a register, for example PIO_OWER, the Arduino library has macros for accessing them in this format REG_PIO?_OWER where ? is either A, B, C or D for the different ports available.

I tend to still use the slow Arduino pinMode() function to set input/output on the pins as it makes the code more readable than the acronym based registers calls such as REG_PIOC_OWER = 0xdeadbeef, but then use the direct registers to set the pins for performance/synchronisation. As yet, I haven't done anything with input, so my examples are all output based.

For basic usage, you would use REG_PIO?_SODR to set output lines high and REG_PIO?_CODR to set them low. For example REG_PIOC_SODR = 0x00000002 would set bit 1 (numbered from zero) on PORTC (this is Due digital pin 33) high. All other pins on PORTC remain unchanged. REG_POIC_CODR = 0x00000002 would set bit 1 on PORTC low. Again all other pins would be unchanged.

As this is still not optimal, or synchronised if you are working with parallel data, there is a register that allows you to write all 32 bits of a port with a single call. These are the REG_PIO?_ODSR, so REG_PIOC_ODSR = 0x00000002 would now set bit 1 on PORTC high and all other bits on PORTC would be set low instantly in a single CPU instruction.

Because it is unlikely that you would ever be in a situation where you need to set all 32 bits of a port at the same time, you would need to store the current value of the pins, perform an AND operation to mask out the ones you want to alter, perform an OR operation to set the ones you want set high then perform your write and again, and this is not optimal. To overcome this, the CPU itself will perform the masking for you. There is a register called OWSR (output write status register) that will mask out any bits that you write to ODSRs that don't match bits set in the OWSR.

So, now if we call REG_PIOC_OWER = 0x00000002 (this sets bit 1 of the OWSR high) and REG_PIOC_OWDR = 0xfffffffd (this clears all bits except bit 1 of the OWSR) and then call REG_PIOC_ODSR = 0x00000002 again, this time it would only change bit 1 of PORTC and all other bits remain unchanged. Pay attention to the fact that OWER enables any bits that are set to 1 in the value you write and that OWDR disables any bits that are set to 1 in the value you write. Even though I understood this when I read it, I still managed to make a code mistake when writing my first test code thinking that OWDR disabled bits that weren't set to 1 in the value I wrote.

I hope this has at least given you a bit of a start in understanding the PIO of the Due CPU. Have a read and a play and if you have any further questions, I'll try to answer them.

Edit: One more thing...

How do you know which bits of the PORTs correspond to which digital lines of the Due? Check this out: Due Pinout


There is a fairly simple equivalence for the basic direct pin access. Below is some sample code which shows how to set a digital pin high and then low. The first is for an Arduino Due, the second is for the Arduino Uno/Mega/etc.

const unsigned int imThePin = 10; //e.g. digital Pin 10

#ifdef _LIB_SAM_

    //First lets get the pin and bit mask - this can be done once at the start and then used later in the code (as long as the variables are in scope
    Pio* imThePort = g_APinDescription[imThePin].pPort; 
    unsigned int imTheMask = g_APinDescription[imThePin].ulPin; 

    //Lets set the pin high
    imThePort->PIO_SODR = imTheMask;
    //And then low
    imThePort->PIO_CODR = imTheMask;

#else

    //First lets get the pin and bit mask - this can be done once at the start and then used later in the code (as long as the variables are in scope
    volatile unsigned char* imThePort = portOutputRegister(digitalPinToPort(imThePin)); 
    unsigned char imTheMask = digitalPinToBitMask(imThePin);

    //Lets set the pin high
    *imThePort |= imTheMask;
    //Now low
    *imThePort &= ~imTheMask;

#endif

Everything that is needed to do that should be included by default - and if not #include <Arduino.h> should be sufficient to get it there.

There are actually functions available that can be called once you have the Pio pointer to do the setting/clearing/pullup resistors/etc. using slightly cleaner looking function calls. A full list can be found in the header file.