11

How do the IO registers of Arduino Due work? On Arduino Uno just set DDRx, then PINx to read, PORTx to write, I'd like to do the same thing with an Arduino Due, but it has many more registers, such as PIO_OWER, PIO_OSER, PIO_CODR, PIO_SODR, etc. I find no correspondence between Arduino Uno and Arduino Due registers.

There are also some useful functions such as pio_clear, pio_set, pio_get, and others, all explained here:

http://asf.atmel.com/docs/3.19.0/sam3x/html/group__sam__drivers__pio__group.html

Now, I think I've understood what the three mentioned functions do, but not others, for example:

pio_configure (Pio *p_pio, const pio_type_t ul_type, const uint32_t ul_mask, const uint32_t ul_attribute)

I can't figure out what ul_attribute and ul_type are.

1

4 Answers 4

7

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

3

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.

1

This is a code example that flashes a led on pin 33. Code borrowed from above - many thanks for the very helpful explanations :) This is the beginning of a project to talk to a TFT touchscreen display with dumping of 16bit color pixel data which needs fast access to the ports. I think I have the code right - particularly the line that sets the pin low. The led is happily flashing.

void setup() 
{
  pinMode(33, OUTPUT); 
  REG_PIOC_OWER = 0x00000002; 
  REG_PIOC_OWDR = 0xfffffffd; 
}

void loop() 
{
  REG_PIOC_ODSR = 0x00000002; 
  delay(1000);             
  REG_PIOC_ODSR = 0x00000000;    
  delay(1000);   
}
1

I actually followed the above examples and did many tests that I am comfident the results will aid anyone looking into addressing the registers directly.

   void setup() {
  pinMode(25, OUTPUT); 
  pinMode(26, OUTPUT);
  pinMode(27, OUTPUT);
  pinMode(28, OUTPUT); 
  pinMode(29, OUTPUT);
  //REG_PIOD_SODR = 0b00000000000000000000000001001111; // written in binary form
  REG_PIOD_SODR = 0x0000004F; //written in Hexadecimal form. Note the differences. Thw two, binary and Hexadecimal are the same.
  delayMicroseconds (250000);
  //REG_PIOD_CODR = 0b00000000000000000000000001001111;
  REG_PIOD_CODR = 0x0000004F;
  delayMicroseconds (2000000);

}

void loop() 
{
  //REG_PIOD_ODSR = 0b00000000000000000000000001001111; // serts all the bits set to 1 HIGH and the rest set to 0 LOW
  REG_PIOD_SODR = 0b00000000000000000000000001001111; // sets all the bits set to 1 HIGH and leaves the rest untouched
  delay(500);             
  REG_PIOD_CODR = 0b00000000000000000000000000000100;//1
  //REG_PIOD_CODR = 0x00000004; //identical to the line above only that it is in Hexadecimal format
  //REG_PIOD_SODR = 0b00000000000000000000000000000100; // will disable the rest bits and leave only one HIGH
  delay(500);
  REG_PIOD_CODR = 0b00000000000000000000000000000010;//2
  delay(500);
  REG_PIOD_CODR = 0b00000000000000000000000001000000;//3
  delay(500);
  REG_PIOD_CODR = 0b00000000000000000000000000001000; //4
  delay(500);
  REG_PIOD_CODR = 0b00000000000000000000000000000001; //5 
  delay(500);

}

To explain better, all pins desired to be OUTPUTs are set via pinMode and then, it is very optional toenable them while at SETUP. In this case, I just wanted the pins to be made HIGH just once while at SETUP and then LOW before the loop starts. I've used REG_PIO?_SODR (in this case, ? is port D so it is REG_PIOD_SODR) which only make its set to 1 HIGH and the rest are left untouched. I have used both in binary and Hexadecimal format. Binary makes it easier to identify which pins to enable. For instance, bit PD0 is the right most bit in the binary number. It starts from Right going to left. For example, 0b00000000000000000000000000000001, bit 1 is PD0, PD1 will be 0b00000000000000000000000000000010 and so on. You've seen examples provided in this format, 0xfffffffd the x means it is in hexadecimal format and b is in binary format. To convert binary to Hexadecimal, youcan use the easiest route via this link

To explain, any Hexadecimal number represents 4 binary numbers, example, Hex 0 is binary 0000, Hex F is binary 1111, Hex 4 is binary 0100, Hex A is binary 1010.

Now that is understood, going to the main loop, I have used the REG_PIOD_SODR because it gives one the freedom to address the pins one wants without touching any other pins on that port. You can use REG_PIOD_ODSR ONLY when you want to set all bits at once. This is ideal when you want many pins set HIGH and/or LOW at once. It is not the best if you want to set just a few pins HIGH/LOW and leave the rest untouched. REG_PIOD_SODR is ideal when some pins needs to be left untouched. You don't want to set one bit HIGH then all pins are HIGH. This command REG_PIOD_CODR sets the bits set 1 LOW and leave all the rest set to 0 untouched.

This code simply makes the bits selected at setup HIGH for the said time in delay then LOW for the given time. In loop, all bits selected as 1 are made HIGH and the rest untouched. The following lines simply turn the selected bits LOW one at a time until all are OFF and then the process repeats.

I hope this oversimplified explanation aided someone.

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