Actions

Difference between revisions of "Fast, arduino compatible digital pin functions"

From Just in Time

m (saving WIP)
(complete redaction)
Line 1: Line 1:
 
{{WIP}}
 
{{WIP}}
 
[[File:Lotsapins.jpg|right|300px]]
 
[[File:Lotsapins.jpg|right|300px]]
 +
When reading, or setting pin values on AVRS, there are typically only two options: the readable way or the fast way. The readable way is offered by the Arduino platform and consists of digital pin functions like ‘’digitalRead’’, ‘’shiftOut’’, etc. The fast way is available both on Arduino and on ‘raw’ AVR and consists of bit-wise AND- and OR-operations. In code this looks like this:
  
Suppose you'd write the following simple–and utterly useless–arduino-sketch:
 
 
<source lang="cpp">
 
<source lang="cpp">
 +
// **************************************************
 +
// The 'readable' way. Use digitalWrite() and friends
 +
 
int myPin = 12;
 
int myPin = 12;
  
Line 16: Line 19:
 
   digitalWrite( myPin, LOW);
 
   digitalWrite( myPin, LOW);
 
}
 
}
</source>
 
Those digitalWrite-calls will set you back more than 50 clock cycles apiece!<ref>[http://billgrundmann.wordpress.com/2009/03/03/to-use-or-not-use-writedigital/ "To use or not use digitalWrite"]</ref> In itself it's not so bad: the convenience that the Arduino environment offers is worth some performance penalty. The problem is: library writers now have to make a choice between using slow digitalWrite as above, or using less readable code like this:
 
  
<source lang="cpp">
+
// **************************************************
 +
// The 'fast' way. Use bitwise operators and defines.
 +
 
 
#define MYPINPORT PORTB
 
#define MYPINPORT PORTB
 
#define MYPINMASK _BV(4)
 
#define MYPINMASK _BV(4)
Line 28: Line 31:
 
     MYPINPORT &= ~MYPINMASK;
 
     MYPINPORT &= ~MYPINMASK;
 
}
 
}
 +
 
</source>
 
</source>
 +
Apart from the obvious differences, there is another, more subtle difference between the two approaches: when giving a name to a pin function, this is typically done by using a variable on Arduino, which is resolved at run-time, while the pins and ports for bitwise operations are normally declared using preprocessor macros, which get resolved at compile time.
  
