Go Down

Topic: Faster Pin I/O on Zero? (Read 11985 times) previous topic - next topic

MartinL

#15
Jul 22, 2015, 12:40 am Last Edit: Jul 22, 2015, 09:32 am by MartinL
Thank you for the link to the application note. At a quick glace, I believe that the ASF is integrated with Atmel Studio. I'm not sure it would work with the Arduino libraries?

As Dirk67 has mentioned, pointers to the base addresses of the port and other registers used on the SAM21D are defined in the file: ..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\samd21g18a.h

Also the structure and definitions associated with the port (port.h) and other registers are held in the directory: ..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\component

The PORTA definition I used above is part of the EPortType enumeration in the file: ..\packages\arduino\hardware\samd\1.6.0\cores\arduino\WVariant.h   

Currently direct access to the registers on the SAM21D does seem unnecessarily complex and convoluted with a lot of digging around in deeply buried files. Especially when compared with the simple register manipulation on the old AVR processors.

Searching through the files though, makes you appreciate how much work Arduino and Atmel put in behind the scenes to abstract us from the processor internals.

thatcadguy

Not sure about the zero, but for the uno: http://www.billporter.info/2010/08/18/ready-set-oscillate-the-fastest-way-to-change-arduino-pins/

MartinL

#17
Jul 24, 2015, 12:46 pm Last Edit: Jul 24, 2015, 12:47 pm by MartinL
Found an even easier method using the definitions found in the file: ..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\instance\port.h.

Here's the simplified example of Blink program that outputs to digital pin 13, but using the processor port number, in this case PA17.

Code: [Select]
void setup() {
  // put your setup code here, to run once:
  REG_PORT_DIRSET0 = PORT_PA17;   // Set the direction of the port pin PA17 to an output
}

void loop() {
  // put your main code here, to run repeatedly:
  REG_PORT_OUTSET0 = PORT_PA17;     // Switch the output to 1 or HIGH
  delay(1000);
  REG_PORT_OUTCLR0 = PORT_PA17;     // Switch the output to 0 or LOW
  delay(1000);
}

Dirk67

#18
Jul 24, 2015, 01:39 pm Last Edit: Jul 24, 2015, 01:41 pm by Dirk67
this is interesting,
though I don't fully understand it ...  ;)

REG_PORT_OUTSET0 is defined as (*(RwReg  *)0x41004418U)
and
REG_PORT_OUTCLR0 (*(RwReg  *)0x41004414U)
(in ..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\instance\port.h)

RwReg is just a typedef defined as:
typedef volatile uint32_t RwReg;
(in ..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\samd21e18a.h)

and for PORT_PA17:
#define PORT_PA17 (1u << 17)
(in ..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\pio\samd21e18a.h)



how can the simple statement
REG_PORT_OUTSET0 = PORT_PA17;
set the port 17 without changing the other bits of the (32bit-) register ?

I just wonder if you have to write
REG_PORT_OUTSET0 |= PORT_PA17;
to not change the other bit of the register (which already might have been set to '1')
:smiley-roll:
?
arduino powered car relais / car "micro PLC" / with USB --> http://goo.gl/ofWFW3

MartinL

#19
Jul 24, 2015, 03:26 pm Last Edit: Jul 24, 2015, 11:53 pm by MartinL
Quote
how can the simple statement
REG_PORT_OUTSET0 = PORT_PA17;
set the port 17 without changing the other bits of the (32bit-) register ?

I just wonder if you have to write
REG_PORT_OUTSET0 |= PORT_PA17;
to not change the other bit of the register (which already might have been set to '1')
:smiley-roll:
?
I believe it's because the SAMD21 uses three registers for port output:

1. OUT - Data Output Value (REG_PORT_OUT0)
2. OUTCLR - Data Output Value Clear (REG_PORT_OUTCLR0)
3. OUTSET - Data Output Value Set (REG_PORT_OUTSET0)

