Thursday, January 9, 2014

FastPins.h or a way to realize really fast IO pin response on the Arduino

During my investigations for the last post I have discovered that there is massive difference in the execution time between the "Arduino way" and the "AVR-GCC way" of setting an output pin.

The result of method 1

  digitalWrite(13,HIGH);
  digitalWrite(13,LOW);

and method 2

  PORTB |= (1<<5);
  PORTB &= ~(1<<5);

is the same, PIN13 with the LED will be switched on and off, except that the first one generates a high pulse of 3.95µs and the second statement gets that handled in just 170ns (0.17µs) on an Arduino with 16MHz. The two pictures below show a comparison of both methods.

Yellow: Original Arduino style
Blue: AVR-GCC style
Zoom of the previous picture

Lets take a look to the pros and cons of both methods

Method 1:

Pro:
  • Super easy just take the original Arduino pin number and set the pin either HIGH or LOW
  • Works on all Arduinos e.g. PIN13 will always be on the same place of the board (UNO, MEGA, Leonardo) 
Con:
  • Slow, it takes 3.95µs to set a pin twice (LOW --> HIGH --> LOW)

Method 2

Pro:
  • Fast, it only takes 0.17µs (32 times faster) to set a pin twice (LOW --> HIGH --> LOW)
  • It could be set more than one pin at a time as long as they are on the same port
Con:
  • Its ugly and cryptic to type
  • The port and the pin number must be known
  • Port and pin number are varying between different boards e.g. PIN13 (LED) is PORTB Pin5 on the UNO, PORTB Pin7 on the MEGA and PORTC Pin7 On the Leonardo
  • Usage makes the Arduino sketch fixed to one board due to different pin assignment
But I still wanted an easy AND fast way to set the output pins. So I figured out that there is something called "preprocessor macros" and to be exact something called "Variadic Macros" which looked like it could be the solution but I didn't understood the documentation until I found this question at stackoverflow.com which brought me on the right way for my solution to make the AVR-GCC code look nice.

Preprocessor macros are text/code snippets which will replace parts of your written program code during the compile cycle of the code, and if possible the calculations are done in the preprocessor during compiling and not at runtime on the Arduino. For example they can be used to make ugly code parts look more readable, and that was exactly that what I wanted.

The Results

I have prepared an include file for the Arduino IDE so that the macros are included into the IDE and I also have written an keywords.txt to highlight the corresponding functions and keywords. It can be downloaded here. Both files must be copied into a sub folder of the Arduino libraries folder.

Due to the disadvantage that the port and the pin number must be known I can highly recommend the homepage of Alberto Piganti to figure out the correct ports and pin numbers. He has prepared a lot of very good pin out diagrams various kinds of Arduinos etc.

Usage of the Library/Include File

The usage is quite easy and more or less self explaining. The first parameter which is given to the function is always the port. If the function only gets two parameters then it sets the complete port to the second value.

  digitalFastWrite(PD,150);

Sets PORTD to 0b10010110

If the function gets three or more parameters then the first parameter is always the port and the last parameter can either be 1 or 0 (HIGH or LOW). This works with up to 8 pins at the same time. All outputs will be set at the same time no matter if only 1 pin is set or if all pins of a port are set.

  digitalFastWrite(PD,0,HIGH);
  digitalFastWrite(PD,0,LOW);

Creates a HIGH LOW pulse on pin 0 of PORTD

  digitalFastWrite(PD,0,1,HIGH);
  digitalFastWrite(PD,0,1,LOW);

Does the same with pin 0&1 of PORTD

Here are some examples to set multiple pins at the same time.

  digitalFastWrite(PD,0,1,2,HIGH);
  digitalFastWrite(PD,0,1,2,LOW);
  digitalFastWrite(PD,0,1,2,3,HIGH);
  digitalFastWrite(PD,0,1,2,3,LOW);
  digitalFastWrite(PD,0,1,2,3,4,5,6,7,HIGH);
  digitalFastWrite(PD,0,1,2,3,4,5,6,7,LOW);

It is also possible to quickly read an digital input. The input and output registers are at different addresses. For this reason use PINx instead of PORTx or Px. If readed from the address of PORTx the status of the internal pullup resistors is read which could be different form the real input state
The following example reads the value of pin 0 from PORTD and copies it into the variable pinState.

  pinState = digitalFastRead(PIND,0);

Until now it is still necessary to use the pinMode() function to set if the pin is an input or an output. But I'm working on it to create something which will be compatible with the above examples so stay tuned.



For further questions just ask it in the comments and I will try to answer them as soon as possible.