I achieved digital read speed = 210Mbps

There is very good topic on this forum about achieving fast pin I/O operations: Port manipulation PINx commands with Arduino Due ? - Arduino Due - Arduino Forum
The code proposed there look like this:

inline void digitalWriteDirect(int pin, boolean val){
  if(val) g_APinDescription[pin].pPort -> PIO_SODR = g_APinDescription[pin].ulPin;
  else    g_APinDescription[pin].pPort -> PIO_CODR = g_APinDescription[pin].ulPin;
}

inline int digitalReadDirect(int pin){
  return !!(g_APinDescription[pin].pPort -> PIO_PDSR & g_APinDescription[pin].ulPin);
}

and is the fastest I was able to find. On my Arduino Due:

  • digitalWriteDirect function executes in 26ns which gives 19,2Mhz clock on pin output
  • digitalReadDirect function executes in 523ns which gives 1,9Mbps (Mega bits per second)

I wanted to use me Due with 8 bit parralel output devices (like 8 bit AD converter), so in order to read whole byte (8 bits) I would have to perform 8 digitalReadDirect executions. It would last ~4,2us which would give me maximal sampling rate around 240ksps (where one sample consists of 8 bits). I found a way to speed up digitalReadDirect for solutions where one wants to read 8 bits at once. The code is:

void setup() {

  pinMode(44, INPUT); // MSB
  pinMode(45, INPUT);
  pinMode(46, INPUT);
  pinMode(47, INPUT);
  pinMode(48, INPUT);
  pinMode(49, INPUT);
  pinMode(50, INPUT);
  pinMode(51, INPUT); // LSB

  Serial.begin(9600);
  delay(1000); 

}

void loop() {

  unsigned char v = (unsigned char)((PIOC -> PIO_PDSR & 0b00000000000011111111000000000000) >> 12);

  Serial.print(v);  
  delay(1000);          // wait a second so as not to send massive amounts of data
  
}

It utilizes the fact that reading whole register takes the same time as reading only one bit from that register. In this example my 8 bits will be on pins from 44 to 51 (44 being MSB). These pin numbers are crucial because they all are located next to each other in terms of Due pin mapping which can be found here: https://www.arduino.cc/en/Hacking/PinMappingSAM3X
Pins 44 to 51 lies in register C on positions from 19 to 12 (in reverse order). Thanks to that we can read only register C and look at bits on position from 19 to 12 which is performed by this line:

(PIOC -> PIO_PDSR & 0b00000000000011111111000000000000)

Then we shift those 8 bit by 12 places to the right, so now our 8 bits will be located as 8 least significat bits of unsigned long variable. Shifting is done here:

(PIOC -> PIO_PDSR & 0b00000000000011111111000000000000) >> 12

At last we can cast our unsigned long variable (32 bit long) to an unsigned char variable (8 bit long), by taking 8 least significant bits and ignoring other 24 bits (which will be zeros). Casting is done here:

 unsigned char v = (unsigned char)((PIOC -> PIO_PDSR & 0b00000000000011111111000000000000) >> 12);

That way in one line be just read whole byte (8 bits of data) and stored it in 8 bit unsigned char variable for further use. The best part is that it takes only 38ns to perform this operation, so we have the frequency of 26,3MBps (Mega BYTES per second), which means that we can read 210Mbps (Mega bits per second).

The downside of this solution is that it cannot be easily copied on other pins, because pins 44 to 51 are the only ones in Due which lies next to each other both as physical pins and on register mapping.

I hope this post will help some of You :slight_smile:

2 Likes