The OUT register is the actual data output register and works in the same way as the AVR processors. Using this register you'd have to OR the register (REG_PORT_OUT0) with the port mask (PORT_PA17).

The other two registers OUTCLR and OUTSET are really there for convenience and speed. Setting a bit in these registers respectively clears and sets the value in the OUT register. Therefore unlike the AVRs you don't need to OR the bitmask with the register to set, or AND the inverse bitmask to clear, the ARM processor's hardware does that for you.

There's even a OUTTGL - Data Output Value Toggle register that allows you to toggle the port output on and off, this can simplify the Blink program even further:

Code: [Select]
void setup() {
  // put your setup code here, to run once:
  REG_PORT_DIRSET0 = PORT_PA17;   // Set the direction of the port pin PA17 to an output
}

void loop() {
  // put your main code here, to run repeatedly:
  REG_PORT_OUTTGL0 = PORT_PA17;     // Toggle the output HIGH and LOW
  delay(1000);
}

cmaglie

I don't know the specific use case, you're are talking about chip pins (i.e. PA17) instead of board's pin number so I guess that you're fine with direct access to SAMD21 registers like PORT.DIRSET or PORT.OUTTGL.

BTW I just want to point out that the code made this way is not portable across different architectures because it's bound to a specific CPU: If you try to run it on a different processor it will likely not work or compile.

That's the reason why I always suggest to use digitalPinToPort macros whenever possible (like in this example) in particular if you're going to make a library or share your sketch it increases the chance that your code may run on other MCU/boards without modifications (at least on Arduino boards that correctly supports these macros!).
C.

68tjs

#21
Aug 20, 2015, 05:21 pm Last Edit: Aug 20, 2015, 05:22 pm by 68tjs
Using digitalPinToPort macros will be more  universal but will always be slower than direct port manipulation.
And I think that the subject was to have the greatest possible speed.

Dirk67

Quote from: Dirk67
and:
why does the arduino core function sets the pull-up resistor each time the "digitalWrite" function is used
Very good question, I have to check that with the original author...
@cmaglie
did you find out in the meantime ?
arduino powered car relais / car "micro PLC" / with USB --> http://goo.gl/ofWFW3

MartinL

#23
Sep 03, 2015, 03:08 pm Last Edit: Sep 04, 2015, 10:07 am by MartinL
Quote
why does the arduino core function sets the pull-up resistor each time the "digitalWrite" function is used
That's not the only problem with digital IO on the Zero.

Setting pinMode() to OUTPUT, disables the INEN (input enable) bit in the port's PINCFGx (pin config) register. This means that when you set a pin as an output, you can't read it back as an input. For example:

Code: [Select]
void setup() {
  Serial.begin(115200);
  pinMode(0, OUTPUT);               // Set pin 0 to an output
  digitalWrite(0, HIGH);            // Set the output HIGH
  Serial.println(digitalRead(0));   // Outputs 0, but should output 1
}

void loop() {}

Outputs 0, which is clearly incorrect. Run this on the UNO and you'll get a 1.

Just wasted half a day debugging that one.

Dirk67

That's not the only problem with digital IO on the Zero.

Setting pinMode() to OUTPUT, disables the INEN (input enable) bit in the port's PINCFGx (pin config) register. This means that when you set a pin as an output, you can't read it back as an input. For example:
[...]
Outputs 0, which is clearly incorrect. Run this on the UNO and you'll get a 1.
I think this is "normal behaviour" on an ARM Cortex core,
since the I/O are set and read "indirect" via memory registers,
that are connected with some kind of serial bus with the I/O's.

It's completely different than an AVR 8-bit core ...

I don't know if the "digitalRead" function of the arduino ide should abstract that for the zero as well (?)
arduino powered car relais / car "micro PLC" / with USB --> http://goo.gl/ofWFW3

MartinL

