Go Down

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

scswift

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
If you're going to use a loop to determine how fast the instructions are, you need to account for the time it takes to do the jump as this will likely overwhelm your result.

Try putting two toggle instructions in the loop, one after the other and you will likely get a very different result.  Keep doubling the number of instructions until it stops speeding up and you'll know the true speed of one versus the other. 

dzim

Hi,

thank you that thread helped me too for getting a faster write speed by replacing the digitalWrite() function.

Is it also possible to get a faster read speed in the same way by replacing digitalRead() ?

MartinL

#32
Oct 27, 2015, 10:13 am Last Edit: Oct 27, 2015, 10:14 am by MartinL
Yes, it's possible using the Zero's IN register. The register definitions are REG_PORT_IN0 for port A and REG_PORT_IN1 for port B.

So, for example to check the input of PA19 is high (digital pin 12):

Code: [Select]
if (REG_PORT_IN0 & PORT_PA19)  // if (digitalRead(12) == HIGH)
Alternatively, to check the input of PA19 is low:

Code: [Select]
if (!(REG_PORT_IN0 | ~PORT_PA19)) // if (digitalRead(12) == LOW)

dzim

Thank you! Your example code works fine.

Do you also know which register  (REG_PORT_IN0 and REG_PORT_IN1) belongs to which pin or where I can find this information?

MartinL

#34
Oct 30, 2015, 10:54 pm Last Edit: Nov 01, 2015, 06:37 pm by MartinL
REG_PORT_IN0 is the 32-bit register for port A, and REG_PORT_IN1 is the register for port B.

Each IN register consists of 32 binary bits. Each bit represents a value on the input pin, (assuming the pin in question has been set to an input); though not all bits in the register are used, as the SAMD21G processor on the Zero has a limited number of pins.

Arduino maps the digital and analog board numbers (D0-D13 and A0-A5), to port numbers (PORT_PA00, PORT_PA01, etc...). This information is contained in the Arduino Zero schematic, whose link is here.

Alternatively, here's the Arduino pin to port pin mapping for the Zero:

D0 - PORT_PA11
D1 - PORT_PA10
D2 - PORT_PA14    (D4 on Zero Pro/M0 Pro)
D3 - PORT_PA09
D4 - PORT_PA08    (D2 on Zero Pro/M0 Pro)
D5 - PORT_PA15
D6 - PORT_PA20
D7 - PORT_PA21
D8 - PORT_PA06
D9 - PORT_PA07
D10 - PORT_PA18
D11 - PORT_PA16
D12 - PORT_PA19
D13 - PORT_PA17

A0 - PORT_PA02
A1 - PORT_PB08
A2 - PORT_PB09
A3 - PORT_PA04
A4 - PORT_PA05
A5 - PORT_PB02

SCK - PORT_PB11
MISO - PORT_PA12
MOSI - PORT_PB10

SCL - PORT_PB23
SDA - PORT_PB22

ATN - PORT_PA13    (Not included on the Zero Pro/M0 Pro)

dzim

Thank you very much!

(I think there is just a small mistake: It should be A2 - PORT_PB09, not A2 - PORT_PA09)

MartinL

Oops. Thanks dzim, it's now corrected.

ChrisChris

I remembered when I was working with stepper motors I need a way to pulse the controller faster, and so I found a nice digitalWriteDirect code ( https://afterhourscoding.wordpress.com/tag/digitalwritedirect/ ) that could be used on the Due, and it was really smooth and fast. Yesterday I decided to mix up what I knew worked between his code and what was found here, And IT WORKS!



inline void digitalWriteDirect(int PIN, boolean val){
  if(val)  PORT->Group[g_APinDescription[PIN].ulPort].OUTSET.reg = (1ul << g_APinDescription[PIN].ulPin);
  else     PORT->Group[g_APinDescription[PIN].ulPort].OUTCLR.reg = (1ul << g_APinDescription[PIN].ulPin);
}
//inline int digitalReadDirect(int PIN){
//  return !!(g_APinDescription[PIN].ulPort=PIO_PDSR &g_APinDescription[PIN].ulPin);
//}  still working on the fast read, wasn't needed as much as Write at the time.

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

void loop() {
  digitalWriteDirect(13, HIGH );
  delay(1000);           
  digitalWriteDirect(13, LOW );   
   delay(1000);

}

OMac

#38
Jun 02, 2017, 05:42 pm Last Edit: Jun 02, 2017, 05:48 pm by OMac
For anyone interested, I've added the faster digitalRead as well.






Code: [Select]


inline void digitalWrite_fast(int pin, bool val)
{
   if (val)  
      PORT->Group[g_APinDescription[pin].ulPort].OUTSET.reg = (1ul << g_APinDescription[pin].ulPin);
   else    
      PORT->Group[g_APinDescription[pin].ulPort].OUTCLR.reg = (1ul << g_APinDescription[pin].ulPin);
}


inline int digitalRead_fast(int pin)
{
   return !!(PORT->Group[g_APinDescription[pin].ulPort].IN.reg & (1ul << g_APinDescription[pin].ulPin));
}


void setup() {                
   pinMode(13, OUTPUT);
   pinMode(12, INPUT);    

}


void loop() {
   int val = digitalRead_fast(12);

   digitalWrite_fast(13, HIGH );
   delay(1000);          
   digitalWrite_fast(13, LOW );  
   delay(1000);

}


Dirk67

thanks @OMac, this is very helpful
arduino powered car relais / car "micro PLC" / with USB --> http://goo.gl/ofWFW3

grumpFish

   
   #define PORT                          (0x41004400) /**< \brief (PORT) APB Base Address */
   #define PORT_IOBUS                    (0x60000000) /**< \brief (PORT) IOBUS Base Address */
   
   #define PORTA           PORT->Group[0] // slower access

   #define FPORTA          PORT_IOBUS->Group[0] // Fast PORT
   #define FPORTB          PORT_IOBUS->Group[1] // Fast PORT


   #define PORT_PA08                  (1u <<  8) /**< \brief PORT Mask  for PA08 */
   #define PORT_PA09                  (1u <<  9) /**< \brief PORT Mask  for PA09 */

   #define PULSE_WIDTH 1 // was 1
   #define PULSE_GAP 2 // was 2
   
   uint32_t PULSEPINS=0;
   uint16_t pulses=100; // so 100 pulses
   uint8_t pulses_len; //


   PULSEPINS = (PORT_PA08|PORT_PA09);

   cpu_irq_enter_critical(); // Kill Interrupts before pulsing starts
   for (pulses=0; pulses < num_pulses; pulses++)
   {
      FPORTA.OUTSET.reg = PULSEPINS; // Pulse ON
      for(pulses_len = 0u; pulses_len < PULSE_WIDTH; pulses_len++) // pulse width
      {
         asm volatile ("nop");
      } // end for
      FPORTA.OUTCLR.reg = PULSEPINS; // Pulse off
      for(pulses_len = 0u; pulses_len < PULSE_GAP; pulses_len++) //  between pulses time
      {
         asm volatile ("nop");
      } // end for time_count
   } // end for pulses
   cpu_irq_leave_critical();

This example uses the poorly documented fast port access... which is a quite bit faster than PORT...
Need to be running on 48 MHZ clock for max speed.

Enjoy..

Go Up