Go Down

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

wholder

Is there a way to do Port Manipulation on the Zero to get faster pin-based I/O, such as when bit-banging protocols like SPI?  I was hoping for a big speed boost on the digitalWrite()-type I/O instructions, but find the Zero hardly improves on the speed of an Uno.  For example, the following code:

void setup() {
  pinMode(10, OUTPUT);
}

void loop() {
  while(1) {
    digitalWrite(10, HIGH);
    digitalWrite(10, LOW);
  }
}

Results in an output square wave of approximately 352 kHz, which is less than 5x of the 72 kHz produced by the same code running on an Uno.  Given the vastly greater clock speed of the Zero, I have a hard time fathoming how pin-based I/O can be this slow.  So, how can I get faster I/O?

Wayne

Dirk67

#1
Jul 05, 2015, 02:02 pm Last Edit: Jul 06, 2015, 09:37 am by Dirk67
I am interested as well
maybe it works somehow like on the DUE ?
--> http://forum.arduino.cc/index.php?topic=129868.15

[edit]
you may compare this with the "digitalWrite" function in:
..\packages\arduino\hardware\samd\1.6.0\cores\arduino\wiring_digital.c

--------------------------

on the other hand,
atmel advertises the SAMD21 as capable of "Single-cycle I/O port access"
if you look in the datasheet / point "22. PORT"
Quote
The PORT is connected to the high-speed bus matrix through an AHB/APB bridge.
The Pin Direction, Data Output Value and Data Input Value registers may also be accessed using the low-latency CPU local bus (IOBUS; ARMĀ® single-cycle I/O port).
and then quite interesting:
Quote
22.5.10 CPU Local Bus
The CPU local bus (IOBUS) is an interface that connects the CPU directly to the PORT. It is a single-cycle bus interface, and does not support wait states. It supports byte, half word and word sizes.
The CPU accesses the PORT module through the IOBUS when it performs read or write from address 0x60000000. The PORT register map is equivalent to the one described in the register description section. This bus is generally used for low latency.
The Data Direction (DIR) and Data Output Value (OUT) registers can be read, written, set, cleared or toggled using this bus, and the Data Input Value (IN) registers can be read.
Since the IOBUS cannot wait for IN register resynchronization, the Control register (CTRL) must be configured to enable continuous sampling of all pins that will need to be read via the IOBUS to prevent stale data from being read.
Which of the 2 bus-systems is used by the arduino core ("AHB/APB bridge" or "CPU local bus" = "IOBUS") ?
[edit] Arduino uses the normal way via "AHB/APB bridge".
arduino powered car relais / car "micro PLC" / with USB --> http://goo.gl/ofWFW3

wholder

I've made some progress on this issue myself by digging into the low-level internals.  The code below can toggle pin 10 at nearly 5 MHz on the Arduino Zero, but it's not exactly user friendly, or easy to understand.  So, I'm hoping there will be an "official" way to do this that will make more sense to less experienced users.

Wayne

#include <Arduino.h>

#define PIN 10

#ifdef _VARIANT_ARDUINO_ZERO_
volatile uint32_t *setPin = &PORT->Group[g_APinDescription[PIN].ulPort].OUTSET.reg;
volatile uint32_t *clrPin = &PORT->Group[g_APinDescription[PIN].ulPort].OUTCLR.reg;

const uint32_t  PinMASK = (1ul << g_APinDescription[PIN].ulPin);
#endif

void setup() {
  pinMode(PIN, OUTPUT);
}

void loop() {
  while(1) {
#ifdef _VARIANT_ARDUINO_ZERO_
  *setPin = PinMASK;
  *clrPin = PinMASK;
#else
    digitalWrite(PIN, HIGH);
    digitalWrite(PIN, LOW);
#endif
  }
}

Dirk67

nice !

but I don't understand, why your code is 5Mhz fast,
and the arduino core 0,352MHz slow ?
(14 times slower)

cause the arduino core here
..\packages\arduino\hardware\samd\1.6.0\cores\arduino\wiring_digital.c
does almost the same by using
Code: [Select]
PORT->Group[g_APinDescription[ulPin].ulPort].OUTCLR.reg = (1ul << g_APinDescription[ulPin].ulPin)is it just due to some function-calls overheads ?