The code above is somewhat less readable. It becomes worse when there are several configurable pins that need their own ''#define''s and unmanageable if some of those pins are on the same port and you'd want to make use of that fact.
+
It is partly the run-time resolving of pin numbers by Arduino’s digital pin functions that makes them so slow; simply setting or clearing a single output pin will set you back more than 50 clock cycles!<ref>[http://billgrundmann.wordpress.com/2009/03/03/to-use-or-not-use-writedigital/ "To use or not use digitalWrite"]</ref> As a developer, you have to choose between fast, but less readable bitwise operators in combination with macros, or readable but slow Arduino digital pin functions, or so it seems…
  
Wouldn't it be nice if ''digitalWrite'' could be implemented in such a way that it would be as fast as handwritten bit manipulations? Toggling an output pin should take 2 clock cycles at most and 1 cycle on tiny AVRs that support such fast toggles.
+
At the same time, both ‘raw’ AVR developers and Arduino developers use AVR-GCC, which is a full-flexed, modern C++ compiler. Modern C++ compilers allow techniques like template meta programming (TMP) which in turn allows the compiler to perform almost arbitrarily complex processing before generating the assembler instructions that end up in your AVR’s firmware. Shouldn’t it be possible to use the compiler to solve the readable/fast dilemma?
  
The Arduino environment employs a modern C++ compiler that enables modern C++ techniques like template metaprogramming (TMP). If the pin we're writing to is known at compile time, TMP can be used to do most of the work at compile time, leaving a single assembler instruction to be performed at run time. All of this can be done with minimal intrusion: the only thing we need to do is to change the pin variables into pin types. The result looks like this:
+
It should, and it is.
 +
 
 +
Look at the following code and spot the differences with the digital pin functions as shown earlier
  
 
<source lang="cpp">
 
<source lang="cpp">
 
#include "FastPinDefinitions.h"
 
#include "FastPinDefinitions.h"
 
 
using namespace FastPinDefinitions;
 
using namespace FastPinDefinitions;
 
DigitalPin<12>::type myPin;
 
DigitalPin<12>::type myPin;
Line 54: Line 60:
 
</source>
 
</source>
  
You see? Almost the same code. The difference is that code above takes only 2 clock cycles for each digitalWrite. And there are more benefits to this:
+
You see? Almost the same code. One difference that you can’t see is that now the digitalWrite function compiles into 1 assembler instruction, taking 2 clock cycles.
* In my Arduino environment, the binary size for the code above shrinks from 882 bytes to 472 bytes. If I throw in a ''shiftOut'', code shrinks from 1042 bytes to 508 bytes.
+
 
* If I use an invalid pin number in the original Arduino environment, my code will compile and silently fail. The FastPins version will throw a compiler error.
+
The big difference of course is that the digital pin functions you see called in the code above are not the same ones as the original Arduino ones. These are overloads. The other big difference is that ‘’myPin’’ in the code above is no longer an integer which is read at run-time to determine the port. Instead it is now a variable of some special type, where the pin number is actually a part of the type, not a value. This means that no time needs to be spent at run time to determine which hardware address to use and which bit to set in that hardware.
 +
 
 +
The generated code is also smaller; in my Arduino environment, the binary size for the code above shrinks from 882 bytes to 472 bytes. If I throw in a ''shiftOut'', code shrinks from 1042 bytes to 508 bytes.
  
 +
An additional advantage is that now, if you specify a nonsensical pin number (like, say, 42) you will be punished with a compiler error instead of being silently ignored at run time, which is the Arduino treatment.
  
  
 
==References==
 
==References==
 
<references/>
 
<references/>

Revision as of 23:15, 4 December 2014

Work in Progress
This page describes a work in progress. All specifications may change and the final product may significantly deviate from what we describe here. It is very well possible that there will be no final product at all.
Warning.png

[[Revision timestamp::20141204231530|]]

Lotsapins.jpg

When reading, or setting pin values on AVRS, there are typically only two options: the readable way or the fast way. The readable way is offered by the Arduino platform and consists of digital pin functions like ‘’digitalRead’’, ‘’shiftOut’’, etc. The fast way is available both on Arduino and on ‘raw’ AVR and consists of bit-wise AND- and OR-operations. In code this looks like this:

<source lang="cpp"> // ************************************************** // The 'readable' way. Use digitalWrite() and friends

int myPin = 12;

void setup() {

 pinMode( myPin, OUTPUT);

}

void loop() {

 digitalWrite( myPin, HIGH);
 digitalWrite( myPin, LOW);

}

// ************************************************** // The 'fast' way. Use bitwise operators and defines.

  1. define MYPINPORT PORTB
  2. define MYPINMASK _BV(4)

void loop() {

   MYPINPORT |= MYPINMASK;
   MYPINPORT &= ~MYPINMASK;

}

</source> Apart from the obvious differences, there is another, more subtle difference between the two approaches: when giving a name to a pin function, this is typically done by using a variable on Arduino, which is resolved at run-time, while the pins and ports for bitwise operations are normally declared using preprocessor macros, which get resolved at compile time.

It is partly the run-time resolving of pin numbers by Arduino’s digital pin functions that makes them so slow; simply setting or clearing a single output pin will set you back more than 50 clock cycles![1] As a developer, you have to choose between fast, but less readable bitwise operators in combination with macros, or readable but slow Arduino digital pin functions, or so it seems…

At the same time, both ‘raw’ AVR developers and Arduino developers use AVR-GCC, which is a full-flexed, modern C++ compiler. Modern C++ compilers allow techniques like template meta programming (TMP) which in turn allows the compiler to perform almost arbitrarily complex processing before generating the assembler instructions that end up in your AVR’s firmware. Shouldn’t it be possible to use the compiler to solve the readable/fast dilemma?

It should, and it is.

Look at the following code and spot the differences with the digital pin functions as shown earlier

<source lang="cpp">

  1. include "FastPinDefinitions.h"

using namespace FastPinDefinitions; DigitalPin<12>::type myPin;

void setup() {

 pinMode( myPin, OUTPUT);

}

void loop() {

 digitalWrite( myPin, HIGH);
 digitalWrite( myPin, LOW);

} </source>

You see? Almost the same code. One difference that you can’t see is that now the digitalWrite function compiles into 1 assembler instruction, taking 2 clock cycles.

The big difference of course is that the digital pin functions you see called in the code above are not the same ones as the original Arduino ones. These are overloads. The other big difference is that ‘’myPin’’ in the code above is no longer an integer which is read at run-time to determine the port. Instead it is now a variable of some special type, where the pin number is actually a part of the type, not a value. This means that no time needs to be spent at run time to determine which hardware address to use and which bit to set in that hardware.

The generated code is also smaller; in my Arduino environment, the binary size for the code above shrinks from 882 bytes to 472 bytes. If I throw in a shiftOut, code shrinks from 1042 bytes to 508 bytes.

An additional advantage is that now, if you specify a nonsensical pin number (like, say, 42) you will be punished with a compiler error instead of being silently ignored at run time, which is the Arduino treatment.


References