#25
Sep 03, 2015, 11:11 pm Last Edit: Sep 04, 2015, 10:07 am by MartinL
Surely one of the principle benefits of using Arduino is that it abstracts users from the internal workings of the processors and allows code portability between them. As a user, I expect the pinMode() function on the UNO to behave in exactly the same way as that on the Zero, otherwise this breaks code portability. (As it did today, as I was porting code from the Mega to the Zero. The cause took half a day of debugging to track down).

The AVR processors provide this behaviour as a consequence of their internal hardware design. The SAMD21 processor has this functionality as an option, it's the input enable (INEN) bit in the port's pin configuration (PINCFGx) register.

In my opinion, the code in the Arduino core file "wiring_digital.c" unnecessarily clears the INEN bit. Adding the following line after pinMode() in the code above, re-enables the INEN bit. The code then behaves like the AVR Arduinos such as the UNO, MEGA etc...

Code: [Select]
PORT->Group[PORTA].PINCFG[g_APinDescription[0].ulPin].bit.INEN = 1;
The pin configuration table on page 383 of the SAMD21 datasheet shows that if the DIR and INEN bits are both set to 1, then both the pin's input and ouput are enabled, (irrespective of the state of the pull-up).

I think the Zero's "wiring_digital.c" file needs to be changed bring its digital IO behaviour into line with the AVR Arduinos. This includes enabling inputs when an output is selected and removing the pull-up activation in the digitalWrite() function.

MartinL

I noticed that someone has already initiated a pull request on Github to correct this behaviour on the Due. Hopefully the changes will also be made to the Zero as well.

Quote
AVR core compatibility issues:
5- pinMode(OUTPUT) [ defaults to HIGH ] #3317 #3310
6- pinMode(OUTPUT) -> digitalRead() [ always return 0 ] #1597 #1533
7- digitalWrite() -> pinMode(OUTPUT) [ does not retain the intended value ]

susan-parker

Lets put some hard numbers on this:


To toggle a port pin HIGH and then immediately LOW, pulse width HIGH is:

  Direct IO      = 125.7 nS

  digitalWrite() = 1.523 uS

= 12x faster.


To toggle a port pin HIGH and LOW in a loop(), I get the following minimum loop timing:

  Direct IO      = 1.654 MHz
 
  digitalWrite() = 303.5 KHz

= 5.4x faster


Timings are standard Arduino setup with PLL main clock from the 32KHz Xosc

Quote
void setup()
  { 
  pinMode(13, OUTPUT);          // initialize digital pin 13 as an output.
//  REG_PORT_DIRSET0 = PORT_PA17;   // Set port pin PA17 direction to output
  }
 
 
void loop()
  {
  digitalWrite(13, HIGH);           // turn the LED on
//  REG_PORT_OUTSET0 = PORT_PA17;     // Set port pin PA17 to 1 / HIGH
  digitalWrite(13, LOW);            // turn the LED off
//  REG_PORT_OUTCLR0 = PORT_PA17;     // Set port pin PA17 to 0 / LOW
  }


westfw

Quote
Does anyone know where (what .h file) things like PORT and OUTCLR and g_APinDescription[] are defined?
Use the following in your code:
Code: [Select]
#include <sam.h>

like avr/io.h, sam.h will include the other files needed depending on the compile line.  The actual file structure is relatively complicated, but the chip-specific address will be somewhere like:
Code: [Select]
packages/arduino/tools/CMSIS/4.0.0-atmel/Device/ATMEL/samd21/include/samd21j18a.h

And the structure defintions and magic values will be in
Code: [Select]
.../packages/arduino/tools/CMSIS/4.0.0-atmel/Device/ATMEL/samd21/include/component/*.h
(port.h for gpio.)
  and/or
.../packages/arduino/tools/CMSIS/4.0.0-atmel/Device/ATMEL/samd21/include/instance/*.h



MartinL

It isn't necessary to use:

Code: [Select]
#include <sam.h>
You can access the registers directly in your Arduino sketch without any includes.

Go Up