and:
why does the arduino core function sets the pull-up resistor each time the "digitalWrite" function is used ?
Code: [Select]
void digitalWrite( uint32_t ulPin, uint32_t ulVal )
{
  // Handle the case the pin isn't usable as PIO
  if ( g_APinDescription[ulPin].ulPinType == PIO_NOT_A_PIN )
  {
    return ;
  }

  // Enable pull-up resistor
  PORT->Group[g_APinDescription[ulPin].ulPort].PINCFG[g_APinDescription[ulPin].ulPin].reg=(uint8_t)(PORT_PINCFG_PULLEN) ;

  switch ( ulVal )
  {
    case LOW:
      PORT->Group[g_APinDescription[ulPin].ulPort].OUTCLR.reg = (1ul << g_APinDescription[ulPin].ulPin) ;
    break ;

    case HIGH:
      PORT->Group[g_APinDescription[ulPin].ulPort].OUTSET.reg = (1ul << g_APinDescription[ulPin].ulPin) ;
    break ;

    default:
    break ;
  }

  return ;
}



arduino powered car relais / car "micro PLC" / with USB --> http://goo.gl/ofWFW3

wholder

#4
Jul 08, 2015, 12:55 am Last Edit: Jul 08, 2015, 12:55 am by wholder
Does anyone know where (what .h file) things like PORT and OUTCLR and g_APinDescription[] are defined?  I can't seem to find how these things are declared and included.  Likewise, is there a .h file I can include that defines register names for direct I/O?

Wayne

Dirk67

#5
Jul 08, 2015, 08:52 am Last Edit: Jul 08, 2015, 10:07 am by Dirk67
I'm not that shure,
but I always thought that the Arduino core uses the ARM CMSIS syntax / components (?)

which you can find in the directory
..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\component

you can find "port.h" there for example,
containing the unions, typedefs, an structs for all that stuff:
Code: [Select]
/* -------- PORT_OUTCLR : (PORT Offset: 0x14) (R/W 32) GROUP Data Output Value Clear -------- */

typedef union {
  struct {
    uint32_t OUTCLR:32;        /*!< bit:  0..31  Port Data Output Value Clear       */
  } bit;                       /*!< Structure used for bit  access                  */
  uint32_t reg;                /*!< Type      used for register access              */
} PORT_OUTCLR_Type;

and then mapped together within a "PortGroup" structure:
Code: [Select]
typedef struct {
  __IO PORT_DIR_Type             DIR;         /**< \brief Offset: 0x00 (R/W 32) Data Direction */
  __IO PORT_DIRCLR_Type          DIRCLR;      /**< \brief Offset: 0x04 (R/W 32) Data Direction Clear */
  __IO PORT_DIRSET_Type          DIRSET;      /**< \brief Offset: 0x08 (R/W 32) Data Direction Set */
  __IO PORT_DIRTGL_Type          DIRTGL;      /**< \brief Offset: 0x0C (R/W 32) Data Direction Toggle */
  __IO PORT_OUT_Type             OUT;         /**< \brief Offset: 0x10 (R/W 32) Data Output Value */
  __IO PORT_OUTCLR_Type          OUTCLR;      /**< \brief Offset: 0x14 (R/W 32) Data Output Value Clear */
  __IO PORT_OUTSET_Type          OUTSET;      /**< \brief Offset: 0x18 (R/W 32) Data Output Value Set */
  __IO PORT_OUTTGL_Type          OUTTGL;      /**< \brief Offset: 0x1C (R/W 32) Data Output Value Toggle */
  __I  PORT_IN_Type              IN;          /**< \brief Offset: 0x20 (R/  32) Data Input Value */
  __IO PORT_CTRL_Type            CTRL;        /**< \brief Offset: 0x24 (R/W 32) Control */
  __O  PORT_WRCONFIG_Type        WRCONFIG;    /**< \brief Offset: 0x28 ( /W 32) Write Configuration */
       RoReg8                    Reserved1[0x4];
  __IO PORT_PMUX_Type            PMUX[16];    /**< \brief Offset: 0x30 (R/W  8) Peripheral Multiplexing n */
  __IO PORT_PINCFG_Type          PINCFG[32];  /**< \brief Offset: 0x40 (R/W  8) Pin Configuration n */
       RoReg8                    Reserved2[0x20];
} PortGroup;




I think the PORT itself (just an address) ist defined in
..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\samd21g18a.h
Code: [Select]
[...]
#define PORT                          (0x41004400U) /**< \brief (PORT) APB Base Address */
#define PORT_IOBUS                    (0x60000000U) /**< \brief (PORT) IOBUS Base Address */
[...]

