Actions

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

From Just in Time

 
(13 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
[[File:Lotsapins.jpg|right|300px]]
 
[[File:Lotsapins.jpg|right|300px]]
This page describes a library that offers overloads of Arduino's digital pin functions (''digitalWrite()'', ''digitalRead()'', ''shiftOut()'', ''shiftIn()''), but with native performance. For example, the ''digitalWrite()'' function of this library produces 1 inlined assembler instruction (''sbi <port>, <bit>'') and runs in two clock cycles (one cycle in reduced core TinyAVRs). This library can be used to include Arduino code in "raw" AVR projects without suffering a memory footprint hit. It is also offered as an Arduino library, providing a much faster implementation of the digital pin functions.
+
This page describes a library that offers overloads of Arduino's digital pin functions (''digitalWrite()'', ''digitalRead()'', ''shiftOut()'', ''shiftIn()''), but with native performance. For example, the ''digitalWrite()'' function of this library produces 1 inlined assembler instruction and runs in 2 clock cycles instead of Arduino's 50+ cycles. This library can be used to include Arduino code in "raw" AVR projects without suffering a memory footprint hit. It is also offered as an Arduino library, providing a much faster implementation of the digital pin functions.
  
The library is header file only and typically produces binary code that is as fast and as small as hand-crafted assembly code. Under certain circumstances, specifically when changing more than one bit at a time, the resulting code is generally faster than the C-style equivalent using ''#define''d macros and bitwise logical operators.
+
The library is header file only and produces binary code that is as fast and as small as hand-crafted assembly code. When changing more than one bit at a time, the resulting code is generally faster than the C-style equivalent using ''#define''d macros and bitwise logical operators.
  
 +
[[Category:AVR]][[Category:C++]]
 
==Fast or readable?==
 
==Fast or readable?==
 
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:
 
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:
Line 42: Line 43:
 
</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 an integer variable on Arduino, which is resolved at run-time. The pins and ports for bitwise operations are normally declared using preprocessor macros, which get resolved at compile time. There are hybrid cases, such as the Arduino SoftwareSerial library<ref name="softSerial">[https://github.com/arduino/Arduino/blob/master/libraries/SoftwareSerial/SoftwareSerial.cpp SoftwareSerial source code], take a look at the tx_pin_write()- or rx_pin_read()-methods</ref>, where a pin number is converted once into a bit mask at run time and then that bit mask is used for the remainder of the program in logical AND- and OR operations.
+
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 an integer variable on Arduino, which is resolved at run-time. The pins and ports for bitwise operations are normally declared using preprocessor macros, which get resolved at compile time. There are hybrid cases, such as the Arduino SoftwareSerial library<ref name="softSerial">[https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/libraries/SoftwareSerial/SoftwareSerial.cpp SoftwareSerial source code], take a look at the tx_pin_write()- or rx_pin_read()-methods</ref>, where a pin number is converted once into a bit mask at run time and then that bit mask is used for the remainder of the program in logical AND- and OR operations.
  
It is partly the run-time resolving of pin numbers that makes Arduino’s digital pin functions so slow<ref>well, that, and checking whether the pin is maybe used for PWM at the time, see the implementation of the [https://github.com/arduino/Arduino/blob/master/hardware/arduino/cores/arduino/wiring_digital.c digitalWrite()-function].</ref>; 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"], a blog by Bill Grundman who, unlike me, is not too lazy to hook up a scope to his AVR and do measurements</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.
+
It is partly the run-time resolving of pin numbers that makes Arduino’s digital pin functions so slow<ref>well, that, and checking whether the pin is maybe used for PWM at the time, see the implementation of the [https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring_digital.c digitalWrite()-function].</ref>; 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"], a blog by Bill Grundman who, unlike me, is not too lazy to hook up a scope to his AVR and do measurements</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.
  
This choice can be clearly seen when looking at the implementations of several standard Arduino libraries. The strictly timed SoftwareSerial library, as mentioned before, uses bitwise operators<ref name="softSerial"/>, while for example the LiquidCrystal library can afford to use the digital pin functions<ref>[https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/LiquidCrystal.cpp LiquidCrystal source code]</ref>.
+
This choice can be clearly seen when looking at the implementations of several standard Arduino libraries. The strictly timed SoftwareSerial library, as mentioned before, uses bitwise operators<ref name="softSerial"/>, while for example the LiquidCrystal library can afford to use the digital pin functions<ref>[https://github.com/arduino/Arduino/blob/master/libraries/LiquidCrystal/src/LiquidCrystal.cpp LiquidCrystal source code]</ref>.
  
 
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?
 
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?
Line 143: Line 144:
 
I've implemented some libraries using this type of pin definitions. As stated above, any such library should take its pin definitions as template arguments because pins are now defined as types, not integer values.  
 
I've implemented some libraries using this type of pin definitions. As stated above, any such library should take its pin definitions as template arguments because pins are now defined as types, not integer values.  
  
 +
Still, more choices need to be made, because there are two possible styles of providing the pin definitions: provide a struct with named members for each of the pins, or provide each pin as a template argument. Currently, I'm using the first approach. For example, the fast-pins version of a bit-banging SPI device is used as follows:
  
Still, more choices need to be made, because there are two possible styles of providing the pin definitions: provide a struct with named members for each of the pins, or provide each pin as a template argument. For example, the fast-pins version of a bit-banging SPI device is used as follows:
+
<source lang="cpp">
 +
/// This struct defines which pins are used by the bit banging spi device
 +
struct spi_pins {
 +
    DigitalPin< 8>::type mosi; // B0
 +
    DigitalPin< 9>::type miso; // B1
 +
    DigitalPin<10>::type clk;  // B2
 +
};
 +
typedef bitbanged_spi<spi_pins> spi;
  
<source lang="cpp">
+
void f()
 +
{
 +
    // ...
 +
    spi::transmit_receive( value);
 +
    // ...
 +
}
 
</source>
 
</source>
 +
This is a little more verbose than just providing the types as template arguments, but&mdash;like with named function arguments&mdash;readers of this code will have a much easier time understanding what the spi class does.
 +
 +
==Is it worth it?==
 +
In summary, the fast pins library offers fast, low-footprint digital pin functions without sacrificing readabillity. Especially for the simple use cases these functions offer these features without any noticable disadvantages.
 +
 +
When writing libraries, some things need to be taken into account, such as the fact that most of the library implementation now becomes part of a header file and pin definitions can no longer be provided as constructor arguments, but should be type arguments to some template.
 +
 +
For all "raw" AVR projects, these fast pin definitions&mdash;and [[Arduino-like pin definitions in C++|the underlying library]] with a less arduino-like interface&mdash;have offered me nothing but advantages: never slower, and sometimes faster, than native code, combined with nicely readable application code.
 +
 +
In my view, the fast pins library is another example of how the C++ programming language can help you make the readable/optimised dilemma go away.
  
 
==Download==
 
==Download==
The Arduino-ified library is available here: [{{filepath:FastPins.zip}} FastPins.zip].
+
The Arduino-ified library is available here: [{{filepath:FastPins.zip}} FastPins.zip]. The sources are also on [https://github.com/DannyHavenith/avr_utilities github], together with