maybe to use the faster IOBUS-system, you can just use the other base address ?
(but maybe you need another startup-code then ?  I don't know...)
(but this will have other disadvantages in some (non-) deterministic behaviour, I think...)


---------------------


the pin discriptions (g_APinDescription[] - array) is found in
..\packages\arduino\hardware\samd\1.6.0\variants\arduino_zero\variant.cpp
I think...

The type-definition itself for g_APinDescription[] is found in:
..\packages\arduino\hardware\samd\1.6.0\cores\arduino\WVariant.h
Code: [Select]
/* Types used for the table below */
typedef struct _PinDescription
{
  EPortType       ulPort ;
  uint32_t        ulPin ;
  EPioType        ulPinType ;
  uint32_t        ulPinAttribute ;
  EAnalogChannel  ulADCChannelNumber ; /* ADC Channel number in the SAM device */
  EPWMChannel     ulPWMChannel ;
  ETCChannel      ulTCChannel ;
  EExt_Interrupts ulExtInt ;
} PinDescription ;

/* Pins table to be instantiated into variant.cpp */
extern const PinDescription g_APinDescription[] ;
arduino powered car relais / car "micro PLC" / with USB --> http://goo.gl/ofWFW3

cmaglie

Another solution is to use the variant macros to access GPIO registers:


#ifdef ARDUINO_ARCH_AVR
  #define REGTYPE uint8_t   // AVR uses 8-bit registers
#else
  #define REGTYPE uint32_t
#endif

REGTYPE pin13;
volatile REGTYPE *mode13;
volatile REGTYPE *out13;

void setup() {
  pin13 = digitalPinToBitMask(13);
  mode13 = portModeRegister(digitalPinToPort(13));
  out13 = portOutputRegister(digitalPinToPort(13));
 
  // set pin 13 port as ouput
  *mode13 |= pin13;
}

void loop() {
  // blink pin 13
  *out13 |= pin13;
  delay(500);
  *out13 &= ~pin13;
  delay(500);
}


This way you don't need to know the details of the underlying abstraction layer used to define pins.

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, BTW have you tried to remove the line of code that always activate the "pull-up"? does it work correctly or the GPIO misbehave in some way?
C.

Markus_L811

#7
Jul 09, 2015, 03:57 pm Last Edit: Jul 09, 2015, 04:01 pm by Markus_L811
I'm not that shure,
but I always thought that the Arduino core uses the ARM CMSIS syntax / components (?)

which you can find in the directory
..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\component

you can find "port.h" there for example,
containing the unions, typedefs, an structs for all that stuff:
Code: [Select]
/* -------- PORT_OUTCLR : (PORT Offset: 0x14) (R/W 32) GROUP Data Output Value Clear -------- */

typedef union {
  struct {
    uint32_t OUTCLR:32;        /*!< bit:  0..31  Port Data Output Value Clear       */
  } bit;                       /*!< Structure used for bit  access                  */
  uint32_t reg;                /*!< Type      used for register access              */
} PORT_OUTCLR_Type;

and then mapped together within a "PortGroup" structure:
Code: [Select]
typedef struct {
  __IO PORT_DIR_Type             DIR;         /**< \brief Offset: 0x00 (R/W 32) Data Direction */
  __IO PORT_DIRCLR_Type          DIRCLR;      /**< \brief Offset: 0x04 (R/W 32) Data Direction Clear */
  __IO PORT_DIRSET_Type          DIRSET;      /**< \brief Offset: 0x08 (R/W 32) Data Direction Set */
  __IO PORT_DIRTGL_Type          DIRTGL;      /**< \brief Offset: 0x0C (R/W 32) Data Direction Toggle */
  __IO PORT_OUT_Type             OUT;         /**< \brief Offset: 0x10 (R/W 32) Data Output Value */
  __IO PORT_OUTCLR_Type          OUTCLR;      /**< \brief Offset: 0x14 (R/W 32) Data Output Value Clear */
  __IO PORT_OUTSET_Type          OUTSET;      /**< \brief Offset: 0x18 (R/W 32) Data Output Value Set */
  __IO PORT_OUTTGL_Type          OUTTGL;      /**< \brief Offset: 0x1C (R/W 32) Data Output Value Toggle */
  __I  PORT_IN_Type              IN;          /**< \brief Offset: 0x20 (R/  32) Data Input Value */
  __IO PORT_CTRL_Type            CTRL;        /**< \brief Offset: 0x24 (R/W 32) Control */
  __O  PORT_WRCONFIG_Type        WRCONFIG;    /**< \brief Offset: 0x28 ( /W 32) Write Configuration */
       RoReg8                    Reserved1[0x4];
  __IO PORT_PMUX_Type            PMUX[16];    /**< \brief Offset: 0x30 (R/W  8) Peripheral Multiplexing n */
  __IO PORT_PINCFG_Type          PINCFG[32];  /**< \brief Offset: 0x40 (R/W  8) Pin Configuration n */
       RoReg8                    Reserved2[0x20];
} PortGroup;




I think the PORT itself (just an address) ist defined in
..\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\samd21g18a.h
Code: [Select]
[...]
#define PORT                          (0x41004400U) /**< \brief (PORT) APB Base Address */
#define PORT_IOBUS                    (0x60000000U) /**< \brief (PORT) IOBUS Base Address */
[...]

maybe to use the faster IOBUS-system, you can just use the other base address ?
(but maybe you need another startup-code then ?  I don't know...)
(but this will have other disadvantages in some (non-) deterministic behaviour, I think...)


---------------------


the pin discriptions (g_APinDescription[] - array) is found in
..\packages\arduino\hardware\samd\1.6.0\variants\arduino_zero\variant.cpp
I think...

The type-definition itself for g_APinDescription[] is found in:
..\packages\arduino\hardware\samd\1.6.0\cores\arduino\WVariant.h
Code: [Select]
/* Types used for the table below */
typedef struct _PinDescription
{
  EPortType       ulPort ;
  uint32_t        ulPin ;
  EPioType        ulPinType ;
  uint32_t        ulPinAttribute ;
  EAnalogChannel  ulADCChannelNumber ; /* ADC Channel number in the SAM device */
  EPWMChannel     ulPWMChannel ;
  ETCChannel      ulTCChannel ;
  EExt_Interrupts ulExtInt ;
} PinDescription ;

/* Pins table to be instantiated into variant.cpp */
extern const PinDescription g_APinDescription[] ;

So then if someone can explain from Atmel samd21 at Page 382, how it works a little bit more so we can get the little Pin direct...

Code: [Select]

22.6.2 Basic Operation
22.6.2.1 Initialization
After reset, all standard-function device I/O pins are connected to the PORT with outputs tri-stated and input buffers
disabled, even if no clocks are running. Specific pins, such as the ones used for connection to a debugger, may be
configured differently, as required by their special function.
Each I/O pin y can be configured and accessed by reading or writing PORT registers. Because PORT registers are
grouped into sets of registers for each group of up to 32 pins, the base address of the register set for pin y is at byte
address PORT + (y/32) * 0x80. (y%32) will be used as the index within each register of that register set.
To use pin y as an output, configure it as output by writing the (y%32) bit in the DIR register to one. To avoid disturbing
the configuration of other pins in that group, this can also be done by writing the (y%32) bit in the DIRSET register to one.
The desired output value can be set by writing the (y%32) bit to that value in register OUT.
Similarly, writing one to a bit in Data Output Value Set (OUTSET) register will set the corresponding bit in Data Output
Value (OUT) register to one. Writing one to a bit in Data Output Value Clear (OUTCLR) register will set the corresponding
bit in Data Output Value (OUT) register to zero. Writing one to a bit in Data Output Value Toggle (OUTTGL) register will
toggle the corresponding bit in Data Output Value (OUT) register.
To use pin y as an input, configure it as input by writing the (y%32) bit in the DIR register to zero. To avoid disturbing the
configuration of other pins in that group, this can also be done by writing the (y%32) bit in DIRCLR register to one. The
desired input value can be read from the (y%32) bit in register IN as soon as the INEN bit in the Pin Configuration register
(PINCFGy) is written to one. Refer to "I/O Multiplexing and Considerations" on page 20 for details on pin configuration.


...gives me headache this damm Basic operations.

I try some direct Pin operations on the same base Dirk67 posted for the Onewire lib, discused there

Rlndkmp

Reading your first post I'm curious why you expect 5 times speed increase on the Zero. The Zero clocks at 48 Mhz, the Uno at 16Mhz, that's 3 times faster.
So the Zero is faster, 72 Khz times 3 = 216 Khz, you measured 325 Khz on the Zero.


wholder

Reading your first post I'm curious why you expect 5 times speed increase on the Zero. The Zero clocks at 48 Mhz, the Uno at 16Mhz, that's 3 times faster.
So the Zero is faster, 72 Khz times 3 = 216 Khz, you measured 325 Khz on the Zero.
If you'll read my later post you'll see that I managed to get nearly 5 MHz out of the Zero using a more direct route for I/O, so the Zero is clearly capable of faster I/O and faster I/O is always better.

Wayne

MartinL

#10
Jul 21, 2015, 01:39 pm Last Edit: Jul 21, 2015, 06:06 pm by MartinL
Thanks for the information on accessing the port registers on the SAMD21G.

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

Code: [Select]

#define PORT_NUMBER 17    // Digital pin 13 is port pin PA17

volatile uint32_t *setPin = &PORT->Group[PORTA].OUTSET.reg;  // Ptr to PortA Data Output Value Set register
volatile uint32_t *clrPin = &PORT->Group[PORTA].OUTCLR.reg;  // Ptr to PortA Data Output Value Clear register
volatile uint32_t *dirPin = &PORT->Group[PORTA].DIRSET.reg;  // Ptr to PortA Data Direction Set register

const uint32_t  PinMASK = (1ul << PORT_NUMBER);   // Generate bit mask, binary one 17 places to the left

// the setup function runs once when you press reset or power the board
void setup()
{
  // initialize digital pin 13 (PA17) as an output.
  *dirPin = PinMASK;
}

// the loop function runs over and over again forever
void loop()
{
  *setPin = PinMASK;        // turn the LED on (HIGH is the voltage level)
  delay(1000);              // wait for a second
  *clrPin = PinMASK;        // turn the LED off by making the voltage LOW
  delay(1000);              // wait for a second
}

MartinL

#11
Jul 21, 2015, 01:53 pm Last Edit: Jul 24, 2015, 01:13 pm by MartinL
Shortly after posting I discovered that PORTA has already been defined. So in the code above you can delete the first two lines that define PORT_A and PORT_B and simply substitute PORTA for PORT_A in the pointer declarations.

EDIT - The code above has been amended to reflect the changes.
EDIT - There's an even easier way on page 2 below

Markus_L811

Shortly after posting I discovered that PORTA has already been defined. So in the code above you can delete the first two lines that define PORT_A and PORT_B and simply substitute PORTA for PORT_A in the pointer declarations.
You can edit your post anyway, btw. can you post a snip from the file you found it in and the file location

MartinL

#13
Jul 21, 2015, 07:03 pm Last Edit: Jul 21, 2015, 07:18 pm by MartinL
Quote
You can edit your post anyway, btw. can you post a snip from the file you found it in and the file location
Thanks, I edited the post and the code.

Unfortunately, I didn't find it in a file. I worked it out from the information provided by wholder and Dirk67, as well as a paragraph at the head of the PORT Register Summary, page 386 of the SAMD21 data sheet. It stated that the ports are organised in groups with port A being group 0, port B group 1, etc...

I guess the same format should also work for the other port registers, to set pull-ups and the pins as inputs.

I'm trying to work out how to access the SAMD21G registers directly from within the Arduino environment, but currently it doesn't appear as easy as the AVR processors.

So far I've discovered that it's also possible to access other registers using a similar format. For example to access the timer TCC0 CTRLA register, I've used:

Code: [Select]
volatile uint32_t *ctrlAReg0 = &TCC0->CTRLA.reg;
This compiles and I can read and write from/to the register.

As yet though I haven't been able to find any definitions (names) for the individual register locations, which were available with the AVR. Any help on this front would be appreciated.

rozling

#14
Jul 21, 2015, 11:17 pm Last Edit: Jul 21, 2015, 11:17 pm by rozling
Coming up against this myself today also.  All I wanted to do was hook up a DJ Hero controller to an Arduino and here I am trawling through data sheets once more  :'(

Anyway this might be something or it might be nothing, but here is an Application Note [Direct link to 250kb PDF]  for a Port Driver from Atmel:

Quote
This driver for AtmelĀ® | SMART SAM devices provides an interface for the configuration and management of the device's General Purpose Input/Output (GPIO) pin functionality, for manual pin state reading and writing.

The following devices can use this module:
  • Atmel | SMART SAM D20/D21
  • Atmel | SMART SAM R21
  • Atmel | SMART SAM D10/D11
also:

Quote
The device GPIO (PORT) module provides an interface between the user application logic and external hardware peripherals, when general pin state manipulation is required. This driver provides an easy-to-use interface to the physical pin input samplers and output drivers, so that pins can be read from or written to for general purpose external hardware control.
Maybe this is something that can be leveraged to map Zero pins to something more user-friendly?

Go Up